futhark 0.21.10 → 0.21.11
raw patch · 114 files changed
+11151/−1964 lines, 114 filesdep +Diffdep ~futhark-datadep ~textPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: Diff
Dependency ranges changed: futhark-data, text
API changes (from Hackage documentation)
- Futhark.CodeGen.RTS.C: chaselevH :: Text
- Futhark.Compiler.CLI: instance GHC.Classes.Eq Futhark.Compiler.CLI.CompilerMode
- Futhark.Compiler.CLI: instance GHC.Classes.Ord Futhark.Compiler.CLI.CompilerMode
- Futhark.Compiler.CLI: instance GHC.Show.Show Futhark.Compiler.CLI.CompilerMode
- Futhark.IR.SOACS.SOAC: instance Futhark.IR.Prop.TypeOf.TypedOp (Futhark.IR.SOACS.SOAC.SOAC rep)
- Futhark.LSP.Compile: tryTakeStateFromMVar :: MVar State -> Maybe FilePath -> LspT () IO State
- Futhark.LSP.State: newtype State
- Futhark.Optimise.Simplify.Rep: mkWiseLetStm :: (ASTRep rep, CanBeWise (Op rep)) => Pat (LetDec rep) -> StmAux (ExpDec rep) -> Exp (Wise rep) -> Stm (Wise rep)
- Futhark.Pipeline: instance GHC.Classes.Eq Futhark.Pipeline.Verbosity
- Futhark.Pipeline: instance GHC.Classes.Ord Futhark.Pipeline.Verbosity
- Futhark.Util: readFileSafely :: FilePath -> IO (Maybe (Either String Text))
- Language.Futhark.Syntax: class (Show vn, Show (f VName), Show (f (Diet, Maybe VName)), Show (f String), Show (f [VName]), Show (f ([VName], [VName])), Show (f PatType), Show (f (PatType, [VName])), Show (f (StructType, [VName])), Show (f (StructRetType, [VName])), Show (f EntryPoint), Show (f StructType), Show (f StructRetType), Show (f PatRetType), Show (f (StructType, Maybe VName)), Show (f (PName, StructType)), Show (f (PName, StructType, Maybe VName)), Show (f (Aliasing, StructRetType)), Show (f (Map VName VName)), Show (f AppRes)) => Showable f vn
- Language.Futhark.Syntax: instance GHC.Show.Show vn => Language.Futhark.Syntax.Showable Language.Futhark.Syntax.Info vn
- Language.Futhark.Syntax: instance GHC.Show.Show vn => Language.Futhark.Syntax.Showable Language.Futhark.Syntax.NoInfo vn
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.AppExpBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.CaseBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.DecBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.DimIndexBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.ExpBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.FieldBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.IdentBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.LoopFormBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.ModBindBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.ModExpBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.ModParamBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.PatBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.ProgBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.SigBindBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.SigExpBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.SpecBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.TypeBindBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.TypeDeclBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.TypeRefBase f vn)
- Language.Futhark.Syntax: instance Language.Futhark.Syntax.Showable f vn => GHC.Show.Show (Language.Futhark.Syntax.ValBindBase f vn)
+ Futhark.AD.Derivatives: pdBinOp :: BinOp -> PrimExp VName -> PrimExp VName -> (PrimExp VName, PrimExp VName)
+ Futhark.AD.Derivatives: pdBuiltin :: Name -> [PrimExp VName] -> Maybe [PrimExp VName]
+ Futhark.AD.Derivatives: pdUnOp :: UnOp -> PrimExp VName -> PrimExp VName
+ Futhark.AD.Fwd: fwdJVP :: MonadFreshNames m => Scope SOACS -> Lambda SOACS -> m (Lambda SOACS)
+ Futhark.AD.Fwd: instance (GHC.Base.Monoid (Futhark.AD.Fwd.Bundled a), Futhark.AD.Fwd.TanBuilder a) => Futhark.AD.Fwd.TanBuilder [a]
+ Futhark.AD.Fwd: instance (GHC.Base.Monoid (Futhark.AD.Fwd.BundledTan a), Futhark.AD.Fwd.Tangent a) => Futhark.AD.Fwd.Tangent [a]
+ Futhark.AD.Fwd: instance Control.Monad.State.Class.MonadState Futhark.AD.Fwd.RState Futhark.AD.Fwd.ADM
+ Futhark.AD.Fwd: instance Futhark.AD.Fwd.TanBuilder (Futhark.IR.Syntax.Core.Param (Futhark.IR.Syntax.Core.TypeBase s u))
+ Futhark.AD.Fwd: instance Futhark.AD.Fwd.TanBuilder (Futhark.IR.Syntax.Core.PatElem (Futhark.IR.Syntax.Core.TypeBase s u))
+ Futhark.AD.Fwd: instance Futhark.AD.Fwd.TanBuilder (Futhark.IR.Syntax.Pat (Futhark.IR.Syntax.Core.TypeBase s u))
+ Futhark.AD.Fwd: instance Futhark.AD.Fwd.Tangent (Futhark.IR.Syntax.Core.TypeBase s u)
+ Futhark.AD.Fwd: instance Futhark.AD.Fwd.Tangent Futhark.IR.Syntax.Core.SubExp
+ Futhark.AD.Fwd: instance Futhark.AD.Fwd.Tangent Futhark.IR.Syntax.SubExpRes
+ Futhark.AD.Fwd: instance Futhark.AD.Fwd.Tangent Language.Futhark.Core.VName
+ Futhark.AD.Fwd: instance Futhark.AD.Fwd.Tangent a => Futhark.AD.Fwd.TanBuilder (Futhark.IR.Syntax.Core.Param (Futhark.IR.Syntax.Core.TypeBase s u), a)
+ Futhark.AD.Fwd: instance Futhark.Builder.Class.MonadBuilder Futhark.AD.Fwd.ADM
+ Futhark.AD.Fwd: instance Futhark.IR.Prop.Scope.HasScope Futhark.IR.SOACS.SOACS Futhark.AD.Fwd.ADM
+ Futhark.AD.Fwd: instance Futhark.IR.Prop.Scope.LocalScope Futhark.IR.SOACS.SOACS Futhark.AD.Fwd.ADM
+ Futhark.AD.Fwd: instance Futhark.MonadFreshNames.MonadFreshNames (Control.Monad.Trans.State.Strict.State Futhark.AD.Fwd.RState)
+ Futhark.AD.Fwd: instance Futhark.MonadFreshNames.MonadFreshNames Futhark.AD.Fwd.ADM
+ Futhark.AD.Fwd: instance GHC.Base.Applicative Futhark.AD.Fwd.ADM
+ Futhark.AD.Fwd: instance GHC.Base.Functor Futhark.AD.Fwd.ADM
+ Futhark.AD.Fwd: instance GHC.Base.Monad Futhark.AD.Fwd.ADM
+ Futhark.AD.Rev: revVJP :: MonadFreshNames m => Scope SOACS -> Lambda SOACS -> m (Lambda SOACS)
+ Futhark.AD.Rev.Loop: diffLoop :: (Stms SOACS -> ADM ()) -> Pat Type -> StmAux () -> Exp SOACS -> ADM () -> ADM ()
+ Futhark.AD.Rev.Loop: instance Data.Foldable.Foldable Futhark.AD.Rev.Loop.LoopInfo
+ Futhark.AD.Rev.Loop: instance Data.Traversable.Traversable Futhark.AD.Rev.Loop.LoopInfo
+ Futhark.AD.Rev.Loop: instance GHC.Base.Functor Futhark.AD.Rev.Loop.LoopInfo
+ Futhark.AD.Rev.Loop: instance GHC.Show.Show a => GHC.Show.Show (Futhark.AD.Rev.Loop.LoopInfo a)
+ Futhark.AD.Rev.Loop: stripmineStms :: Stms SOACS -> ADM (Stms SOACS)
+ Futhark.AD.Rev.Map: vjpMap :: VjpOps -> [Adj] -> StmAux () -> SubExp -> Lambda SOACS -> [VName] -> ADM ()
+ Futhark.AD.Rev.Monad: AdjSparse :: Sparse -> Adj
+ Futhark.AD.Rev.Monad: AdjVal :: SubExp -> Adj
+ Futhark.AD.Rev.Monad: AdjZero :: Shape -> PrimType -> Adj
+ Futhark.AD.Rev.Monad: AssumeBounds :: InBounds
+ Futhark.AD.Rev.Monad: CheckBounds :: Maybe SubExp -> InBounds
+ Futhark.AD.Rev.Monad: OutOfBounds :: InBounds
+ Futhark.AD.Rev.Monad: RState :: Map VName Adj -> Substitutions -> Substitutions -> VNameSource -> RState
+ Futhark.AD.Rev.Monad: Sparse :: Shape -> PrimType -> [(InBounds, SubExp, SubExp)] -> Sparse
+ Futhark.AD.Rev.Monad: VjpOps :: ([Adj] -> [VName] -> Lambda SOACS -> ADM (Lambda SOACS)) -> (Stm SOACS -> ADM () -> ADM ()) -> VjpOps
+ Futhark.AD.Rev.Monad: [sparseIdxVals] :: Sparse -> [(InBounds, SubExp, SubExp)]
+ Futhark.AD.Rev.Monad: [sparseShape] :: Sparse -> Shape
+ Futhark.AD.Rev.Monad: [sparseType] :: Sparse -> PrimType
+ Futhark.AD.Rev.Monad: [stateAdjs] :: RState -> Map VName Adj
+ Futhark.AD.Rev.Monad: [stateLoopTape] :: RState -> Substitutions
+ Futhark.AD.Rev.Monad: [stateNameSource] :: RState -> VNameSource
+ Futhark.AD.Rev.Monad: [stateSubsts] :: RState -> Substitutions
+ Futhark.AD.Rev.Monad: [vjpLambda] :: VjpOps -> [Adj] -> [VName] -> Lambda SOACS -> ADM (Lambda SOACS)
+ Futhark.AD.Rev.Monad: [vjpStm] :: VjpOps -> Stm SOACS -> ADM () -> ADM ()
+ Futhark.AD.Rev.Monad: addLambda :: Type -> ADM (Lambda SOACS)
+ Futhark.AD.Rev.Monad: addSubstitution :: VName -> VName -> ADM ()
+ Futhark.AD.Rev.Monad: adjFromParam :: Param t -> Adj
+ Futhark.AD.Rev.Monad: adjFromVar :: VName -> Adj
+ Futhark.AD.Rev.Monad: adjVName :: VName -> ADM VName
+ Futhark.AD.Rev.Monad: adjVal :: Adj -> ADM VName
+ Futhark.AD.Rev.Monad: adjsReps :: [Adj] -> ([SubExp], [SubExp] -> [Adj])
+ Futhark.AD.Rev.Monad: copyConsumedArrsInBody :: [VName] -> Body SOACS -> ADM Substitutions
+ Futhark.AD.Rev.Monad: copyConsumedArrsInStm :: Stm SOACS -> ADM (Substitutions, Stms SOACS)
+ Futhark.AD.Rev.Monad: data ADM a
+ Futhark.AD.Rev.Monad: data Adj
+ Futhark.AD.Rev.Monad: data InBounds
+ Futhark.AD.Rev.Monad: data RState
+ Futhark.AD.Rev.Monad: data Sparse
+ Futhark.AD.Rev.Monad: data VjpOps
+ Futhark.AD.Rev.Monad: insAdj :: VName -> VName -> ADM ()
+ Futhark.AD.Rev.Monad: insSubExpAdj :: SubExp -> VName -> ADM ()
+ Futhark.AD.Rev.Monad: instance Control.Monad.State.Class.MonadState Futhark.AD.Rev.Monad.RState Futhark.AD.Rev.Monad.ADM
+ Futhark.AD.Rev.Monad: instance Futhark.Builder.Class.MonadBuilder Futhark.AD.Rev.Monad.ADM
+ Futhark.AD.Rev.Monad: instance Futhark.IR.Prop.Scope.HasScope Futhark.IR.SOACS.SOACS Futhark.AD.Rev.Monad.ADM
+ Futhark.AD.Rev.Monad: instance Futhark.IR.Prop.Scope.LocalScope Futhark.IR.SOACS.SOACS Futhark.AD.Rev.Monad.ADM
+ Futhark.AD.Rev.Monad: instance Futhark.MonadFreshNames.MonadFreshNames (Control.Monad.Trans.State.Strict.State Futhark.AD.Rev.Monad.RState)
+ Futhark.AD.Rev.Monad: instance Futhark.MonadFreshNames.MonadFreshNames Futhark.AD.Rev.Monad.ADM
+ Futhark.AD.Rev.Monad: instance Futhark.Transform.Substitute.Substitute Futhark.AD.Rev.Monad.Adj
+ Futhark.AD.Rev.Monad: instance GHC.Base.Applicative Futhark.AD.Rev.Monad.ADM
+ Futhark.AD.Rev.Monad: instance GHC.Base.Functor Futhark.AD.Rev.Monad.ADM
+ Futhark.AD.Rev.Monad: instance GHC.Base.Monad Futhark.AD.Rev.Monad.ADM
+ Futhark.AD.Rev.Monad: instance GHC.Classes.Eq Futhark.AD.Rev.Monad.Adj
+ Futhark.AD.Rev.Monad: instance GHC.Classes.Eq Futhark.AD.Rev.Monad.InBounds
+ Futhark.AD.Rev.Monad: instance GHC.Classes.Eq Futhark.AD.Rev.Monad.Sparse
+ Futhark.AD.Rev.Monad: instance GHC.Classes.Ord Futhark.AD.Rev.Monad.Adj
+ Futhark.AD.Rev.Monad: instance GHC.Classes.Ord Futhark.AD.Rev.Monad.InBounds
+ Futhark.AD.Rev.Monad: instance GHC.Classes.Ord Futhark.AD.Rev.Monad.Sparse
+ Futhark.AD.Rev.Monad: instance GHC.Show.Show Futhark.AD.Rev.Monad.Adj
+ Futhark.AD.Rev.Monad: instance GHC.Show.Show Futhark.AD.Rev.Monad.InBounds
+ Futhark.AD.Rev.Monad: instance GHC.Show.Show Futhark.AD.Rev.Monad.Sparse
+ Futhark.AD.Rev.Monad: isActive :: VName -> ADM Bool
+ Futhark.AD.Rev.Monad: lookupAdj :: VName -> ADM Adj
+ Futhark.AD.Rev.Monad: lookupAdjVal :: VName -> ADM VName
+ Futhark.AD.Rev.Monad: lookupLoopTape :: VName -> ADM (Maybe VName)
+ Futhark.AD.Rev.Monad: noAdjsFor :: Names -> ADM a -> ADM a
+ Futhark.AD.Rev.Monad: oneExp :: Type -> Exp rep
+ Futhark.AD.Rev.Monad: renameLoopTape :: Substitutions -> ADM ()
+ Futhark.AD.Rev.Monad: returnSweepCode :: ADM a -> ADM a
+ Futhark.AD.Rev.Monad: runADM :: MonadFreshNames m => ADM a -> m a
+ Futhark.AD.Rev.Monad: setAdj :: VName -> Adj -> ADM ()
+ Futhark.AD.Rev.Monad: setLoopTape :: VName -> VName -> ADM ()
+ Futhark.AD.Rev.Monad: subAD :: ADM a -> ADM a
+ Futhark.AD.Rev.Monad: subSubsts :: ADM a -> ADM a
+ Futhark.AD.Rev.Monad: substLoopTape :: VName -> VName -> ADM ()
+ Futhark.AD.Rev.Monad: tabNest :: Int -> [VName] -> ([VName] -> [VName] -> ADM [VName]) -> ADM [VName]
+ Futhark.AD.Rev.Monad: unitAdjOfType :: Type -> ADM Adj
+ Futhark.AD.Rev.Monad: updateAdj :: VName -> VName -> ADM ()
+ Futhark.AD.Rev.Monad: updateAdjIndex :: VName -> (InBounds, SubExp) -> SubExp -> ADM ()
+ Futhark.AD.Rev.Monad: updateAdjSlice :: Slice SubExp -> VName -> VName -> ADM ()
+ Futhark.AD.Rev.Monad: updateSubExpAdj :: SubExp -> VName -> ADM ()
+ Futhark.AD.Rev.Monad: zeroExp :: Type -> Exp rep
+ Futhark.AD.Rev.Reduce: diffMinMaxReduce :: VjpOps -> VName -> StmAux () -> SubExp -> BinOp -> SubExp -> VName -> ADM () -> ADM ()
+ Futhark.AD.Rev.Reduce: diffReduce :: VjpOps -> [VName] -> SubExp -> [VName] -> Reduce SOACS -> ADM ()
+ Futhark.AD.Rev.SOAC: vjpSOAC :: VjpOps -> Pat Type -> StmAux () -> SOAC SOACS -> ADM () -> ADM ()
+ Futhark.AD.Rev.Scan: diffScan :: VjpOps -> [VName] -> SubExp -> [VName] -> Scan SOACS -> ADM ()
+ Futhark.AD.Rev.Scatter: vjpScatter :: VjpOps -> Pat Type -> StmAux () -> (SubExp, [VName], Lambda SOACS, [(Shape, Int, VName)]) -> ADM () -> ADM ()
+ Futhark.Analysis.Metrics: bodyMetrics :: OpMetrics (Op rep) => Body rep -> MetricsM ()
+ Futhark.Analysis.PrimExp: (~*~) :: PrimExp v -> PrimExp v -> PrimExp v
+ Futhark.Analysis.PrimExp: (~+~) :: PrimExp v -> PrimExp v -> PrimExp v
+ Futhark.Analysis.PrimExp: (~-~) :: PrimExp v -> PrimExp v -> PrimExp v
+ Futhark.Analysis.PrimExp: (~/~) :: PrimExp v -> PrimExp v -> PrimExp v
+ Futhark.Analysis.PrimExp: fMax16 :: TPrimExp Half v -> TPrimExp Half v -> TPrimExp Half v
+ Futhark.Analysis.PrimExp: fMax32 :: TPrimExp Float v -> TPrimExp Float v -> TPrimExp Float v
+ Futhark.Analysis.PrimExp: fMin16 :: TPrimExp Half v -> TPrimExp Half v -> TPrimExp Half v
+ Futhark.Analysis.PrimExp: fMin32 :: TPrimExp Float v -> TPrimExp Float v -> TPrimExp Float v
+ Futhark.Analysis.PrimExp: infix 6 ~-~
+ Futhark.Analysis.PrimExp: infix 7 ~/~
+ Futhark.CLI.Main: main :: IO ()
+ Futhark.Compiler.Config: ToExecutable :: CompilerMode
+ Futhark.Compiler.Config: ToLibrary :: CompilerMode
+ Futhark.Compiler.Config: ToServer :: CompilerMode
+ Futhark.Compiler.Config: data CompilerMode
+ Futhark.Compiler.Config: instance GHC.Classes.Eq Futhark.Compiler.Config.CompilerMode
+ Futhark.Compiler.Config: instance GHC.Classes.Eq Futhark.Compiler.Config.Verbosity
+ Futhark.Compiler.Config: instance GHC.Classes.Ord Futhark.Compiler.Config.CompilerMode
+ Futhark.Compiler.Config: instance GHC.Classes.Ord Futhark.Compiler.Config.Verbosity
+ Futhark.Compiler.Config: instance GHC.Show.Show Futhark.Compiler.Config.CompilerMode
+ Futhark.Compiler.Program: ProgWarning :: Loc -> Doc -> ProgError
+ Futhark.Compiler.Program: instance Data.Loc.Located Futhark.Compiler.Program.ProgError
+ Futhark.Compiler.Program: lpFilePaths :: LoadedProg -> [FilePath]
+ Futhark.Compiler.Program: type VFS = Map FilePath Text
+ Futhark.Construct: eDimInBounds :: MonadBuilder m => m (Exp (Rep m)) -> m (Exp (Rep m)) -> m (Exp (Rep m))
+ Futhark.IR.Aliases: consumedInStms :: Aliased rep => Stms rep -> Names
+ Futhark.IR.Aliases: removeBodyAliases :: CanBeAliased (Op rep) => Body (Aliases rep) -> Body rep
+ Futhark.IR.GPU: JVP :: Lambda rep -> [SubExp] -> [SubExp] -> SOAC rep
+ Futhark.IR.GPU: VJP :: Lambda rep -> [SubExp] -> [SubExp] -> SOAC rep
+ Futhark.IR.GPU.Op: GPUBody :: [Type] -> Body rep -> HostOp rep op
+ Futhark.IR.MC: JVP :: Lambda rep -> [SubExp] -> [SubExp] -> SOAC rep
+ Futhark.IR.MC: VJP :: Lambda rep -> [SubExp] -> [SubExp] -> SOAC rep
+ Futhark.IR.Prop.Names: namesIntSet :: Names -> IntSet
+ Futhark.IR.Prop.Types: isAcc :: TypeBase shape u -> Bool
+ Futhark.IR.SOACS: usesAD :: Prog SOACS -> Bool
+ Futhark.IR.SOACS.SOAC: JVP :: Lambda rep -> [SubExp] -> [SubExp] -> SOAC rep
+ Futhark.IR.SOACS.SOAC: VJP :: Lambda rep -> [SubExp] -> [SubExp] -> SOAC rep
+ Futhark.IR.SOACS.SOAC: instance Futhark.IR.Prop.ASTRep rep => Futhark.IR.Prop.TypeOf.TypedOp (Futhark.IR.SOACS.SOAC.SOAC rep)
+ Futhark.IR.SOACS.Simplify: removeUnusedSOACInput :: forall rep. (Aliased rep, Buildable rep, BuilderOps rep, HasSOAC rep) => TopDownRuleOp rep
+ Futhark.IR.Syntax: stmsLast :: Stms lore -> Maybe (Stms lore, Stm lore)
+ Futhark.IR.Syntax: subExpResVName :: SubExpRes -> Maybe VName
+ Futhark.IR.Syntax.Core: mapAttrs :: (Attr -> a) -> Attrs -> [a]
+ Futhark.LSP.Compile: tryTakeStateFromIORef :: IORef State -> Maybe FilePath -> LspT () IO State
+ Futhark.LSP.PositionMapping: StaleFile :: VirtualFile -> Maybe PositionMapping -> StaleFile
+ Futhark.LSP.PositionMapping: [staleContent] :: StaleFile -> VirtualFile
+ Futhark.LSP.PositionMapping: [staleMapping] :: StaleFile -> Maybe PositionMapping
+ Futhark.LSP.PositionMapping: data PositionMapping
+ Futhark.LSP.PositionMapping: data StaleFile
+ Futhark.LSP.PositionMapping: instance GHC.Show.Show Futhark.LSP.PositionMapping.PositionMapping
+ Futhark.LSP.PositionMapping: instance GHC.Show.Show Futhark.LSP.PositionMapping.StaleFile
+ Futhark.LSP.PositionMapping: mappingFromDiff :: [Text] -> [Text] -> PositionMapping
+ Futhark.LSP.PositionMapping: toCurrentLoc :: Maybe PositionMapping -> Loc -> Maybe Loc
+ Futhark.LSP.PositionMapping: toStalePos :: Maybe PositionMapping -> Pos -> Maybe Pos
+ Futhark.LSP.State: [staleData] :: State -> Map FilePath StaleFile
+ Futhark.LSP.State: data State
+ Futhark.LSP.State: getStaleContent :: State -> FilePath -> Maybe VirtualFile
+ Futhark.LSP.State: getStaleMapping :: State -> FilePath -> Maybe PositionMapping
+ Futhark.LSP.State: updateStaleContent :: FilePath -> VirtualFile -> LoadedProg -> State -> State
+ Futhark.LSP.State: updateStaleMapping :: Maybe FilePath -> Maybe PositionMapping -> State -> State
+ Futhark.LSP.Tool: computeMapping :: State -> Maybe FilePath -> LspM () (Maybe PositionMapping)
+ Futhark.Optimise.GenRedOpt: instance GHC.Classes.Eq Futhark.Optimise.GenRedOpt.Cost
+ Futhark.Optimise.GenRedOpt: optimiseGenRed :: Pass GPU GPU
+ Futhark.Optimise.HistAccs: histAccsGPU :: Pass GPU GPU
+ Futhark.Optimise.MergeGPUBodies: instance GHC.Base.Monoid Futhark.Optimise.MergeGPUBodies.Group
+ Futhark.Optimise.MergeGPUBodies: instance GHC.Base.Monoid Futhark.Optimise.MergeGPUBodies.Usage
+ Futhark.Optimise.MergeGPUBodies: instance GHC.Base.Semigroup Futhark.Optimise.MergeGPUBodies.Group
+ Futhark.Optimise.MergeGPUBodies: instance GHC.Base.Semigroup Futhark.Optimise.MergeGPUBodies.Usage
+ Futhark.Optimise.MergeGPUBodies: mergeGPUBodies :: Pass GPU GPU
+ Futhark.Optimise.ReduceDeviceSyncs: reduceDeviceSyncs :: Pass GPU GPU
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: MoveToDevice :: MigrationStatus
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: StayOnHost :: MigrationStatus
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: UsedOnHost :: MigrationStatus
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: analyseProg :: Prog GPU -> MigrationTable
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: data MigrationStatus
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: data MigrationTable
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: instance GHC.Base.Monoid Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.BodyStats
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: instance GHC.Base.Semigroup Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.BodyStats
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: instance GHC.Classes.Eq Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.MigrationStatus
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: instance GHC.Classes.Ord Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.MigrationStatus
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: instance GHC.Show.Show Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.MigrationStatus
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: shouldMove :: VName -> MigrationTable -> Bool
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: shouldMoveStm :: Stm GPU -> MigrationTable -> Bool
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: statusOf :: VName -> MigrationTable -> MigrationStatus
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable: usedOnHost :: VName -> MigrationTable -> Bool
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: Exhausted :: Exhaustion
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: FoundSink :: Result a
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: FromNode :: Id -> Exhaustion -> Routing
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: FromSource :: Routing
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: NoRoute :: Routing
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: Normal :: EdgeType
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: NotExhausted :: Exhaustion
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: Produced :: a -> Result a
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: Reversed :: EdgeType
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: ToNodes :: IdSet -> Maybe IdSet -> Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: ToSink :: Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: Vertex :: Id -> m -> Routing -> Edges -> Vertex m
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: [vertexEdges] :: Vertex m -> Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: [vertexId] :: Vertex m -> Id
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: [vertexMeta] :: Vertex m -> m
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: [vertexRouting] :: Vertex m -> Routing
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: addEdges :: Edges -> Id -> Graph m -> Graph m
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: adjust :: (Vertex m -> Vertex m) -> Id -> Graph m -> Graph m
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: connectToSink :: Id -> Graph m -> Graph m
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: data EdgeType
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: data Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: data Exhaustion
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: data Graph m
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: data Result a
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: data Routing
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: data Vertex m
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: data Visited a
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: declareEdges :: [Id] -> Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: empty :: Graph m
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: fold :: Graph m -> (a -> EdgeType -> Vertex m -> a) -> (a, Visited ()) -> EdgeType -> Id -> (a, Visited ())
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: insert :: Vertex m -> Graph m -> Graph m
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Base.Monoid (Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.RoutingResult a)
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Base.Monoid Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Base.Semigroup (Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.RoutingResult a)
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Base.Semigroup Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Base.Semigroup a => GHC.Base.Semigroup (Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Result a)
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Classes.Eq Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.EdgeType
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Classes.Eq Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Classes.Eq Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Exhaustion
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Classes.Eq Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Routing
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Classes.Eq a => GHC.Classes.Eq (Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Result a)
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Classes.Ord Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.EdgeType
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Classes.Ord Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Classes.Ord Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Exhaustion
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Classes.Ord Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Routing
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Show.Show Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Show.Show Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Exhaustion
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: instance GHC.Show.Show Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph.Routing
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: isSinkConnected :: Id -> Graph m -> Bool
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: lookup :: Id -> Graph m -> Maybe (Vertex m)
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: member :: Id -> Graph m -> Bool
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: none :: Visited a
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: oneEdge :: Id -> Edges
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: reduce :: Monoid a => Graph m -> (a -> EdgeType -> Vertex m -> a) -> Visited (Result a) -> EdgeType -> Id -> (Result a, Visited (Result a))
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: route :: Id -> Graph m -> (Maybe Id, Graph m)
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: routeMany :: [Id] -> Graph m -> ([Id], Graph m)
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: type Id = Int
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: type IdSet = IntSet
+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph: vertex :: Id -> m -> Vertex m
+ Futhark.Optimise.Simplify.Engine: blockMigrated :: SimplifiableRep rep => SimpleM rep (Lambda (Wise rep), Stms (Wise rep)) -> SimpleM rep (Lambda (Wise rep), Stms (Wise rep))
+ Futhark.Optimise.Simplify.Engine: constructBody :: SimplifiableRep rep => Stms (Wise rep) -> Result -> SimpleM rep (Body (Wise rep))
+ Futhark.Optimise.Simplify.Engine: isConsuming :: Aliased rep => BlockPred rep
+ Futhark.Optimise.Simplify.Engine: isDeviceMigrated :: SimplifiableRep rep => BlockPred (Wise rep)
+ Futhark.Optimise.Simplify.Rep: informBody :: Informing rep => Body rep -> Body (Wise rep)
+ Futhark.Optimise.Simplify.Rep: mkWiseStm :: (ASTRep rep, CanBeWise (Op rep)) => Pat (LetDec rep) -> StmAux (ExpDec rep) -> Exp (Wise rep) -> Stm (Wise rep)
+ Futhark.Optimise.TileLoops.Shared: changeEnv :: Env -> VName -> Exp GPU -> TileM Env
+ Futhark.Optimise.TileLoops.Shared: forLoop :: SubExp -> [VName] -> (VName -> [VName] -> Builder GPU (Body GPU)) -> Builder GPU VName
+ Futhark.Optimise.TileLoops.Shared: forLoop' :: SubExp -> [VName] -> (VName -> [VName] -> Builder GPU (Body GPU)) -> Builder GPU [VName]
+ Futhark.Optimise.TileLoops.Shared: index :: MonadBuilder m => String -> VName -> [VName] -> m VName
+ Futhark.Optimise.TileLoops.Shared: type Env = (WithEnv, IxFnEnv)
+ Futhark.Optimise.TileLoops.Shared: update :: MonadBuilder m => String -> VName -> [VName] -> SubExp -> m VName
+ Futhark.Pass.AD: applyAD :: Pass SOACS SOACS
+ Futhark.Pass.AD: applyADInnermost :: Pass SOACS SOACS
+ Futhark.Pass.AD: instance GHC.Classes.Eq Futhark.Pass.AD.Mode
+ Futhark.Pipeline: condPipeline :: (Prog rep -> Bool) -> Pipeline rep rep -> Pipeline rep rep
+ Futhark.Util: pairs :: [a] -> [(a, a)]
+ Futhark.Util: traverseFold :: (Monoid m, Traversable t, Applicative f) => (a -> f m) -> t a -> f m
+ Futhark.Util: unpairs :: [(a, a)] -> [a]
+ Futhark.Util.Console: inYellow :: String -> String
+ Futhark.Util.IntegralExp: pow :: IntegralExp e => e -> e -> e
+ Futhark.Util.ProgressBar: ProgressBar :: Int -> Double -> Double -> ProgressBar
+ Futhark.Util.ProgressBar: [progressBarBound] :: ProgressBar -> Double
+ Futhark.Util.ProgressBar: [progressBarElapsed] :: ProgressBar -> Double
+ Futhark.Util.ProgressBar: [progressBarSteps] :: ProgressBar -> Int
+ Futhark.Util.ProgressBar: data ProgressBar
+ Futhark.Util.ProgressBar: progressBar :: ProgressBar -> Text
+ Futhark.Util.ProgressBar: progressSpinner :: Int -> Text
+ Language.Futhark.Prop: determineSizeWitnesses :: StructType -> (Set VName, Set VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.AppExpBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.CaseBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.DecBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.DecBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.DimIndexBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ExpBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.FieldBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.IdentBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.LoopFormBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ModBindBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ModBindBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ModExpBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ModExpBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ModParamBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ModParamBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.PatBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ProgBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ProgBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.SigBindBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.SigBindBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.SigExpBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.SigExpBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.SpecBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.SpecBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.TypeBindBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.TypeBindBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.TypeDeclBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.TypeRefBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.TypeRefBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ValBindBase Language.Futhark.Syntax.Info Language.Futhark.Core.VName)
+ Language.Futhark.Syntax: instance GHC.Show.Show (Language.Futhark.Syntax.ValBindBase Language.Futhark.Syntax.NoInfo Language.Futhark.Core.Name)
+ Language.Futhark.Syntax: instance GHC.Show.Show vn => GHC.Show.Show (Language.Futhark.Syntax.AppExpBase Language.Futhark.Syntax.NoInfo vn)
+ Language.Futhark.Syntax: instance GHC.Show.Show vn => GHC.Show.Show (Language.Futhark.Syntax.CaseBase Language.Futhark.Syntax.NoInfo vn)
+ Language.Futhark.Syntax: instance GHC.Show.Show vn => GHC.Show.Show (Language.Futhark.Syntax.DimIndexBase Language.Futhark.Syntax.NoInfo vn)
+ Language.Futhark.Syntax: instance GHC.Show.Show vn => GHC.Show.Show (Language.Futhark.Syntax.ExpBase Language.Futhark.Syntax.NoInfo vn)
+ Language.Futhark.Syntax: instance GHC.Show.Show vn => GHC.Show.Show (Language.Futhark.Syntax.FieldBase Language.Futhark.Syntax.NoInfo vn)
+ Language.Futhark.Syntax: instance GHC.Show.Show vn => GHC.Show.Show (Language.Futhark.Syntax.IdentBase Language.Futhark.Syntax.NoInfo vn)
+ Language.Futhark.Syntax: instance GHC.Show.Show vn => GHC.Show.Show (Language.Futhark.Syntax.LoopFormBase Language.Futhark.Syntax.NoInfo vn)
+ Language.Futhark.Syntax: instance GHC.Show.Show vn => GHC.Show.Show (Language.Futhark.Syntax.PatBase Language.Futhark.Syntax.NoInfo vn)
+ Language.Futhark.Syntax: instance GHC.Show.Show vn => GHC.Show.Show (Language.Futhark.Syntax.TypeDeclBase Language.Futhark.Syntax.NoInfo vn)
+ Language.Futhark.TypeChecker.Unify: Usage :: Maybe String -> SrcLoc -> Usage
- Futhark.IR.GPU: soacType :: SOAC rep -> [Type]
+ Futhark.IR.GPU: soacType :: Typed (LParamInfo rep) => SOAC rep -> [Type]
- Futhark.IR.MC: soacType :: SOAC rep -> [Type]
+ Futhark.IR.MC: soacType :: Typed (LParamInfo rep) => SOAC rep -> [Type]
- Futhark.IR.SOACS.SOAC: soacType :: SOAC rep -> [Type]
+ Futhark.IR.SOACS.SOAC: soacType :: Typed (LParamInfo rep) => SOAC rep -> [Type]
- Futhark.IR.SegOp: segOpRules :: (HasSegOp rep, BuilderOps rep, Buildable rep) => RuleBook rep
+ Futhark.IR.SegOp: segOpRules :: (HasSegOp rep, BuilderOps rep, Buildable rep, Aliased rep) => RuleBook rep
- Futhark.LSP.Compile: tryReCompile :: MVar State -> Maybe FilePath -> LspT () IO ()
+ Futhark.LSP.Compile: tryReCompile :: IORef State -> Maybe FilePath -> LspT () IO ()
- Futhark.LSP.Handlers: handlers :: MVar State -> Handlers (LspM ())
+ Futhark.LSP.Handlers: handlers :: IORef State -> Handlers (LspM ())
- Futhark.LSP.State: State :: Maybe LoadedProg -> State
+ Futhark.LSP.State: State :: Maybe LoadedProg -> Map FilePath StaleFile -> State
- Futhark.Optimise.BlkRegTiling: mmBlkRegTiling :: Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))
+ Futhark.Optimise.BlkRegTiling: mmBlkRegTiling :: Env -> Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))
- Language.Futhark.Prop: progModuleTypes :: Ord vn => ProgBase f vn -> Set vn
+ Language.Futhark.Prop: progModuleTypes :: ProgBase Info VName -> Set VName
- Language.Futhark.TypeChecker.Types: checkForDuplicateNames :: MonadTypeChecker m => [UncheckedPat] -> m ()
+ Language.Futhark.TypeChecker.Types: checkForDuplicateNames :: MonadTypeChecker m => [UncheckedTypeParam] -> [UncheckedPat] -> m ()
- Language.Futhark.TypeChecker.Unify: doUnification :: Loc -> [TypeParam] -> StructType -> StructType -> Either TypeError StructType
+ Language.Futhark.TypeChecker.Unify: doUnification :: Loc -> [TypeParam] -> [TypeParam] -> StructType -> StructType -> Either TypeError StructType
Files
- docs/conf.py +86/−7
- docs/error-index.rst +339/−92
- docs/installation.rst +18/−40
- docs/man/futhark-test.rst +7/−10
- futhark.cabal +24/−5
- prelude/ad.fut +23/−0
- prelude/array.fut +4/−4
- prelude/prelude.fut +1/−0
- prelude/soacs.fut +7/−7
- prelude/zip.fut +4/−4
- rts/c/chaselev.h +0/−176
- rts/c/cuda.h +3/−1
- rts/python/opencl.py +6/−1
- src/Futhark/AD/Derivatives.hs +386/−0
- src/Futhark/AD/Fwd.hs +486/−0
- src/Futhark/AD/Rev.hs +444/−0
- src/Futhark/AD/Rev/Loop.hs +456/−0
- src/Futhark/AD/Rev/Map.hs +233/−0
- src/Futhark/AD/Rev/Monad.hs +551/−0
- src/Futhark/AD/Rev/Reduce.hs +204/−0
- src/Futhark/AD/Rev/SOAC.hs +90/−0
- src/Futhark/AD/Rev/Scan.hs +154/−0
- src/Futhark/AD/Rev/Scatter.hs +188/−0
- src/Futhark/Analysis/LastUse.hs +4/−0
- src/Futhark/Analysis/Metrics.hs +2/−0
- src/Futhark/Analysis/PrimExp.hs +87/−2
- src/Futhark/Bench.hs +18/−17
- src/Futhark/CLI/Bench.hs +32/−43
- src/Futhark/CLI/Dev.hs +5/−0
- src/Futhark/CLI/LSP.hs +3/−3
- src/Futhark/CLI/Literate.hs +27/−1
- src/Futhark/CLI/Main.hs +144/−0
- src/Futhark/CLI/Test.hs +17/−14
- src/Futhark/CodeGen/Backends/CCUDA.hs +24/−2
- src/Futhark/CodeGen/Backends/COpenCL.hs +2/−2
- src/Futhark/CodeGen/Backends/GenericPython.hs +2/−2
- src/Futhark/CodeGen/ImpCode.hs +3/−1
- src/Futhark/CodeGen/ImpGen/GPU.hs +6/−0
- src/Futhark/CodeGen/OpenCL/Heuristics.hs +2/−2
- src/Futhark/CodeGen/RTS/C.hs +0/−6
- src/Futhark/Compiler.hs +10/−3
- src/Futhark/Compiler/CLI.hs +0/−7
- src/Futhark/Compiler/Config.hs +19/−2
- src/Futhark/Compiler/Program.hs +38/−19
- src/Futhark/Construct.hs +9/−0
- src/Futhark/Doc/Generator.hs +16/−15
- src/Futhark/IR/Aliases.hs +16/−3
- src/Futhark/IR/GPU/Op.hs +43/−2
- src/Futhark/IR/GPU/Simplify.hs +35/−2
- src/Futhark/IR/Mem/Simplify.hs +1/−1
- src/Futhark/IR/Parse.hs +16/−1
- src/Futhark/IR/Primitive.hs +1/−1
- src/Futhark/IR/Prop/Aliases.hs +1/−1
- src/Futhark/IR/Prop/Names.hs +6/−0
- src/Futhark/IR/Prop/Types.hs +8/−2
- src/Futhark/IR/SOACS.hs +25/−0
- src/Futhark/IR/SOACS/SOAC.hs +48/−2
- src/Futhark/IR/SOACS/Simplify.hs +22/−7
- src/Futhark/IR/SegOp.hs +19/−8
- src/Futhark/IR/Syntax.hs +15/−6
- src/Futhark/IR/Syntax/Core.hs +5/−0
- src/Futhark/Internalise/Defunctorise.hs +9/−1
- src/Futhark/Internalise/Exps.hs +14/−0
- src/Futhark/LSP/Compile.hs +54/−28
- src/Futhark/LSP/Handlers.hs +15/−16
- src/Futhark/LSP/PositionMapping.hs +80/−0
- src/Futhark/LSP/State.hs +39/−3
- src/Futhark/LSP/Tool.hs +57/−5
- src/Futhark/Optimise/BlkRegTiling.hs +1199/−903
- src/Futhark/Optimise/CSE.hs +11/−4
- src/Futhark/Optimise/Fusion.hs +5/−4
- src/Futhark/Optimise/GenRedOpt.hs +432/−0
- src/Futhark/Optimise/HistAccs.hs +163/−0
- src/Futhark/Optimise/MergeGPUBodies.hs +634/−0
- src/Futhark/Optimise/ReduceDeviceSyncs.hs +848/−0
- src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable.hs +1620/−0
- src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable/Graph.hs +569/−0
- src/Futhark/Optimise/Simplify.hs +1/−1
- src/Futhark/Optimise/Simplify/Engine.hs +38/−4
- src/Futhark/Optimise/Simplify/Rep.hs +6/−5
- src/Futhark/Optimise/Sink.hs +50/−1
- src/Futhark/Optimise/TileLoops.hs +33/−26
- src/Futhark/Optimise/TileLoops/Shared.hs +140/−17
- src/Futhark/Pass.hs +3/−3
- src/Futhark/Pass/AD.hs +102/−0
- src/Futhark/Pass/ExpandAllocations.hs +1/−0
- src/Futhark/Pass/ExplicitAllocations/GPU.hs +2/−0
- src/Futhark/Pass/ExtractMulticore.hs +4/−0
- src/Futhark/Passes.hs +35/−3
- src/Futhark/Pipeline.hs +11/−10
- src/Futhark/Script.hs +2/−2
- src/Futhark/Test.hs +6/−2
- src/Futhark/Transform/FirstOrderTransform.hs +4/−0
- src/Futhark/Util.hs +20/−8
- src/Futhark/Util/Console.hs +5/−0
- src/Futhark/Util/IntegralExp.hs +2/−0
- src/Futhark/Util/ProgressBar.hs +57/−0
- src/Language/Futhark/Interpreter.hs +4/−0
- src/Language/Futhark/Prop.hs +82/−26
- src/Language/Futhark/Query.hs +1/−4
- src/Language/Futhark/Syntax.hs +64/−56
- src/Language/Futhark/TypeChecker/Modules.hs +48/−49
- src/Language/Futhark/TypeChecker/Monad.hs +2/−1
- src/Language/Futhark/TypeChecker/Terms.hs +7/−3
- src/Language/Futhark/TypeChecker/Terms/Monad.hs +13/−7
- src/Language/Futhark/TypeChecker/Terms/Pat.hs +6/−3
- src/Language/Futhark/TypeChecker/Types.hs +35/−25
- src/Language/Futhark/TypeChecker/Unify.hs +31/−14
- src/Language/Futhark/Warnings.hs +1/−1
- src/main.hs +4/−140
- unittests/Futhark/AD/DerivativesTests.hs +50/−0
- unittests/Futhark/IR/Mem/IxFunTests.hs +1/−0
- unittests/Language/Futhark/TypeChecker/TypesTests.hs +84/−65
- unittests/futhark_tests.hs +2/−0
docs/conf.py view
@@ -16,8 +16,11 @@ import sys import os import re-from pygments.lexer import RegexLexer+from pygments.lexer import RegexLexer, bygroups from pygments import token+from pygments import unistring as uni+from pygments.token import Text, Comment, Operator, Keyword, Name, String, \+ Number, Punctuation, Whitespace from sphinx.highlighting import lexers # If extensions (or modules to document with autodoc) are in another directory,@@ -103,18 +106,94 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' + class FutharkLexer(RegexLexer):+ """+ A Futhark lexer++ .. versionadded:: 2.8+ """ name = 'Futhark'+ url = 'https://futhark-lang.org/'+ aliases = ['futhark']+ filenames = ['*.fut']+ mimetypes = ['text/x-futhark'] + num_types = ('i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64')++ other_types = ('bool', )++ reserved = ('if', 'then', 'else', 'def', 'let', 'loop', 'in', 'with', 'type',+ 'val', 'entry', 'for', 'while', 'do', 'case', 'match',+ 'include', 'import', 'module', 'open', 'local', 'assert', '_')++ ascii = ('NUL', 'SOH', '[SE]TX', 'EOT', 'ENQ', 'ACK',+ 'BEL', 'BS', 'HT', 'LF', 'VT', 'FF', 'CR', 'S[OI]', 'DLE',+ 'DC[1-4]', 'NAK', 'SYN', 'ETB', 'CAN',+ 'EM', 'SUB', 'ESC', '[FGRU]S', 'SP', 'DEL')++ num_postfix = r'(%s)?' % '|'.join(num_types)++ identifier_re = '[a-zA-Z_][a-zA-Z_0-9\']*'++ # opstart_re = '+\-\*/%=\!><\|&\^'+ tokens = { 'root': [- (r'(if|then|else|let|loop|in|val|for|do|with|local|open|include|import|type|def|entry|module|while|module)\b', token.Keyword),- (r"#?[a-zA-Z_][a-zA-Z0-9_']*", token.Name),- (r"--.*", token.Comment),- (r'.', token.Text)- ]- }+ (r'--(.*?)$', Comment.Single),+ (r'\s+', Whitespace),+ (r'\(\)', Punctuation),+ (r'\b(%s)(?!\')\b' % '|'.join(reserved), Keyword.Reserved),+ (r'\b(%s)(?!\')\b' % '|'.join(num_types + other_types), Keyword.Type), + # Identifiers+ (r'#\[([a-zA-Z_\(\) ]*)\]', Comment.Preproc),+ (r'[#!]?(%s\.)*%s' % (identifier_re, identifier_re), Name),++ (r'\\', Operator),+ (r'[-+/%=!><|&*^][-+/%=!><|&*^.]*', Operator),+ (r'[][(),:;`{}?.\']', Punctuation),++ # Numbers+ (r'0[xX]_*[\da-fA-F](_*[\da-fA-F])*_*[pP][+-]?\d(_*\d)*' + num_postfix,+ Number.Float),+ (r'0[xX]_*[\da-fA-F](_*[\da-fA-F])*\.[\da-fA-F](_*[\da-fA-F])*'+ r'(_*[pP][+-]?\d(_*\d)*)?' + num_postfix, Number.Float),+ (r'\d(_*\d)*_*[eE][+-]?\d(_*\d)*' + num_postfix, Number.Float),+ (r'\d(_*\d)*\.\d(_*\d)*(_*[eE][+-]?\d(_*\d)*)?' + num_postfix, Number.Float),+ (r'0[bB]_*[01](_*[01])*' + num_postfix, Number.Bin),+ (r'0[xX]_*[\da-fA-F](_*[\da-fA-F])*' + num_postfix, Number.Hex),+ (r'\d(_*\d)*' + num_postfix, Number.Integer),++ # Character/String Literals+ (r"'", String.Char, 'character'),+ (r'"', String, 'string'),+ # Special+ (r'\[[a-zA-Z_\d]*\]', Keyword.Type),+ (r'\(\)', Name.Builtin),+ ],+ 'character': [+ # Allows multi-chars, incorrectly.+ (r"[^\\']'", String.Char, '#pop'),+ (r"\\", String.Escape, 'escape'),+ ("'", String.Char, '#pop'),+ ],+ 'string': [+ (r'[^\\"]+', String),+ (r"\\", String.Escape, 'escape'),+ ('"', String, '#pop'),+ ],++ 'escape': [+ (r'[abfnrtv"\'&\\]', String.Escape, '#pop'),+ (r'\^[][' + uni.Lu + r'@^_]', String.Escape, '#pop'),+ ('|'.join(ascii), String.Escape, '#pop'),+ (r'o[0-7]+', String.Escape, '#pop'),+ (r'x[\da-fA-F]+', String.Escape, '#pop'),+ (r'\d+', String.Escape, '#pop'),+ (r'(\s+)(\\)', bygroups(Whitespace, String.Escape), '#pop'),+ ],+ } lexers['futhark'] = FutharkLexer()
docs/error-index.rst view
@@ -16,8 +16,10 @@ A core principle of uniqueness typing (see :ref:`in-place-updates`) is that after a variable is "consumed", it must not be used again. For-example, this is invalid, and will result in the error above::+example, this is invalid, and will result in the error above: +.. code-block:: futhark+ let y = x with [0] = 0 in x @@ -27,14 +29,18 @@ consumed, its *aliases* are also considered consumed. Aliasing is the possibility of two variables occupying the same memory at run-time. For example, this will fail as above, because ``y`` and ``x`` are-aliased::+aliased: +.. code-block:: futhark+ let y = x let z = y with [0] = 0 in x -We can always break aliasing by using a ``copy`` expression::+We can always break aliasing by using a ``copy`` expression: +.. code-block:: futhark+ let y = copy x let z = y with [0] = 0 in x@@ -46,17 +52,21 @@ This error message occurs for programs that try to perform a consumption (such as an in-place update) on variables that are not-consumable. For example, it would occur for the following program::+consumable. For example, it would occur for the following program: - let f (a: []i32) =+.. code-block:: futhark++ def f (a: []i32) = let a[0] = a[0]+1 in a Only arrays with a a *unique array type* can be consumed. Such a type is written by prefixing the array type with an asterisk. The program-could be fixed by writing it like this::+could be fixed by writing it like this: - let f (a: *[]i32) =+.. code-block:: futhark++ def f (a: *[]i32) = let a[0] = a[0]+1 in a @@ -65,9 +75,11 @@ :ref:`in-place-updates` for the full details. You can always obtain a unique copy of an array by using-``copy``::+``copy``: - let f (a: []i32) =+.. code-block:: futhark++ def f (a: []i32) = let a = copy a let a[0] = a[0]+1 in a@@ -80,18 +92,22 @@ "Unique-typed return value of *x* is aliased to *y*, which is not consumable" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This can be caused by a function like this::+This can be caused by a function like this: - let f (xs: []i32) : *[]i32 = xs+.. code-block:: futhark + def f (xs: []i32) : *[]i32 = xs+ We are saying that ``f`` returns a *unique* array - meaning it has no aliases - but at the same time, it aliases the parameter *xs*, which is not marked as being unique (see :ref:`in-place-updates`). This violates one of the core guarantees provided by uniqueness types, namely that a unique return value does not alias any value that might be used in the future. Imagine if this was permitted, and we had a-program that used ``f``::+program that used ``f``: +.. code-block:: futhark+ let b = f a let b[0] = x ...@@ -102,10 +118,12 @@ As with most uniqueness errors, it can be fixed by using ``copy xs`` to break the aliasing. We can also change the type of ``f`` to take a-unique array as input::+unique array as input: - let f (xs: *[]i32) : *[]i32 = xs+.. code-block:: futhark + def f (xs: *[]i32) : *[]i32 = xs+ This makes ``xs`` "consumable", in the sense used by the error message. .. _unique-return-aliased:@@ -113,46 +131,55 @@ "A unique-typed component of the return value of *x* is aliased to some other component" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Caused by programs like the following::+Caused by programs like the following: - let main (xs: *[]i32) : (*[]i32, *[]i32) = (xs, xs)+.. code-block:: futhark + def main (xs: *[]i32) : (*[]i32, *[]i32) = (xs, xs)+ While we are allowed to "consume" ``xs``, as it is a unique parameter, this function is trying to return two unique values that alias each other. This violates one of the core guarantees provided by uniqueness types, namely that a unique return value does not alias any value that might be used in the future (see :ref:`in-place-updates`) - and in this case, the two values alias each other. We can fix this by-inserting copies to break the aliasing::+inserting copies to break the aliasing: - let main (xs: *[]i32) : (*[]i32, *[]i32) = (xs, copy xs)+.. code-block:: futhark + def main (xs: *[]i32) : (*[]i32, *[]i32) = (xs, copy xs)+ .. _consuming-parameter: "Consuming parameter passed non-unique argument" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Caused by programs like the following::+Caused by programs like the following: - let update (xs: *[]i32) = xs with [0] = 0+.. code-block:: futhark - let f (ys: []i32) = update ys+ def update (xs: *[]i32) = xs with [0] = 0 + def f (ys: []i32) = update ys+ The update ``function`` *consumes* its ``xs`` argument to perform an :ref:`in-place update <in-place-updates>`, as denoted by the asterisk before the type. However, the ``f`` function tries to pass an array that it is not allowed to consume (no asterisk before the type). - One solution is to change the type of ``f`` so that it also consumes-its input, which allows it to pass it on to ``update``::+its input, which allows it to pass it on to ``update``: - let f (ys: *[]i32) = update ys+.. code-block:: futhark -Another solution to ``copy`` the array that we pass to ``update``::+ def f (ys: *[]i32) = update ys - let f (ys: []i32) = update (copy ys)+Another solution to ``copy`` the array that we pass to ``update``: +.. code-block:: futhark++ def f (ys: []i32) = update (copy ys)+ .. _consuming-argument: "Non-consuming higher-order parameter passed consuming argument."@@ -160,75 +187,97 @@ This error occurs when we have a higher-order function that expects a function that does *not* consume its arguments, and we pass it one-that does::+that does: - let apply 'a 'b (f: a -> b) (x: a) = f x+.. code-block:: futhark - let consume (xs: *[]i32) = xs with [0] = 0+ def apply 'a 'b (f: a -> b) (x: a) = f x - let f (arr: *[]i32) = apply consume arr+ def consume (xs: *[]i32) = xs with [0] = 0 + def f (arr: *[]i32) = apply consume arr+ We can fix this by changing ``consume`` so that it does not have to-consume its argument, by adding a ``copy``::+consume its argument, by adding a ``copy``: - let consume (xs: []i32) = copy xs with [0] = 0+.. code-block:: futhark + def consume (xs: []i32) = copy xs with [0] = 0+ Or we can create a variant of ``apply`` that accepts a consuming-function::+function: - let apply 'a 'b (f: *a -> b) (x: *a) = f x+.. code-block:: futhark + def apply 'a 'b (f: *a -> b) (x: *a) = f x+ .. _alias-free-variable: "Function result aliases the free variable *x*" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Caused by definitions such as the following::+Caused by definitions such as the following: - let x = [1,2,3]+.. code-block:: futhark - let f () = x+ def x = [1,2,3] + def f () = x+ To simplify the tracking of aliases, the Futhark type system requires that the result of a function may only alias the function parameters,-not any free variables. Use ``copy`` to fix this::+not any free variables. Use ``copy`` to fix this: - let f () = copy x+.. code-block:: futhark + def f () = copy x+ .. _inaccessible-size: "Parameter *x* refers to size *y* which will not be accessible to the caller ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This happens when the size of an array parameter depends on a name-that cannot be expressed in the function type::+that cannot be expressed in the function type: - let f (x: i64, y: i64) (A: [x]bool) = true+.. code-block:: futhark -Intuitively, this function might have the following type::+ def f (x: i64, y: i64) (A: [x]bool) = true +Intuitively, this function might have the following type:++.. code-block:: futhark+ val f : (x: i64, y: i64) -> [x]bool -> bool But this is not currently a valid Futhark type. In a function type, each parameter can be named *as a whole*, but it cannot be taken apart in a pattern. In this case, we could fix it by splitting the tuple-parameter into two separate parameters::+parameter into two separate parameters: - let f (x: i64) (y: i64) (A: [x]bool) = true+.. code-block:: futhark -This gives the following type::+ def f (x: i64) (y: i64) (A: [x]bool) = true +This gives the following type:++.. code-block:: futhark+ val f : (x: i64) -> (y: i64) -> [x]bool -> bool Another workaround is to loosen the static safety, and use a size-coercion to give A its expected size::+coercion to give A its expected size: - let f (x: i64, y: i64) (A_unsized: []bool) =+.. code-block:: futhark++ def f (x: i64, y: i64) (A_unsized: []bool) = let A = A_unsized :> [x]bool in true -This will produce a function with the following type::+This will produce a function with the following type: +.. code-block:: futhark+ val f [d] : (i64, i64) -> [d]bool -> bool This does however lose the constraint that the size of the array must@@ -236,10 +285,12 @@ fail at run-time. The error is not always due to an explicit type annotation. It might-also be due to size inference::+also be due to size inference: - let f (x: i64, y: i64) (A: []bool) = zip A (iota x)+.. code-block:: futhark + def f (x: i64, y: i64) (A: []bool) = zip A (iota x)+ Here the type rules force ``A`` to have size ``x``, leading to a problematic type. It can be fixed using the techniques above. @@ -251,14 +302,18 @@ "Size *x* unused in pattern." ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Caused by expressions like this::+Caused by expressions like this: - let [n] (y: i32) = x+.. code-block:: futhark -And functions like this::+ def [n] (y: i32) = x - let f [n] (x: i32) = x+And functions like this: +.. code-block:: futhark++ def f [n] (x: i32) = x+ Since ``n`` is not the size of anything, it cannot be assigned a value at runtime. Hence this program is rejected. @@ -269,9 +324,11 @@ Causality check errors occur when the program is written in such a way that a size is needed before it is actually computed. See-:ref:`causality` for the full rules. Contrived example::+:ref:`causality` for the full rules. Contrived example: - let f (b: bool) (xs: []i32) =+.. code-block:: futhark++ def f (b: bool) (xs: []i32) = let a = [] : [][]i32 let b = [filter (>0) xs] in a[0] == b[0]@@ -293,9 +350,11 @@ This error occurs when you define a function that can never be applied, as it requires an input of a specific size, and that size is-not known. Somewhat contrived example::+not known. Somewhat contrived example: - let f (x: bool) =+.. code-block:: futhark++ def f (x: bool) = let n = if x then 10 else 20 in \(y: [n]bool) -> ... @@ -311,9 +370,11 @@ actually mean to. However, in the case that that the above really is what you intend, the workaround is to make the function fully polymorphic, and then perform a size coercion to the desired size-inside the function body itself::+inside the function body itself: - let f (x: bool) =+.. code-block:: futhark++ def f (x: bool) = let n = if x then 10 else 20 in \(y_any: []bool) -> let y = y_any :> [n]bool@@ -328,17 +389,23 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This occurs most commonly when we use function composition with one or-more functions that return an *existential size*. Example::+more functions that return an *existential size*. Example: +.. code-block:: futhark+ filter (>0) >-> length -The ``filter`` function has this type::+The ``filter`` function has this type: +.. code-block:: futhark+ val filter [n] 't : (t -> bool) -> [n]t -> ?[m].[m]t That is, ``filter`` returns an array whose size is not known until the-function actually returns. The ``length`` function has this type::+function actually returns. The ``length`` function has this type: +.. code-block:: futhark+ val length [n] 't : [n]t -> i64 Whenever ``length`` occurs (as in the composition above), the type@@ -351,8 +418,10 @@ type of ``length``, and therefore the program is rejected. The common workaround is to use *pipelining* instead of composition-whenever we use functions with existential return types::+whenever we use functions with existential return types: +.. code-block:: futhark+ xs |> filter (>0) |> length This works because ``|>`` is left-associative, and hence the ``xs |>@@ -362,6 +431,155 @@ We can of course also write it as ``length (filter (>0) xs)``, with no use of either pipelining or composition. +.. _unused-existential:++"Existential size *n* not used as array size"+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~++This error occurs for type expressions that use explicit existential+quantification in an incorrect way, such as the following examples:++.. code-block:: futhark++ ?[n].bool++ ?[n].bool -> [n]bool++When we use existential quantification, we are required to use the+size within its scope, *and* it must not exclusively be used to the+right of function arrow.++To understand the motivation behind this rule, consider that when we+use an existential quantifier we are saying that there is *some size*,+it just cannot be known statically, but must be read from some value+(i.e. array) at runtime. In the first example above, the existential+size ``n`` is not used at all, so the actual value cannot be+determined at runtime. In the second example, while an array+``[n]bool`` does exist, it is part of a function type, and at runtime+functions are black boxes and don't "carry" the size of their+parameter or result types.++The workaround is to actually use the existential size. This can be+as simple as adding a *witness array* of type ``[n]()``:++.. code-block:: futhark++ ?[n].([n](),bool)++ ?[n].([n](), bool -> [n]bool)++Such an array will take up no space at runtime.++.. _unify-param-existential:++"Parameter *x* used as size would go out of scope."+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~++This error tends to happen because higher-order functions are used in+a way that causes a size requirement to become impossible to+constrait. Real programs that run into this issue are quite complex,+but to illustrate the problem, consider the following contrived+function:++.. code-block:: futhark++ def f (n: i64) (m: i64) (b: [n][m]bool) = b[0,0]++We have the following type:++.. code-block:: futhark++ val f : (n: i64) -> (m: i64) -> (b: [n][m]bool) -> bool++Now suppose we say:++.. code-block:: futhark++ def g = uncurry f++What should be the type of ``g``? Intuitively, something like this:++.. code-block:: futhark++ val g : (n: i64, m: i64) -> (b: [n][m]bool) -> bool++But this is *not* expressible in the Futhark type system - and even if+it were, it would not be easy to infer this in general, as it depends+on exactly what ``uncurry`` does, which the type checker does not+know.++As a workaround, we can use explicit type annotation and size+coercions to give ``g`` an acceptable type:++.. code-block:: futhark++ def g [a][b] (n,m) (b: [a][b]bool) = f n m (b :> [n][m]bool)++Another workaround, which is often the right one in cases not as+contrived as above, is to modify ``f`` itself to produce a *witness*+of the constraint, in the form of an array of shape ``[n][m]``:++.. code-block:: futhark++ def f (n: i64) (m: i64) : ([n][m](), [n][m]bool -> bool) =+ (replicate n (replicate m ()), \b -> b[0,0])++Then ``uncurry f`` works just fine and has the following type:++.. code-block:: futhark++ (i64, i64) -> ?[n][m].([n][m](), [n][m]bool -> bool)++Programming with such *explicit size witnesses* is a fairly advanced+technique, but often necessary when writing advanced size-dependent+code.++.. _ambiguous-size:++"Ambiguous size *x*"+~~~~~~~~~~~~~~~~~~~~++There are various sources for this error, but they all have the same+ultimate cause: the type checker cannot figure out how some symbolic+size name should be resolved to a concrete size. The simplest+example, although contrived, is probably this:++.. code-block:: futhark++ let [n][m] (xss: [n][m]i64) = []++The type checker can infer that ``n`` should be zero, but how can it+possibly figure out the shape of the (non-existent) rows of the+two-dimensional array? This can be fixed in many ways, but adding a+type ascription to the array is one of them: ``[] : [0][2]i64``.++Another common case arises when using holes. For an expression+``length ???``, how would the type checker figure out the intended+size of the array that the hole represents? Again, this can be solved+with a type ascription: ``length (??? : [10]bool)``.++Finally, ambiguous sizes can also occur for functions that use size+parameters only in "non-witnessing" position, meaning sizes that are+not actually uses as sizes of real arrays. An example:++.. code-block:: futhark++ def f [n] (g: [n]i64 -> i64) : i64 = n++ def main = f (\xs -> xs[0])++Note that ``f`` is a higher order function, and that the size+parameter ``n`` is only used in the type of the ``g`` function.+Futhark's value model is such that given a value of type ``[n]i64 ->+i64``, we cannot extract an ``n`` from it. Using a function such as+``f`` is only valid when ``n`` can be inferred from the usage, which+is not the case here. Again, we can fix it by adding a type+ascription to disambiguate:++.. code-block:: futhark++ def main = f (\(xs:[1]i64) -> xs[0])+ Module errors ------------- @@ -371,18 +589,22 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This occurs when the program uses the ``entry`` keyword inside a-module::+module: +.. code-block:: futhark+ module m = { entry f x = x + 1 } Entry points can only be declared at the top level of a file. When we wish to make a function from inside a module available as an entry-point, we must define a wrapper function::+point, we must define a wrapper function: +.. code-block:: futhark+ module m = {- let f x = x + 1+ def f x = x + 1 } entry f = m.f@@ -390,18 +612,22 @@ .. _module-is-parametric: "Module *x* is a parametric module-----------------------------------+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -A parametric module is a module-level function::+A parametric module is a module-level function: +.. code-block:: futhark+ module PM (P: {val x : i64}) = {- let y = x + 2+ def y = x + 2 } If we directly try to access the component of ``PM``, as ``PM.y``, we will get an error. To use ``PM`` we must first apply it to a module-of the expected type::+of the expected type: +.. code-block:: futhark+ module M = PM { val x = 2 : i64 } Now we can say ``M.y``. See :ref:`module-system` for more.@@ -416,14 +642,18 @@ This occurs for overloaded constants such as ``1234`` that are inferred by context to have a type that is too narrow for their value.-Example::+Example: +.. code-block::+ 257 : u8 It is not an error to have a *non-overloaded* numeric constant whose value is too large for its type. The following is perfectly-cromulent::+cromulent: +.. code-block::+ 257u8 In such cases, the behaviour is overflow (so this is equivalent to@@ -435,30 +665,38 @@ ~~~~~~~~~~~~~~~~~~~ There are various cases where the type checker is unable to infer the-full type of something. For example::+full type of something. For example: - let f r = r.x+.. code-block:: futhark + def f r = r.x+ We know that ``r`` must be a record with a field called ``x``, but maybe the record could also have other fields as well. Instead of assuming a perhaps too narrow type, the type checker signals an error. The solution is always to add a type annotation in one or more places-to disambiguate the type::+to disambiguate the type: - let f (r: {x:bool, y:i32}) = r.x+.. code-block:: futhark + def f (r: {x:bool, y:i32}) = r.x+ Usually the best spot to add such an annotation is on a function parameter, as above. But for ambiguous sum types, we often have to-put it on the return type. Consider::+put it on the return type. Consider: - let f (x: bool) = #some x+.. code-block:: futhark + def f (x: bool) = #some x+ The type of this function is ambiguous, because the type checker must know what other possible contructors (apart from ``#some``) are-possible. We fix it with a type annotation on the return type::+possible. We fix it with a type annotation on the return type: - let f (x: bool) : (#some bool | #none) = #just x+.. code-block:: futhark + def f (x: bool) : (#some bool | #none) = #just x+ See :ref:`typeabbrevs` for how to avoid typing long types in several places. @@ -477,26 +715,31 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Futhark requires ``match`` expressions to be *exhaustive* - that is,-cover all possible forms of the value being pattern-matches.-Example::+cover all possible forms of the value being matched. Example: - let f (x: i32) =+.. code-block:: futhark++ def f (x: i32) = match x case 0 -> false case 1 -> true Usually this is an actual bug, and you fix it by adding the missing cases. But sometimes you *know* that the missing cases will never actually occur at run-time. To satisfy the type checker, you can turn-the final case into a wildcard that matches anything::+the final case into a wildcard that matches anything: - let f (x: i32) =+.. code-block:: futhark++ def f (x: i32) = match x case 0 -> false case _ -> true Alternatively, you can add a wildcard case that explicitly asserts-that it should never happen::+that it should never happen: - let f (x: i32) =+.. code-block:: futhark++ def f (x: i32) = match x case 0 -> false case 1 -> true case _ -> assert false false@@ -511,13 +754,17 @@ When performing a :ref:`record update <record_update>`, the type of the field we are updating must be known. This restriction is based on a limitation in the type type checker, so the notion of "known" is a bit-subtle::+subtle: - let f r : {x:i32} = r with x = 0+.. code-block:: futhark + def f r : {x:i32} = r with x = 0+ Even though the return type annotation disambiguates the type, this program still fails to type check. This is because the return type is not consulted until *after* the body of the function has been checked.-The solution is to put a type annotation on the parameter instead::+The solution is to put a type annotation on the parameter instead: - let f (r : {x:i32}) = r with x = 0+.. code-block:: futhark++ def f (r : {x:i32}) = r with x = 0
docs/installation.rst view
@@ -36,14 +36,13 @@ Compiling from source --------------------- -The recommended way to compile Futhark is with the `Haskell Tool-Stack`_, which handles dependencies and compilation of the Futhark-compiler. You will therefore need to install the ``stack`` tool.-Fortunately, the ``stack`` developers provide ample documentation-about `installing Stack`_ on a multitude of operating systems. If-you're lucky, it may even be in your local package repository.+To compile Futhark you must first install an appropriate version of+GHC, either with [ghcup](https://www.haskell.org/ghcup/) or a package+manager. Any version since GHC 8.10 should work. You also need the+``cabal`` command line program, which ghcup will install for you as+well. -You can either retrieve a `source release tarball+You then either retrieve a `source release tarball <https://github.com/diku-dk/futhark/releases>`_ or perform a checkout of our Git repository:: @@ -53,48 +52,27 @@ $ cd futhark -To get all the prerequisites for building the Futhark compiler-(including, if necessary, the appropriate version of the Haskell-compiler), run::+First you must run the following command to download metadata about+Futhark's dependencies:: - $ stack setup+ $ make configure -Note that this will not install anything system-wide and will have no-effect outside the Futhark build directory. Now you can run the-following command to build the Futhark compiler, including all-dependencies::+To build the Futhark compiler and all of its dependencies, run:: - $ stack build+ $ make build -The Futhark compiler and its tools will now be built. This step-typically requires at least 8GiB of memory. You may be able to build-it on a smaller machine by adding the ``--fast`` option, although the-resulting Futhark compiler binary will run slower.+This step typically requires at least 8GiB of memory. This will+create files in your ``~/.cabal`` and ``~/.ghc`` directories. After building, you can copy the binaries to your ``$HOME/.local/bin`` directory by running:: - $ stack install--Note that this does not install the Futhark manual pages.--Compiling with ``cabal``-~~~~~~~~~~~~~~~~~~~~~~~~--You can also compile Futhark with ``cabal``. If so, you must install-an appropriate version of GHC (usually the newest) and ``cabal``-yourself, for example through your favourite package manager. On-Linux, you can always use `ghcup-<https://gitlab.haskell.org/haskell/ghcup>`_. Then clone the-repository as listed above and run::-- $ cabal update- $ cabal build--To install the Futhark binaries to a specific location, for example-``$HOME/.local/bin``, run::+ $ make install - $ cabal install --install-method=copy --overwrite-policy=always --installdir=$HOME/.local/bin/+You can set the ``PREFIX`` environment variable to indicate a+different installation path. Note that this does not install the+Futhark manual pages. You can delete ``~/.cabal`` and ``~/.ghc``+after this if you wish - the ``futhark`` binary will still work. Installing from a precompiled snapshot --------------------------------------
docs/man/futhark-test.rst view
@@ -14,9 +14,10 @@ DESCRIPTION =========== -This tool is used to test Futhark programs based on input/output-datasets. If a directory is given, all contained files with a-``.fut`` extension are considered.+Test Futhark programs based on input/output datasets. All contained+``.fut`` files within a given directory are considered. By default,+tests are carried out with compiled code. This can be changed with+the ``-i`` option. A Futhark test program is an ordinary Futhark program, with at least one test block describing input/output test cases and possibly other@@ -89,10 +90,6 @@ This is used to test the type checker. -By default, both the interpreter and compiler is run on all test cases-(except those that have specified ``compiled``), although this can be-changed with command-line options to ``futhark test``.- Tuple syntax is not supported when specifying input and output values. Instead, you can write an N-tuple as its constituent N values. Beware of syntax errors in the values - the errors reported by@@ -135,12 +132,12 @@ By default, ``--cache-file`` is not passed. -c- Only run compiled code - do not run any interpreters.+ Only run compiled code - do not run the interpreter. This is the+ default. -C Compile the programs, but do not run them. - --concurrency=NUM The number of tests to run concurrently. Defaults to the number of@@ -153,7 +150,7 @@ where *foo* is the backend used. -i- Only interpret - do not run any compilers.+ Test with the interpreter. -t Type-check the programs, but do not run them.
futhark.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: futhark-version: 0.21.10+version: 0.21.11 synopsis: An optimising compiler for a functional, array-oriented language. description: Futhark is a small programming language designed to be compiled to@@ -55,6 +55,16 @@ exposed-modules: Futhark Futhark.Actions+ Futhark.AD.Derivatives+ Futhark.AD.Fwd+ Futhark.AD.Rev+ Futhark.AD.Rev.Loop+ Futhark.AD.Rev.Map+ Futhark.AD.Rev.Monad+ Futhark.AD.Rev.Reduce+ Futhark.AD.Rev.Scan+ Futhark.AD.Rev.Scatter+ Futhark.AD.Rev.SOAC Futhark.Analysis.Alias Futhark.Analysis.CallGraph Futhark.Analysis.DataDependencies@@ -88,6 +98,7 @@ Futhark.CLI.Doc Futhark.CLI.Literate Futhark.CLI.LSP+ Futhark.CLI.Main Futhark.CLI.Misc Futhark.CLI.Multicore Futhark.CLI.MulticoreWASM@@ -216,6 +227,7 @@ Futhark.LSP.Handlers Futhark.LSP.Tool Futhark.LSP.State+ Futhark.LSP.PositionMapping Futhark.MonadFreshNames Futhark.Optimise.BlkRegTiling Futhark.Optimise.CSE@@ -223,12 +235,18 @@ Futhark.Optimise.Fusion Futhark.Optimise.Fusion.Composing Futhark.Optimise.Fusion.LoopKernel+ Futhark.Optimise.GenRedOpt+ Futhark.Optimise.HistAccs Futhark.Optimise.InPlaceLowering Futhark.Optimise.InPlaceLowering.LowerIntoStm Futhark.Optimise.InPlaceLowering.SubstituteIndices Futhark.Optimise.InliningDeadFun Futhark.Optimise.MemoryBlockMerging Futhark.Optimise.MemoryBlockMerging.GreedyColoring+ Futhark.Optimise.MergeGPUBodies+ Futhark.Optimise.ReduceDeviceSyncs+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable+ Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph Futhark.Optimise.Simplify Futhark.Optimise.Simplify.Engine Futhark.Optimise.Simplify.Rep@@ -244,6 +262,7 @@ Futhark.Optimise.TileLoops.Shared Futhark.Optimise.Unstream Futhark.Pass+ Futhark.Pass.AD Futhark.Pass.ExpandAllocations Futhark.Pass.ExplicitAllocations Futhark.Pass.ExplicitAllocations.GPU@@ -284,6 +303,7 @@ Futhark.Util.Log Futhark.Util.Options Futhark.Util.Pretty+ Futhark.Util.ProgressBar Futhark.Util.Table Futhark.Version Language.Futhark@@ -336,13 +356,14 @@ , bmp >=1.2.6.3 , containers >=0.6.2.1 , cryptohash-md5+ , Diff >=0.4.1 , directory >=1.3.0.0 , directory-tree >=0.12.1 , dlist >=0.6.0.1 , file-embed >=0.0.14.0 , filepath >=1.4.1.1 , free >=4.12.4- , futhark-data >= 1.0.3.0+ , futhark-data >= 1.1.0.0 , futhark-server >= 1.1.2.1 , futhark-manifest >= 1.0.0.0 , githash >=0.1.6.1@@ -379,19 +400,17 @@ executable futhark main-is: src/main.hs- other-modules:- Paths_futhark ghc-options: -Wall -Wcompat -Wno-incomplete-uni-patterns -Wredundant-constraints -Wincomplete-record-updates -Wmissing-export-lists -Wunused-packages -threaded -rtsopts "-with-rtsopts=-N -qg1 -A16M" build-depends: base , futhark- , text default-language: Haskell2010 test-suite unit type: exitcode-stdio-1.0 main-is: futhark_tests.hs other-modules:+ Futhark.AD.DerivativesTests Futhark.BenchTests Futhark.Pkg.SolveTests Futhark.IR.Prop.RearrangeTests
+ prelude/ad.fut view
@@ -0,0 +1,23 @@+-- | Definitions related to automatic differentiation.+--+-- **Warning:** This is an experimental feature. It has several known+-- bugs. Documentation is very sparse. You probably don't want to+-- use this unless you have the soul of a hero.++-- | Jacobian-Vector Product ("forward mode"), producing also the+-- primal result as the first element of the result tuple.+let jvp2 'a 'b (f: a -> b) (x: a) (x': a) : (b, b) =+ intrinsics.jvp2 (f, x, x')++-- | Vector-Jacobian Product ("reverse mode"), producing also the+-- primal result as the first element of the result tuple.+let vjp2 'a 'b (f: a -> b) (x: a) (y': b) : (b, a) =+ intrinsics.vjp2 (f, x, y')++-- | Jacobian-Vector Product ("forward mode").+let jvp 'a 'b (f: a -> b) (x: a) (x': a) : b =+ (jvp2 f x x').1++-- | Vector-Jacobian Product ("reverse mode").+let vjp 'a 'b (f: a -> b) (x: a) (y': b) : a =+ (vjp2 f x y').1
prelude/array.fut view
@@ -49,12 +49,12 @@ -- -- **Complexity:** O(1). def split [n] 't (i: i64) (xs: [n]t): ([i]t, []t) =- (xs[:i] :> [i]t, xs[i:])+ (xs[0:i], xs[i:]) -- | Return the elements of the array in reverse order. -- -- **Complexity:** O(1).-def reverse [n] 't (x: [n]t): [n]t = x[::-1] :> [n]t+def reverse [n] 't (x: [n]t): [n]t = x[::-1] -- | Concatenate two arrays. Warning: never try to perform a reduction -- with this operator; it will not work.@@ -78,7 +78,7 @@ -- For example, if `b==rotate r a`, then `b[x] = a[x+r]`. -- -- **Complexity:** O(1).-def rotate [n] 't (r: i64) (xs: [n]t): [n]t = intrinsics.rotate (r, xs) :> *[n]t+def rotate [n] 't (r: i64) (xs: [n]t): [n]t = intrinsics.rotate (r, xs) -- | Construct an array of consecutive integers of the given length, -- starting at 0.@@ -152,7 +152,7 @@ -- -- **Complexity:** O(1). def transpose [n] [m] 't (a: [n][m]t): [m][n]t =- intrinsics.transpose a :> [m][n]t+ intrinsics.transpose a -- | True if all of the input elements are true. Produces true on an -- empty array.
prelude/prelude.fut view
@@ -5,6 +5,7 @@ open import "array" open import "math" open import "functional"+open import "ad" -- | Create single-precision float from integer. def r32 (x: i32): f32 = f32.i32 x
prelude/soacs.fut view
@@ -48,7 +48,7 @@ -- -- **Span:** *O(S(f))* def map 'a [n] 'x (f: a -> x) (as: [n]a): *[n]x =- intrinsics.map (f, as) :> *[n]x+ intrinsics.map (f, as) -- | Apply the given function to each element of a single array. --@@ -150,7 +150,7 @@ -- -- **Span:** *O(log(n) ✕ W(op))* def scan [n] 'a (op: a -> a -> a) (ne: a) (as: [n]a): *[n]a =- intrinsics.scan (op, ne, as) :> *[n]a+ intrinsics.scan (op, ne, as) -- | Remove all those elements of `as` that do not satisfy the -- predicate `p`.@@ -219,7 +219,7 @@ -- -- **Span:** *O(S(f))* def map_stream [n] 'a 'b (f: (k: i64) -> [k]a -> [k]b) (as: [n]a): *[n]b =- intrinsics.map_stream (f, as) :> *[n]b+ intrinsics.map_stream (f, as) -- | Similar to `map_stream`@term, but the chunks do not necessarily -- correspond to subsequences of the original array (they may be@@ -229,7 +229,7 @@ -- -- **Span:** *O(S(f))* def map_stream_per [n] 'a 'b (f: (k: i64) -> [k]a -> [k]b) (as: [n]a): *[n]b =- intrinsics.map_stream_per (f, as) :> *[n]b+ intrinsics.map_stream_per (f, as) -- | Return `true` if the given function returns `true` for all -- elements in the array.@@ -278,7 +278,7 @@ -- -- **Span:** *O(1)* def scatter 't [m] [n] (dest: *[m]t) (is: [n]i64) (vs: [n]t): *[m]t =- intrinsics.scatter (dest, is, vs) :> *[m]t+ intrinsics.scatter (dest, is, vs) -- | `scatter_2d as is vs` is the equivalent of a `scatter` on a 2-dimensional -- array.@@ -287,7 +287,7 @@ -- -- **Span:** *O(1)* def scatter_2d 't [m] [n] [l] (dest: *[m][n]t) (is: [l](i64, i64)) (vs: [l]t): *[m][n]t =- intrinsics.scatter_2d (dest, is, vs) :> *[m][n]t+ intrinsics.scatter_2d (dest, is, vs) -- | `scatter_3d as is vs` is the equivalent of a `scatter` on a 3-dimensional -- array.@@ -296,4 +296,4 @@ -- -- **Span:** *O(1)* def scatter_3d 't [m] [n] [o] [l] (dest: *[m][n][o]t) (is: [l](i64, i64, i64)) (vs: [l]t): *[m][n][o]t =- intrinsics.scatter_3d (dest, is, vs) :> *[m][n][o]t+ intrinsics.scatter_3d (dest, is, vs)
prelude/zip.fut view
@@ -11,15 +11,15 @@ -- depended upon by soacs.fut. So we just define a quick-and-dirty -- internal one here that uses the intrinsic version. local def internal_map 'a [n] 'x (f: a -> x) (as: [n]a): [n]x =- intrinsics.map (f, as) :> [n]x+ intrinsics.map (f, as) -- | Construct an array of pairs from two arrays. def zip [n] 'a 'b (as: [n]a) (bs: [n]b): *[n](a,b) =- intrinsics.zip (as, bs) :> *[n](a,b)+ intrinsics.zip (as, bs) -- | Construct an array of pairs from two arrays. def zip2 [n] 'a 'b (as: [n]a) (bs: [n]b): *[n](a,b) =- zip as bs :> *[n](a,b)+ zip as bs -- | As `zip2`@term, but with one more array. def zip3 [n] 'a 'b 'c (as: [n]a) (bs: [n]b) (cs: [n]c): *[n](a,b,c) =@@ -35,7 +35,7 @@ -- | Turn an array of pairs into two arrays. def unzip [n] 'a 'b (xs: [n](a,b)): ([n]a, [n]b) =- intrinsics.unzip xs :> ([n]a, [n]b)+ intrinsics.unzip xs -- | Turn an array of pairs into two arrays. def unzip2 [n] 'a 'b (xs: [n](a,b)): ([n]a, [n]b) =
− rts/c/chaselev.h
@@ -1,176 +0,0 @@-// Start of chaselev.h--/* Implementation of Chase-lev's concurrent lock-free deque- from ``Dynamic Circular Work-Stealing Deque`` (2005)- This implementation was ported from- https://github.com/deepsea-inria/heartbeat-- !!!- This implementation leaks memory,- if the circular array is grown- as we don't maintain a list of the old buffers.- However, we can't safely free it either as a stealing thread might- be reading from it.- !!!- */--#if defined(MCCHASELEV)-#include <stdlib.h>-#include <assert.h>-#include <string.h>---static struct subtask* const STEAL_RES_EMPTY = (struct subtask*) 0;-static struct subtask* const STEAL_RES_ABORT = (struct subtask*) 1;--static const int strong = 0;-static const int backoff_nb_cycles = 1l << 10;---static inline struct subtask* cb_get(struct subtask **buf, int64_t capacity, int64_t i) {- return (struct subtask*)__atomic_load_n(&buf[i % capacity], __ATOMIC_RELAXED);-}--static inline void cb_put (struct subtask **buf, int64_t capacity, int64_t i, struct subtask* x) {- __atomic_store_n(&buf[i % capacity], x, __ATOMIC_RELAXED);-}--struct deque_buffer* grow(struct subtask **old_array,- int64_t old_capacity,- int64_t new_capacity,- int64_t b,- int64_t t)-{- struct deque_buffer* new_deque_buffer = malloc(sizeof(struct deque_buffer));- new_deque_buffer->size = new_capacity;- new_deque_buffer->array = calloc(new_capacity, sizeof(struct subtask*));-- for (int64_t i = t; i < b; i++) {- cb_put(new_deque_buffer->array, new_capacity, i, cb_get(old_array, old_capacity, i));- }- return new_deque_buffer;-}--static inline int deque_init(struct deque *q, int64_t capacity) {- assert(q != NULL);- memset(q, 0, sizeof(struct deque));-- q->buffer = malloc(sizeof(struct deque_buffer));- q->buffer->array = calloc(capacity, sizeof(struct subtask*));- q->buffer->size = capacity;-- q->dead = 0;-- if (q->buffer->array == NULL) {- return -1;- }-- if (q->buffer == NULL) {- return -1;- }- return 0;-}--static inline void deque_destroy(struct deque* q)-{- q->dead = 1;- free(q->buffer->array);- free(q->buffer);-}--static inline int cas_top (struct deque *q, int64_t old_val, int64_t new_val) {- int64_t ov = old_val;- if(__atomic_compare_exchange_n(&q->top, &ov, new_val, strong,- __ATOMIC_SEQ_CST, __ATOMIC_RELAXED)) {- return 1;- }- spin_for(backoff_nb_cycles);- return 0;-}---void push_back(struct deque *q, struct subtask*subtask)-{- assert(subtask != NULL);- assert(q != NULL);-- int64_t b = __atomic_load_n(&q->bottom, __ATOMIC_RELAXED); // load atomically- int64_t t = __atomic_load_n(&q->top, __ATOMIC_ACQUIRE); // load atomically- struct deque_buffer *buffer = __atomic_load_n(&q->buffer, __ATOMIC_RELAXED);- if (b-t >= (buffer->size - 1)) {- // grow_queue- struct subtask **old_array = buffer->array;- int64_t old_capacity = __atomic_load_n(&buffer->size, __ATOMIC_RELAXED);- int64_t new_capacity = old_capacity * 2;- struct deque_buffer *new_buffer = grow(old_array, old_capacity, new_capacity, b, t);- __atomic_store_n(&q->buffer, new_buffer, __ATOMIC_RELEASE);- buffer = __atomic_load_n(&q->buffer, __ATOMIC_RELAXED);- memset(old_array, 0, sizeof(struct subtask*) * old_capacity);- // free(old_array); Not safe!!- }-- cb_put(buffer->array, buffer->size, b, subtask);- __atomic_thread_fence(__ATOMIC_RELEASE);- __atomic_store_n(&q->bottom, b+1, __ATOMIC_RELAXED);- return;-}---struct subtask * pop_back(struct deque *q)-{- int64_t b = __atomic_load_n(&q->bottom, __ATOMIC_RELAXED) - 1; // load atomically- struct deque_buffer *buffer = __atomic_load_n(&q->buffer, __ATOMIC_RELAXED);- __atomic_store_n(&q->bottom, b, __ATOMIC_RELAXED);- __atomic_thread_fence(__ATOMIC_SEQ_CST);- int64_t t = __atomic_load_n(&q->top, __ATOMIC_RELAXED);- if (b < t) {- __atomic_store_n(&q->bottom, t, __ATOMIC_RELAXED);- return NULL;- }- struct subtask* item = cb_get(buffer->array, buffer->size, b);- if (b > t) {- return item;- }-- // else there's only one item left- // Did we win the race?- if (!cas_top(q, t, t + 1)) {- item = NULL;- }- __atomic_store_n(&q->bottom, t+1, __ATOMIC_RELAXED);- return item;-}--struct subtask* steal(struct deque *q)-{- assert(q != NULL);-- int64_t t = __atomic_load_n(&q->top, __ATOMIC_ACQUIRE); // load atomically- __atomic_thread_fence(__ATOMIC_SEQ_CST);- int64_t b = __atomic_load_n(&q->bottom, __ATOMIC_ACQUIRE); // load atomically- if (t >= b) {- return STEAL_RES_EMPTY;- }-- struct deque_buffer *buffer = __atomic_load_n(&q->buffer, __ATOMIC_CONSUME);- struct subtask* item = cb_get(buffer->array, buffer->size, t);- if (!cas_top(q, t, t + 1)) {- return STEAL_RES_ABORT;- }-- return item;-}---static inline size_t nb_subtasks(struct deque *q)-{- return (size_t)__atomic_load_n(&q->bottom, __ATOMIC_RELAXED) - __atomic_load_n(&q->top, __ATOMIC_RELAXED);-}--static inline int empty(struct deque *q)-{- return nb_subtasks(q) < 1;-}--#endif-// end of chaselev.h
rts/c/cuda.h view
@@ -290,7 +290,9 @@ { 7, 0, "compute_70" }, { 7, 2, "compute_72" }, { 7, 5, "compute_75" },- { 8, 0, "compute_80" }+ { 8, 0, "compute_80" },+ { 8, 6, "compute_80" },+ { 8, 7, "compute_80" } }; int major = device_query(dev, COMPUTE_CAPABILITY_MAJOR);
rts/python/opencl.py view
@@ -218,7 +218,12 @@ build_options += ["-DLOCKSTEP_WIDTH={}".format(lockstep_width)] - build_options += ["-D{}={}".format(s.replace('z', 'zz').replace('.', 'zi').replace('#', 'zh'),v) for (s,v) in self.sizes.items()]+ build_options += ["-D{}={}".format(s.+ replace('z', 'zz').+ replace('.', 'zi').+ replace('#', 'zh').+ replace('\'', 'zq'),+ v) for (s,v) in self.sizes.items()] if (self.platform.name == 'Oclgrind'): build_options += ['-DEMULATE_F16']
+ src/Futhark/AD/Derivatives.hs view
@@ -0,0 +1,386 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Partial derivatives of scalar Futhark operations and built-in functions.+module Futhark.AD.Derivatives+ ( pdBuiltin,+ pdBinOp,+ pdUnOp,+ )+where++import Data.Bifunctor (bimap)+import Futhark.Analysis.PrimExp.Convert+import Futhark.IR.Syntax.Core+import Futhark.Util.IntegralExp+import Prelude hiding (quot)++iConst :: IntType -> Integer -> PrimExp VName+iConst it x = ValueExp $ IntValue $ intValue it x++fConst :: FloatType -> Double -> PrimExp VName+fConst ft x = ValueExp $ FloatValue $ floatValue ft x++untyped2 :: (TPrimExp t v, TPrimExp t v) -> (PrimExp v, PrimExp v)+untyped2 = bimap untyped untyped++-- | @pdUnOp op x@ computes the partial derivatives of @op@+-- with respect to @x@.+pdUnOp :: UnOp -> PrimExp VName -> PrimExp VName+pdUnOp (Abs it) a = UnOpExp (SSignum it) a+pdUnOp (FAbs ft) a = UnOpExp (FSignum ft) a+pdUnOp Not x = x+pdUnOp (Complement it) x = UnOpExp (Complement it) x+pdUnOp (SSignum it) _ = iConst it 0+pdUnOp (USignum it) _ = iConst it 0+pdUnOp (FSignum ft) _ = fConst ft 0++type OnBinOp t v = TPrimExp t v -> TPrimExp t v -> (TPrimExp t v, TPrimExp t v)++intBinOp ::+ OnBinOp Int8 v ->+ OnBinOp Int16 v ->+ OnBinOp Int32 v ->+ OnBinOp Int64 v ->+ IntType ->+ PrimExp v ->+ PrimExp v ->+ (PrimExp v, PrimExp v)+intBinOp f _ _ _ Int8 a b = untyped2 $ f (isInt8 a) (isInt8 b)+intBinOp _ f _ _ Int16 a b = untyped2 $ f (isInt16 a) (isInt16 b)+intBinOp _ _ f _ Int32 a b = untyped2 $ f (isInt32 a) (isInt32 b)+intBinOp _ _ _ f Int64 a b = untyped2 $ f (isInt64 a) (isInt64 b)++floatBinOp ::+ OnBinOp Half v ->+ OnBinOp Float v ->+ OnBinOp Double v ->+ FloatType ->+ PrimExp v ->+ PrimExp v ->+ (PrimExp v, PrimExp v)+floatBinOp f _ _ Float16 a b = untyped2 $ f (isF16 a) (isF16 b)+floatBinOp _ f _ Float32 a b = untyped2 $ f (isF32 a) (isF32 b)+floatBinOp _ _ f Float64 a b = untyped2 $ f (isF64 a) (isF64 b)++-- | @pdBinOp op x y@ computes the partial derivatives of @op@ with+-- respect to @x@ and @y@.+pdBinOp :: BinOp -> PrimExp VName -> PrimExp VName -> (PrimExp VName, PrimExp VName)+pdBinOp (Add it _) _ _ = (iConst it 1, iConst it 1)+pdBinOp (Sub it _) _ _ = (iConst it 1, iConst it (-1))+pdBinOp (Mul _ _) x y = (y, x)+pdBinOp (Pow it) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = ((x `pow` (y - 1)) * y, 0) -- FIXME (wrt y)+pdBinOp (SDiv it _) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = (1 `quot` y, negate (x `quot` (y * y)))+pdBinOp (SDivUp it _) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = (1 `quot` y, negate (x `quot` (y * y)))+pdBinOp (SQuot it _) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = (1 `quot` y, negate (x `quot` (y * y)))+pdBinOp (UDiv it _) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = (1 `quot` y, negate (x `quot` (y * y)))+pdBinOp (UDivUp it _) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = (1 `quot` y, negate (x `quot` (y * y)))+pdBinOp (UMod it _) _ _ = (iConst it 1, iConst it 0) -- FIXME+pdBinOp (SMod it _) _ _ = (iConst it 1, iConst it 0) -- FIXME+pdBinOp (SRem it _) _ _ = (iConst it 1, iConst it 0) -- FIXME+pdBinOp (FMod ft) _ _ = (fConst ft 1, fConst ft 0) -- FIXME+pdBinOp (UMax it) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = (fromBoolExp (x .>=. y), fromBoolExp (x .<. y))+pdBinOp (SMax it) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = (fromBoolExp (x .>=. y), fromBoolExp (x .<. y))+pdBinOp (UMin it) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = (fromBoolExp (x .<=. y), fromBoolExp (x .>. y))+pdBinOp (SMin it) a b =+ intBinOp derivs derivs derivs derivs it a b+ where+ derivs x y = (fromBoolExp (x .<=. y), fromBoolExp (x .>. y))+--+pdBinOp (Shl it) a b =+ pdBinOp (Mul it OverflowWrap) a $ BinOpExp (Pow it) (iConst it 2) b+pdBinOp (LShr it) a b =+ pdBinOp (UDiv it Unsafe) a $ BinOpExp (Pow it) (iConst it 2) b+pdBinOp (AShr it) a b =+ pdBinOp (SDiv it Unsafe) a $ BinOpExp (Pow it) (iConst it 2) b+pdBinOp (And it) _a _b = (iConst it 0, iConst it 0) -- FIXME+pdBinOp (Or it) _a _b = (iConst it 0, iConst it 0) -- FIXME+pdBinOp (Xor it) _a _b = (iConst it 0, iConst it 0) -- FIXME+--+pdBinOp (FAdd ft) _ _ = (fConst ft 1, fConst ft 1)+pdBinOp (FSub ft) _ _ = (fConst ft 1, fConst ft (-1))+pdBinOp (FMul _) x y = (y, x)+pdBinOp (FDiv ft) a b =+ floatBinOp derivs derivs derivs ft a b+ where+ derivs x y = (1 / y, negate (x / (y * y)))+pdBinOp (FPow ft) a b =+ floatBinOp derivs derivs derivs ft a b+ where+ derivs x y = (y * (x ** (y - 1)), (x ** y) * log x)+pdBinOp (FMax ft) a b =+ floatBinOp derivs derivs derivs ft a b+ where+ derivs x y = (fromBoolExp (x .>=. y), fromBoolExp (x .<. y))+pdBinOp (FMin ft) a b =+ floatBinOp derivs derivs derivs ft a b+ where+ derivs x y = (fromBoolExp (x .<=. y), fromBoolExp (x .>. y))+pdBinOp LogAnd a b = (b, a)+pdBinOp LogOr _ _ = (ValueExp $ BoolValue True, ValueExp $ BoolValue False)++-- | @pdBuiltin f args i@ computes the partial derivative of @f@+-- applied to @args@ with respect to each of its arguments. Returns+-- 'Nothing' if no such derivative is known.+pdBuiltin :: Name -> [PrimExp VName] -> Maybe [PrimExp VName]+pdBuiltin "sqrt16" [x] =+ Just [untyped $ 1 / (2 * sqrt (isF16 x))]+pdBuiltin "sqrt32" [x] =+ Just [untyped $ 1 / (2 * sqrt (isF32 x))]+pdBuiltin "sqrt64" [x] =+ Just [untyped $ 1 / (2 * sqrt (isF64 x))]+pdBuiltin "cbrt16" [x] =+ Just [untyped $ 1 / (3 * cbrt16 (isF16 x) * cbrt16 (isF16 x))]+ where+ cbrt16 a = isF16 $ FunExp "cbrt16" [untyped a] $ FloatType Float16+pdBuiltin "cbrt32" [x] =+ Just [untyped $ 1 / (3 * cbrt32 (isF32 x) * cbrt32 (isF32 x))]+ where+ cbrt32 a = isF32 $ FunExp "cbrt32" [untyped a] $ FloatType Float32+pdBuiltin "cbrt64" [x] =+ Just [untyped $ 1 / (3 * cbrt64 (isF64 x) * cbrt64 (isF64 x))]+ where+ cbrt64 a = isF64 $ FunExp "cbrt64" [untyped a] $ FloatType Float32+pdBuiltin "log16" [x] =+ Just [untyped $ 1 / isF16 x]+pdBuiltin "log32" [x] =+ Just [untyped $ 1 / isF32 x]+pdBuiltin "log64" [x] =+ Just [untyped $ 1 / isF64 x]+pdBuiltin "log10_16" [x] =+ Just [untyped $ 1 / (isF16 x * log 10)]+pdBuiltin "log10_32" [x] =+ Just [untyped $ 1 / (isF32 x * log 10)]+pdBuiltin "log10_64" [x] =+ Just [untyped $ 1 / (isF64 x * log 10)]+pdBuiltin "log2_16" [x] =+ Just [untyped $ 1 / (isF16 x * log 2)]+pdBuiltin "log2_32" [x] =+ Just [untyped $ 1 / (isF32 x * log 2)]+pdBuiltin "log2_64" [x] =+ Just [untyped $ 1 / (isF64 x * log 2)]+pdBuiltin "exp16" [x] =+ Just [untyped $ exp (isF16 x)]+pdBuiltin "exp32" [x] =+ Just [untyped $ exp (isF32 x)]+pdBuiltin "exp64" [x] =+ Just [untyped $ exp (isF64 x)]+pdBuiltin "sin16" [x] =+ Just [untyped $ cos (isF16 x)]+pdBuiltin "sin32" [x] =+ Just [untyped $ cos (isF32 x)]+pdBuiltin "sin64" [x] =+ Just [untyped $ cos (isF64 x)]+pdBuiltin "sinh16" [x] =+ Just [untyped $ cosh (isF16 x)]+pdBuiltin "sinh32" [x] =+ Just [untyped $ cosh (isF32 x)]+pdBuiltin "sinh64" [x] =+ Just [untyped $ cosh (isF64 x)]+pdBuiltin "cos16" [x] =+ Just [untyped $ -sin (isF16 x)]+pdBuiltin "cos32" [x] =+ Just [untyped $ -sin (isF32 x)]+pdBuiltin "cos64" [x] =+ Just [untyped $ -sin (isF64 x)]+pdBuiltin "cosh16" [x] =+ Just [untyped $ sinh (isF16 x)]+pdBuiltin "cosh32" [x] =+ Just [untyped $ sinh (isF32 x)]+pdBuiltin "cosh64" [x] =+ Just [untyped $ sinh (isF64 x)]+pdBuiltin "tan16" [x] =+ Just [untyped $ 1 / (cos (isF16 x) * cos (isF16 x))]+pdBuiltin "tan32" [x] =+ Just [untyped $ 1 / (cos (isF32 x) * cos (isF32 x))]+pdBuiltin "tan64" [x] =+ Just [untyped $ 1 / (cos (isF64 x) * cos (isF64 x))]+pdBuiltin "asin16" [x] =+ Just [untyped $ 1 / sqrt (1 - isF16 x * isF16 x)]+pdBuiltin "asin32" [x] =+ Just [untyped $ 1 / sqrt (1 - isF32 x * isF32 x)]+pdBuiltin "asin64" [x] =+ Just [untyped $ 1 / sqrt (1 - isF64 x * isF64 x)]+pdBuiltin "asinh16" [x] =+ Just [untyped $ 1 / sqrt (1 + isF16 x * isF16 x)]+pdBuiltin "asinh32" [x] =+ Just [untyped $ 1 / sqrt (1 + isF32 x * isF32 x)]+pdBuiltin "asinh64" [x] =+ Just [untyped $ 1 / sqrt (1 + isF64 x * isF64 x)]+pdBuiltin "acos16" [x] =+ Just [untyped $ -1 / sqrt (1 - isF16 x * isF16 x)]+pdBuiltin "acos32" [x] =+ Just [untyped $ -1 / sqrt (1 - isF32 x * isF32 x)]+pdBuiltin "acos64" [x] =+ Just [untyped $ -1 / sqrt (1 - isF64 x * isF64 x)]+pdBuiltin "acosh16" [x] =+ Just [untyped $ 1 / sqrt (isF16 x * isF16 x - 1)]+pdBuiltin "acosh32" [x] =+ Just [untyped $ 1 / sqrt (isF32 x * isF32 x - 1)]+pdBuiltin "acosh64" [x] =+ Just [untyped $ 1 / sqrt (isF64 x * isF64 x - 1)]+pdBuiltin "atan16" [x] =+ Just [untyped $ 1 / (1 + isF16 x * isF16 x)]+pdBuiltin "atan32" [x] =+ Just [untyped $ 1 / (1 + isF32 x * isF32 x)]+pdBuiltin "atan64" [x] =+ Just [untyped $ 1 / (1 + isF64 x * isF64 x)]+pdBuiltin "atanh16" [x] =+ Just [untyped $ cosh (isF16 x) * cosh (isF16 x)]+pdBuiltin "atanh32" [x] =+ Just [untyped $ cosh (isF32 x) * cosh (isF32 x)]+pdBuiltin "atanh64" [x] =+ Just [untyped $ cosh (isF64 x) * cosh (isF64 x)]+pdBuiltin "atan2_16" [x, y] =+ Just+ [ untyped $ -isF16 y / (isF16 x * isF16 x + isF16 y * isF16 y),+ untyped $ -isF16 x / (isF16 x * isF16 x + isF16 y * isF16 y)+ ]+pdBuiltin "atan2_32" [x, y] =+ Just+ [ untyped $ -isF32 y / (isF32 x * isF32 x + isF32 y * isF32 y),+ untyped $ -isF32 x / (isF32 x * isF32 x + isF32 y * isF32 y)+ ]+pdBuiltin "atan2_64" [x, y] =+ Just+ [ untyped $ -isF64 y / (isF64 x * isF64 x + isF64 y * isF64 y),+ untyped $ -isF64 x / (isF64 x * isF64 x + isF64 y * isF64 y)+ ]+pdBuiltin "tanh16" [x] =+ Just [untyped $ 1 - tanh (isF16 x) * tanh (isF16 x)]+pdBuiltin "tanh32" [x] =+ Just [untyped $ 1 - tanh (isF32 x) * tanh (isF32 x)]+pdBuiltin "tanh64" [x] =+ Just [untyped $ 1 - tanh (isF64 x) * tanh (isF64 x)]+pdBuiltin "fma16" [a, b, _c] =+ Just [b, a, fConst Float16 1]+pdBuiltin "fma32" [a, b, _c] =+ Just [b, a, fConst Float32 1]+pdBuiltin "fma64" [a, b, _c] =+ Just [b, a, fConst Float64 1]+pdBuiltin "mad16" [a, b, _c] =+ Just [b, a, fConst Float16 1]+pdBuiltin "mad32" [a, b, _c] =+ Just [b, a, fConst Float32 1]+pdBuiltin "mad64" [a, b, _c] =+ Just [b, a, fConst Float64 1]+pdBuiltin "from_bits16" [_] =+ Just [fConst Float16 1]+pdBuiltin "from_bits32" [_] =+ Just [fConst Float32 1]+pdBuiltin "from_bits64" [_] =+ Just [fConst Float64 1]+pdBuiltin "to_bits16" [_] =+ Just [iConst Int16 1]+pdBuiltin "to_bits32" [_] =+ Just [iConst Int32 1]+pdBuiltin "to_bits64" [_] =+ Just [iConst Int64 1]+pdBuiltin "hypot16" [x, y] =+ Just+ [ untyped $ isF16 x / isF16 (FunExp "hypot16" [x, y] $ FloatType Float16),+ untyped $ isF16 y / isF16 (FunExp "hypot16" [x, y] $ FloatType Float16)+ ]+pdBuiltin "hypot32" [x, y] =+ Just+ [ untyped $ isF32 x / isF32 (FunExp "hypot32" [x, y] $ FloatType Float32),+ untyped $ isF32 y / isF32 (FunExp "hypot32" [x, y] $ FloatType Float32)+ ]+pdBuiltin "hypot64" [x, y] =+ Just+ [ untyped $ isF64 x / isF64 (FunExp "hypot64" [x, y] $ FloatType Float64),+ untyped $ isF64 y / isF64 (FunExp "hypot64" [x, y] $ FloatType Float64)+ ]+pdBuiltin "lerp16" [v0, v1, t] =+ Just+ [ untyped $ 1 - fMax16 0 (fMin16 1 (isF16 t)),+ untyped $ fMax16 0 (fMin16 1 (isF16 t)),+ untyped $ isF16 v1 - isF16 v0+ ]+pdBuiltin "lerp32" [v0, v1, t] =+ Just+ [ untyped $ 1 - fMax32 0 (fMin32 1 (isF32 t)),+ untyped $ fMax32 0 (fMin32 1 (isF32 t)),+ untyped $ isF32 v1 - isF32 v0+ ]+pdBuiltin "lerp64" [v0, v1, t] =+ Just+ [ untyped $ 1 - fMax64 0 (fMin64 1 (isF64 t)),+ untyped $ fMax64 0 (fMin64 1 (isF64 t)),+ untyped $ isF64 v1 - isF64 v0+ ]+pdBuiltin "erf16" [z] =+ Just [untyped $ (2 / sqrt pi) * exp (negate (isF16 z * isF16 z))]+pdBuiltin "erf32" [z] =+ Just [untyped $ (2 / sqrt pi) * exp (negate (isF32 z * isF32 z))]+pdBuiltin "erf64" [z] =+ Just [untyped $ (2 / sqrt pi) * exp (negate (isF64 z * isF64 z))]+pdBuiltin "erfc16" [z] =+ Just [untyped $ negate $ (2 / sqrt pi) * exp (negate (isF16 z * isF16 z))]+pdBuiltin "erfc32" [z] =+ Just [untyped $ negate $ (2 / sqrt pi) * exp (negate (isF32 z * isF32 z))]+pdBuiltin "erfc64" [z] =+ Just [untyped $ negate $ (2 / sqrt pi) * exp (negate (isF64 z * isF64 z))]+-- More problematic derivatives follow below.+pdBuiltin "mul_hi8" [x, y] = Just [y, x]+pdBuiltin "mul_hi16" [x, y] = Just [y, x]+pdBuiltin "mul_hi32" [x, y] = Just [y, x]+pdBuiltin "mul_hi64" [x, y] = Just [y, x]+pdBuiltin "mad_hi8" [a, b, _c] = Just [b, a, iConst Int8 1]+pdBuiltin "mad_hi16" [a, b, _c] = Just [b, a, iConst Int16 1]+pdBuiltin "mad_hi32" [a, b, _c] = Just [b, a, iConst Int32 1]+pdBuiltin "mad_hi64" [a, b, _c] = Just [b, a, iConst Int64 1]+pdBuiltin "isnan16" [_] = Just [untyped false]+pdBuiltin "isnan32" [_] = Just [untyped false]+pdBuiltin "isnan64" [_] = Just [untyped false]+pdBuiltin "isinf16" [_] = Just [untyped false]+pdBuiltin "isinf32" [_] = Just [untyped false]+pdBuiltin "isinf64" [_] = Just [untyped false]+pdBuiltin "round16" [_] = Just [fConst Float16 0]+pdBuiltin "round32" [_] = Just [fConst Float32 0]+pdBuiltin "round64" [_] = Just [fConst Float64 0]+pdBuiltin "ceil16" [_] = Just [fConst Float16 0]+pdBuiltin "ceil32" [_] = Just [fConst Float32 0]+pdBuiltin "ceil64" [_] = Just [fConst Float64 0]+pdBuiltin "floor16" [_] = Just [fConst Float16 0]+pdBuiltin "floor32" [_] = Just [fConst Float32 0]+pdBuiltin "floor64" [_] = Just [fConst Float64 0]+pdBuiltin "clz8" [_] = Just [iConst Int32 0]+pdBuiltin "clz16" [_] = Just [iConst Int32 0]+pdBuiltin "clz32" [_] = Just [iConst Int32 0]+pdBuiltin "clz64" [_] = Just [iConst Int32 0]+pdBuiltin "ctz8" [_] = Just [iConst Int32 0]+pdBuiltin "ctz16" [_] = Just [iConst Int32 0]+pdBuiltin "ctz32" [_] = Just [iConst Int32 0]+pdBuiltin "ctz64" [_] = Just [iConst Int32 0]+pdBuiltin "popc8" [_] = Just [iConst Int32 0]+pdBuiltin "popc16" [_] = Just [iConst Int32 0]+pdBuiltin "popc32" [_] = Just [iConst Int32 0]+pdBuiltin "popc64" [_] = Just [iConst Int32 0]+pdBuiltin _ _ = Nothing
+ src/Futhark/AD/Fwd.hs view
@@ -0,0 +1,486 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++module Futhark.AD.Fwd (fwdJVP) where++import Control.Monad+import Control.Monad.RWS.Strict+import Control.Monad.State.Strict+import Data.Bifunctor (second)+import qualified Data.Kind+import Data.List (transpose)+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.Map as M+import Futhark.AD.Derivatives+import Futhark.Analysis.PrimExp.Convert+import Futhark.Builder+import Futhark.Construct+import Futhark.IR.SOACS++zeroTan :: Type -> ADM SubExp+zeroTan (Prim t) = pure $ constant $ blankPrimValue t+zeroTan t = error $ "zeroTan on non-primitive type: " ++ pretty t++zeroExp :: Type -> Exp SOACS+zeroExp (Prim pt) =+ BasicOp $ SubExp $ Constant $ blankPrimValue pt+zeroExp (Array pt shape _) =+ BasicOp $ Replicate shape $ Constant $ blankPrimValue pt+zeroExp t = error $ "zeroExp: " ++ show t++tanType :: TypeBase s u -> ADM (TypeBase s u)+tanType (Acc acc ispace ts u) = do+ ts_tan <- mapM tanType ts+ pure $ Acc acc ispace (ts ++ ts_tan) u+tanType t = pure t++slocal' :: ADM a -> ADM a+slocal' = slocal id++slocal :: (RState -> RState) -> ADM a -> ADM a+slocal f m = do+ s <- get+ modify f+ a <- m+ modify $ \s' -> s' {stateTans = stateTans s}+ pure a++data RState = RState+ { stateTans :: M.Map VName VName,+ stateNameSource :: VNameSource+ }++newtype ADM a = ADM (BuilderT SOACS (State RState) a)+ deriving+ ( Functor,+ Applicative,+ Monad,+ MonadState RState,+ MonadFreshNames,+ HasScope SOACS,+ LocalScope SOACS+ )++instance MonadBuilder ADM where+ type Rep ADM = SOACS+ mkExpDecM pat e = ADM $ mkExpDecM pat e+ mkBodyM bnds res = ADM $ mkBodyM bnds res+ mkLetNamesM pat e = ADM $ mkLetNamesM pat e++ addStms = ADM . addStms+ collectStms (ADM m) = ADM $ collectStms m++instance MonadFreshNames (State RState) where+ getNameSource = gets stateNameSource+ putNameSource src = modify (\env -> env {stateNameSource = src})++runADM :: MonadFreshNames m => ADM a -> m a+runADM (ADM m) =+ modifyNameSource $ \vn ->+ second stateNameSource $+ runState+ (fst <$> runBuilderT m mempty)+ (RState mempty vn)++tanVName :: VName -> ADM VName+tanVName v = newVName (baseString v <> "_tan")++insertTan :: VName -> VName -> ADM ()+insertTan v v' =+ modify $ \env -> env {stateTans = M.insert v v' (stateTans env)}++class TanBuilder a where+ type Bundled a :: Data.Kind.Type+ type Bundled a = [a]+ newTan :: a -> ADM a+ bundleNew :: a -> ADM (Bundled a)++instance (Monoid (Bundled a), TanBuilder a) => TanBuilder [a] where+ type Bundled [a] = Bundled a+ newTan = mapM newTan+ bundleNew = fmap mconcat . mapM bundleNew++instance TanBuilder (PatElem (TypeBase s u)) where+ newTan (PatElem p t)+ | isAcc t = do+ insertTan p p+ t' <- tanType t+ pure $ PatElem p t'+ | otherwise = do+ p' <- tanVName p+ insertTan p p'+ t' <- tanType t+ pure $ PatElem p' t'+ bundleNew pe@(PatElem _ t) = do+ pe' <- newTan pe+ if isAcc t+ then pure [pe']+ else pure [pe, pe']++instance TanBuilder (Pat (TypeBase s u)) where+ type Bundled (Pat (TypeBase s u)) = Pat (TypeBase s u)+ newTan (Pat pes) = Pat <$> newTan pes+ bundleNew (Pat pes) = Pat <$> bundleNew pes++instance TanBuilder (Param (TypeBase s u)) where+ newTan (Param _ p t) = do+ PatElem p' t' <- newTan $ PatElem p t+ pure $ Param mempty p' t'+ bundleNew param@(Param _ _ (Prim Unit)) =+ pure [param]+ bundleNew param@(Param _ _ t) = do+ param' <- newTan param+ if isAcc t+ then pure [param']+ else pure [param, param']++instance Tangent a => TanBuilder (Param (TypeBase s u), a) where+ newTan (p, x) = (,) <$> newTan p <*> tangent x+ bundleNew (p, x) = do+ b <- bundleNew p+ x_tan <- tangent x+ pure $ zip b [x, x_tan]++class Tangent a where+ type BundledTan a :: Data.Kind.Type+ type BundledTan a = [a]+ tangent :: a -> ADM a+ bundleTan :: a -> ADM (BundledTan a)++instance Tangent (TypeBase s u) where+ tangent = tanType+ bundleTan t+ | isAcc t = do+ t' <- tangent t+ pure [t']+ | otherwise = do+ t' <- tangent t+ pure [t, t']++instance (Monoid (BundledTan a), Tangent a) => Tangent [a] where+ type BundledTan [a] = BundledTan a+ tangent = mapM tangent+ bundleTan = (mconcat <$>) . mapM bundleTan++instance Tangent VName where+ tangent v = do+ maybeTan <- gets $ M.lookup v . stateTans+ case maybeTan of+ Just v_tan -> pure v_tan+ Nothing -> do+ t <- lookupType v+ letExp (baseString v <> "_implicit_tan") $ zeroExp t+ bundleTan v = do+ t <- lookupType v+ if isAcc t+ then pure [v]+ else do+ v_tan <- tangent v+ pure [v, v_tan]++instance Tangent SubExp where+ tangent (Constant c) = zeroTan $ Prim $ primValueType c+ tangent (Var v) = Var <$> tangent v+ bundleTan c@Constant {} = do+ c_tan <- tangent c+ pure [c, c_tan]+ bundleTan (Var v) = fmap Var <$> bundleTan v++instance Tangent SubExpRes where+ tangent (SubExpRes cs se) = SubExpRes cs <$> tangent se+ bundleTan (SubExpRes cs se) = map (SubExpRes cs) <$> bundleTan se++basicFwd :: Pat Type -> StmAux () -> BasicOp -> ADM ()+basicFwd pat aux op = do+ pat_tan <- newTan pat+ case op of+ SubExp se -> do+ se_tan <- tangent se+ addStm $ Let pat_tan aux $ BasicOp $ SubExp se_tan+ Opaque opaqueop se -> do+ se_tan <- tangent se+ addStm $ Let pat_tan aux $ BasicOp $ Opaque opaqueop se_tan+ ArrayLit ses t -> do+ ses_tan <- tangent ses+ addStm $ Let pat_tan aux $ BasicOp $ ArrayLit ses_tan t+ UnOp unop x -> do+ let t = unOpType unop+ x_pe = primExpFromSubExp t x+ dx = pdUnOp unop x_pe+ x_tan <- primExpFromSubExp t <$> tangent x+ auxing aux $ letBindNames (patNames pat_tan) <=< toExp $ x_tan ~*~ dx+ BinOp bop x y -> do+ let t = binOpType bop+ x_tan <- primExpFromSubExp t <$> tangent x+ y_tan <- primExpFromSubExp t <$> tangent y+ let (wrt_x, wrt_y) =+ pdBinOp bop (primExpFromSubExp t x) (primExpFromSubExp t y)+ auxing aux $+ letBindNames (patNames pat_tan) <=< toExp $+ x_tan ~*~ wrt_x ~+~ y_tan ~*~ wrt_y+ CmpOp {} ->+ addStm $ Let pat_tan aux $ BasicOp op+ ConvOp cop x -> do+ x_tan <- tangent x+ addStm $ Let pat_tan aux $ BasicOp $ ConvOp cop x_tan+ Assert {} -> pure ()+ Index arr slice -> do+ arr_tan <- tangent arr+ addStm $ Let pat_tan aux $ BasicOp $ Index arr_tan slice+ Update safety arr slice se -> do+ arr_tan <- tangent arr+ se_tan <- tangent se+ addStm $ Let pat_tan aux $ BasicOp $ Update safety arr_tan slice se_tan+ Concat d (arr :| arrs) w -> do+ arr_tan <- tangent arr+ arrs_tans <- tangent arrs+ addStm $ Let pat_tan aux $ BasicOp $ Concat d (arr_tan :| arrs_tans) w+ Copy arr -> do+ arr_tan <- tangent arr+ addStm $ Let pat_tan aux $ BasicOp $ Copy arr_tan+ Manifest ds arr -> do+ arr_tan <- tangent arr+ addStm $ Let pat_tan aux $ BasicOp $ Manifest ds arr_tan+ Iota n _ _ it -> do+ addStm $ Let pat_tan aux $ BasicOp $ Replicate (Shape [n]) (intConst it 0)+ Replicate n x -> do+ x_tan <- tangent x+ addStm $ Let pat_tan aux $ BasicOp $ Replicate n x_tan+ Scratch t shape ->+ addStm $ Let pat_tan aux $ BasicOp $ Scratch t shape+ Reshape reshape arr -> do+ arr_tan <- tangent arr+ addStm $ Let pat_tan aux $ BasicOp $ Reshape reshape arr_tan+ Rearrange perm arr -> do+ arr_tan <- tangent arr+ addStm $ Let pat_tan aux $ BasicOp $ Rearrange perm arr_tan+ Rotate rots arr -> do+ arr_tan <- tangent arr+ addStm $ Let pat_tan aux $ BasicOp $ Rotate rots arr_tan+ _ -> error $ "basicFwd: Unsupported op " ++ pretty op++fwdLambda :: Lambda SOACS -> ADM (Lambda SOACS)+fwdLambda l@(Lambda params body ret) =+ Lambda <$> bundleNew params <*> inScopeOf l (fwdBody body) <*> bundleTan ret++fwdStreamLambda :: Lambda SOACS -> ADM (Lambda SOACS)+fwdStreamLambda l@(Lambda params body ret) =+ Lambda <$> ((take 1 params ++) <$> bundleNew (drop 1 params)) <*> inScopeOf l (fwdBody body) <*> bundleTan ret++interleave :: [a] -> [a] -> [a]+interleave xs ys = concat $ transpose [xs, ys]++zeroFromSubExp :: SubExp -> ADM VName+zeroFromSubExp (Constant c) =+ letExp "zero" $+ BasicOp $ SubExp $ Constant $ blankPrimValue $ primValueType c+zeroFromSubExp (Var v) = do+ t <- lookupType v+ letExp "zero" $ zeroExp t++fwdSOAC :: Pat Type -> StmAux () -> SOAC SOACS -> ADM ()+fwdSOAC pat aux (Screma size xs (ScremaForm scs reds f)) = do+ pat' <- bundleNew pat+ xs' <- bundleTan xs+ scs' <- mapM fwdScan scs+ reds' <- mapM fwdRed reds+ f' <- fwdLambda f+ addStm $ Let pat' aux $ Op $ Screma size xs' $ ScremaForm scs' reds' f'+ where+ fwdScan :: Scan SOACS -> ADM (Scan SOACS)+ fwdScan sc = do+ op' <- fwdLambda $ scanLambda sc+ neutral_tans <- mapM zeroFromSubExp $ scanNeutral sc+ pure $+ Scan+ { scanNeutral = scanNeutral sc `interleave` map Var neutral_tans,+ scanLambda = op'+ }+ fwdRed :: Reduce SOACS -> ADM (Reduce SOACS)+ fwdRed red = do+ op' <- fwdLambda $ redLambda red+ neutral_tans <- mapM zeroFromSubExp $ redNeutral red+ pure $+ Reduce+ { redComm = redComm red,+ redLambda = op',+ redNeutral = redNeutral red `interleave` map Var neutral_tans+ }+fwdSOAC pat aux (Stream size xs form nes lam) = do+ pat' <- bundleNew pat+ lam' <- fwdStreamLambda lam+ xs' <- bundleTan xs+ nes_tan <- mapM (fmap Var . zeroFromSubExp) nes+ let nes' = interleave nes nes_tan+ case form of+ Sequential ->+ addStm $ Let pat' aux $ Op $ Stream size xs' Sequential nes' lam'+ Parallel o comm lam0 -> do+ lam0' <- fwdLambda lam0+ let form' = Parallel o comm lam0'+ addStm $ Let pat' aux $ Op $ Stream size xs' form' nes' lam'+fwdSOAC pat aux (Hist w arrs ops bucket_fun) = do+ pat' <- bundleNew pat+ ops' <- mapM fwdHist ops+ bucket_fun' <- fwdHistBucket bucket_fun+ arrs' <- bundleTan arrs+ addStm $ Let pat' aux $ Op $ Hist w arrs' ops' bucket_fun'+ where+ n_indices = sum $ map (shapeRank . histShape) ops+ fwdBodyHist (Body _ stms res) = buildBody_ $ do+ mapM_ fwdStm stms+ let (res_is, res_vs) = splitAt n_indices res+ (res_is ++) <$> bundleTan res_vs+ fwdHistBucket l@(Lambda params body ret) =+ let (r_is, r_vs) = splitAt n_indices ret+ in Lambda+ <$> bundleNew params+ <*> inScopeOf l (fwdBodyHist body)+ <*> ((r_is ++) <$> bundleTan r_vs)++ fwdHist :: HistOp SOACS -> ADM (HistOp SOACS)+ fwdHist (HistOp shape rf dest nes op) = do+ dest' <- bundleTan dest+ nes_tan <- mapM (fmap Var . zeroFromSubExp) nes+ op' <- fwdLambda op+ pure $+ HistOp+ { histShape = shape,+ histRaceFactor = rf,+ histDest = dest',+ histNeutral = interleave nes nes_tan,+ histOp = op'+ }+fwdSOAC (Pat pes) aux (Scatter w ivs lam as) = do+ as_tan <- mapM (\(s, n, a) -> do a_tan <- tangent a; pure (s, n, a_tan)) as+ pes_tan <- newTan pes+ ivs' <- bundleTan ivs+ let (as_ws, as_ns, _as_vs) = unzip3 as+ n_indices = sum $ zipWith (*) as_ns $ map length as_ws+ lam' <- fwdScatterLambda n_indices lam+ let s = Let (Pat (pes ++ pes_tan)) aux $ Op $ Scatter w ivs' lam' $ as ++ as_tan+ addStm s+ where+ fwdScatterLambda :: Int -> Lambda SOACS -> ADM (Lambda SOACS)+ fwdScatterLambda n_indices (Lambda params body ret) = do+ params' <- bundleNew params+ ret_tan <- tangent $ drop n_indices ret+ body' <- fwdBodyScatter n_indices body+ let indices = concat $ replicate 2 $ take n_indices ret+ ret' = indices ++ drop n_indices ret ++ ret_tan+ pure $ Lambda params' body' ret'+ fwdBodyScatter :: Int -> Body SOACS -> ADM (Body SOACS)+ fwdBodyScatter n_indices (Body _ stms res) = do+ (res_tan, stms') <- collectStms $ do+ mapM_ fwdStm stms+ tangent $ drop n_indices res+ let indices = concat $ replicate 2 $ take n_indices res+ res' = indices ++ drop n_indices res ++ res_tan+ pure $ mkBody stms' res'+fwdSOAC _ _ JVP {} =+ error "fwdSOAC: nested JVP not allowed."+fwdSOAC _ _ VJP {} =+ error "fwdSOAC: nested VJP not allowed."++fwdStm :: Stm SOACS -> ADM ()+fwdStm (Let pat aux (BasicOp (UpdateAcc acc i x))) = do+ pat' <- bundleNew pat+ x' <- bundleTan x+ acc_tan <- tangent acc+ addStm $ Let pat' aux $ BasicOp $ UpdateAcc acc_tan i x'+fwdStm stm@(Let pat aux (BasicOp e)) = do+ -- XXX: this has to be too naive.+ unless (any isAcc $ patTypes pat) $+ addStm stm+ basicFwd pat aux e+fwdStm stm@(Let pat _ (Apply f args _ _))+ | Just (ret, argts) <- M.lookup f builtInFunctions = do+ addStm stm+ arg_tans <-+ zipWith primExpFromSubExp argts <$> mapM (tangent . fst) args+ pat_tan <- newTan pat+ let arg_pes = zipWith primExpFromSubExp argts (map fst args)+ case pdBuiltin f arg_pes of+ Nothing ->+ error $ "No partial derivative defined for builtin function: " ++ pretty f+ Just derivs -> do+ let convertTo tt e+ | e_t == tt = e+ | otherwise =+ case (tt, e_t) of+ (IntType tt', IntType ft) -> ConvOpExp (SExt ft tt') e+ (FloatType tt', FloatType ft) -> ConvOpExp (FPConv ft tt') e+ (Bool, FloatType ft) -> ConvOpExp (FToB ft) e+ (FloatType tt', Bool) -> ConvOpExp (BToF tt') e+ _ -> error $ "fwdStm.convertTo: " ++ pretty (f, tt, e_t)+ where+ e_t = primExpType e+ zipWithM_ (letBindNames . pure) (patNames pat_tan)+ =<< mapM toExp (zipWith (~*~) (map (convertTo ret) arg_tans) derivs)+fwdStm (Let pat aux (If cond t f (IfDec ret ifsort))) = do+ t' <- slocal' $ fwdBody t+ f' <- slocal' $ fwdBody f+ pat' <- bundleNew pat+ ret' <- bundleTan ret+ addStm $ Let pat' aux $ If cond t' f' $ IfDec ret' ifsort+fwdStm (Let pat aux (DoLoop val_pats loop@(WhileLoop v) body)) = do+ val_pats' <- bundleNew val_pats+ pat' <- bundleNew pat+ body' <-+ localScope (scopeOfFParams (map fst val_pats) <> scopeOf loop) $+ slocal' $ fwdBody body+ addStm $ Let pat' aux $ DoLoop val_pats' (WhileLoop v) body'+fwdStm (Let pat aux (DoLoop val_pats loop@(ForLoop i it bound loop_vars) body)) = do+ pat' <- bundleNew pat+ val_pats' <- bundleNew val_pats+ loop_vars' <- bundleNew loop_vars+ body' <-+ localScope (scopeOfFParams (map fst val_pats) <> scopeOf loop) $+ slocal' $ fwdBody body+ addStm $ Let pat' aux $ DoLoop val_pats' (ForLoop i it bound loop_vars') body'+fwdStm (Let pat aux (WithAcc inputs lam)) = do+ inputs' <- forM inputs $ \(shape, arrs, op) -> do+ arrs_tan <- tangent arrs+ op' <- case op of+ Nothing -> pure Nothing+ Just (op_lam, nes) -> do+ nes_tan <- mapM (fmap Var . zeroFromSubExp) nes+ op_lam' <- fwdLambda op_lam+ case op_lam' of+ Lambda ps body ret -> do+ let op_lam'' = Lambda (removeIndexTans (shapeRank shape) ps) body ret+ pure $ Just (op_lam'', interleave nes nes_tan)+ pure (shape, arrs <> arrs_tan, op')+ pat' <- bundleNew pat+ lam' <- fwdLambda lam+ addStm $ Let pat' aux $ WithAcc inputs' lam'+ where+ removeIndexTans 0 ps = ps+ removeIndexTans i (p : _ : ps) = p : removeIndexTans (i - 1) ps+ removeIndexTans _ ps = ps+fwdStm (Let pat aux (Op soac)) = fwdSOAC pat aux soac+fwdStm stm =+ error $ "unhandled forward mode AD for Stm: " ++ pretty stm ++ "\n" ++ show stm++fwdBody :: Body SOACS -> ADM (Body SOACS)+fwdBody (Body _ stms res) = buildBody_ $ do+ mapM_ fwdStm stms+ bundleTan res++fwdBodyTansLast :: Body SOACS -> ADM (Body SOACS)+fwdBodyTansLast (Body _ stms res) = buildBody_ $ do+ mapM_ fwdStm stms+ (res <>) <$> tangent res++fwdJVP :: MonadFreshNames m => Scope SOACS -> Lambda SOACS -> m (Lambda SOACS)+fwdJVP scope l@(Lambda params body ret) =+ runADM . localScope scope . inScopeOf l $ do+ params_tan <- newTan params+ body_tan <- fwdBodyTansLast body+ ret_tan <- tangent ret+ pure $ Lambda (params ++ params_tan) body_tan (ret <> ret_tan)
+ src/Futhark/AD/Rev.hs view
@@ -0,0 +1,444 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++-- Naming scheme:+--+-- An adjoint-related object for "x" is named "x_adj". This means+-- both actual adjoints and statements.+--+-- Do not assume "x'" means anything related to derivatives.+module Futhark.AD.Rev (revVJP) where++import Control.Monad+import Data.List ((\\))+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.Map as M+import Futhark.AD.Derivatives+import Futhark.AD.Rev.Loop+import Futhark.AD.Rev.Monad+import Futhark.AD.Rev.SOAC+import Futhark.Analysis.PrimExp.Convert+import Futhark.Builder+import Futhark.IR.SOACS+import Futhark.Tools+import Futhark.Transform.Rename+import Futhark.Transform.Substitute+import Futhark.Util (takeLast)++patName :: Pat Type -> ADM VName+patName (Pat [pe]) = pure $ patElemName pe+patName pat = error $ "Expected single-element pattern: " ++ pretty pat++-- The vast majority of BasicOps require no special treatment in the+-- forward pass and produce one value (and hence one adjoint). We+-- deal with that case here.+commonBasicOp :: Pat Type -> StmAux () -> BasicOp -> ADM () -> ADM (VName, VName)+commonBasicOp pat aux op m = do+ addStm $ Let pat aux $ BasicOp op+ m+ pat_v <- patName pat+ pat_adj <- lookupAdjVal pat_v+ pure (pat_v, pat_adj)++diffBasicOp :: Pat Type -> StmAux () -> BasicOp -> ADM () -> ADM ()+diffBasicOp pat aux e m =+ case e of+ CmpOp cmp x y -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ do+ let t = cmpOpType cmp+ update contrib = do+ void $ updateSubExpAdj x contrib+ void $ updateSubExpAdj y contrib++ case t of+ FloatType ft ->+ update <=< letExp "contrib" $+ If+ (Var pat_adj)+ (resultBody [constant (floatValue ft (1 :: Int))])+ (resultBody [constant (floatValue ft (0 :: Int))])+ (IfDec [Prim (FloatType ft)] IfNormal)+ IntType it ->+ update <=< letExp "contrib" $ BasicOp $ ConvOp (BToI it) (Var pat_adj)+ Bool ->+ update pat_adj+ Unit ->+ pure ()+ --+ ConvOp op x -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ do+ contrib <-+ letExp "contrib" $ BasicOp $ ConvOp (flipConvOp op) $ Var pat_adj+ updateSubExpAdj x contrib+ --+ UnOp op x -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m++ returnSweepCode $ do+ let t = unOpType op+ contrib <- do+ let x_pe = primExpFromSubExp t x+ pat_adj' = primExpFromSubExp t (Var pat_adj)+ dx = pdUnOp op x_pe+ letExp "contrib" <=< toExp $ pat_adj' ~*~ dx++ updateSubExpAdj x contrib+ --+ BinOp op x y -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m++ returnSweepCode $ do+ let t = binOpType op+ (wrt_x, wrt_y) =+ pdBinOp op (primExpFromSubExp t x) (primExpFromSubExp t y)++ pat_adj' = primExpFromSubExp t $ Var pat_adj++ adj_x <- letExp "binop_x_adj" <=< toExp $ pat_adj' ~*~ wrt_x+ adj_y <- letExp "binop_y_adj" <=< toExp $ pat_adj' ~*~ wrt_y+ updateSubExpAdj x adj_x+ updateSubExpAdj y adj_y+ --+ SubExp se -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ updateSubExpAdj se pat_adj+ --+ Assert {} ->+ void $ commonBasicOp pat aux e m+ --+ ArrayLit elems _ -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ t <- lookupType pat_adj+ returnSweepCode $ do+ forM_ (zip [(0 :: Int64) ..] elems) $ \(i, se) -> do+ let slice = fullSlice t [DimFix (constant i)]+ updateSubExpAdj se <=< letExp "elem_adj" $ BasicOp $ Index pat_adj slice+ --+ Index arr slice -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ do+ void $ updateAdjSlice slice arr pat_adj+ FlatIndex {} -> error "FlatIndex not handled by AD yet."+ FlatUpdate {} -> error "FlatUpdate not handled by AD yet."+ --+ Opaque _ se -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ updateSubExpAdj se pat_adj+ --+ Reshape _ arr -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ do+ arr_dims <- arrayDims <$> lookupType arr+ void $+ updateAdj arr <=< letExp "adj_reshape" $+ BasicOp $ Reshape (map DimNew arr_dims) pat_adj+ --+ Rearrange perm arr -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $+ void $+ updateAdj arr <=< letExp "adj_rearrange" $+ BasicOp $ Rearrange (rearrangeInverse perm) pat_adj+ --+ Rotate rots arr -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ do+ let neg = BasicOp . BinOp (Sub Int64 OverflowWrap) (intConst Int64 0)+ rots' <- mapM (letSubExp "rot_neg" . neg) rots+ void $+ updateAdj arr <=< letExp "adj_rotate" $+ BasicOp $ Rotate rots' pat_adj+ --+ Replicate (Shape ns) x -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ do+ x_t <- subExpType x+ lam <- addLambda x_t+ ne <- letSubExp "zero" $ zeroExp x_t+ n <- letSubExp "rep_size" =<< foldBinOp (Mul Int64 OverflowUndef) (intConst Int64 1) ns+ pat_adj_flat <-+ letExp (baseString pat_adj <> "_flat") $+ BasicOp $ Reshape (map DimNew $ n : arrayDims x_t) pat_adj+ reduce <- reduceSOAC [Reduce Commutative lam [ne]]+ updateSubExpAdj x+ =<< letExp "rep_contrib" (Op $ Screma n [pat_adj_flat] reduce)+ --+ Concat d (arr :| arrs) _ -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ do+ let sliceAdj _ [] = pure []+ sliceAdj start (v : vs) = do+ v_t <- lookupType v+ let w = arraySize 0 v_t+ slice = DimSlice start w (intConst Int64 1)+ pat_adj_slice <-+ letExp (baseString pat_adj <> "_slice") $+ BasicOp $ Index pat_adj (sliceAt v_t d [slice])+ start' <- letSubExp "start" $ BasicOp $ BinOp (Add Int64 OverflowUndef) start w+ slices <- sliceAdj start' vs+ pure $ pat_adj_slice : slices++ slices <- sliceAdj (intConst Int64 0) $ arr : arrs++ zipWithM_ updateAdj (arr : arrs) slices+ --+ Copy se -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ void $ updateAdj se pat_adj+ --+ Manifest _ se -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ void $ updateAdj se pat_adj+ --+ Scratch {} ->+ void $ commonBasicOp pat aux e m+ --+ Iota n _ _ t -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ do+ ne <- letSubExp "zero" $ zeroExp $ Prim $ IntType t+ lam <- addLambda $ Prim $ IntType t+ reduce <- reduceSOAC [Reduce Commutative lam [ne]]+ updateSubExpAdj n+ =<< letExp "iota_contrib" (Op $ Screma n [pat_adj] reduce)+ --+ Update safety arr slice v -> do+ (_pat_v, pat_adj) <- commonBasicOp pat aux e m+ returnSweepCode $ do+ v_adj <- letExp "update_val_adj" $ BasicOp $ Index pat_adj slice+ t <- lookupType v_adj+ v_adj_copy <-+ case t of+ Array {} -> letExp "update_val_adj_copy" $ BasicOp $ Copy v_adj+ _ -> pure v_adj+ updateSubExpAdj v v_adj_copy+ zeroes <- letSubExp "update_zero" . zeroExp =<< subExpType v+ void $+ updateAdj arr+ =<< letExp "update_src_adj" (BasicOp $ Update safety pat_adj slice zeroes)+ -- See Note [Adjoints of accumulators]+ UpdateAcc _ is vs -> do+ addStm $ Let pat aux $ BasicOp e+ m+ pat_adjs <- mapM lookupAdjVal (patNames pat)+ returnSweepCode $ do+ forM_ (zip pat_adjs vs) $ \(adj, v) -> do+ adj_i <- letExp "updateacc_val_adj" $ BasicOp $ Index adj $ Slice $ map DimFix is+ updateSubExpAdj v adj_i++vjpOps :: VjpOps+vjpOps = VjpOps diffLambda diffStm++diffStm :: Stm SOACS -> ADM () -> ADM ()+diffStm (Let pat aux (BasicOp e)) m =+ diffBasicOp pat aux e m+diffStm stm@(Let pat _ (Apply f args _ _)) m+ | Just (ret, argts) <- M.lookup f builtInFunctions = do+ addStm stm+ m++ pat_adj <- lookupAdjVal =<< patName pat+ let arg_pes = zipWith primExpFromSubExp argts (map fst args)+ pat_adj' = primExpFromSubExp ret (Var pat_adj)+ convert ft tt+ | ft == tt = id+ convert (IntType ft) (IntType tt) = ConvOpExp (SExt ft tt)+ convert (FloatType ft) (FloatType tt) = ConvOpExp (FPConv ft tt)+ convert Bool (FloatType tt) = ConvOpExp (BToF tt)+ convert (FloatType ft) Bool = ConvOpExp (FToB ft)+ convert ft tt = error $ "diffStm.convert: " ++ pretty (f, ft, tt)++ contribs <-+ case pdBuiltin f arg_pes of+ Nothing ->+ error $ "No partial derivative defined for builtin function: " ++ pretty f+ Just derivs ->+ forM (zip derivs argts) $ \(deriv, argt) ->+ letExp "contrib" <=< toExp . convert ret argt $ pat_adj' ~*~ deriv++ zipWithM_ updateSubExpAdj (map fst args) contribs+diffStm stm@(Let pat _ (If cond tbody fbody _)) m = do+ addStm stm+ m+ returnSweepCode $ do+ let tbody_free = freeIn tbody+ fbody_free = freeIn fbody+ branches_free = namesToList $ tbody_free <> fbody_free++ adjs <- mapM lookupAdj $ patNames pat++ branches_free_adj <-+ ( pure . takeLast (length branches_free)+ <=< letTupExp "branch_adj"+ <=< renameExp+ )+ =<< eIf+ (eSubExp cond)+ (diffBody adjs branches_free tbody)+ (diffBody adjs branches_free fbody)+ zipWithM_ insAdj branches_free branches_free_adj+diffStm (Let pat aux (Op soac)) m =+ vjpSOAC vjpOps pat aux soac m+diffStm (Let pat aux loop@DoLoop {}) m =+ diffLoop diffStms pat aux loop m+-- See Note [Adjoints of accumulators]+diffStm stm@(Let pat _aux (WithAcc inputs lam)) m = do+ addStm stm+ m+ returnSweepCode $ do+ adjs <- mapM lookupAdj $ patNames pat+ lam' <- renameLambda lam+ free_vars <- filterM isActive $ namesToList $ freeIn lam'+ free_accs <- filterM (fmap isAcc . lookupType) free_vars+ let free_vars' = free_vars \\ free_accs+ lam'' <- diffLambda' adjs free_vars' lam'+ inputs' <- mapM renameInputLambda inputs+ free_adjs <- letTupExp "with_acc_contrib" $ WithAcc inputs' lam''+ zipWithM_ insAdj (arrs <> free_vars') free_adjs+ where+ arrs = concatMap (\(_, as, _) -> as) inputs+ renameInputLambda (shape, as, Just (f, nes)) = do+ f' <- renameLambda f+ pure (shape, as, Just (f', nes))+ renameInputLambda input = pure input+ diffLambda' res_adjs get_adjs_for (Lambda params body ts) =+ localScope (scopeOfLParams params) $ do+ Body () stms res <- diffBody res_adjs get_adjs_for body+ let body' = Body () stms $ take (length inputs) res <> takeLast (length get_adjs_for) res+ ts' <- mapM lookupType get_adjs_for+ pure $ Lambda params body' $ take (length inputs) ts <> ts'+diffStm stm _ = error $ "diffStm unhandled:\n" ++ pretty stm++diffStms :: Stms SOACS -> ADM ()+diffStms all_stms+ | Just (stm, stms) <- stmsHead all_stms = do+ (subst, copy_stms) <- copyConsumedArrsInStm stm+ let (stm', stms') = substituteNames subst (stm, stms)+ diffStms copy_stms >> diffStm stm' (diffStms stms')+ forM_ (M.toList subst) $ \(from, to) ->+ setAdj from =<< lookupAdj to+ | otherwise =+ pure ()++-- | Preprocess statements before differentiating.+-- For now, it's just stripmining.+preprocess :: Stms SOACS -> ADM (Stms SOACS)+preprocess = stripmineStms++diffBody :: [Adj] -> [VName] -> Body SOACS -> ADM (Body SOACS)+diffBody res_adjs get_adjs_for (Body () stms res) = subAD $+ subSubsts $ do+ let onResult (SubExpRes _ (Constant _)) _ = pure ()+ onResult (SubExpRes _ (Var v)) v_adj = void $ updateAdj v =<< adjVal v_adj+ (adjs, stms') <- collectStms $ do+ zipWithM_ onResult (takeLast (length res_adjs) res) res_adjs+ diffStms =<< preprocess stms+ mapM lookupAdjVal get_adjs_for+ pure $ Body () stms' $ res <> varsRes adjs++diffLambda :: [Adj] -> [VName] -> Lambda SOACS -> ADM (Lambda SOACS)+diffLambda res_adjs get_adjs_for (Lambda params body _) =+ localScope (scopeOfLParams params) $ do+ Body () stms res <- diffBody res_adjs get_adjs_for body+ let body' = Body () stms $ takeLast (length get_adjs_for) res+ ts' <- mapM lookupType get_adjs_for+ pure $ Lambda params body' ts'++revVJP :: MonadFreshNames m => Scope SOACS -> Lambda SOACS -> m (Lambda SOACS)+revVJP scope (Lambda params body ts) =+ runADM . localScope (scope <> scopeOfLParams params) $ do+ params_adj <- forM (zip (map resSubExp (bodyResult body)) ts) $ \(se, t) ->+ Param mempty <$> maybe (newVName "const_adj") adjVName (subExpVar se) <*> pure t++ body' <-+ localScope (scopeOfLParams params_adj) $+ diffBody+ (map adjFromParam params_adj)+ (map paramName params)+ body++ pure $ Lambda (params ++ params_adj) body' (ts <> map paramType params)++-- Note [Adjoints of accumulators]+--+-- The general case of taking adjoints of WithAcc is tricky. We make+-- some assumptions and lay down a basic design.+--+-- First, we assume that any WithAccs that occur in the program are+-- the result of previous invocations of VJP. This means we can rely+-- on the operator having a constant adjoint (it's some kind of+-- addition).+--+-- Second, the adjoint of an accumulator is an array of the same type+-- as the underlying array. For example, the adjoint type of the+-- primal type 'acc(c, [n], {f64})' is '[n]f64'. In principle the+-- adjoint of 'acc(c, [n], {f64,f32})' should be two arrays of type+-- '[]f64', '[]f32'. Our current design assumes that adjoints are+-- single variables. This is fixable.+--+-- # Adjoint of UpdateAcc+--+-- Consider primal code+--+-- update_acc(acc, i, v)+--+-- Interpreted as an imperative statement, this means+--+-- acc[i] ⊕= v+--+-- for some '⊕'. Normally all the compiler knows of '⊕' is that it+-- is associative and commutative, but because we assume that all+-- accumulators are the result of previous AD transformations, we+-- can assume that '⊕' actually behaves like addition - that is, has+-- unit partial derivatives. So the return sweep is+--+-- v += acc_adj[i]+--+-- # Adjoint of Map+--+-- Suppose we have primal code+--+-- let acc' =+-- map (...) acc+--+-- where "acc : acc(c, [n], {f64})" and the width of the Map is "w".+-- Our normal transformation for Map input arrays is to similarly map+-- their adjoint, but clearly this doesn't work here because the+-- semantics of mapping an adjoint is an "implicit replicate". So+-- when generating the return sweep we actually perform that+-- replication:+--+-- map (...) (replicate w acc_adj)+--+-- But what about the contributions to "acc'"? Those we also have to+-- take special care of. The result of the map itself is actually a+-- multidimensional array:+--+-- let acc_contribs =+-- map (...) (replicate w acc'_adj)+--+-- which we must then sum to add to the contribution.+--+-- acc_adj += sum(acc_contribs)+--+-- I'm slightly worried about the asymptotics of this, since my+-- intuition of this is that the contributions might be rather sparse.+-- (Maybe completely zero? If so it will be simplified away+-- entirely.) Perhaps a better solution is to treat+-- accumulator-inputs in the primal code as we do free variables, and+-- create accumulators for them in the return sweep.+--+-- # Consumption+--+-- A minor problem is that our usual way of handling consumption (Note+-- [Consumption]) is not viable, because accumulators are not+-- copyable. Fortunately, while the accumulators that are consumed in+-- the forward sweep will also be present in the return sweep given+-- our current translation rules, they will be dead code. As long as+-- we are careful to run dead code elimination after revVJP, we should+-- be good.
+ src/Futhark/AD/Rev/Loop.hs view
@@ -0,0 +1,456 @@+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Futhark.AD.Rev.Loop (diffLoop, stripmineStms) where++import Control.Monad+import Data.Foldable (toList)+import Data.List (nub, (\\))+import qualified Data.Map as M+import Data.Maybe+import Futhark.AD.Rev.Monad+import qualified Futhark.Analysis.Alias as Alias+import Futhark.Analysis.PrimExp.Convert+import Futhark.Builder+import Futhark.IR.Aliases (consumedInStms)+import Futhark.IR.SOACS+import Futhark.Tools+import Futhark.Transform.Rename+import Futhark.Transform.Substitute+import Futhark.Util (traverseFold)++-- | A convenience function to bring the components of a for-loop into+-- scope and throw an error if the passed 'Exp' is not a for-loop.+bindForLoop ::+ PrettyRep rep =>+ Exp rep ->+ ( [(Param (FParamInfo rep), SubExp)] ->+ LoopForm rep ->+ VName ->+ IntType ->+ SubExp ->+ [(Param (LParamInfo rep), VName)] ->+ Body rep ->+ a+ ) ->+ a+bindForLoop (DoLoop val_pats form@(ForLoop i it bound loop_vars) body) f =+ f val_pats form i it bound loop_vars body+bindForLoop e _ = error $ "bindForLoop: not a for-loop:\n" <> pretty e++-- | A convenience function to rename a for-loop and then bind the+-- renamed components.+renameForLoop ::+ (MonadFreshNames m, Renameable rep, PrettyRep rep) =>+ Exp rep ->+ ( Exp rep ->+ [(Param (FParamInfo rep), SubExp)] ->+ LoopForm rep ->+ VName ->+ IntType ->+ SubExp ->+ [(Param (LParamInfo rep), VName)] ->+ Body rep ->+ m a+ ) ->+ m a+renameForLoop loop f = renameExp loop >>= \loop' -> bindForLoop loop' (f loop')++-- | Is the loop a while-loop?+isWhileLoop :: Exp rep -> Bool+isWhileLoop (DoLoop _ WhileLoop {} _) = True+isWhileLoop _ = False++-- | Transforms a 'ForLoop' into a 'ForLoop' with an empty list of+-- loop variables.+removeLoopVars :: MonadBuilder m => Exp (Rep m) -> m (Exp (Rep m))+removeLoopVars loop =+ bindForLoop loop $ \val_pats form i _it _bound loop_vars body -> do+ let indexify (x_param, xs) = do+ xs_t <- lookupType xs+ x' <-+ letExp (baseString $ paramName x_param) $+ BasicOp $ Index xs $ fullSlice xs_t [DimFix (Var i)]+ pure (paramName x_param, x')+ (substs_list, subst_stms) <- collectStms $ mapM indexify loop_vars+ let Body aux' stms' res' = substituteNames (M.fromList substs_list) body+ pure $ DoLoop val_pats form $ Body aux' (subst_stms <> stms') res'++-- | Augments a while-loop to also compute the number of iterations.+computeWhileIters :: Exp SOACS -> ADM SubExp+computeWhileIters (DoLoop val_pats (WhileLoop b) body) = do+ bound_v <- newVName "bound"+ let t = Prim $ IntType Int64+ bound_param = Param mempty bound_v t+ bound_init <- letSubExp "bound_init" $ zeroExp t+ body' <- localScope (scopeOfFParams [bound_param]) $+ buildBody_ $ do+ bound_plus_one <-+ let one = Constant $ IntValue $ intValue Int64 (1 :: Int)+ in letSubExp "bound+1" $ BasicOp $ BinOp (Add Int64 OverflowUndef) (Var bound_v) one+ addStms $ bodyStms body+ pure (pure (subExpRes bound_plus_one) <> bodyResult body)+ res <- letTupExp' "loop" $ DoLoop ((bound_param, bound_init) : val_pats) (WhileLoop b) body'+ pure $ head res+computeWhileIters e = error $ "convertWhileIters: not a while-loop:\n" <> pretty e++-- | Converts a 'WhileLoop' into a 'ForLoop'. Requires that the+-- surrounding 'DoLoop' is annotated with a @#[bound(n)]@ attribute,+-- where @n@ is an upper bound on the number of iterations of the+-- while-loop. The resulting for-loop will execute for @n@ iterations on+-- all inputs, so the tighter the bound the better.+convertWhileLoop :: SubExp -> Exp SOACS -> ADM (Exp SOACS)+convertWhileLoop bound_se (DoLoop val_pats (WhileLoop cond) body) =+ localScope (scopeOfFParams $ map fst val_pats) $ do+ i <- newVName "i"+ body' <-+ eBody+ [ eIf+ (pure $ BasicOp $ SubExp $ Var cond)+ (pure body)+ (resultBodyM $ map (Var . paramName . fst) val_pats)+ ]+ pure $ DoLoop val_pats (ForLoop i Int64 bound_se mempty) body'+convertWhileLoop _ e = error $ "convertWhileLoopBound: not a while-loop:\n" <> pretty e++-- | @nestifyLoop n bound loop@ transforms a loop into a depth-@n@ loop nest+-- of @bound@-iteration loops. This transformation does not preserve+-- the original semantics of the loop: @n@ and @bound@ may be arbitrary and have+-- no relation to the number of iterations of @loop@.+nestifyLoop ::+ SubExp ->+ Integer ->+ Exp SOACS ->+ ADM (Exp SOACS)+nestifyLoop bound_se = nestifyLoop' bound_se+ where+ nestifyLoop' offset n loop = bindForLoop loop nestify+ where+ nestify val_pats _form i it _bound loop_vars body+ | n > 1 = do+ renameForLoop loop $ \_loop' val_pats' _form' i' it' _bound' loop_vars' body' -> do+ let loop_params = map fst val_pats+ loop_params' = map fst val_pats'+ loop_inits' = map (Var . paramName) loop_params+ val_pats'' = zip loop_params' loop_inits'+ outer_body <-+ buildBody_ $+ do+ offset' <-+ letSubExp "offset" $+ BasicOp $ BinOp (Mul it OverflowUndef) offset (Var i)++ inner_body <- insertStmsM $ do+ i_inner <-+ letExp "i_inner" $+ BasicOp $ BinOp (Add it OverflowUndef) offset' (Var i')+ pure $ substituteNames (M.singleton i' i_inner) body'++ inner_loop <-+ letTupExp "inner_loop"+ =<< nestifyLoop'+ offset'+ (n - 1)+ (DoLoop val_pats'' (ForLoop i' it' bound_se loop_vars') inner_body)+ pure $ varsRes inner_loop+ pure $+ DoLoop val_pats (ForLoop i it bound_se loop_vars) outer_body+ | n == 1 =+ pure $ DoLoop val_pats (ForLoop i it bound_se loop_vars) body+ | otherwise = pure loop++-- | @stripmine n pat loop@ stripmines a loop into a depth-@n@ loop nest.+-- An additional @bound - (floor(bound^(1/n)))^n@-iteration remainder loop is+-- inserted after the stripmined loop which executes the remaining iterations+-- so that the stripmined loop is semantically equivalent to the original loop.+stripmine :: Integer -> Pat Type -> Exp SOACS -> ADM (Stms SOACS)+stripmine n pat loop = do+ loop' <- removeLoopVars loop+ bindForLoop loop' $ \_val_pats _form _i it bound _loop_vars _body -> do+ let n_root = Constant $ FloatValue $ floatValue Float64 (1 / fromIntegral n :: Double)+ bound_float <- letSubExp "bound_f64" $ BasicOp $ ConvOp (UIToFP it Float64) bound+ bound' <- letSubExp "bound" $ BasicOp $ BinOp (FPow Float64) bound_float n_root+ bound_int <- letSubExp "bound_int" $ BasicOp $ ConvOp (FPToUI Float64 it) bound'+ total_iters <-+ letSubExp "total_iters" $+ BasicOp $ BinOp (Pow it) bound_int (Constant $ IntValue $ intValue it n)+ remain_iters <-+ letSubExp "remain_iters" $ BasicOp $ BinOp (Sub it OverflowUndef) bound total_iters+ mined_loop <- nestifyLoop bound_int n loop+ pat' <- renamePat pat+ renameForLoop loop $ \_loop' val_pats' _form' i' it' _bound' loop_vars' body' -> do+ remain_body <- insertStmsM $ do+ i_remain <-+ letExp "i_remain" $+ BasicOp $ BinOp (Add it OverflowUndef) total_iters (Var i')+ pure $ substituteNames (M.singleton i' i_remain) body'+ let loop_params_rem = map fst val_pats'+ loop_inits_rem = map (Var . patElemName) $ patElems pat'+ val_pats_rem = zip loop_params_rem loop_inits_rem+ remain_loop = DoLoop val_pats_rem (ForLoop i' it' remain_iters loop_vars') remain_body+ collectStms_ $ do+ letBind pat' mined_loop+ letBind pat remain_loop++-- | Stripmines a statement. Only has an effect when the statement's+-- expression is a for-loop with a @#[stripmine(n)]@ attribute, where+-- @n@ is the nesting depth.+stripmineStm :: Stm SOACS -> ADM (Stms SOACS)+stripmineStm stm@(Let pat aux loop@(DoLoop _ ForLoop {} _)) =+ case nums of+ (n : _) -> stripmine n pat loop+ _ -> pure $ oneStm stm+ where+ extractNum (AttrComp "stripmine" [AttrInt n]) = Just n+ extractNum _ = Nothing+ nums = catMaybes $ mapAttrs extractNum $ stmAuxAttrs aux+stripmineStm stm = pure $ oneStm stm++stripmineStms :: Stms SOACS -> ADM (Stms SOACS)+stripmineStms = traverseFold stripmineStm++-- | Forward pass transformation of a loop. This includes modifying the loop+-- to save the loop values at each iteration onto a tape as well as copying+-- any consumed arrays in the loop's body and consuming said copies in lieu of+-- the originals (which will be consumed later in the reverse pass).+fwdLoop :: Pat Type -> StmAux () -> Exp SOACS -> ADM ()+fwdLoop pat aux loop =+ bindForLoop loop $ \val_pats form i _it bound _loop_vars body -> do+ bound64 <- asIntS Int64 bound+ let loop_params = map fst val_pats+ is_true_dep = inAttrs (AttrName "true_dep") . paramAttrs+ dont_copy_params = filter is_true_dep loop_params+ dont_copy = map paramName dont_copy_params+ loop_params_to_copy = loop_params \\ dont_copy_params++ empty_saved_array <-+ forM loop_params_to_copy $ \p ->+ letSubExp (baseString (paramName p) <> "_empty_saved")+ =<< eBlank (arrayOf (paramDec p) (Shape [bound64]) NoUniqueness)++ (body', (saved_pats, saved_params)) <- buildBody $+ localScope (scopeOfFParams loop_params) $+ inScopeOf form $ do+ copy_substs <- copyConsumedArrsInBody dont_copy body+ addStms $ bodyStms body+ i_i64 <- asIntS Int64 $ Var i+ (saved_updates, saved_pats_params) <- fmap unzip $+ forM loop_params_to_copy $ \p -> do+ let v = paramName p+ t = paramDec p+ saved_param_v <- newVName $ baseString v <> "_saved"+ saved_pat_v <- newVName $ baseString v <> "_saved"+ setLoopTape v saved_pat_v+ let saved_param = Param mempty saved_param_v $ arrayOf t (Shape [bound64]) Unique+ saved_pat = PatElem saved_pat_v $ arrayOf t (Shape [bound64]) NoUniqueness+ saved_update <-+ localScope (scopeOfFParams [saved_param]) $+ letInPlace+ (baseString v <> "_saved_update")+ saved_param_v+ (fullSlice (fromDecl $ paramDec saved_param) [DimFix i_i64])+ $ substituteNames copy_substs $ BasicOp $ SubExp $ Var v+ pure (saved_update, (saved_pat, saved_param))+ pure (bodyResult body <> varsRes saved_updates, unzip saved_pats_params)++ let pat' = pat <> Pat saved_pats+ val_pats' = val_pats <> zip saved_params empty_saved_array+ addStm $ Let pat' aux $ DoLoop val_pats' form body'++-- | Construct a loop value-pattern for the adjoint of the+-- given variable.+valPatAdj :: VName -> ADM (Param DeclType, SubExp)+valPatAdj v = do+ v_adj <- adjVName v+ init_adj <- lookupAdjVal v+ t <- lookupType init_adj+ pure (Param mempty v_adj (toDecl t Unique), Var init_adj)++valPatAdjs :: LoopInfo [VName] -> ADM (LoopInfo [(Param DeclType, SubExp)])+valPatAdjs = (mapM . mapM) valPatAdj++-- | Reverses a loop by substituting the loop index as well as reversing+-- the arrays that loop variables are bound to.+reverseIndices :: Exp SOACS -> ADM (Substitutions, Substitutions, Stms SOACS)+reverseIndices loop = do+ bindForLoop loop $ \_val_pats form i it bound loop_vars _body -> do+ bound_minus_one <-+ inScopeOf form $+ let one = Constant $ IntValue $ intValue it (1 :: Int)+ in letSubExp "bound-1" $ BasicOp $ BinOp (Sub it OverflowUndef) bound one++ var_arrays_substs <- fmap M.fromList $+ inScopeOf form $ do+ forM (map snd loop_vars) $ \xs -> do+ xs_t <- lookupType xs+ xs_rev <-+ letExp "reverse" $+ BasicOp $+ Index xs $+ fullSlice+ xs_t+ [DimSlice bound_minus_one bound (Constant (IntValue (Int64Value (-1))))]+ pure (xs, xs_rev)++ (i_rev, i_stms) <- collectStms $+ inScopeOf form $ do+ letExp (baseString i <> "_rev") $+ BasicOp $ BinOp (Sub it OverflowWrap) bound_minus_one (Var i)++ pure (var_arrays_substs, M.singleton i i_rev, i_stms)++-- | Pures a substitution which substitutes values in the reverse+-- loop body with values from the tape.+restore :: Stms SOACS -> [Param DeclType] -> VName -> ADM Substitutions+restore stms_adj loop_params' i' =+ M.fromList . catMaybes <$> mapM f loop_params'+ where+ dont_copy =+ map paramName $ filter (inAttrs (AttrName "true_dep") . paramAttrs) loop_params'+ f p+ | v `notElem` dont_copy = do+ m_vs <- lookupLoopTape v+ case m_vs of+ Nothing -> pure Nothing+ Just vs -> do+ vs_t <- lookupType vs+ i_i64' <- asIntS Int64 $ Var i'+ v' <- letExp "restore" $ BasicOp $ Index vs $ fullSlice vs_t [DimFix i_i64']+ t <- lookupType v+ v'' <- case (t, v `elem` consumed) of+ (Array {}, True) -> letExp "restore_copy" $ BasicOp $ Copy v'+ _ -> pure v'+ pure $ Just (v, v'')+ | otherwise = pure Nothing+ where+ v = paramName p+ consumed = namesToList $ consumedInStms $ fst $ Alias.analyseStms mempty stms_adj++-- | A type to keep track of and seperate values corresponding to different+-- parts of the loop.+data LoopInfo a = LoopInfo+ { loopRes :: a,+ loopFree :: a,+ loopVars :: a,+ loopVals :: a+ }+ deriving (Functor, Foldable, Traversable, Show)++-- | Transforms a for-loop into its reverse-mode derivative.+revLoop :: (Stms SOACS -> ADM ()) -> Pat Type -> Exp SOACS -> ADM ()+revLoop diffStms pat loop =+ bindForLoop loop $ \val_pats _form _i _it _bound _loop_vars _body ->+ renameForLoop loop $+ \loop' val_pats' form' i' _it' _bound' loop_vars' body' -> do+ let loop_params = map fst val_pats+ (loop_params', loop_vals') = unzip val_pats'+ loop_var_arrays' = map snd loop_vars'+ getVName Constant {} = Nothing+ getVName (Var v) = Just v+ loop_vnames =+ LoopInfo+ { loopRes = mapMaybe subExpResVName $ bodyResult body',+ loopFree =+ (namesToList (freeIn loop') \\ loop_var_arrays') \\ mapMaybe getVName loop_vals',+ loopVars = loop_var_arrays',+ loopVals = nub $ mapMaybe getVName loop_vals'+ }++ renameLoopTape $ M.fromList $ zip (map paramName loop_params) (map paramName loop_params')++ forM_ (zip (bodyResult body') $ patElems pat) $ \(se_res, pe) ->+ case subExpResVName se_res of+ Just v -> setAdj v =<< lookupAdj (patElemName pe)+ Nothing -> pure ()++ (var_array_substs, i_subst, i_stms) <-+ reverseIndices loop'++ val_pat_adjs <- valPatAdjs loop_vnames+ let val_pat_adjs_list = concat $ toList val_pat_adjs++ (loop_adjs, stms_adj) <- collectStms $+ inScopeOf form' $+ localScope (scopeOfFParams (map fst val_pat_adjs_list <> loop_params')) $ do+ addStms i_stms+ (loop_adjs, stms_adj) <- collectStms $+ subAD $ do+ zipWithM_+ (\val_pat v -> insAdj v (paramName $ fst val_pat))+ val_pat_adjs_list+ (concat $ toList loop_vnames)+ diffStms $ bodyStms body'++ let update_var_arrays v vs = do+ vs_t <- lookupType vs+ v_adj <- lookupAdjVal v+ updateAdjSlice (fullSlice vs_t [DimFix $ Var i']) vs v_adj+ zipWithM_+ update_var_arrays+ (map (paramName . fst) loop_vars')+ (loopVars loop_vnames)++ loop_res_adjs <- mapM (lookupAdjVal . paramName) loop_params'+ loop_free_adjs <- mapM lookupAdjVal $ loopFree loop_vnames+ loop_vars_adjs <- mapM lookupAdjVal $ loopVars loop_vnames+ loop_vals_adjs <- mapM lookupAdjVal $ loopVals loop_vnames++ pure $+ LoopInfo+ { loopRes = loop_res_adjs,+ loopFree = loop_free_adjs,+ loopVars = loop_vars_adjs,+ loopVals = loop_vals_adjs+ }+ (substs, restore_stms) <-+ collectStms $ restore stms_adj loop_params' i'+ addStms $ substituteNames i_subst restore_stms+ addStms $ substituteNames i_subst $ substituteNames substs stms_adj+ pure loop_adjs++ inScopeOf stms_adj $+ localScope (scopeOfFParams $ map fst val_pat_adjs_list) $ do+ let body_adj = mkBody stms_adj $ varsRes $ concat $ toList loop_adjs+ restore_true_deps = M.fromList $+ flip mapMaybe (zip loop_params' $ patElems pat) $ \(p, pe) ->+ if p `elem` filter (inAttrs (AttrName "true_dep") . paramAttrs) loop_params'+ then Just (paramName p, patElemName pe)+ else Nothing+ adjs' <-+ letTupExp "loop_adj" $+ substituteNames (restore_true_deps <> var_array_substs) $+ DoLoop val_pat_adjs_list form' body_adj+ let (loop_res_adjs, loop_free_var_val_adjs) =+ splitAt (length $ loopRes loop_adjs) adjs'+ (loop_free_adjs, loop_var_val_adjs) =+ splitAt (length $ loopFree loop_adjs) loop_free_var_val_adjs+ (loop_var_adjs, loop_val_adjs) =+ splitAt (length $ loopVars loop_adjs) loop_var_val_adjs+ returnSweepCode $ do+ zipWithM_ updateSubExpAdj loop_vals' loop_res_adjs+ zipWithM_ insAdj (loopFree loop_vnames) loop_free_adjs+ zipWithM_ insAdj (loopVars loop_vnames) loop_var_adjs+ zipWithM_ updateAdj (loopVals loop_vnames) loop_val_adjs++-- | Transforms a loop into its reverse-mode derivative.+diffLoop :: (Stms SOACS -> ADM ()) -> Pat Type -> StmAux () -> Exp SOACS -> ADM () -> ADM ()+diffLoop diffStms pat aux loop m+ | isWhileLoop loop =+ let getBound (AttrComp "bound" [AttrInt b]) = Just b+ getBound _ = Nothing+ bounds = catMaybes $ mapAttrs getBound $ stmAuxAttrs aux+ in case bounds of+ (bound : _) -> do+ let bound_se = Constant $ IntValue $ intValue Int64 bound+ for_loop <- convertWhileLoop bound_se loop+ diffLoop diffStms pat aux for_loop m+ _ -> do+ bound <- computeWhileIters loop+ for_loop <- convertWhileLoop bound =<< renameExp loop+ diffLoop diffStms pat aux for_loop m+ | otherwise = do+ fwdLoop pat aux loop+ m+ revLoop diffStms pat loop
+ src/Futhark/AD/Rev/Map.hs view
@@ -0,0 +1,233 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Futhark.AD.Rev.Map (vjpMap) where++import Control.Monad+import Data.Bifunctor (first)+import Futhark.AD.Rev.Monad+import Futhark.Analysis.PrimExp.Convert+import Futhark.Builder+import Futhark.IR.SOACS+import Futhark.Tools+import Futhark.Transform.Rename+import Futhark.Util (splitAt3)++-- | A classification of a free variable based on its adjoint. The+-- 'VName' stored is *not* the adjoint, but the primal variable.+data AdjVar+ = -- | Adjoint is already an accumulator.+ FreeAcc VName+ | -- | Currently has no adjoint, but should be given one, and is an+ -- array with this shape and element type.+ FreeArr VName Shape PrimType+ | -- | Does not need an accumulator adjoint (might still be an array).+ FreeNonAcc VName++classifyAdjVars :: [VName] -> ADM [AdjVar]+classifyAdjVars = mapM f+ where+ f v = do+ v_adj <- lookupAdjVal v+ v_adj_t <- lookupType v_adj+ case v_adj_t of+ Array pt shape _ ->+ pure $ FreeArr v shape pt+ Acc {} ->+ pure $ FreeAcc v+ _ ->+ pure $ FreeNonAcc v++partitionAdjVars :: [AdjVar] -> ([(VName, (Shape, PrimType))], [VName], [VName])+partitionAdjVars [] = ([], [], [])+partitionAdjVars (fv : fvs) =+ case fv of+ FreeArr v shape t -> ((v, (shape, t)) : xs, ys, zs)+ FreeAcc v -> (xs, v : ys, zs)+ FreeNonAcc v -> (xs, ys, v : zs)+ where+ (xs, ys, zs) = partitionAdjVars fvs++buildRenamedBody ::+ MonadBuilder m =>+ m (Result, a) ->+ m (Body (Rep m), a)+buildRenamedBody m = do+ (body, x) <- buildBody m+ body' <- renameBody body+ pure (body', x)++withAcc ::+ [(Shape, [VName], Maybe (Lambda SOACS, [SubExp]))] ->+ ([VName] -> ADM Result) ->+ ADM [VName]+withAcc [] m =+ mapM (letExp "withacc_res" . BasicOp . SubExp . resSubExp) =<< m []+withAcc inputs m = do+ (cert_params, acc_params) <- fmap unzip $+ forM inputs $ \(shape, arrs, _) -> do+ cert_param <- newParam "acc_cert_p" $ Prim Unit+ ts <- mapM (fmap (stripArray (shapeRank shape)) . lookupType) arrs+ acc_param <- newParam "acc_p" $ Acc (paramName cert_param) shape ts NoUniqueness+ pure (cert_param, acc_param)+ acc_lam <-+ subAD $ mkLambda (cert_params ++ acc_params) $ m $ map paramName acc_params+ letTupExp "withhacc_res" $ WithAcc inputs acc_lam++vjpMap :: VjpOps -> [Adj] -> StmAux () -> SubExp -> Lambda SOACS -> [VName] -> ADM ()+vjpMap ops res_adjs _ w map_lam as+ | Just res_ivs <- mapM isSparse res_adjs = returnSweepCode $ do+ -- Since at most only a constant number of adjoint are nonzero+ -- (length res_ivs), there is no need for the return sweep code to+ -- contain a Map at all.++ free <- filterM isActive $ namesToList $ freeIn map_lam+ free_ts <- mapM lookupType free+ let adjs_for = map paramName (lambdaParams map_lam) ++ free+ adjs_ts = map paramType (lambdaParams map_lam) ++ free_ts++ let oneHot res_i adj_v = zipWith f [0 :: Int ..] $ lambdaReturnType map_lam+ where+ f j t+ | res_i == j = adj_v+ | otherwise = AdjZero (arrayShape t) (elemType t)+ -- Values for the out-of-bounds case does not matter, as we will+ -- be writing to an out-of-bounds index anyway, which is ignored.+ ooBounds adj_i = subAD . buildRenamedBody $ do+ forM_ (zip as adjs_ts) $ \(a, t) -> do+ scratch <- letSubExp "oo_scratch" =<< eBlank t+ updateAdjIndex a (OutOfBounds, adj_i) scratch+ first subExpsRes . adjsReps <$> mapM lookupAdj as+ inBounds res_i adj_i adj_v = subAD . buildRenamedBody $ do+ forM_ (zip (lambdaParams map_lam) as) $ \(p, a) -> do+ a_t <- lookupType a+ letBindNames [paramName p] $+ BasicOp $ Index a $ fullSlice a_t [DimFix adj_i]+ adj_elems <-+ fmap (map resSubExp) . bodyBind . lambdaBody+ =<< vjpLambda ops (oneHot res_i (AdjVal adj_v)) adjs_for map_lam+ forM_ (zip as adj_elems) $ \(a, a_adj_elem) -> do+ updateAdjIndex a (AssumeBounds, adj_i) a_adj_elem+ first subExpsRes . adjsReps <$> mapM lookupAdj as++ -- Generate an iteration of the map function for every+ -- position. This is a bit inefficient - probably we could do+ -- some deduplication.+ forPos res_i (check, adj_i, adj_v) = do+ as_adj <-+ case check of+ CheckBounds b -> do+ (obbranch, mkadjs) <- ooBounds adj_i+ (ibbranch, _) <- inBounds res_i adj_i adj_v+ fmap mkadjs . letTupExp' "map_adj_elem"+ =<< eIf+ (maybe (eDimInBounds (eSubExp w) (eSubExp adj_i)) eSubExp b)+ (pure ibbranch)+ (pure obbranch)+ AssumeBounds -> do+ (body, mkadjs) <- inBounds res_i adj_i adj_v+ mkadjs . map resSubExp <$> bodyBind body+ OutOfBounds ->+ mapM lookupAdj as++ zipWithM setAdj as as_adj++ -- Generate an iteration of the map function for every result.+ forRes res_i = mapM_ (forPos res_i)++ zipWithM_ forRes [0 ..] res_ivs+ where+ isSparse (AdjSparse (Sparse shape _ ivs)) = do+ guard $ shapeDims shape == [w]+ Just ivs+ isSparse _ =+ Nothing+-- See Note [Adjoints of accumulators] for how we deal with+-- accumulators - it's a bit tricky here.+vjpMap ops pat_adj aux w map_lam as = returnSweepCode $ do+ pat_adj_vals <- forM (zip pat_adj (lambdaReturnType map_lam)) $ \(adj, t) ->+ case t of+ Acc {} -> letExp "acc_adj_rep" . BasicOp . Replicate (Shape [w]) . Var =<< adjVal adj+ _ -> adjVal adj+ pat_adj_params <-+ mapM (newParam "map_adj_p" . rowType <=< lookupType) pat_adj_vals++ map_lam' <- renameLambda map_lam+ free <- filterM isActive $ namesToList $ freeIn map_lam'++ accAdjoints free $ \free_with_adjs free_without_adjs -> do+ free_adjs <- mapM lookupAdjVal free_with_adjs+ free_adjs_ts <- mapM lookupType free_adjs+ free_adjs_params <- mapM (newParam "free_adj_p") free_adjs_ts+ let lam_rev_params =+ lambdaParams map_lam' ++ pat_adj_params ++ free_adjs_params+ adjs_for = map paramName (lambdaParams map_lam') ++ free+ lam_rev <-+ mkLambda lam_rev_params . subAD . noAdjsFor free_without_adjs $ do+ zipWithM_ insAdj free_with_adjs $ map paramName free_adjs_params+ bodyBind . lambdaBody+ =<< vjpLambda ops (map adjFromParam pat_adj_params) adjs_for map_lam'++ (param_contribs, free_contribs) <-+ fmap (splitAt (length (lambdaParams map_lam'))) $+ auxing aux . letTupExp "map_adjs" . Op $+ Screma w (as ++ pat_adj_vals ++ free_adjs) (mapSOAC lam_rev)++ -- Crucial that we handle the free contribs first in case 'free'+ -- and 'as' intersect.+ zipWithM_ freeContrib free free_contribs+ let param_ts = map paramType (lambdaParams map_lam')+ forM_ (zip3 param_ts as param_contribs) $ \(param_t, a, param_contrib) ->+ case param_t of+ Acc {} -> freeContrib a param_contrib+ _ -> updateAdj a param_contrib+ where+ addIdxParams n lam = do+ idxs <- replicateM n $ newParam "idx" $ Prim int64+ pure $ lam {lambdaParams = idxs ++ lambdaParams lam}++ accAddLambda n t = addIdxParams n =<< addLambda t++ withAccInput (v, (shape, pt)) = do+ v_adj <- lookupAdjVal v+ add_lam <- accAddLambda (shapeRank shape) $ Prim pt+ zero <- letSubExp "zero" $ zeroExp $ Prim pt+ pure (shape, [v_adj], Just (add_lam, [zero]))++ accAdjoints free m = do+ (arr_free, acc_free, nonacc_free) <-+ partitionAdjVars <$> classifyAdjVars free+ arr_free' <- mapM withAccInput arr_free+ -- We only consider those input arrays that are also not free in+ -- the lambda.+ let as_nonfree = filter (`notElem` free) as+ (arr_adjs, acc_adjs, rest_adjs) <-+ fmap (splitAt3 (length arr_free) (length acc_free)) . withAcc arr_free' $ \accs -> do+ zipWithM_ insAdj (map fst arr_free) accs+ () <- m (acc_free ++ map fst arr_free) (namesFromList nonacc_free)+ acc_free_adj <- mapM lookupAdjVal acc_free+ arr_free_adj <- mapM (lookupAdjVal . fst) arr_free+ nonacc_free_adj <- mapM lookupAdjVal nonacc_free+ as_nonfree_adj <- mapM lookupAdjVal as_nonfree+ pure $ varsRes $ arr_free_adj <> acc_free_adj <> nonacc_free_adj <> as_nonfree_adj+ zipWithM_ insAdj acc_free acc_adjs+ zipWithM_ insAdj (map fst arr_free) arr_adjs+ let (nonacc_adjs, as_nonfree_adjs) = splitAt (length nonacc_free) rest_adjs+ zipWithM_ insAdj nonacc_free nonacc_adjs+ zipWithM_ insAdj as_nonfree as_nonfree_adjs++ freeContrib v contribs = do+ contribs_t <- lookupType contribs+ case rowType contribs_t of+ Acc {} -> void $ insAdj v contribs+ t -> do+ lam <- addLambda t+ zero <- letSubExp "zero" $ zeroExp t+ reduce <- reduceSOAC [Reduce Commutative lam [zero]]+ contrib_sum <-+ letExp (baseString v <> "_contrib_sum") $+ Op $ Screma w [contribs] reduce+ void $ updateAdj v contrib_sum
+ src/Futhark/AD/Rev/Monad.hs view
@@ -0,0 +1,551 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++-- Naming scheme:+--+-- An adjoint-related object for "x" is named "x_adj". This means+-- both actual adjoints and statements.+--+-- Do not assume "x'" means anything related to derivatives.+module Futhark.AD.Rev.Monad+ ( ADM,+ RState (..),+ runADM,+ Adj (..),+ InBounds (..),+ Sparse (..),+ adjFromParam,+ adjFromVar,+ lookupAdj,+ lookupAdjVal,+ adjVal,+ updateAdj,+ updateSubExpAdj,+ updateAdjSlice,+ updateAdjIndex,+ setAdj,+ insAdj,+ insSubExpAdj,+ adjsReps,+ --+ copyConsumedArrsInStm,+ copyConsumedArrsInBody,+ addSubstitution,+ returnSweepCode,+ --+ adjVName,+ subAD,+ noAdjsFor,+ subSubsts,+ isActive,+ --+ tabNest,+ oneExp,+ zeroExp,+ unitAdjOfType,+ addLambda,+ --+ VjpOps (..),+ --+ setLoopTape,+ lookupLoopTape,+ substLoopTape,+ renameLoopTape,+ )+where++import Control.Monad+import Control.Monad.State.Strict+import Data.Bifunctor (second)+import Data.List (foldl')+import qualified Data.Map as M+import Data.Maybe+import qualified Futhark.Analysis.Alias as Alias+import Futhark.Analysis.PrimExp.Convert+import Futhark.Builder+import Futhark.IR.Aliases (consumedInStms)+import Futhark.IR.Prop.Aliases+import Futhark.IR.SOACS+import Futhark.Tools+import Futhark.Transform.Substitute+import Futhark.Util (chunks)++zeroExp :: Type -> Exp rep+zeroExp (Prim pt) =+ BasicOp $ SubExp $ Constant $ blankPrimValue pt+zeroExp (Array pt shape _) =+ BasicOp $ Replicate shape $ Constant $ blankPrimValue pt+zeroExp t = error $ "zeroExp: " ++ pretty t++onePrim :: PrimType -> PrimValue+onePrim (IntType it) = IntValue $ intValue it (1 :: Int)+onePrim (FloatType ft) = FloatValue $ floatValue ft (1 :: Double)+onePrim Bool = BoolValue True+onePrim Unit = UnitValue++oneExp :: Type -> Exp rep+oneExp (Prim t) = BasicOp $ SubExp $ constant $ onePrim t+oneExp (Array pt shape _) =+ BasicOp $ Replicate shape $ Constant $ onePrim pt+oneExp t = error $ "oneExp: " ++ pretty t++-- | Whether 'Sparse' should check bounds or assume they are correct.+-- The latter results in simpler code.+data InBounds+ = -- | If a SubExp is provided, it references a boolean that is true+ -- when in-bounds.+ CheckBounds (Maybe SubExp)+ | AssumeBounds+ | -- | Dynamically these will always fail, so don't bother+ -- generating code for the update. This is only needed to ensure+ -- a consistent representation of sparse Jacobians.+ OutOfBounds+ deriving (Eq, Ord, Show)++-- | A symbolic representation of an array that is all zeroes, except at one+-- index.+data Sparse = Sparse+ { -- | The shape of the array.+ sparseShape :: Shape,+ -- | Element type of the array.+ sparseType :: PrimType,+ -- | Locations and values of nonzero values. Indexes may be+ -- negative, in which case the value is ignored (unless+ -- 'AssumeBounds' is used).+ sparseIdxVals :: [(InBounds, SubExp, SubExp)]+ }+ deriving (Eq, Ord, Show)++-- | The adjoint of a variable.+data Adj+ = AdjSparse Sparse+ | AdjVal SubExp+ | AdjZero Shape PrimType+ deriving (Eq, Ord, Show)++instance Substitute Adj where+ substituteNames m (AdjVal (Var v)) = AdjVal $ Var $ substituteNames m v+ substituteNames _ adj = adj++zeroArray :: MonadBuilder m => Shape -> Type -> m VName+zeroArray shape t+ | shapeRank shape == 0 =+ letExp "zero" $ zeroExp t+ | otherwise = do+ zero <- letSubExp "zero" $ zeroExp t+ attributing (oneAttr "sequential") $+ letExp "zeroes_" $ BasicOp $ Replicate shape zero++sparseArray :: (MonadBuilder m, Rep m ~ SOACS) => Sparse -> m VName+sparseArray (Sparse shape t ivs) = do+ flip (foldM f) ivs =<< zeroArray shape (Prim t)+ where+ arr_t = Prim t `arrayOfShape` shape+ f arr (check, i, se) = do+ let stm s =+ letExp "sparse" . BasicOp $+ Update s arr (fullSlice arr_t [DimFix i]) se+ case check of+ AssumeBounds -> stm Unsafe+ CheckBounds _ -> stm Safe+ OutOfBounds -> pure arr++adjFromVar :: VName -> Adj+adjFromVar = AdjVal . Var++adjFromParam :: Param t -> Adj+adjFromParam = adjFromVar . paramName++unitAdjOfType :: Type -> ADM Adj+unitAdjOfType t = AdjVal <$> letSubExp "adj_unit" (oneExp t)++-- | The values representing an adjoint in symbolic form. This is+-- used for when we wish to return an Adj from a Body or similar+-- without forcing manifestation. Also returns a function for+-- reassembling the Adj from a new representation (the list must have+-- the same length).+adjRep :: Adj -> ([SubExp], [SubExp] -> Adj)+adjRep (AdjVal se) = ([se], \[se'] -> AdjVal se')+adjRep (AdjZero shape pt) = ([], \[] -> AdjZero shape pt)+adjRep (AdjSparse (Sparse shape pt ivs)) =+ (concatMap ivRep ivs, AdjSparse . Sparse shape pt . repIvs ivs)+ where+ ivRep (_, i, v) = [i, v]+ repIvs ((check, _, _) : ivs') (i : v : ses) =+ (check', i, v) : repIvs ivs' ses+ where+ check' = case check of+ AssumeBounds -> AssumeBounds+ CheckBounds b -> CheckBounds b+ OutOfBounds -> CheckBounds (Just (constant False)) -- sic!+ repIvs _ _ = []++-- | Conveniently convert a list of Adjs to their representation, as+-- well as produce a function for converting back.+adjsReps :: [Adj] -> ([SubExp], [SubExp] -> [Adj])+adjsReps adjs =+ let (reps, fs) = unzip $ map adjRep adjs+ in (concat reps, zipWith ($) fs . chunks (map length reps))++data RState = RState+ { stateAdjs :: M.Map VName Adj,+ stateLoopTape :: Substitutions,+ stateSubsts :: Substitutions,+ stateNameSource :: VNameSource+ }++newtype ADM a = ADM (BuilderT SOACS (State RState) a)+ deriving+ ( Functor,+ Applicative,+ Monad,+ MonadState RState,+ MonadFreshNames,+ HasScope SOACS,+ LocalScope SOACS+ )++instance MonadBuilder ADM where+ type Rep ADM = SOACS+ mkExpDecM pat e = ADM $ mkExpDecM pat e+ mkBodyM bnds res = ADM $ mkBodyM bnds res+ mkLetNamesM pat e = ADM $ mkLetNamesM pat e++ addStms = ADM . addStms+ collectStms (ADM m) = ADM $ collectStms m++instance MonadFreshNames (State RState) where+ getNameSource = gets stateNameSource+ putNameSource src = modify (\env -> env {stateNameSource = src})++runADM :: MonadFreshNames m => ADM a -> m a+runADM (ADM m) =+ modifyNameSource $ \vn ->+ second stateNameSource $+ runState+ (fst <$> runBuilderT m mempty)+ (RState mempty mempty mempty vn)++adjVal :: Adj -> ADM VName+adjVal (AdjVal se) = letExp "const_adj" $ BasicOp $ SubExp se+adjVal (AdjSparse sparse) = sparseArray sparse+adjVal (AdjZero shape t) = zeroArray shape $ Prim t++setAdj :: VName -> Adj -> ADM ()+setAdj v v_adj = modify $ \env ->+ env {stateAdjs = M.insert v v_adj $ stateAdjs env}++insAdj :: VName -> VName -> ADM ()+insAdj v = setAdj v . AdjVal . Var++adjVName :: VName -> ADM VName+adjVName v = newVName (baseString v <> "_adj")++-- | Create copies of all arrays consumed in the given statement, and+-- return statements which include copies of the consumed arrays.+--+-- See Note [Consumption].+copyConsumedArrsInStm :: Stm SOACS -> ADM (Substitutions, Stms SOACS)+copyConsumedArrsInStm s = inScopeOf s $ collectStms $ copyConsumedArrsInStm' s+ where+ copyConsumedArrsInStm' stm =+ let onConsumed v = inScopeOf s $ do+ v_t <- lookupType v+ case v_t of+ Array {} -> do+ v' <- letExp (baseString v <> "_ad_copy") (BasicOp $ Copy v)+ addSubstitution v' v+ pure [(v, v')]+ _ -> pure mempty+ in M.fromList . mconcat+ <$> mapM onConsumed (namesToList $ consumedInStms $ fst (Alias.analyseStms mempty (oneStm stm)))++copyConsumedArrsInBody :: [VName] -> Body SOACS -> ADM Substitutions+copyConsumedArrsInBody dontCopy b =+ mconcat <$> mapM onConsumed (filter (`notElem` dontCopy) $ namesToList $ consumedInBody (Alias.analyseBody mempty b))+ where+ onConsumed v = do+ v_t <- lookupType v+ case v_t of+ Acc {} -> error $ "copyConsumedArrsInBody: Acc " <> pretty v+ Array {} -> M.singleton v <$> letExp (baseString v <> "_ad_copy") (BasicOp $ Copy v)+ _ -> pure mempty++returnSweepCode :: ADM a -> ADM a+returnSweepCode m = do+ (a, stms) <- collectStms m+ substs <- gets stateSubsts+ addStms $ substituteNames substs stms+ pure a++addSubstitution :: VName -> VName -> ADM ()+addSubstitution v v' = modify $ \env ->+ env {stateSubsts = M.insert v v' $ stateSubsts env}++-- While evaluating this action, pretend these variables have no+-- adjoints. Restore current adjoints afterwards. This is used for+-- handling certain nested operations. XXX: feels like this should+-- really be part of subAD, somehow. Main challenge is that we don't+-- want to blank out Accumulator adjoints. Also, might be inefficient+-- to blank out array adjoints.+noAdjsFor :: Names -> ADM a -> ADM a+noAdjsFor names m = do+ old <- gets $ \env -> mapMaybe (`M.lookup` stateAdjs env) names'+ modify $ \env -> env {stateAdjs = foldl' (flip M.delete) (stateAdjs env) names'}+ x <- m+ modify $ \env -> env {stateAdjs = M.fromList (zip names' old) <> stateAdjs env}+ pure x+ where+ names' = namesToList names++addBinOp :: PrimType -> BinOp+addBinOp (IntType it) = Add it OverflowWrap+addBinOp (FloatType ft) = FAdd ft+addBinOp Bool = LogAnd+addBinOp Unit = LogAnd++tabNest :: Int -> [VName] -> ([VName] -> [VName] -> ADM [VName]) -> ADM [VName]+tabNest = tabNest' []+ where+ tabNest' is 0 vs f = f (reverse is) vs+ tabNest' is n vs f = do+ vs_ts <- mapM lookupType vs+ let w = arraysSize 0 vs_ts+ iota <-+ letExp "tab_iota" . BasicOp $+ Iota w (intConst Int64 0) (intConst Int64 1) Int64+ iparam <- newParam "i" $ Prim int64+ params <- forM vs $ \v ->+ newParam (baseString v <> "_p") . rowType =<< lookupType v+ ((ret, res), stms) <- collectStms . localScope (scopeOfLParams (iparam : params)) $ do+ res <- tabNest' (paramName iparam : is) (n - 1) (map paramName params) f+ ret <- mapM lookupType res+ pure (ret, varsRes res)+ let lam = Lambda (iparam : params) (Body () stms res) ret+ letTupExp "tab" $ Op $ Screma w (iota : vs) (mapSOAC lam)++-- | Construct a lambda for adding two values of the given type.+addLambda :: Type -> ADM (Lambda SOACS)+addLambda (Prim pt) = binOpLambda (addBinOp pt) pt+addLambda t@Array {} = do+ xs_p <- newParam "xs" t+ ys_p <- newParam "ys" t+ lam <- addLambda $ rowType t+ body <- insertStmsM $ do+ res <-+ letSubExp "lam_map" . Op $+ Screma (arraySize 0 t) [paramName xs_p, paramName ys_p] (mapSOAC lam)+ pure $ resultBody [res]+ pure+ Lambda+ { lambdaParams = [xs_p, ys_p],+ lambdaReturnType = [t],+ lambdaBody = body+ }+addLambda t =+ error $ "addLambda: " ++ show t++-- Construct an expression for adding the two variables.+addExp :: VName -> VName -> ADM (Exp SOACS)+addExp x y = do+ x_t <- lookupType x+ case x_t of+ Prim pt ->+ pure $ BasicOp $ BinOp (addBinOp pt) (Var x) (Var y)+ Array {} -> do+ lam <- addLambda $ rowType x_t+ pure $ Op $ Screma (arraySize 0 x_t) [x, y] (mapSOAC lam)+ _ ->+ error $ "addExp: unexpected type: " ++ pretty x_t++lookupAdj :: VName -> ADM Adj+lookupAdj v = do+ maybeAdj <- gets $ M.lookup v . stateAdjs+ case maybeAdj of+ Nothing -> do+ v_t <- lookupType v+ case v_t of+ Acc _ shape [Prim t] _ -> pure $ AdjZero shape t+ _ -> pure $ AdjZero (arrayShape v_t) (elemType v_t)+ Just v_adj -> pure v_adj++lookupAdjVal :: VName -> ADM VName+lookupAdjVal v = adjVal =<< lookupAdj v++updateAdj :: VName -> VName -> ADM ()+updateAdj v d = do+ maybeAdj <- gets $ M.lookup v . stateAdjs+ case maybeAdj of+ Nothing ->+ insAdj v d+ Just adj -> do+ v_adj <- adjVal adj+ v_adj_t <- lookupType v_adj+ case v_adj_t of+ Acc {} -> do+ dims <- arrayDims <$> lookupType d+ ~[v_adj'] <-+ tabNest (length dims) [d, v_adj] $ \is [d', v_adj'] ->+ letTupExp "acc" $+ BasicOp $ UpdateAcc v_adj' (map Var is) [Var d']+ insAdj v v_adj'+ _ -> do+ v_adj' <- letExp (baseString v <> "_adj") =<< addExp v_adj d+ insAdj v v_adj'++updateAdjSlice :: Slice SubExp -> VName -> VName -> ADM ()+updateAdjSlice (Slice [DimFix i]) v d =+ updateAdjIndex v (AssumeBounds, i) (Var d)+updateAdjSlice slice v d = do+ t <- lookupType v+ v_adj <- lookupAdjVal v+ v_adj_t <- lookupType v_adj+ v_adj' <- case v_adj_t of+ Acc {} -> do+ let dims = sliceDims slice+ ~[v_adj'] <-+ tabNest (length dims) [d, v_adj] $ \is [d', v_adj'] -> do+ slice' <-+ traverse (toSubExp "index") $+ fixSlice (fmap pe64 slice) $ map le64 is+ letTupExp (baseString v_adj') . BasicOp $+ UpdateAcc v_adj' slice' [Var d']+ pure v_adj'+ _ -> do+ v_adjslice <-+ if primType t+ then pure v_adj+ else letExp (baseString v ++ "_slice") $ BasicOp $ Index v_adj slice+ letInPlace "updated_adj" v_adj slice =<< addExp v_adjslice d+ insAdj v v_adj'++updateSubExpAdj :: SubExp -> VName -> ADM ()+updateSubExpAdj Constant {} _ = pure ()+updateSubExpAdj (Var v) d = void $ updateAdj v d++insSubExpAdj :: SubExp -> VName -> ADM ()+insSubExpAdj Constant {} _ = pure ()+insSubExpAdj (Var v) d = void $ insAdj v d++-- The index may be negative, in which case the update has no effect.+updateAdjIndex :: VName -> (InBounds, SubExp) -> SubExp -> ADM ()+updateAdjIndex v (check, i) se = do+ maybeAdj <- gets $ M.lookup v . stateAdjs+ t <- lookupType v+ let iv = (check, i, se)+ case maybeAdj of+ Nothing -> do+ setAdj v $ AdjSparse $ Sparse (arrayShape t) (elemType t) [iv]+ Just AdjZero {} ->+ setAdj v $ AdjSparse $ Sparse (arrayShape t) (elemType t) [iv]+ Just (AdjSparse (Sparse shape pt ivs)) ->+ setAdj v $ AdjSparse $ Sparse shape pt $ iv : ivs+ Just adj@AdjVal {} -> do+ v_adj <- adjVal adj+ v_adj_t <- lookupType v_adj+ se_v <- letExp "se_v" $ BasicOp $ SubExp se+ insAdj v+ =<< case v_adj_t of+ Acc {}+ | check == OutOfBounds ->+ pure v_adj+ | otherwise -> do+ dims <- arrayDims <$> lookupType se_v+ ~[v_adj'] <-+ tabNest (length dims) [se_v, v_adj] $ \is [se_v', v_adj'] ->+ letTupExp "acc" $+ BasicOp $ UpdateAcc v_adj' (i : map Var is) [Var se_v']+ pure v_adj'+ _ -> do+ let stms s = do+ v_adj_i <- letExp (baseString v_adj <> "_i") $ BasicOp $ Index v_adj $ fullSlice v_adj_t [DimFix i]+ se_update <- letSubExp "updated_adj_i" =<< addExp se_v v_adj_i+ letExp (baseString v_adj) $+ BasicOp $ Update s v_adj (fullSlice v_adj_t [DimFix i]) se_update+ case check of+ CheckBounds _ -> stms Safe+ AssumeBounds -> stms Unsafe+ OutOfBounds -> pure v_adj++-- | Is this primal variable active in the AD sense? FIXME: this is+-- (obviously) much too conservative.+isActive :: VName -> ADM Bool+isActive = fmap (/= Prim Unit) . lookupType++subAD :: ADM a -> ADM a+subAD m = do+ old_state_adjs <- gets stateAdjs+ x <- m+ modify $ \s -> s {stateAdjs = old_state_adjs}+ pure x++subSubsts :: ADM a -> ADM a+subSubsts m = do+ old_state_substs <- gets stateSubsts+ x <- m+ modify $ \s -> s {stateSubsts = old_state_substs}+ pure x++data VjpOps = VjpOps+ { vjpLambda :: [Adj] -> [VName] -> Lambda SOACS -> ADM (Lambda SOACS),+ vjpStm :: Stm SOACS -> ADM () -> ADM ()+ }++-- | @setLoopTape v vs@ establishes @vs@ as the name of the array+-- where values of loop parameter @v@ from the forward pass are+-- stored.+setLoopTape :: VName -> VName -> ADM ()+setLoopTape v vs = modify $ \env ->+ env {stateLoopTape = M.insert v vs $ stateLoopTape env}++-- | Look-up the name of the array where @v@ is stored.+lookupLoopTape :: VName -> ADM (Maybe VName)+lookupLoopTape v = gets $ M.lookup v . stateLoopTape++-- | @substLoopTape v v'@ substitutes the key @v@ for @v'@. That is,+-- if @v |-> vs@ then after the substitution @v' |-> vs@ (and @v@+-- points to nothing).+substLoopTape :: VName -> VName -> ADM ()+substLoopTape v v' = mapM_ (setLoopTape v') =<< lookupLoopTape v++-- | Renames the keys of the loop tape. Useful for fixing the+-- the names in the loop tape after a loop rename.+renameLoopTape :: Substitutions -> ADM ()+renameLoopTape = mapM_ (uncurry substLoopTape) . M.toList++-- Note [Consumption]+--+-- Parts of this transformation depends on duplicating computation.+-- This is a problem when a primal expression consumes arrays (via+-- e.g. Update). For example, consider how we handle this conditional:+--+-- if b then ys with [0] = 0 else ys+--+-- This consumes the array 'ys', which means that when we later+-- generate code for the return sweep, we can no longer use 'ys'.+-- This is a problem, because when we call 'diffBody' on the branch+-- bodies, we'll keep the primal code (maybe it'll be removed by+-- simplification later - we cannot know). A similar issue occurs for+-- SOACs. Our solution is to make copies of all consumes arrays:+--+-- let ys_copy = copy ys+--+-- Then we generate code for the return sweep as normal, but replace+-- _every instance_ of 'ys' in the generated code with 'ys_copy'.+-- This works because Futhark does not have *semantic* in-place+-- updates - any uniqueness violation can be replaced with copies (on+-- arrays, anyway).+--+-- If we are lucky, the uses of 'ys_copy' will be removed by+-- simplification, and there will be no overhead. But even if not,+-- this is still (asymptotically) efficient because the array that is+-- being consumed must in any case have been produced within the code+-- that we are differentiating, so a copy is at most a scalar+-- overhead. This is _not_ the case when loops are involved.+--+-- Also, the above only works for arrays, not accumulator variables.+-- Those will need some other mechanism.
+ src/Futhark/AD/Rev/Reduce.hs view
@@ -0,0 +1,204 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Futhark.AD.Rev.Reduce+ ( diffReduce,+ diffMinMaxReduce,+ )+where++import Control.Monad+import Futhark.AD.Rev.Monad+import Futhark.Analysis.PrimExp.Convert+import Futhark.Builder+import Futhark.IR.SOACS+import Futhark.Tools+import Futhark.Transform.Rename++eReverse :: MonadBuilder m => VName -> m VName+eReverse arr = do+ arr_t <- lookupType arr+ let w = arraySize 0 arr_t+ start <-+ letSubExp "rev_start" $+ BasicOp $ BinOp (Sub Int64 OverflowUndef) w (intConst Int64 1)+ let stride = intConst Int64 (-1)+ slice = fullSlice arr_t [DimSlice start w stride]+ letExp (baseString arr <> "_rev") $ BasicOp $ Index arr slice++eRotate :: MonadBuilder m => [SubExp] -> VName -> m VName+eRotate rots arr = letExp (baseString arr <> "_rot") $ BasicOp $ Rotate rots arr++scanExc ::+ (MonadBuilder m, Rep m ~ SOACS) =>+ String ->+ Scan SOACS ->+ [VName] ->+ m [VName]+scanExc desc scan arrs = do+ w <- arraysSize 0 <$> mapM lookupType arrs+ form <- scanSOAC [scan]+ res_incl <- letTupExp (desc <> "_incl") $ Op $ Screma w arrs form+ res_incl_rot <- mapM (eRotate [intConst Int64 (-1)]) res_incl++ iota <-+ letExp "iota" . BasicOp $+ Iota w (intConst Int64 0) (intConst Int64 1) Int64++ iparam <- newParam "iota_param" $ Prim int64+ vparams <- mapM (newParam "vp") ts+ let params = iparam : vparams++ body <- runBodyBuilder . localScope (scopeOfLParams params) $ do+ let first_elem =+ eCmpOp+ (CmpEq int64)+ (eSubExp (Var (paramName iparam)))+ (eSubExp (intConst Int64 0))+ eBody+ [ eIf+ first_elem+ (resultBodyM nes)+ (resultBodyM $ map (Var . paramName) vparams)+ ]++ let lam = Lambda params body ts+ letTupExp desc $ Op $ Screma w (iota : res_incl_rot) (mapSOAC lam)+ where+ nes = scanNeutral scan+ ts = lambdaReturnType $ scanLambda scan++mkF :: Lambda SOACS -> ADM ([VName], Lambda SOACS)+mkF lam = do+ lam_l <- renameLambda lam+ lam_r <- renameLambda lam+ let q = length $ lambdaReturnType lam+ (lps, aps) = splitAt q $ lambdaParams lam_l+ (ips, rps) = splitAt q $ lambdaParams lam_r+ lam' <- mkLambda (lps <> aps <> rps) $ do+ lam_l_res <- bodyBind $ lambdaBody lam_l+ forM_ (zip ips lam_l_res) $ \(ip, SubExpRes cs se) ->+ certifying cs $ letBindNames [paramName ip] $ BasicOp $ SubExp se+ bodyBind $ lambdaBody lam_r+ pure (map paramName aps, lam')++diffReduce :: VjpOps -> [VName] -> SubExp -> [VName] -> Reduce SOACS -> ADM ()+diffReduce _ops [adj] w [a] red+ | Just [(op, _, _, _)] <- lamIsBinOp $ redLambda red,+ isAdd op = do+ adj_rep <-+ letExp (baseString adj <> "_rep") $+ BasicOp $ Replicate (Shape [w]) $ Var adj+ void $ updateAdj a adj_rep+ where+ isAdd FAdd {} = True+ isAdd Add {} = True+ isAdd _ = False+--+-- Differentiating a general single reduce:+-- let y = reduce \odot ne as+-- Forward sweep:+-- let ls = scan_exc \odot ne as+-- let rs = scan_exc \odot' ne (reverse as)+-- Reverse sweep:+-- let as_c = map3 (f_bar y_bar) ls as (reverse rs)+-- where+-- x \odot' y = y \odot x+-- y_bar is the adjoint of the result y+-- f l_i a_i r_i = l_i \odot a_i \odot r_i+-- f_bar = the reverse diff of f with respect to a_i under the adjoint y_bar+-- The plan is to create+-- one scanomap SOAC which computes ls and rs+-- another map which computes as_c+--+diffReduce ops pat_adj w as red = do+ red' <- renameRed red+ flip_red <- renameRed =<< flipReduce red+ ls <- scanExc "ls" (redToScan red') as+ rs <-+ mapM eReverse+ =<< scanExc "ls" (redToScan flip_red)+ =<< mapM eReverse as++ (as_params, f) <- mkF $ redLambda red++ f_adj <- vjpLambda ops (map adjFromVar pat_adj) as_params f++ as_adj <- letTupExp "adjs" $ Op $ Screma w (ls ++ as ++ rs) (mapSOAC f_adj)++ zipWithM_ updateAdj as as_adj+ where+ renameRed (Reduce comm lam nes) =+ Reduce comm <$> renameLambda lam <*> pure nes++ redToScan :: Reduce SOACS -> Scan SOACS+ redToScan (Reduce _ lam nes) = Scan lam nes+ flipReduce (Reduce comm lam nes) = do+ lam' <- renameLambda lam {lambdaParams = flipParams $ lambdaParams lam}+ pure $ Reduce comm lam' nes+ flipParams ps = uncurry (flip (++)) $ splitAt (length ps `div` 2) ps++--+-- Special case of reduce with min/max:+-- let x = reduce minmax ne as+-- Forward trace (assuming w = length as):+-- let (x, x_ind) =+-- reduce (\ acc_v acc_i v i ->+-- if (acc_v == v) then (acc_v, min acc_i i)+-- else if (acc_v == minmax acc_v v)+-- then (acc_v, acc_i)+-- else (v, i))+-- (ne_min, -1)+-- (zip as (iota w))+-- Reverse trace:+-- num_elems = i64.bool (0 <= x_ind)+-- m_bar_repl = replicate num_elems m_bar+-- as_bar[x_ind:num_elems:1] += m_bar_repl+diffMinMaxReduce ::+ VjpOps -> VName -> StmAux () -> SubExp -> BinOp -> SubExp -> VName -> ADM () -> ADM ()+diffMinMaxReduce _ops x aux w minmax ne as m = do+ let t = binOpType minmax++ acc_v_p <- newParam "acc_v" $ Prim t+ acc_i_p <- newParam "acc_i" $ Prim int64+ v_p <- newParam "v" $ Prim t+ i_p <- newParam "i" $ Prim int64+ red_lam <-+ mkLambda [acc_v_p, acc_i_p, v_p, i_p] $+ fmap varsRes . letTupExp "idx_res"+ =<< eIf+ (eCmpOp (CmpEq t) (eParam acc_v_p) (eParam v_p))+ ( eBody+ [ eParam acc_v_p,+ eBinOp (SMin Int64) (eParam acc_i_p) (eParam i_p)+ ]+ )+ ( eBody+ [ eIf+ ( eCmpOp+ (CmpEq t)+ (eParam acc_v_p)+ (eBinOp minmax (eParam acc_v_p) (eParam v_p))+ )+ (eBody [eParam acc_v_p, eParam acc_i_p])+ (eBody [eParam v_p, eParam i_p])+ ]+ )++ red_iota <-+ letExp "red_iota" $+ BasicOp $ Iota w (intConst Int64 0) (intConst Int64 1) Int64+ form <- reduceSOAC [Reduce Commutative red_lam [ne, intConst Int64 (-1)]]+ x_ind <- newVName (baseString x <> "_ind")+ auxing aux $ letBindNames [x, x_ind] $ Op $ Screma w [as, red_iota] form++ m++ x_adj <- lookupAdjVal x+ in_bounds <-+ letSubExp "minmax_in_bounds" . BasicOp $+ CmpOp (CmpSlt Int64) (intConst Int64 0) w+ updateAdjIndex as (CheckBounds (Just in_bounds), Var x_ind) (Var x_adj)
+ src/Futhark/AD/Rev/SOAC.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Futhark.AD.Rev.SOAC (vjpSOAC) where++import Control.Monad+import Futhark.AD.Rev.Map+import Futhark.AD.Rev.Monad+import Futhark.AD.Rev.Reduce+import Futhark.AD.Rev.Scan+import Futhark.AD.Rev.Scatter+import Futhark.Analysis.PrimExp.Convert+import Futhark.Builder+import Futhark.IR.SOACS+import Futhark.Tools+import Futhark.Util (chunks)++-- We split any multi-op scan or reduction into multiple operations so+-- we can detect special cases. Post-AD, the result may be fused+-- again.+splitScanRed ::+ VjpOps ->+ ([a] -> ADM (ScremaForm SOACS), a -> [SubExp]) ->+ (Pat Type, StmAux (), [a], SubExp, [VName]) ->+ ADM () ->+ ADM ()+splitScanRed vjpops (opSOAC, opNeutral) (pat, aux, ops, w, as) m = do+ let ks = map (length . opNeutral) ops+ pat_per_op = map Pat $ chunks ks $ patElems pat+ as_per_op = chunks ks as+ onOps (op : ops') (op_pat : op_pats') (op_as : op_as') = do+ op_form <- opSOAC [op]+ vjpSOAC vjpops op_pat aux (Screma w op_as op_form) $+ onOps ops' op_pats' op_as'+ onOps _ _ _ = m+ onOps ops pat_per_op as_per_op++commonSOAC :: Pat Type -> StmAux () -> SOAC SOACS -> ADM () -> ADM [Adj]+commonSOAC pat aux soac m = do+ addStm $ Let pat aux $ Op soac+ m+ returnSweepCode $ mapM lookupAdj $ patNames pat++vjpSOAC :: VjpOps -> Pat Type -> StmAux () -> SOAC SOACS -> ADM () -> ADM ()+vjpSOAC ops pat aux soac@(Screma w as form) m+ | Just reds <- isReduceSOAC form,+ length reds > 1 =+ splitScanRed ops (reduceSOAC, redNeutral) (pat, aux, reds, w, as) m+ | Just [red] <- isReduceSOAC form,+ [x] <- patNames pat,+ [ne] <- redNeutral red,+ [a] <- as,+ Just [(op, _, _, _)] <- lamIsBinOp $ redLambda red,+ isMinMaxOp op =+ diffMinMaxReduce ops x aux w op ne a m+ | Just red <- singleReduce <$> isReduceSOAC form = do+ pat_adj <- mapM adjVal =<< commonSOAC pat aux soac m+ diffReduce ops pat_adj w as red+ where+ isMinMaxOp (SMin _) = True+ isMinMaxOp (UMin _) = True+ isMinMaxOp (FMin _) = True+ isMinMaxOp (SMax _) = True+ isMinMaxOp (UMax _) = True+ isMinMaxOp (FMax _) = True+ isMinMaxOp _ = False+vjpSOAC ops pat aux soac@(Screma w as form) m+ | Just scans <- isScanSOAC form,+ length scans > 1 =+ splitScanRed ops (scanSOAC, scanNeutral) (pat, aux, scans, w, as) m+ | Just red <- singleScan <$> isScanSOAC form = do+ void $ commonSOAC pat aux soac m+ diffScan ops (patNames pat) w as red+vjpSOAC ops pat aux soac@(Screma w as form) m+ | Just lam <- isMapSOAC form = do+ pat_adj <- commonSOAC pat aux soac m+ vjpMap ops pat_adj aux w lam as+vjpSOAC ops pat _aux (Screma w as form) m+ | Just (reds, map_lam) <-+ isRedomapSOAC form = do+ (mapstm, redstm) <-+ redomapToMapAndReduce pat (w, reds, map_lam, as)+ vjpStm ops mapstm $ vjpStm ops redstm m+vjpSOAC ops pat aux (Scatter w lam ass written_info) m =+ vjpScatter ops pat aux (w, lam, ass, written_info) m+vjpSOAC _ _ _ soac _ =+ error $ "vjpSOAC unhandled:\n" ++ pretty soac
+ src/Futhark/AD/Rev/Scan.hs view
@@ -0,0 +1,154 @@+module Futhark.AD.Rev.Scan (diffScan) where++import Control.Monad+import Futhark.AD.Rev.Monad+import Futhark.Analysis.PrimExp.Convert+import Futhark.Builder+import Futhark.IR.SOACS+import Futhark.Tools+import Futhark.Transform.Rename+import Futhark.Util (pairs, unpairs)++data FirstOrSecond = WrtFirst | WrtSecond++-- computes `d(x op y)/dx` or d(x op y)/dy+mkScanAdjointLam :: VjpOps -> Lambda SOACS -> FirstOrSecond -> ADM (Lambda SOACS)+mkScanAdjointLam ops lam0 which = do+ let len = length $ lambdaReturnType lam0+ lam <- renameLambda lam0+ let p2diff =+ case which of+ WrtFirst -> take len $ lambdaParams lam+ WrtSecond -> drop len $ lambdaParams lam+ p_adjs <- mapM unitAdjOfType (lambdaReturnType lam)+ vjpLambda ops p_adjs (map paramName p2diff) lam++-- Should generate something like:+-- `\ j -> let i = n - 1 - j+-- if i < n-1 then ( ys_adj[i], df2dx ys[i] xs[i+1]) else (0,1) )`+-- where `ys` is the result of scan+-- `xs` is the input of scan+-- `ys_adj` is the known adjoint of ys+-- `j` draw values from `iota n`+mkScanFusedMapLam :: VjpOps -> SubExp -> Lambda SOACS -> [VName] -> [VName] -> [VName] -> ADM (Lambda SOACS)+mkScanFusedMapLam ops w scn_lam xs ys ys_adj = do+ lam <- mkScanAdjointLam ops scn_lam WrtFirst+ ys_ts <- mapM lookupType ys+ par_i <- newParam "i" $ Prim int64+ let i = paramName par_i+ mkLambda [par_i] $+ fmap varsRes . letTupExp "x"+ =<< eIf+ (toExp $ le64 i .==. 0)+ ( buildBody_ $ do+ zs <- mapM (letSubExp "ct_zero" . zeroExp . rowType) ys_ts+ os <- mapM (letSubExp "ct_one" . oneExp . rowType) ys_ts+ pure $ subExpsRes $ unpairs $ zip zs os+ )+ ( buildBody_ $ do+ j <- letSubExp "j" =<< toExp (pe64 w - (le64 i + 1))+ j1 <- letSubExp "j1" =<< toExp (pe64 w - le64 i)+ let index idx arr t = BasicOp $ Index arr $ fullSlice t [DimFix idx]+ y_s <- forM (zip ys_adj ys_ts) $ \(y_, t) ->+ letSubExp (baseString y_ ++ "_j") $ index j y_ t+ lam_rs <-+ eLambda lam . map pure $+ zipWith (index j) ys ys_ts ++ zipWith (index j1) xs ys_ts+ pure $ unpairs $ zip (subExpsRes y_s) lam_rs+ )++-- \(a1, b1) (a2, b2) -> (a2 + b2 * a1, b1 * b2)+mkScanLinFunO :: Type -> ADM (Scan SOACS)+mkScanLinFunO t = do+ let pt = elemType t+ zero <- letSubExp "zeros" $ zeroExp t+ one <- letSubExp "ones" $ oneExp t+ tmp <- mapM newVName ["a1", "b1", "a2", "b2"]+ let [a1, b1, a2, b2] = tmp+ pet = primExpFromSubExp pt . Var+ lam <- mkLambda (map (\v -> Param mempty v t) [a1, b1, a2, b2]) . fmap varsRes $+ tabNest (arrayRank t) [a1, b1, a2, b2] $ \_ [a1', b1', a2', b2'] -> do+ x <- letExp "x" <=< toExp $ pet a2' ~+~ pet b2' ~*~ pet a1'+ y <- letExp "y" <=< toExp $ pet b1' ~*~ pet b2'+ pure [x, y]+ pure $ Scan lam [zero, one]++-- build the map following the scan with linear-function-composition:+-- for each (ds,cs) length-n array results of the scan, combine them as:+-- `let rs = map2 (\ d_i c_i -> d_i + c_i * y_adj[n-1]) d c |> reverse`+-- but insert explicit indexing to reverse inside the map.+mkScan2ndMaps :: SubExp -> (Type, VName, (VName, VName)) -> ADM VName+mkScan2ndMaps w (arr_tp, y_adj, (ds, cs)) = do+ nm1 <- letSubExp "nm1" =<< toExp (pe64 w - 1)+ y_adj_last <-+ letExp (baseString y_adj ++ "_last") $+ BasicOp $ Index y_adj $ fullSlice arr_tp [DimFix nm1]++ par_i <- newParam "i" $ Prim int64+ lam <- mkLambda [par_i] $ do+ let i = paramName par_i+ j <- letSubExp "j" =<< toExp (pe64 w - (le64 i + 1))+ dj <- letExp (baseString ds ++ "_dj") $ BasicOp $ Index ds $ fullSlice arr_tp [DimFix j]+ cj <- letExp (baseString cs ++ "_cj") $ BasicOp $ Index cs $ fullSlice arr_tp [DimFix j]++ let pet = primExpFromSubExp (elemType arr_tp) . Var+ fmap varsRes . tabNest (arrayRank (rowType arr_tp)) [y_adj_last, dj, cj] $ \_ [y_adj_last', dj', cj'] ->+ letTupExp "res" <=< toExp $ pet dj' ~+~ pet cj' ~*~ pet y_adj_last'++ iota <- letExp "iota" $ BasicOp $ Iota w (intConst Int64 0) (intConst Int64 1) Int64+ letExp "after_scan" $ Op (Screma w [iota] (ScremaForm [] [] lam))++-- perform the final map, which is fusable with the maps obtained from `mkScan2ndMaps`+-- let xs_contribs =+-- map3 (\ i a r -> if i==0 then r else (df2dy (ys[i-1]) a) \bar{*} r)+-- (iota n) xs rs+mkScanFinalMap :: VjpOps -> SubExp -> Lambda SOACS -> [VName] -> [VName] -> [VName] -> ADM [VName]+mkScanFinalMap ops w scan_lam xs ys rs = do+ let eltps = lambdaReturnType scan_lam+ lam <- mkScanAdjointLam ops scan_lam WrtSecond+ par_i <- newParam "i" $ Prim int64+ let i = paramName par_i+ par_x <- mapM (\(x, t) -> newParam (baseString x ++ "_par_x") t) $ zip xs eltps+ par_r <- mapM (\(r, t) -> newParam (baseString r ++ "_par_r") t) $ zip rs eltps++ map_lam <-+ mkLambda (par_i : par_x ++ par_r) $+ fmap varsRes . letTupExp "scan_contribs"+ =<< eIf+ (toExp $ le64 i .==. 0)+ (resultBodyM $ map (Var . paramName) par_r)+ ( buildBody_ $ do+ im1 <- letSubExp "im1" =<< toExp (le64 i - 1)+ ys_im1 <- forM ys $ \y -> do+ y_t <- lookupType y+ letSubExp (baseString y ++ "_last") $ BasicOp $ Index y $ fullSlice y_t [DimFix im1]++ lam_res <-+ mapM (letExp "const" . BasicOp . SubExp . resSubExp)+ =<< eLambda lam (map eSubExp $ ys_im1 ++ map (Var . paramName) par_x)++ fmap (varsRes . mconcat) . forM (zip3 lam_res (map paramName par_r) eltps) $+ \(lam_r, r, eltp) -> do+ let pet = primExpFromSubExp (elemType eltp) . Var++ tabNest (arrayRank eltp) [lam_r, r] $ \_ [lam_r', r'] ->+ letTupExp "res" <=< toExp $ pet lam_r' ~*~ pet r'+ )++ iota <- letExp "iota" $ BasicOp $ Iota w (intConst Int64 0) (intConst Int64 1) Int64+ letTupExp "scan_contribs" $ Op (Screma w (iota : xs ++ rs) (ScremaForm [] [] map_lam))++diffScan :: VjpOps -> [VName] -> SubExp -> [VName] -> Scan SOACS -> ADM ()+diffScan ops ys w as scan = do+ ys_adj <- mapM lookupAdjVal ys+ as_ts <- mapM lookupType as+ map1_lam <- mkScanFusedMapLam ops w (scanLambda scan) as ys ys_adj+ scans_lin_fun_o <- mapM mkScanLinFunO $ lambdaReturnType $ scanLambda scan+ iota <-+ letExp "iota" $ BasicOp $ Iota w (intConst Int64 0) (intConst Int64 1) Int64+ r_scan <-+ letTupExp "adj_ctrb_scan" $+ Op (Screma w [iota] (ScremaForm scans_lin_fun_o [] map1_lam))+ red_nms <- mapM (mkScan2ndMaps w) (zip3 as_ts ys_adj (pairs r_scan))+ as_contribs <- mkScanFinalMap ops w (scanLambda scan) as ys red_nms+ zipWithM_ updateAdj as as_contribs
+ src/Futhark/AD/Rev/Scatter.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeFamilies #-}++module Futhark.AD.Rev.Scatter (vjpScatter) where++import Control.Monad+import Futhark.AD.Rev.Monad+import Futhark.Analysis.PrimExp.Convert+import Futhark.Builder+import Futhark.IR.SOACS+import Futhark.Tools+import Futhark.Util (chunk)++withinBounds :: [(SubExp, VName)] -> TPrimExp Bool VName+withinBounds [] = TPrimExp $ ValueExp (BoolValue True)+withinBounds [(q, i)] = (le64 i .<. pe64 q) .&&. (pe64 (intConst Int64 (-1)) .<. le64 i)+withinBounds (qi : qis) = withinBounds [qi] .&&. withinBounds qis++-- Generates a potential tower-of-maps lambda body for an indexing operation.+-- Assuming parameters:+-- `arr` the array that is indexed+-- `[(w_1, i_1), (w_2, i_2), ..., (w_k, i_k)]` outer lambda formal parameters and their bounds+-- `[n_1,n_2,...]ptp` the type of the index expression `arr[i_1,i_2,...,i_k]`+-- Generates something like:+-- (\ i_1 i_2 ->+-- map (\j_1 -> ... if (i_1 >= 0 && i_1 < w_1) &&+-- (i_2 >= 0 && i_2 < w_2) && ...+-- then arr[i_1, i_2, ... j_1, ...]+-- else 0+-- ) (iota n_1)+-- )+-- The idea is that you do not want to put under the `if` something+-- that is an array because it would not flatten well!+genIdxLamBody :: VName -> [(SubExp, Param Type)] -> Type -> ADM (Body SOACS)+genIdxLamBody as wpis = genRecLamBody as wpis []+ where+ genRecLamBody :: VName -> [(SubExp, Param Type)] -> [Param Type] -> Type -> ADM (Body SOACS)+ genRecLamBody arr w_pis nest_pis (Array t (Shape []) _) =+ genRecLamBody arr w_pis nest_pis (Prim t)+ genRecLamBody arr w_pis nest_pis (Array t (Shape (s : ss)) _) = do+ new_ip <- newParam "i" (Prim int64)+ let t' = Prim t `arrayOfShape` Shape ss+ inner_lam <-+ mkLambda [new_ip] $+ bodyBind =<< genRecLamBody arr w_pis (nest_pis ++ [new_ip]) t'+ let (_, orig_pis) = unzip w_pis+ buildBody_ . localScope (scopeOfLParams (orig_pis ++ nest_pis)) $ do+ iota_v <- letExp "iota" $ BasicOp $ Iota s (intConst Int64 0) (intConst Int64 1) Int64+ r <- letSubExp (baseString arr ++ "_elem") $ Op $ Screma s [iota_v] (mapSOAC inner_lam)+ pure [subExpRes r]+ genRecLamBody arr w_pis nest_pis (Prim ptp) = do+ let (ws, orig_pis) = unzip w_pis+ let inds = map paramName (orig_pis ++ nest_pis)+ localScope (scopeOfLParams (orig_pis ++ nest_pis)) $+ eBody+ [ eIf+ (toExp $ withinBounds $ zip ws $ map paramName orig_pis)+ ( do+ r <- letSubExp "r" $ BasicOp $ Index arr $ Slice $ map (DimFix . Var) inds+ resultBodyM [r]+ )+ (resultBodyM [Constant $ blankPrimValue ptp])+ ]+ genRecLamBody _ _ _ _ = error "In Rev.hs, helper function genRecLamBody, unreachable case reached!"++--+-- Original:+-- let ys = scatter xs is vs+-- Assumes no duplicate indices in `is`+-- Forward Sweep:+-- let xs_save = gather xs is+-- let ys = scatter xs is vs+-- Return Sweep:+-- let vs_ctrbs = gather is ys_adj+-- let vs_adj \overline{+}= vs_ctrbs -- by map or generalized reduction+-- let xs_adj = scatter ys_adj is \overline{0}+-- let xs = scatter ys is xs_save+vjpScatter1 ::+ PatElem Type ->+ StmAux () ->+ (SubExp, [VName], (ShapeBase SubExp, Int, VName)) ->+ ADM () ->+ ADM ()+vjpScatter1 pys aux (w, ass, (shp, num_vals, xs)) m = do+ let rank = length $ shapeDims shp+ (all_inds, val_as) = splitAt (rank * num_vals) ass+ inds_as = chunk rank all_inds+ xs_t <- lookupType xs+ let val_t = stripArray (shapeRank shp) xs_t+ -- computing xs_save+ xs_saves <- mkGather inds_as xs xs_t+ -- performing the scatter+ id_lam <-+ mkIdentityLambda $+ replicate (shapeRank shp) (Prim int64) ++ replicate (shapeRank shp) val_t+ addStm $ Let (Pat [pys]) aux $ Op $ Scatter w ass id_lam [(shp, num_vals, xs)]+ m+ let ys = patElemName pys+ -- XXX: Since our restoration of xs will consume ys, we have to+ -- make a copy of ys in the chance that it is actually the result+ -- of the program. In that case the asymptotics will not be+ -- (locally) preserved, but since ys must necessarily have been+ -- constructed somewhere close, they are probably globally OK.+ ys_copy <- letExp (baseString ys <> "_copy") $ BasicOp $ Copy ys+ returnSweepCode $ do+ ys_adj <- lookupAdjVal ys+ -- computing vs_ctrbs and updating vs_adj+ vs_ctrbs <- mkGather inds_as ys_adj xs_t+ zipWithM_ updateAdj val_as vs_ctrbs -- use Slice?+ -- creating xs_adj+ zeros <-+ replicateM (length val_as) . letExp "zeros" $+ zeroExp $ xs_t `setOuterSize` w+ let f_tps = replicate (rank * num_vals) (Prim int64) ++ replicate num_vals val_t+ f <- mkIdentityLambda f_tps+ xs_adj <-+ letExp (baseString xs ++ "_adj") . Op $+ Scatter w (all_inds ++ zeros) f [(shp, num_vals, ys_adj)]+ insAdj xs xs_adj -- reusing the ys_adj for xs_adj!+ f' <- mkIdentityLambda f_tps+ xs_rc <-+ auxing aux . letExp (baseString xs <> "_rc") . Op $+ Scatter w (all_inds ++ xs_saves) f' [(shp, num_vals, ys)]+ addSubstitution xs xs_rc+ addSubstitution ys ys_copy+ where+ -- Creates a potential map-nest that indexes in full the array,+ -- and applies the condition of indices within bounds at the+ -- deepest level in the nest so that everything can be parallel.+ mkGather :: [[VName]] -> VName -> Type -> ADM [VName]+ mkGather inds_as arr arr_t = do+ ips <- forM inds_as $ \idxs ->+ mapM (\idx -> newParam (baseString idx ++ "_elem") (Prim int64)) idxs++ gather_lam <- mkLambda (concat ips) . fmap mconcat . forM ips $ \idxs -> do+ let q = length idxs+ (ws, eltp) = (take q $ arrayDims arr_t, stripArray q arr_t)+ bodyBind =<< genIdxLamBody arr (zip ws idxs) eltp+ let soac = Screma w (concat inds_as) (mapSOAC gather_lam)+ letTupExp (baseString arr ++ "_gather") $ Op soac++vjpScatter ::+ VjpOps ->+ Pat Type ->+ StmAux () ->+ (SubExp, [VName], Lambda SOACS, [(Shape, Int, VName)]) ->+ ADM () ->+ ADM ()+vjpScatter ops (Pat pes) aux (w, ass, lam, written_info) m+ | isIdentityLambda lam,+ [(shp, num_vals, xs)] <- written_info,+ [pys] <- pes =+ vjpScatter1 pys aux (w, ass, (shp, num_vals, xs)) m+ | isIdentityLambda lam = do+ let sind = splitInd written_info+ (inds, vals) = splitAt sind ass+ lst_stms <- chunkScatterInps (inds, vals) (zip pes written_info)+ diffScatters (stmsFromList lst_stms)+ | otherwise =+ error "vjpScatter: cannot handle"+ where+ splitInd [] = 0+ splitInd ((shp, num_res, _) : rest) =+ num_res * length (shapeDims shp) + splitInd rest+ chunkScatterInps (acc_inds, acc_vals) [] =+ case (acc_inds, acc_vals) of+ ([], []) -> pure []+ _ -> error "chunkScatterInps: cannot handle"+ chunkScatterInps+ (acc_inds, acc_vals)+ ((pe, info@(shp, num_vals, _)) : rest) = do+ let num_inds = num_vals * length (shapeDims shp)+ (curr_inds, other_inds) = splitAt num_inds acc_inds+ (curr_vals, other_vals) = splitAt num_vals acc_vals+ vtps <- mapM lookupType curr_vals+ f <- mkIdentityLambda (replicate num_inds (Prim int64) ++ vtps)+ let stm =+ Let (Pat [pe]) aux . Op $+ Scatter w (curr_inds ++ curr_vals) f [info]+ stms_rest <- chunkScatterInps (other_inds, other_vals) rest+ pure $ stm : stms_rest+ diffScatters all_stms+ | Just (stm, stms) <- stmsHead all_stms =+ vjpStm ops stm $ diffScatters stms+ | otherwise = m
src/Futhark/Analysis/LastUse.hs view
@@ -67,6 +67,10 @@ (lumap'', used'') <- analyseKernelBody (lumap', used') body let nms = (freeIn lvl <> freeIn tps) `namesSubtract` used'' pure (insertNames pat_name nms lumap'', used'' <> nms)+analyseGPUOp pat_name (lumap, used) (Inner (GPUBody ts body)) = do+ (lumap', used') <- analyseBody lumap used body+ let nms = freeIn ts+ pure (insertNames pat_name nms lumap', used' <> nms) segOpHelper :: (FreeIn (OpWithAliases (Op rep)), ASTRep rep) =>
src/Futhark/Analysis/Metrics.hs view
@@ -16,6 +16,7 @@ MetricsM, stmMetrics, lambdaMetrics,+ bodyMetrics, ) where @@ -91,6 +92,7 @@ funDefMetrics :: OpMetrics (Op rep) => FunDef rep -> MetricsM () funDefMetrics = bodyMetrics . funDefBody +-- | Compute metrics for this body. bodyMetrics :: OpMetrics (Op rep) => Body rep -> MetricsM () bodyMetrics = mapM_ stmMetrics . bodyStms
src/Futhark/Analysis/PrimExp.hs view
@@ -55,8 +55,18 @@ zExt32, zExt64, sExtAs,+ fMin16,+ fMin32, fMin64,+ fMax16,+ fMax32, fMax64,++ -- * Untyped construction+ (~*~),+ (~/~),+ (~+~),+ (~-~), ) where @@ -429,6 +439,15 @@ TPrimExp $ constFoldPrimExp z | otherwise = numBad "divRoundingUp" (x, y) + TPrimExp x `pow` TPrimExp y+ | Just z <-+ msum+ [ asIntOp Pow x y,+ asFloatOp FPow x y+ ] =+ TPrimExp $ constFoldPrimExp z+ | otherwise = numBad "pow" (x, y)+ sgn (TPrimExp (ValueExp (IntValue i))) = Just $ signum $ valueIntegral i sgn _ = Nothing @@ -628,13 +647,29 @@ zExt64 :: IntExp t => TPrimExp t v -> TPrimExp Int64 v zExt64 = isInt64 . zExt Int64 . untyped +-- | 16-bit float minimum.+fMin16 :: TPrimExp Half v -> TPrimExp Half v -> TPrimExp Half v+fMin16 x y = isF16 $ BinOpExp (FMin Float16) (untyped x) (untyped y)++-- | 32-bit float minimum.+fMin32 :: TPrimExp Float v -> TPrimExp Float v -> TPrimExp Float v+fMin32 x y = isF32 $ BinOpExp (FMin Float32) (untyped x) (untyped y)+ -- | 64-bit float minimum. fMin64 :: TPrimExp Double v -> TPrimExp Double v -> TPrimExp Double v-fMin64 x y = TPrimExp $ BinOpExp (FMin Float64) (untyped x) (untyped y)+fMin64 x y = isF64 $ BinOpExp (FMin Float64) (untyped x) (untyped y) +-- | 16-bit float maximum.+fMax16 :: TPrimExp Half v -> TPrimExp Half v -> TPrimExp Half v+fMax16 x y = isF16 $ BinOpExp (FMax Float16) (untyped x) (untyped y)++-- | 32-bit float maximum.+fMax32 :: TPrimExp Float v -> TPrimExp Float v -> TPrimExp Float v+fMax32 x y = isF32 $ BinOpExp (FMax Float32) (untyped x) (untyped y)+ -- | 64-bit float maximum. fMax64 :: TPrimExp Double v -> TPrimExp Double v -> TPrimExp Double v-fMax64 x y = TPrimExp $ BinOpExp (FMax Float64) (untyped x) (untyped y)+fMax64 x y = isF64 $ BinOpExp (FMax Float64) (untyped x) (untyped y) -- | Convert result of some integer expression to have the same type -- as another, using sign extension.@@ -672,3 +707,53 @@ S.union (leafExpTypes e1) (leafExpTypes e2) leafExpTypes (FunExp _ pes _) = S.unions $ map leafExpTypes pes++-- | Multiplication of untyped 'PrimExp's, which must have the same+-- type.+(~*~) :: PrimExp v -> PrimExp v -> PrimExp v+x ~*~ y = BinOpExp op x y+ where+ t = primExpType x+ op = case t of+ IntType it -> Mul it OverflowUndef+ FloatType ft -> FMul ft+ Bool -> LogAnd+ Unit -> LogAnd++-- | Division of untyped 'PrimExp's, which must have the same+-- type. For integers, this is unsafe signed division.+(~/~) :: PrimExp v -> PrimExp v -> PrimExp v+x ~/~ y = BinOpExp op x y+ where+ t = primExpType x+ op = case t of+ IntType it -> SDiv it Unsafe+ FloatType ft -> FDiv ft+ Bool -> LogAnd+ Unit -> LogAnd++-- | Addition of untyped 'PrimExp's, which must have the same type.+(~+~) :: PrimExp v -> PrimExp v -> PrimExp v+x ~+~ y = BinOpExp op x y+ where+ t = primExpType x+ op = case t of+ IntType it -> Add it OverflowUndef+ FloatType ft -> FAdd ft+ Bool -> LogOr+ Unit -> LogOr++-- | Subtraction of untyped 'PrimExp's, which must have the same type.+(~-~) :: PrimExp v -> PrimExp v -> PrimExp v+x ~-~ y = BinOpExp op x y+ where+ t = primExpType x+ op = case t of+ IntType it -> Sub it OverflowUndef+ FloatType ft -> FSub ft+ Bool -> LogOr+ Unit -> LogOr++infix 7 ~*~, ~/~++infix 6 ~+~, ~-~
src/Futhark/Bench.hs view
@@ -141,7 +141,8 @@ -- | How to run a benchmark. data RunOptions = RunOptions- { runMinRuns :: Int,+ { -- | Applies both to initial and convergence phase.+ runMinRuns :: Int, runMinTime :: NominalDiffTime, runTimeout :: Int, runVerbose :: Int,@@ -154,8 +155,8 @@ runResultAction :: (Int, Maybe Double) -> IO () } --- | A list of @(autocorrelation,rsd)@ pairs. When the--- autocorrelation is above the first element and the RSD is above the+-- | A list of @(autocorrelation,rse)@ pairs. When the+-- autocorrelation is above the first element and the RSE is above the -- second element, we want more runs. convergenceCriteria :: [(Double, Double)] convergenceCriteria =@@ -168,9 +169,9 @@ -- Returns the next run count. nextRunCount :: Int -> Double -> Double -> Int-nextRunCount runs rsd acor = if any check convergenceCriteria then div runs 2 else 0+nextRunCount runs rse acor = if any check convergenceCriteria then div runs 2 else 0 where- check (acor_lb, rsd_lb) = acor > acor_lb && rsd > rsd_lb+ check (acor_lb, rse_lb) = acor > acor_lb && rse > rse_lb type BenchM = ExceptT T.Text IO @@ -216,7 +217,7 @@ BenchM (DL.DList (RunResult, [T.Text])) runConvergence do_run opts initial_r = let runtimes = resultRuntimes (DL.toList initial_r)- (n, _, rsd, acor) = runtimesMetrics runtimes+ (n, _, rse, acor) = runtimesMetrics runtimes in -- If the runtimes collected during the runMinimum phase are -- unstable enough that we need more in order to converge, we throw -- away the runMinimum runtimes. This is because they often exhibit@@ -224,11 +225,11 @@ -- outliers that poison the metrics we use to determine convergence. -- By throwing them away we converge much faster, and still get the -- right result.- case nextRunCount n rsd acor of+ case nextRunCount n rse acor of x | x > 0, runConvergencePhase opts ->- moreRuns mempty mempty rsd x+ moreRuns mempty mempty rse (x `max` runMinRuns opts) | otherwise -> pure initial_r where@@ -237,30 +238,30 @@ runtimesMetrics runtimes = let n = U.length runtimes- rsd = (fastStdDev runtimes / sqrt (fromIntegral n)) / mean runtimes+ rse = (fastStdDev runtimes / sqrt (fromIntegral n)) / mean runtimes (x, _, _) = autocorrelation runtimes in ( n, realToFrac (U.sum runtimes) :: NominalDiffTime,- rsd,+ rse, fromMaybe 1 (x U.!? 1) ) - sample rsd = do+ sample rse = do x <- do_run- liftIO $ runResultAction opts (runMicroseconds (fst x), Just rsd)+ liftIO $ runResultAction opts (runMicroseconds (fst x), Just rse) pure x - moreRuns runtimes r rsd x = do- r' <- replicateM x $ sample rsd+ moreRuns runtimes r rse x = do+ r' <- replicateM x $ sample rse loop (runtimes <> resultRuntimes r') (r <> DL.fromList r') loop runtimes r = do- let (n, total, rsd, acor) = runtimesMetrics runtimes- case nextRunCount n rsd acor of+ let (n, total, rse, acor) = runtimesMetrics runtimes+ case nextRunCount n rse acor of x | x > 0, total < runConvergenceMaxTime opts ->- moreRuns runtimes r rsd x+ moreRuns runtimes r rse x | otherwise -> pure r
src/Futhark/CLI/Bench.hs view
@@ -18,14 +18,17 @@ import Data.Maybe import Data.Ord import qualified Data.Text as T+import qualified Data.Text.IO as T import Data.Time.Clock (NominalDiffTime, UTCTime, diffUTCTime, getCurrentTime) import qualified Data.Vector.Unboxed as U import Futhark.Bench import Futhark.Server import Futhark.Test-import Futhark.Util (atMostChars, fancyTerminal, maybeNth, pmapIO)+import Futhark.Util (atMostChars, fancyTerminal, pmapIO) import Futhark.Util.Console import Futhark.Util.Options+import Futhark.Util.Pretty (prettyText)+import Futhark.Util.ProgressBar import Statistics.Resampling (Estimator (..), resample) import Statistics.Resampling.Bootstrap (bootstrapBCA) import Statistics.Types (cl95, confIntLDX, confIntUDX, estError, estPoint)@@ -248,50 +251,36 @@ runResultAction = f } -progressBar :: Double -> Double -> Int -> String-progressBar cur bound steps =- "|" <> map cell [1 .. steps] <> "| "- where- step_size :: Double- step_size = bound / fromIntegral steps- chars = " ▏▎▍▍▌▋▊▉█"- char i = fromMaybe ' ' $ maybeNth (i :: Int) chars- num_chars = fromIntegral $ length chars-- cell :: Int -> Char- cell i- | i' * step_size <= cur = char 9- | otherwise =- char (floor (((cur - (i' - 1) * step_size) * num_chars) / step_size))- where- i' = fromIntegral i- descString :: String -> Int -> String descString desc pad_to = desc ++ ": " ++ replicate (pad_to - length desc) ' ' -progressBarSteps :: Int-progressBarSteps = 10+progress :: Double -> T.Text+progress elapsed =+ progressBar+ ( ProgressBar+ { progressBarSteps = 10,+ progressBarBound = 1,+ progressBarElapsed = elapsed+ }+ ) -interimResult :: Int -> Int -> Double -> Double -> String-interimResult us_sum runs elapsed bound =- printf "%10.0fμs " avg- <> progressBar elapsed bound progressBarSteps- <> (" " <> show runs <> " runs")+interimResult :: Int -> Int -> Double -> T.Text+interimResult us_sum runs elapsed =+ T.pack (printf "%10.0fμs " avg) <> progress elapsed+ <> (" " <> prettyText runs <> " runs") where avg :: Double avg = fromIntegral us_sum / fromIntegral runs -convergenceBar :: (String -> IO ()) -> IORef Int -> Int -> Int -> Double -> IO ()-convergenceBar p spin_count us_sum i rsd' = do+convergenceBar :: (T.Text -> IO ()) -> IORef Int -> Int -> Int -> Double -> IO ()+convergenceBar p spin_count us_sum i rse' = do spin_idx <- readIORef spin_count- let spin_char = spin_load !! spin_idx- p $ printf "%10.0fμs %c (RSD of mean: %2.4f; %4d runs)" avg spin_char rsd' i- let spin_count' = (spin_idx + 1) `mod` 10- writeIORef spin_count spin_count'+ let spin = progressSpinner spin_idx+ p $ T.pack $ printf "%10.0fμs %s (RSE of mean: %2.4f; %4d runs)" avg spin rse' i+ writeIORef spin_count (spin_idx + 1) where avg :: Double avg = fromIntegral us_sum / fromIntegral i- spin_load = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏" data BenchPhase = Initial | Convergence @@ -301,11 +290,11 @@ count <- newIORef (0, 0) phase_var <- newIORef Initial spin_count <- newIORef 0- pure $ \(us, rsd) -> do+ pure $ \(us, rse) -> do putStr "\r" -- Go to start of line. let p s =- putStr $- descString (atMostChars maxDatasetNameLength dataset_desc) pad_to ++ s+ T.putStr $+ T.pack (descString (atMostChars maxDatasetNameLength dataset_desc) pad_to) <> s (us_sum, i) <- readIORef count @@ -322,27 +311,27 @@ phase <- readIORef phase_var - case (us, phase, rsd) of+ case (us, phase, rse) of (Nothing, _, _) -> let elapsed = determineProgress i- in p $ replicate 13 ' ' <> progressBar elapsed 1.0 progressBarSteps+ in p $ T.pack (replicate 13 ' ') <> progress elapsed (Just us', Initial, Nothing) -> do let us_sum' = us_sum + us' i' = i + 1 writeIORef count (us_sum', i') let elapsed = determineProgress i'- p $ interimResult us_sum' i' elapsed 1.0- (Just us', Initial, Just rsd') -> do+ p $ interimResult us_sum' i' elapsed+ (Just us', Initial, Just rse') -> do -- Switched from phase 1 to convergence; discard all -- prior results. writeIORef count (us', 1) writeIORef phase_var Convergence- convergenceBar p spin_count us' 1 rsd'- (Just us', Convergence, Just rsd') -> do+ convergenceBar p spin_count us' 1 rse'+ (Just us', Convergence, Just rse') -> do let us_sum' = us_sum + us' i' = i + 1 writeIORef count (us_sum', i')- convergenceBar p spin_count us_sum' i' rsd'+ convergenceBar p spin_count us_sum' i' rse' (Just _, Convergence, Nothing) -> pure () -- Probably should not happen. putStr " " -- Just to move the cursor away from the progress bar.
src/Futhark/CLI/Dev.hs view
@@ -33,6 +33,7 @@ import Futhark.Optimise.CSE import Futhark.Optimise.DoubleBuffer import Futhark.Optimise.Fusion+import Futhark.Optimise.HistAccs import Futhark.Optimise.InPlaceLowering import Futhark.Optimise.InliningDeadFun import qualified Futhark.Optimise.MemoryBlockMerging as MemoryBlockMerging@@ -40,6 +41,7 @@ import Futhark.Optimise.TileLoops import Futhark.Optimise.Unstream import Futhark.Pass+import Futhark.Pass.AD import Futhark.Pass.ExpandAllocations import qualified Futhark.Pass.ExplicitAllocations.GPU as GPU import qualified Futhark.Pass.ExplicitAllocations.Seq as Seq@@ -549,8 +551,11 @@ soacsPassOption inlineAggressively [], soacsPassOption inlineConservatively [], soacsPassOption removeDeadFunctions [],+ soacsPassOption applyAD [],+ soacsPassOption applyADInnermost [], kernelsPassOption babysitKernels [], kernelsPassOption tileLoops [],+ kernelsPassOption histAccsGPU [], kernelsPassOption unstreamGPU [], kernelsPassOption sinkGPU [], typedPassOption soacsProg GPU extractKernels [],
src/Futhark/CLI/LSP.hs view
@@ -5,8 +5,8 @@ -- | @futhark lsp@ module Futhark.CLI.LSP (main) where -import Control.Concurrent.MVar (newMVar) import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.IORef (newIORef) import Futhark.LSP.Handlers (handlers) import Futhark.LSP.State (emptyState) import Futhark.Util (debug)@@ -22,7 +22,7 @@ -- | Run @futhark lsp@ main :: String -> [String] -> IO () main _prog _args = do- state_mvar <- newMVar emptyState+ state_mvar <- newIORef emptyState debug "Init with emptyState" setupLogger Nothing ["futhark"] DEBUG _ <-@@ -30,7 +30,7 @@ ServerDefinition { onConfigurationChange = const $ const $ Right (), defaultConfig = (),- doInitialize = \env _req -> do pure $ Right env,+ doInitialize = \env _req -> pure $ Right env, staticHandlers = handlers state_mvar, interpretHandler = \env -> Iso (runLspT env) liftIO, options =
src/Futhark/CLI/Literate.hs view
@@ -34,6 +34,7 @@ import Futhark.Test.Values import Futhark.Util ( directoryContents,+ fancyTerminal, hashText, nubOrd, runProgramWithExitCode,@@ -41,6 +42,8 @@ import Futhark.Util.Options import Futhark.Util.Pretty (prettyText, prettyTextOneLine) import qualified Futhark.Util.Pretty as PP+import Futhark.Util.ProgressBar+import System.Console.ANSI (clearLine) import System.Directory ( copyFile, createDirectoryIfMissing,@@ -750,10 +753,33 @@ format = fromMaybe "webm" $ videoFormat params bmpfile dir j = dir </> printf "frame%010d.bmp" (j :: Int) - renderFrames dir (stepfun, closure) initial num_frames =+ (progressStep, progressDone)+ | fancyTerminal,+ scriptVerbose (envOpts env) > 0 =+ ( \j num_frames -> do+ liftIO . T.putStr $+ "\r"+ <> progressBar+ (ProgressBar 40 (fromIntegral num_frames) (fromIntegral j))+ <> "generating frame "+ <> prettyText j+ <> "/"+ <> prettyText num_frames+ <> " "+ liftIO $ hFlush stdout,+ liftIO $ do+ T.putStr "\r"+ clearLine+ )+ | otherwise =+ (\_ _ -> pure (), pure ())++ renderFrames dir (stepfun, closure) initial num_frames = do foldM_ frame initial [0 .. num_frames - 1]+ progressDone where frame old_state j = do+ progressStep j num_frames v <- evalExp literateBuiltin (envServer env) . Call (FuncFut stepfun)
+ src/Futhark/CLI/Main.hs view
@@ -0,0 +1,144 @@+{-# LANGUAGE OverloadedStrings #-}++-- | The main function for the @futhark@ command line program.+module Futhark.CLI.Main (main) where++import Control.Exception+import Data.List (sortOn)+import Data.Maybe+import qualified Data.Text as T+import qualified Data.Text.IO as T+import qualified Futhark.CLI.Autotune as Autotune+import qualified Futhark.CLI.Bench as Bench+import qualified Futhark.CLI.C as C+import qualified Futhark.CLI.CUDA as CCUDA+import qualified Futhark.CLI.Check as Check+import qualified Futhark.CLI.Datacmp as Datacmp+import qualified Futhark.CLI.Dataset as Dataset+import qualified Futhark.CLI.Defs as Defs+import qualified Futhark.CLI.Dev as Dev+import qualified Futhark.CLI.Doc as Doc+import qualified Futhark.CLI.LSP as LSP+import qualified Futhark.CLI.Literate as Literate+import qualified Futhark.CLI.Misc as Misc+import qualified Futhark.CLI.Multicore as Multicore+import qualified Futhark.CLI.MulticoreWASM as MulticoreWASM+import qualified Futhark.CLI.OpenCL as OpenCL+import qualified Futhark.CLI.Pkg as Pkg+import qualified Futhark.CLI.PyOpenCL as PyOpenCL+import qualified Futhark.CLI.Python as Python+import qualified Futhark.CLI.Query as Query+import qualified Futhark.CLI.REPL as REPL+import qualified Futhark.CLI.Run as Run+import qualified Futhark.CLI.Test as Test+import qualified Futhark.CLI.WASM as WASM+import Futhark.Error+import Futhark.Util (maxinum)+import Futhark.Util.Options+import GHC.IO.Encoding (setLocaleEncoding)+import GHC.IO.Exception (IOErrorType (..), IOException (..))+import System.Environment+import System.Exit+import System.IO+import Prelude++type Command = String -> [String] -> IO ()++commands :: [(String, (Command, String))]+commands =+ sortOn+ fst+ [ ("dev", (Dev.main, "Run compiler passes directly.")),+ ("repl", (REPL.main, "Run interactive Read-Eval-Print-Loop.")),+ ("run", (Run.main, "Run a program through the (slow!) interpreter.")),+ ("c", (C.main, "Compile to sequential C.")),+ ("opencl", (OpenCL.main, "Compile to C calling OpenCL.")),+ ("cuda", (CCUDA.main, "Compile to C calling CUDA.")),+ ("multicore", (Multicore.main, "Compile to multicore C.")),+ ("python", (Python.main, "Compile to sequential Python.")),+ ("pyopencl", (PyOpenCL.main, "Compile to Python calling PyOpenCL.")),+ ("wasm", (WASM.main, "Compile to WASM with sequential C")),+ ("wasm-multicore", (MulticoreWASM.main, "Compile to WASM with multicore C")),+ ("test", (Test.main, "Test Futhark programs.")),+ ("bench", (Bench.main, "Benchmark Futhark programs.")),+ ("dataset", (Dataset.main, "Generate random test data.")),+ ("datacmp", (Datacmp.main, "Compare Futhark data files for equality.")),+ ("dataget", (Misc.mainDataget, "Extract test data.")),+ ("doc", (Doc.main, "Generate documentation for Futhark code.")),+ ("pkg", (Pkg.main, "Manage local packages.")),+ ("check", (Check.main, "Type-check a program.")),+ ("check-syntax", (Misc.mainCheckSyntax, "Syntax-check a program.")),+ ("imports", (Misc.mainImports, "Print all non-builtin imported Futhark files.")),+ ("hash", (Misc.mainHash, "Print hash of program AST.")),+ ("autotune", (Autotune.main, "Autotune threshold parameters.")),+ ("defs", (Defs.main, "Show location and name of all definitions.")),+ ("query", (Query.main, "Query semantic information about program.")),+ ("literate", (Literate.main, "Process a literate Futhark program.")),+ ("lsp", (LSP.main, "Run LSP server.")),+ ("thanks", (Misc.mainThanks, "Express gratitude."))+ ]++msg :: String+msg =+ unlines $+ ["<command> options...", "Commands:", ""]+ ++ [ " " <> cmd <> replicate (k - length cmd) ' ' <> desc+ | (cmd, (_, desc)) <- commands+ ]+ where+ k = maxinum (map (length . fst) commands) + 3++-- | Catch all IO exceptions and print a better error message if they+-- happen.+reportingIOErrors :: IO () -> IO ()+reportingIOErrors =+ flip+ catches+ [ Handler onExit,+ Handler onICE,+ Handler onIOException,+ Handler onError+ ]+ where+ onExit :: ExitCode -> IO ()+ onExit = throwIO++ onICE :: InternalError -> IO ()+ onICE (Error CompilerLimitation s) = do+ T.hPutStrLn stderr "Known compiler limitation encountered. Sorry."+ T.hPutStrLn stderr "Revise your program or try a different Futhark compiler."+ T.hPutStrLn stderr s+ exitWith $ ExitFailure 1+ onICE (Error CompilerBug s) = do+ T.hPutStrLn stderr "Internal compiler error."+ T.hPutStrLn stderr "Please report this at https://github.com/diku-dk/futhark/issues."+ T.hPutStrLn stderr s+ exitWith $ ExitFailure 1++ onError :: SomeException -> IO ()+ onError e+ | Just UserInterrupt <- asyncExceptionFromException e =+ pure () -- This corresponds to CTRL-C, which is not an error.+ | otherwise = do+ T.hPutStrLn stderr "Internal compiler error (unhandled IO exception)."+ T.hPutStrLn stderr "Please report this at https://github.com/diku-dk/futhark/issues"+ T.hPutStrLn stderr $ T.pack $ show e+ exitWith $ ExitFailure 1++ onIOException :: IOException -> IO ()+ onIOException e+ | ioe_type e == ResourceVanished =+ exitWith $ ExitFailure 1+ | otherwise = throw e++main :: IO ()+main = reportingIOErrors $ do+ hSetEncoding stdout utf8+ hSetEncoding stderr utf8+ setLocaleEncoding utf8+ args <- getArgs+ prog <- getProgName+ case args of+ cmd : args'+ | Just (m, _) <- lookup cmd commands -> m (unwords [prog, cmd]) args'+ _ -> mainWithOptions () [] msg (const . const Nothing) prog args
src/Futhark/CLI/Test.hs view
@@ -99,6 +99,17 @@ context prog_ctx $ pureTestResults $ liftIO $ withServer (futharkServerCfg to_run to_run_args) f +data TestMode+ = -- | Only type check.+ TypeCheck+ | -- | Only compile (do not run).+ Compile+ | -- | Test compiled code.+ Compiled+ | -- | Test interpreted code.+ Interpreted+ deriving (Eq, Show)+ data TestCase = TestCase { _testCaseMode :: TestMode, testCaseProgram :: FilePath,@@ -242,13 +253,13 @@ let backend = configBackend progs extra_compiler_options = configExtraCompilerOptions progs - unless (mode == Compile) $+ when (mode `elem` [Compiled, Interpreted]) $ context "Generating reference outputs" $ -- We probably get the concurrency at the test program level, -- so force just one data set at a time here. ensureReferenceOutput (Just 1) (FutharkExe futhark) "c" program ios - unless (mode == Interpreted) $+ when (mode == Compiled) $ context ("Compiling with --backend=" <> T.pack backend) $ do compileTestProgram extra_compiler_options (FutharkExe futhark) backend program warnings mapM_ (testMetrics progs program) structures@@ -265,7 +276,7 @@ let run = runCompiledEntry (FutharkExe futhark) server program concat <$> mapM run ios - unless (mode == Compile || mode == Compiled) $+ when (mode == Interpreted) $ context "Interpreting" $ accErrors_ $ map (runInterpretedEntry (FutharkExe futhark) program) ios @@ -323,8 +334,8 @@ | not (match regex $ T.unpack err) = E.throwError $ "Expected error:\n " <> regex_s- <> "\nGot error:\n "- <> err+ <> "\nGot error:\n"+ <> T.unlines (map (" " <>) (T.lines err)) checkError _ _ = pure () @@ -607,7 +618,7 @@ defaultConfig :: TestConfig defaultConfig = TestConfig- { configTestMode = Everything,+ { configTestMode = Compiled, configExclude = ["disable"], configPrograms = ProgConfig@@ -657,14 +668,6 @@ addOption :: String -> ProgConfig -> ProgConfig addOption option config = config {configExtraOptions = configExtraOptions config ++ [option]}--data TestMode- = TypeCheck- | Compile- | Compiled- | Interpreted- | Everything- deriving (Eq, Show) commandLineOptions :: [FunOptDescr TestConfig] commandLineOptions =
src/Futhark/CodeGen/Backends/CCUDA.hs view
@@ -29,6 +29,7 @@ ) import Futhark.MonadFreshNames import qualified Language.C.Quote.OpenCL as C+import qualified Language.C.Syntax as C import NeatInterpolation (untrimming) -- | Compile the program to C with calls to CUDA.@@ -137,7 +138,26 @@ } ] +-- We detect the special case of writing a constant and turn it into a+-- non-blocking write. This may be slightly faster, as it prevents+-- unnecessary synchronisation of the context, and writing a constant+-- is fairly common. This is only possible because we can give the+-- constant infinite lifetime (with 'static'), which is not the case+-- for ordinary variables. writeCUDAScalar :: GC.WriteScalar OpenCL ()+writeCUDAScalar mem idx t "device" _ val@C.Const {} = do+ val' <- newVName "write_static"+ let (bef, aft) = profilingEnclosure copyScalarToDev+ GC.item+ [C.citem|{static $ty:t $id:val' = $exp:val;+ $items:bef+ CUDA_SUCCEED_OR_RETURN(+ cuMemcpyHtoDAsync($exp:mem + $exp:idx * sizeof($ty:t),+ &$id:val',+ sizeof($ty:t),+ 0));+ $items:aft+ }|] writeCUDAScalar mem idx t "device" _ val = do val' <- newVName "write_tmp" let (bef, aft) = profilingEnclosure copyScalarToDev@@ -167,10 +187,12 @@ cuMemcpyDtoH(&$id:val, $exp:mem + $exp:idx * sizeof($ty:t), sizeof($ty:t)));- $items:aft+ $items:aft } |]- GC.stm [C.cstm|if (futhark_context_sync(ctx) != 0) { return 1; }|]+ GC.stm+ [C.cstm|if (ctx->failure_is_an_option && futhark_context_sync(ctx) != 0)+ { return 1; }|] pure [C.cexp|$id:val|] readCUDAScalar _ _ _ space _ = error $ "Cannot write to '" ++ space ++ "' memory space."
src/Futhark/CodeGen/Backends/COpenCL.hs view
@@ -205,8 +205,8 @@ 0, NULL, $exp:(profilingEvent copyScalarFromDev))); |] GC.stm- [C.cstm|if (ctx->failure_is_an_option &&- futhark_context_sync(ctx) != 0) { return 1; }|]+ [C.cstm|if (ctx->failure_is_an_option && futhark_context_sync(ctx) != 0)+ { return 1; }|] pure [C.cexp|$id:val|] readOpenCLScalar _ _ _ space _ = error $ "Cannot read from '" ++ space ++ "' memory space."
src/Futhark/CodeGen/Backends/GenericPython.hs view
@@ -56,10 +56,10 @@ import Futhark.CodeGen.Backends.GenericPython.Options import qualified Futhark.CodeGen.ImpCode as Imp import Futhark.CodeGen.RTS.Python-import Futhark.Compiler.CLI (CompilerMode (..))+import Futhark.Compiler.Config (CompilerMode (..)) import Futhark.IR.Primitive hiding (Bool) import Futhark.IR.Prop (isBuiltInFunction, subExpVars)-import Futhark.IR.Syntax (Space (..))+import Futhark.IR.Syntax.Core (Space (..)) import Futhark.MonadFreshNames import Futhark.Util (zEncodeString) import Futhark.Util.Pretty (pretty, prettyText)
src/Futhark/CodeGen/ImpCode.hs view
@@ -365,7 +365,9 @@ declared x = go declared x set (SetMem x y _) = namesFromList [x, y]- set (Call _ _ args) = foldMap onArg args+ set (Call dests _ args) =+ -- Some of the dests might not be memory, but it does not matter.+ namesFromList dests <> foldMap onArg args where onArg ExpArg {} = mempty onArg (MemArg x) = oneName x
src/Futhark/CodeGen/ImpGen/GPU.hs view
@@ -143,6 +143,12 @@ mkTV (patElemName pe) int32 <-- sExt32 num_groups opCompiler dest (Inner (SegOp op)) = segOpCompiler dest op+opCompiler (Pat pes) (Inner (GPUBody _ (Body _ stms res))) = do+ tid <- newVName "tid"+ sKernelThread "gpuseq" tid (defKernelAttrs 1 1) $+ compileStms (freeIn res) stms $+ forM_ (zip pes res) $ \(pe, SubExpRes _ se) ->+ copyDWIM (patElemName pe) [DimFix 0] se [] opCompiler pat e = compilerBugS $ "opCompiler: Invalid pattern\n "
src/Futhark/CodeGen/OpenCL/Heuristics.hs view
@@ -55,8 +55,8 @@ -- to perform well in practice. SizeHeuristic "" DeviceGPU NumGroups $ 4 * max_compute_units, SizeHeuristic "" DeviceGPU GroupSize 256,- SizeHeuristic "" DeviceGPU TileSize 32,- SizeHeuristic "" DeviceGPU RegTileSize 2,+ SizeHeuristic "" DeviceGPU TileSize 16,+ SizeHeuristic "" DeviceGPU RegTileSize 4, SizeHeuristic "" DeviceGPU Threshold $ 32 * 1024, SizeHeuristic "" DeviceCPU LockstepWidth 1, SizeHeuristic "" DeviceCPU NumGroups max_compute_units,
src/Futhark/CodeGen/RTS/C.hs view
@@ -3,7 +3,6 @@ -- | Code snippets used by the C backends. module Futhark.CodeGen.RTS.C ( atomicsH,- chaselevH, cudaH, freeListH, halfH,@@ -32,11 +31,6 @@ atomicsH :: T.Text atomicsH = $(embedStringFile "rts/c/atomics.h") {-# NOINLINE atomicsH #-}---- | @rts/c/chaselev.h@-chaselevH :: T.Text-chaselevH = $(embedStringFile "rts/c/chaselev.h")-{-# NOINLINE chaselevH #-} -- | @rts/c/cuda.h@ cudaH :: T.Text
src/Futhark/Compiler.hs view
@@ -22,8 +22,9 @@ import Control.Monad import Control.Monad.Except import Data.Bifunctor (first)+import Data.List (sortOn) import qualified Data.List.NonEmpty as NE-import Data.Loc (Loc (NoLoc))+import Data.Loc (Loc (..), posCoff, posFile) import qualified Data.Text.IO as T import qualified Futhark.Analysis.Alias as Alias import Futhark.Compiler.Config@@ -34,7 +35,7 @@ import Futhark.Internalise import Futhark.MonadFreshNames import Futhark.Pipeline-import Futhark.Util.Console (inRed)+import Futhark.Util.Console (inRed, inYellow) import Futhark.Util.Log import Futhark.Util.Pretty (Doc, line, ppr, prettyText, punctuate, stack, text, (</>)) import qualified Language.Futhark as E@@ -135,12 +136,18 @@ -- | Prettyprint program errors as suitable for showing on a text console. pprProgErrors :: NE.NonEmpty ProgError -> Doc-pprProgErrors = stack . punctuate line . map onError . NE.toList+pprProgErrors = stack . punctuate line . map onError . sortOn (rep . locOf) . NE.toList where+ rep NoLoc = ("", 0)+ rep (Loc p _) = (posFile p, posCoff p) onError (ProgError NoLoc msg) = msg onError (ProgError loc msg) = text (inRed $ "Error at " <> locStr (srclocOf loc) <> ":") </> msg+ onError (ProgWarning NoLoc msg) =+ msg+ onError (ProgWarning loc msg) =+ text (inYellow $ "Warning at " <> locStr (srclocOf loc) <> ":") </> msg -- | Throw an exception formatted with 'pprProgErrors' if there's -- an error.
src/Futhark/Compiler/CLI.hs view
@@ -172,13 +172,6 @@ compilerEntryPoints :: [Name] } --- | Are we compiling a library or an executable?-data CompilerMode- = ToLibrary- | ToExecutable- | ToServer- deriving (Eq, Ord, Show)- -- | The configuration of the compiler. newCompilerConfig :: cfg -> CompilerConfig cfg newCompilerConfig x =
src/Futhark/Compiler/Config.hs view
@@ -3,11 +3,28 @@ ( FutharkConfig (..), newFutharkConfig, Verbosity (..),+ CompilerMode (..), ) where -import Futhark.IR-import Futhark.Pipeline+import Futhark.IR.Syntax.Core (Name)++-- | Are we compiling a library or an executable?+data CompilerMode+ = ToLibrary+ | ToExecutable+ | ToServer+ deriving (Eq, Ord, Show)++-- | How much information to print to stderr while the compiler is running.+data Verbosity+ = -- | Silence is golden.+ NotVerbose+ | -- | Print messages about which pass is running.+ Verbose+ | -- | Also print logs from individual passes.+ VeryVerbose+ deriving (Eq, Ord) -- | The compiler configuration. This only contains options related -- to core compiler functionality, such as reading the initial program
src/Futhark/Compiler/Program.hs view
@@ -15,8 +15,10 @@ noLoadedProg, lpImports, lpWarnings,+ lpFilePaths, reloadProg, extendProg,+ VFS, ) where @@ -35,7 +37,7 @@ import Data.Bifunctor (first) import Data.List (intercalate, sort) import qualified Data.List.NonEmpty as NE-import Data.Loc (Loc (..), locOf)+import Data.Loc (Loc (..), Located, locOf) import qualified Data.Map as M import Data.Maybe (mapMaybe) import qualified Data.Text as T@@ -43,7 +45,7 @@ import Data.Time.Clock (UTCTime, getCurrentTime) import Futhark.FreshNames import Futhark.Util (interactWithFileSafely, nubOrd, startupTime)-import Futhark.Util.Pretty (Doc, align, line, ppr, text, (</>))+import Futhark.Util.Pretty (Doc, align, ppr, text) import qualified Language.Futhark as E import Language.Futhark.Parser (SyntaxError (..), parseFuthark) import Language.Futhark.Prelude@@ -66,10 +68,18 @@ -- | Note that the location may be 'NoLoc'. This essentially only -- happens when the problem is that a root file cannot be found.-data ProgError = ProgError Loc Doc+data ProgError+ = ProgError Loc Doc+ | -- | Not actually an error, but we want them reported+ -- with errors.+ ProgWarning Loc Doc type WithErrors = Either (NE.NonEmpty ProgError) +instance Located ProgError where+ locOf (ProgError l _) = l+ locOf (ProgWarning l _) = l+ -- | A mapping from absolute pathnames to text representing a virtual -- file system. Before loading a file from the file system, this -- mapping is consulted. If the desired pathname has an entry here,@@ -279,10 +289,10 @@ case E.checkProg (asImports imports) src import_name prog' of (prog_ws, Left (E.TypeError loc notes msg)) -> do let err' = msg <> ppr notes- Left . singleError . ProgError (locOf loc) $- if anyWarnings prog_ws- then ppr prog_ws </> line <> ppr err'- else ppr err'+ warningToError (wloc, wmsg) = ProgWarning (locOf wloc) wmsg+ Left $+ ProgError (locOf loc) err'+ NE.:| map warningToError (listWarnings prog_ws) (prog_ws, Right (m, src')) -> let warnHole (loc, t) = singleWarning (E.srclocOf loc) $ "Hole of type: " <> align (ppr t)@@ -345,22 +355,31 @@ lpWarnings :: LoadedProg -> Warnings lpWarnings = foldMap (cfWarnings . lfMod) . lpFiles +-- | The absolute paths of the files that are part of this program.+lpFilePaths :: LoadedProg -> [FilePath]+lpFilePaths = map lfPath . lpFiles+ unchangedImports :: MonadIO m => VNameSource ->+ VFS -> [LoadedFile CheckedFile] -> m ([LoadedFile CheckedFile], VNameSource)-unchangedImports src [] = pure ([], src)-unchangedImports src (f : fs)+unchangedImports src _ [] = pure ([], src)+unchangedImports src vfs (f : fs) | isBuiltin (includeToFilePath (lfImportName f)) =- first (f :) <$> unchangedImports src fs+ first (f :) <$> unchangedImports src vfs fs | otherwise = do- changed <-- maybe True (either (const True) (> lfModTime f))- <$> liftIO (interactWithFileSafely (getModificationTime $ lfPath f))- if changed+ let file_path = lfPath f+ if M.member file_path vfs then pure ([], cfNameSource $ lfMod f)- else first (f :) <$> unchangedImports src fs+ else do+ changed <-+ maybe True (either (const True) (> lfModTime f))+ <$> liftIO (interactWithFileSafely (getModificationTime file_path))+ if changed+ then pure ([], cfNameSource $ lfMod f)+ else first (f :) <$> unchangedImports src vfs fs -- | A "loaded program" containing no actual files. Use this as a -- starting point for 'reloadProg'@@ -375,10 +394,10 @@ -- | Find out how many of the old imports can be used. Here we are -- forced to be overly conservative, because our type checker -- enforces a linear ordering.-usableLoadedProg :: MonadIO m => LoadedProg -> [FilePath] -> m LoadedProg-usableLoadedProg (LoadedProg roots imports src) new_roots+usableLoadedProg :: MonadIO m => LoadedProg -> VFS -> [FilePath] -> m LoadedProg+usableLoadedProg (LoadedProg roots imports src) vfs new_roots | sort roots == sort new_roots = do- (imports', src') <- unchangedImports src imports+ (imports', src') <- unchangedImports src vfs imports pure $ LoadedProg [] imports' src' | otherwise = pure noLoadedProg@@ -407,7 +426,7 @@ VFS -> IO (Either (NE.NonEmpty ProgError) LoadedProg) reloadProg lp new_roots vfs = do- lp' <- usableLoadedProg lp new_roots+ lp' <- usableLoadedProg lp vfs new_roots extendProg lp' new_roots vfs -- | Read and type-check some Futhark files.
src/Futhark/Construct.hs view
@@ -85,6 +85,7 @@ eSliceArray, eBlank, eAll,+ eDimInBounds, eOutOfBounds, -- * Other building blocks@@ -374,6 +375,14 @@ pure $ BasicOp $ Index arr $ fullSlice arr_t $ skips ++ [slice i' n'] where slice j m = DimSlice j m (constant (1 :: Int64))++-- | @eInBoundsForDim w i@ produces @0 <= i < w@.+eDimInBounds :: MonadBuilder m => m (Exp (Rep m)) -> m (Exp (Rep m)) -> m (Exp (Rep m))+eDimInBounds w i =+ eBinOp+ LogAnd+ (eCmpOp (CmpSle Int64) (eSubExp (intConst Int64 0)) i)+ (eCmpOp (CmpSlt Int64) i w) -- | Are these indexes out-of-bounds for the array? eOutOfBounds ::
src/Futhark/Doc/Generator.hs view
@@ -141,12 +141,13 @@ forM imports $ \(current, fm) -> let ctx = Context- current- fm- imports- mempty- file_map- (progModuleTypes $ fileProg fm)+ { ctxCurrent = current,+ ctxFileMod = fm,+ ctxImports = imports,+ ctxNoLink = mempty,+ ctxFileMap = file_map,+ ctxVisibleMTys = progModuleTypes $ fileProg fm+ } in flip runReaderT ctx $ do (first_paragraph, maybe_abstract, maybe_sections) <- headerDoc $ fileProg fm @@ -519,10 +520,10 @@ prettyShapeDecl :: ShapeDecl (DimDecl VName) -> DocM Html prettyShapeDecl (ShapeDecl ds) =- mconcat <$> mapM (fmap brackets . dimDeclHtml) ds+ mconcat <$> mapM dimDeclHtml ds typeArgHtml :: TypeArg (DimDecl VName) -> DocM Html-typeArgHtml (TypeArgDim d _) = brackets <$> dimDeclHtml d+typeArgHtml (TypeArgDim d _) = dimDeclHtml d typeArgHtml (TypeArgType t _) = typeHtml t modParamHtml :: [ModParamBase Info VName] -> DocM Html@@ -616,7 +617,7 @@ TEArray at d _ -> do at' <- typeExpHtml at d' <- dimExpHtml d- pure $ brackets d' <> at'+ pure $ d' <> at' TETuple ts _ -> parens . commas <$> mapM typeExpHtml ts TERecord fs _ -> braces . commas <$> mapM ppField fs where@@ -691,14 +692,14 @@ concat (replicate (length (splitPath src) - 1) "../") ++ dest dimDeclHtml :: DimDecl VName -> DocM Html-dimDeclHtml (NamedDim v) = qualNameHtml v-dimDeclHtml (ConstDim n) = pure $ toHtml (show n)-dimDeclHtml AnyDim {} = pure mempty+dimDeclHtml (NamedDim v) = brackets <$> qualNameHtml v+dimDeclHtml (ConstDim n) = pure $ brackets $ toHtml (show n)+dimDeclHtml AnyDim {} = pure $ brackets mempty dimExpHtml :: DimExp VName -> DocM Html-dimExpHtml DimExpAny = pure mempty-dimExpHtml (DimExpNamed v _) = qualNameHtml v-dimExpHtml (DimExpConst n _) = pure $ toHtml (show n)+dimExpHtml DimExpAny = pure $ brackets mempty+dimExpHtml (DimExpNamed v _) = brackets <$> qualNameHtml v+dimExpHtml (DimExpConst n _) = pure $ brackets $ toHtml (show n) typeArgExpHtml :: TypeArgExp VName -> DocM Html typeArgExpHtml (TypeArgExpDim d _) = dimExpHtml d
src/Futhark/IR/Aliases.hs view
@@ -38,6 +38,7 @@ removeFunDefAliases, removeExpAliases, removeStmAliases,+ removeBodyAliases, removeLambdaAliases, removePatAliases, removeScopeAliases,@@ -46,6 +47,7 @@ AliasesAndConsumed, trackAliases, mkStmsAliases,+ consumedInStms, ) where @@ -226,6 +228,13 @@ Stm rep removeStmAliases = runIdentity . rephraseStm removeAliases +-- | Remove alias information from body.+removeBodyAliases ::+ CanBeAliased (Op rep) =>+ Body (Aliases rep) ->+ Body rep+removeBodyAliases = runIdentity . rephraseBody removeAliases+ -- | Remove alias information from lambda. removeLambdaAliases :: CanBeAliased (Op rep) =>@@ -320,6 +329,10 @@ Names ) +-- | The variables consumed in these statements.+consumedInStms :: Aliased rep => Stms rep -> Names+consumedInStms = snd . flip mkStmsAliases []+ -- | A helper function for computing the aliases of a sequence of -- statements. You'd use this while recursing down the statements -- from first to last. The 'AliasesAndConsumed' parameter is the@@ -348,13 +361,13 @@ aliasesOfAliases = mconcat . map look . namesToList look k = M.findWithDefault mempty k aliasmap -mkAliasedLetStm ::+mkAliasedStm :: (ASTRep rep, CanBeAliased (Op rep)) => Pat (LetDec rep) -> StmAux (ExpDec rep) -> Exp (Aliases rep) -> Stm (Aliases rep)-mkAliasedLetStm pat (StmAux cs attrs dec) e =+mkAliasedStm pat (StmAux cs attrs dec) e = Let (mkAliasedPat pat e) (StmAux cs attrs (AliasDec $ consumedInExp e, dec))@@ -372,7 +385,7 @@ env <- asksScope removeScopeAliases flip runReaderT env $ do Let pat dec _ <- mkLetNames names $ removeExpAliases e- pure $ mkAliasedLetStm pat dec e+ pure $ mkAliasedStm pat dec e mkBody stms res = let Body bodyrep _ _ = mkBody (fmap removeStmAliases stms) res
src/Futhark/IR/GPU/Op.hs view
@@ -25,10 +25,13 @@ ) where +import Control.Monad+import qualified Data.Sequence as SQ+import qualified Futhark.Analysis.Alias as Alias import Futhark.Analysis.Metrics import qualified Futhark.Analysis.SymbolTable as ST import Futhark.IR-import Futhark.IR.Aliases (Aliases)+import Futhark.IR.Aliases (Aliases, removeBodyAliases) import Futhark.IR.GPU.Sizes import Futhark.IR.Prop.Aliases import Futhark.IR.SegOp@@ -45,7 +48,6 @@ (<+>), ) import qualified Futhark.Util.Pretty as PP-import Prelude hiding (id, (.)) -- | At which level the *body* of a t'SegOp' executes. data SegLevel@@ -240,6 +242,9 @@ SegOp (SegOp SegLevel rep) | SizeOp SizeOp | OtherOp op+ | -- | Code to run sequentially on the GPU,+ -- in a single thread.+ GPUBody [Type] (Body rep) deriving (Eq, Ord, Show) -- | A helper for defining 'TraverseOpStms'.@@ -250,6 +255,9 @@ traverseHostOpStms _ f (SegOp segop) = SegOp <$> traverseSegOpStms f segop traverseHostOpStms _ _ (SizeOp sizeop) = pure $ SizeOp sizeop traverseHostOpStms onOtherOp f (OtherOp other) = OtherOp <$> onOtherOp f other+traverseHostOpStms _ f (GPUBody ts body) = do+ stms <- f mempty $ bodyStms body+ pure $ GPUBody ts $ body {bodyStms = stms} instance (ASTRep rep, Substitute op) => Substitute (HostOp rep op) where substituteNames substs (SegOp op) =@@ -258,50 +266,65 @@ OtherOp $ substituteNames substs op substituteNames substs (SizeOp op) = SizeOp $ substituteNames substs op+ substituteNames substs (GPUBody ts body) =+ GPUBody (substituteNames substs ts) (substituteNames substs body) instance (ASTRep rep, Rename op) => Rename (HostOp rep op) where rename (SegOp op) = SegOp <$> rename op rename (OtherOp op) = OtherOp <$> rename op rename (SizeOp op) = SizeOp <$> rename op+ rename (GPUBody ts body) = GPUBody <$> rename ts <*> rename body instance (ASTRep rep, IsOp op) => IsOp (HostOp rep op) where safeOp (SegOp op) = safeOp op safeOp (OtherOp op) = safeOp op safeOp (SizeOp op) = safeOp op+ safeOp GPUBody {} = True cheapOp (SegOp op) = cheapOp op cheapOp (OtherOp op) = cheapOp op cheapOp (SizeOp op) = cheapOp op+ cheapOp (GPUBody types body) =+ -- Current GPUBody usage only benefits from hoisting kernels that+ -- transfer scalars to device.+ SQ.null (bodyStms body) && all ((== 0) . arrayRank) types instance TypedOp op => TypedOp (HostOp rep op) where opType (SegOp op) = opType op opType (OtherOp op) = opType op opType (SizeOp op) = opType op+ opType (GPUBody ts _) =+ pure $ staticShapes $ map (`arrayOfRow` intConst Int64 1) ts instance (Aliased rep, AliasedOp op, ASTRep rep) => AliasedOp (HostOp rep op) where opAliases (SegOp op) = opAliases op opAliases (OtherOp op) = opAliases op opAliases (SizeOp op) = opAliases op+ opAliases (GPUBody ts _) = map (const mempty) ts consumedInOp (SegOp op) = consumedInOp op consumedInOp (OtherOp op) = consumedInOp op consumedInOp (SizeOp op) = consumedInOp op+ consumedInOp (GPUBody _ body) = consumedInBody body instance (ASTRep rep, FreeIn op) => FreeIn (HostOp rep op) where freeIn' (SegOp op) = freeIn' op freeIn' (OtherOp op) = freeIn' op freeIn' (SizeOp op) = freeIn' op+ freeIn' (GPUBody ts body) = freeIn' ts <> freeIn' body instance (CanBeAliased (Op rep), CanBeAliased op, ASTRep rep) => CanBeAliased (HostOp rep op) where type OpWithAliases (HostOp rep op) = HostOp (Aliases rep) (OpWithAliases op) addOpAliases aliases (SegOp op) = SegOp $ addOpAliases aliases op+ addOpAliases aliases (GPUBody ts body) = GPUBody ts $ Alias.analyseBody aliases body addOpAliases aliases (OtherOp op) = OtherOp $ addOpAliases aliases op addOpAliases _ (SizeOp op) = SizeOp op removeOpAliases (SegOp op) = SegOp $ removeOpAliases op removeOpAliases (OtherOp op) = OtherOp $ removeOpAliases op removeOpAliases (SizeOp op) = SizeOp op+ removeOpAliases (GPUBody ts body) = GPUBody ts $ removeBodyAliases body instance (CanBeWise (Op rep), CanBeWise op, ASTRep rep) => CanBeWise (HostOp rep op) where type OpWithWisdom (HostOp rep op) = HostOp (Wise rep) (OpWithWisdom op)@@ -309,10 +332,12 @@ removeOpWisdom (SegOp op) = SegOp $ removeOpWisdom op removeOpWisdom (OtherOp op) = OtherOp $ removeOpWisdom op removeOpWisdom (SizeOp op) = SizeOp op+ removeOpWisdom (GPUBody ts body) = GPUBody ts $ removeBodyWisdom body addOpWisdom (SegOp op) = SegOp $ addOpWisdom op addOpWisdom (OtherOp op) = OtherOp $ addOpWisdom op addOpWisdom (SizeOp op) = SizeOp op+ addOpWisdom (GPUBody ts body) = GPUBody ts $ informBody body instance (ASTRep rep, ST.IndexOp op) => ST.IndexOp (HostOp rep op) where indexOp vtable k (SegOp op) is = ST.indexOp vtable k op is@@ -323,11 +348,14 @@ ppr (SegOp op) = ppr op ppr (OtherOp op) = ppr op ppr (SizeOp op) = ppr op+ ppr (GPUBody ts body) =+ "gpu" <+> PP.colon <+> ppTuple' ts <+> PP.nestedBlock "{" "}" (ppr body) instance (OpMetrics (Op rep), OpMetrics op) => OpMetrics (HostOp rep op) where opMetrics (SegOp op) = opMetrics op opMetrics (OtherOp op) = opMetrics op opMetrics (SizeOp op) = opMetrics op+ opMetrics (GPUBody _ body) = inside "GPUBody" $ bodyMetrics body checkSegLevel :: TC.Checkable rep =>@@ -356,5 +384,18 @@ typeCheckHostOp checker lvl _ (SegOp op) = TC.checkOpWith (checker $ segLevel op) $ typeCheckSegOp (checkSegLevel lvl) op+typeCheckHostOp _ Just {} _ GPUBody {} =+ TC.bad $ TC.TypeError "GPUBody may not be nested in SegOps." typeCheckHostOp _ _ f (OtherOp op) = f op typeCheckHostOp _ _ _ (SizeOp op) = typeCheckSizeOp op+typeCheckHostOp _ Nothing _ (GPUBody ts body) = do+ mapM_ TC.checkType ts+ void $ TC.checkBody body+ body_ts <-+ extendedScope+ (traverse subExpResType (bodyResult body))+ (scopeOf (bodyStms body))+ unless (body_ts == ts) . TC.bad . TC.TypeError . unlines $+ [ "Expected type: " ++ prettyTuple ts,+ "Got body type: " ++ prettyTuple body_ts+ ]
src/Futhark/IR/GPU/Simplify.hs view
@@ -15,6 +15,7 @@ ) where +import qualified Futhark.Analysis.UsageTable as UT import Futhark.IR.GPU import qualified Futhark.IR.SOACS.Simplify as SOAC import Futhark.MonadFreshNames@@ -72,6 +73,17 @@ simplifyKernelOp _ (SizeOp (CalcNumGroups w max_num_groups group_size)) = do w' <- Engine.simplify w pure (SizeOp $ CalcNumGroups w' max_num_groups group_size, mempty)+simplifyKernelOp _ (GPUBody ts body) = do+ ts' <- Engine.simplify ts+ (hoisted, body') <-+ Engine.simplifyBody keepOnGPU mempty (map (const mempty) ts) body+ pure (GPUBody ts' body', hoisted)+ where+ keepOnGPU _ _ = keepExpOnGPU . stmExp+ keepExpOnGPU (BasicOp Index {}) = True+ keepExpOnGPU (BasicOp (ArrayLit _ t)) | primType t = True+ keepExpOnGPU DoLoop {} = True+ keepExpOnGPU _ = False instance TraverseOpStms (Wise GPU) where traverseOpStms = traverseHostOpStms traverseSOACStms@@ -96,7 +108,28 @@ [ RuleOp SOAC.simplifyKnownIterationSOAC, RuleOp SOAC.removeReplicateMapping, RuleOp SOAC.liftIdentityMapping,- RuleOp SOAC.simplifyMapIota+ RuleOp SOAC.simplifyMapIota,+ RuleOp SOAC.removeUnusedSOACInput ]- [ RuleBasicOp removeUnnecessaryCopy+ [ RuleBasicOp removeUnnecessaryCopy,+ RuleOp removeDeadGPUBodyResult ]++-- | Remove the unused return values of a GPUBody.+removeDeadGPUBodyResult :: BottomUpRuleOp (Wise GPU)+removeDeadGPUBodyResult (_, used) pat aux (GPUBody types body)+ | -- Figure out which of the names in 'pat' are used...+ pat_used <- map (`UT.isUsedDirectly` used) $ patNames pat,+ -- If they are not all used, then this rule applies.+ not (and pat_used) =+ -- Remove the parts of the GPUBody results that correspond to dead+ -- return value bindings. Note that this leaves dead code in the+ -- kernel, but that will be removed later.+ let pick :: [a] -> [a]+ pick = map snd . filter fst . zip pat_used+ pat' = pick (patElems pat)+ types' = pick types+ body' = body {bodyResult = pick (bodyResult body)}+ in Simplify $ auxing aux $ letBind (Pat pat') $ Op $ GPUBody types' body'+ | otherwise = Skip+removeDeadGPUBodyResult _ _ _ _ = Skip
src/Futhark/IR/Mem/Simplify.hs view
@@ -143,7 +143,7 @@ v_copy <- newVName $ baseString v <> "_nonext_copy" let v_pat = Pat [PatElem v_copy $ MemArray pt shape u $ ArrayIn mem ixfun]- addStm $ mkWiseLetStm v_pat (defAux ()) $ BasicOp (Copy v)+ addStm $ mkWiseStm v_pat (defAux ()) $ BasicOp (Copy v) pure $ SubExpRes cs $ Var v_copy | Just mem <- lookup (patElemName pat_elem) oldmem_to_mem = pure $ SubExpRes cs $ Var mem
src/Futhark/IR/Parse.hs view
@@ -578,6 +578,8 @@ keyword "redomap" *> pScrema pRedomapForm, keyword "scanomap" *> pScrema pScanomapForm, keyword "screma" *> pScrema pScremaForm,+ keyword "vjp" *> pVJP,+ keyword "jvp" *> pJVP, pScatter, pHist, pStream@@ -659,6 +661,18 @@ <*> pure SOAC.Sequential <*> braces (pSubExp `sepBy` pComma) <* pComma <*> pLambda pr+ pVJP =+ parens $+ SOAC.VJP+ <$> pLambda pr <* pComma+ <*> braces (pSubExp `sepBy` pComma) <* pComma+ <*> braces (pSubExp `sepBy` pComma)+ pJVP =+ parens $+ SOAC.JVP+ <$> pLambda pr <* pComma+ <*> braces (pSubExp `sepBy` pComma) <* pComma+ <*> braces (pSubExp `sepBy` pComma) pSizeClass :: Parser GPU.SizeClass pSizeClass =@@ -843,7 +857,8 @@ choice [ GPU.SegOp <$> pSegOp pr pSegLevel, GPU.SizeOp <$> pSizeOp,- GPU.OtherOp <$> pOther+ GPU.OtherOp <$> pOther,+ keyword "gpu" $> GPU.GPUBody <*> (pColon *> pTypes) <*> braces (pBody pr) ] pMCOp :: PR rep -> Parser op -> Parser (MC.MCOp rep op)
src/Futhark/IR/Primitive.hs view
@@ -1670,7 +1670,7 @@ negativeIshInt (Int32Value k) = k < 0 negativeIshInt (Int64Value k) = k < 0 --- | The size of a value of a given primitive type in bites.+-- | The size of a value of a given primitive type in bits. primBitSize :: PrimType -> Int primBitSize = (* 8) . primByteSize
src/Futhark/IR/Prop/Aliases.hs view
@@ -52,7 +52,7 @@ vnameAliases :: VName -> Names vnameAliases = oneName --- | The alises of a subexpression.+-- | The aliases of a subexpression. subExpAliases :: SubExp -> Names subExpAliases Constant {} = mempty subExpAliases (Var v) = vnameAliases v
src/Futhark/IR/Prop/Names.hs view
@@ -10,6 +10,7 @@ ( -- * Free names Names, namesIntMap,+ namesIntSet, nameIn, notNameIn, oneName,@@ -46,6 +47,7 @@ import Control.Monad.State.Strict import Data.Foldable import qualified Data.IntMap.Strict as IM+import qualified Data.IntSet as IS import qualified Data.Map.Strict as M import Futhark.IR.Prop.Patterns import Futhark.IR.Prop.Scope@@ -62,6 +64,10 @@ -- | Retrieve the data structure underlying the names representation. namesIntMap :: Names -> IM.IntMap VName namesIntMap (Names m) = m++-- | Retrieve the set of tags in the names set.+namesIntSet :: Names -> IS.IntSet+namesIntSet (Names m) = IM.keysSet m instance Ord Names where x `compare` y = if x == y then EQ else LT
src/Futhark/IR/Prop/Types.hs view
@@ -13,6 +13,7 @@ staticShapes, staticShapes1, primType,+ isAcc, arrayOf, arrayOfRow, arrayOfShape,@@ -137,7 +138,7 @@ unique = (== Unique) . uniqueness -- | Convert types with non-existential shapes to types with--- non-existential shapes. Only the representation is changed, so all+-- existential shapes. Only the representation is changed, so all -- the shapes will be 'Free'. staticShapes :: [TypeBase Shape u] -> [TypeBase ExtShape u] staticShapes = map staticShapes1@@ -287,12 +288,17 @@ primType Prim {} = True primType _ = False +-- | Is this an accumulator?+isAcc :: TypeBase shape u -> Bool+isAcc Acc {} = True+isAcc _ = False+ -- | Returns the bottommost type of an array. For @[][]i32@, this -- would be @i32@. If the given type is not an array, it is returned. elemType :: TypeBase shape u -> PrimType elemType (Array t _ _) = t elemType (Prim t) = t-elemType Acc {} = error "Acc"+elemType Acc {} = error "elemType Acc" elemType Mem {} = error "elemType Mem" -- | Swap the two outer dimensions of the type.
src/Futhark/IR/SOACS.hs view
@@ -4,6 +4,7 @@ -- | A simple representation with SOACs and nested parallelism. module Futhark.IR.SOACS ( SOACS,+ usesAD, -- * Module re-exports module Futhark.IR.Prop,@@ -46,3 +47,27 @@ instance BuilderOps SOACS instance PrettyRep SOACS++usesAD :: Prog SOACS -> Bool+usesAD prog = any stmUsesAD (progConsts prog) || any funUsesAD (progFuns prog)+ where+ funUsesAD = bodyUsesAD . funDefBody+ bodyUsesAD = any stmUsesAD . bodyStms+ stmUsesAD = expUsesAD . stmExp+ lamUsesAD = bodyUsesAD . lambdaBody+ expUsesAD (Op JVP {}) = True+ expUsesAD (Op VJP {}) = True+ expUsesAD (Op (Stream _ _ _ _ lam)) = lamUsesAD lam+ expUsesAD (Op (Screma _ _ (ScremaForm scans reds lam))) =+ lamUsesAD lam+ || any (lamUsesAD . scanLambda) scans+ || any (lamUsesAD . redLambda) reds+ expUsesAD (Op (Hist _ _ ops lam)) =+ lamUsesAD lam || any (lamUsesAD . histOp) ops+ expUsesAD (Op (Scatter _ _ lam _)) =+ lamUsesAD lam+ expUsesAD (If _ tbody fbody _) = bodyUsesAD tbody || bodyUsesAD fbody+ expUsesAD (DoLoop _ _ body) = bodyUsesAD body+ expUsesAD (WithAcc _ lam) = lamUsesAD lam+ expUsesAD BasicOp {} = False+ expUsesAD Apply {} = False
src/Futhark/IR/SOACS/SOAC.hs view
@@ -122,6 +122,10 @@ -- -- The final lambda produces indexes and values for the 'HistOp's. Hist SubExp [VName] [HistOp rep] (Lambda rep)+ | -- FIXME: this should not be here+ JVP (Lambda rep) [SubExp] [SubExp]+ | -- FIXME: this should not be here+ VJP (Lambda rep) [SubExp] [SubExp] | -- | A combination of scan, reduction, and map. The first -- t'SubExp' is the size of the input arrays. Screma SubExp [VName] (ScremaForm rep)@@ -400,6 +404,14 @@ SOACMapper frep trep m -> SOAC frep -> m (SOAC trep)+mapSOACM tv (JVP lam args vec) =+ JVP <$> mapOnSOACLambda tv lam+ <*> mapM (mapOnSOACSubExp tv) args+ <*> mapM (mapOnSOACSubExp tv) vec+mapSOACM tv (VJP lam args vec) =+ VJP <$> mapOnSOACLambda tv lam+ <*> mapM (mapOnSOACSubExp tv) args+ <*> mapM (mapOnSOACSubExp tv) vec mapSOACM tv (Stream size arrs form accs lam) = Stream <$> mapOnSOACSubExp tv size <*> mapM (mapOnSOACVName tv) arrs@@ -490,7 +502,13 @@ renamer = SOACMapper rename rename rename -- | The type of a SOAC.-soacType :: SOAC rep -> [Type]+soacType :: Typed (LParamInfo rep) => SOAC rep -> [Type]+soacType (JVP lam _ _) =+ lambdaReturnType lam+ ++ lambdaReturnType lam+soacType (VJP lam _ _) =+ lambdaReturnType lam+ ++ map paramType (lambdaParams lam) soacType (Stream outersize _ _ accs lam) = map (substNamesInType substs) rtp where@@ -509,12 +527,14 @@ soacType (Screma w _arrs form) = scremaType w form -instance TypedOp (SOAC rep) where+instance ASTRep rep => TypedOp (SOAC rep) where opType = pure . staticShapes . soacType instance (ASTRep rep, Aliased rep) => AliasedOp (SOAC rep) where opAliases = map (const mempty) . soacType + consumedInOp JVP {} = mempty+ consumedInOp VJP {} = mempty -- Only map functions can consume anything. The operands to scan -- and reduce functions are always considered "fresh". consumedInOp (Screma _ arrs (ScremaForm _ _ map_lam)) =@@ -556,6 +576,10 @@ where type OpWithAliases (SOAC rep) = SOAC (Aliases rep) + addOpAliases aliases (JVP lam args vec) =+ JVP (Alias.analyseLambda aliases lam) args vec+ addOpAliases aliases (VJP lam args vec) =+ VJP (Alias.analyseLambda aliases lam) args vec addOpAliases aliases (Stream size arr form accs lam) = Stream size arr (analyseStreamForm form) accs $ Alias.analyseLambda aliases lam@@ -648,6 +672,8 @@ -- | Type-check a SOAC. typeCheckSOAC :: TC.Checkable rep => SOAC (Aliases rep) -> TC.TypeM rep ()+typeCheckSOAC JVP {} = pure ()+typeCheckSOAC VJP {} = pure () typeCheckSOAC (Stream size arrexps form accexps lam) = do TC.require [Prim int64] size accargs <- mapM TC.checkArg accexps@@ -820,6 +846,10 @@ ++ " wrong for given scan and reduction functions." instance OpMetrics (Op rep) => OpMetrics (SOAC rep) where+ opMetrics (VJP lam _ _) =+ inside "VJP" $ lambdaMetrics lam+ opMetrics (JVP lam _ _) =+ inside "JVP" $ lambdaMetrics lam opMetrics (Stream _ _ _ _ lam) = inside "Stream" $ lambdaMetrics lam opMetrics (Scatter _len _ lam _) =@@ -833,6 +863,22 @@ lambdaMetrics map_lam instance PrettyRep rep => PP.Pretty (SOAC rep) where+ ppr (VJP lam args vec) =+ text "vjp"+ <> parens+ ( PP.align $+ ppr lam <> comma+ </> PP.braces (commasep $ map ppr args) <> comma+ </> PP.braces (commasep $ map ppr vec)+ )+ ppr (JVP lam args vec) =+ text "jvp"+ <> parens+ ( PP.align $+ ppr lam <> comma+ </> PP.braces (commasep $ map ppr args) <> comma+ </> PP.braces (commasep $ map ppr vec)+ ) ppr (Stream size arrs form acc lam) = case form of Parallel o comm lam0 ->
src/Futhark/IR/SOACS/Simplify.hs view
@@ -17,6 +17,7 @@ HasSOAC (..), simplifyKnownIterationSOAC, removeReplicateMapping,+ removeUnusedSOACInput, liftIdentityMapping, simplifyMapIota, SOACS,@@ -85,6 +86,16 @@ simplifySOAC :: Simplify.SimplifiableRep rep => Simplify.SimplifyOp rep (SOAC (Wise rep))+simplifySOAC (VJP lam arr vec) = do+ (lam', hoisted) <- Engine.simplifyLambda lam+ arr' <- mapM Engine.simplify arr+ vec' <- mapM Engine.simplify vec+ pure (VJP lam' arr' vec', hoisted)+simplifySOAC (JVP lam arr vec) = do+ (lam', hoisted) <- Engine.simplifyLambda lam+ arr' <- mapM Engine.simplify arr+ vec' <- mapM Engine.simplify vec+ pure (JVP lam' arr' vec', hoisted) simplifySOAC (Stream outerdim arr form nes lam) = do outerdim' <- Engine.simplify outerdim (form', form_hoisted) <- simplifyStreamForm form@@ -392,17 +403,21 @@ Left (p, v) -- | Remove inputs that are not used inside the SOAC.-removeUnusedSOACInput :: TopDownRuleOp (Wise SOACS)-removeUnusedSOACInput _ pat aux (Screma w arrs (ScremaForm scan reduce map_lam))- | (used, unused) <- partition usedInput params_and_arrs,+removeUnusedSOACInput ::+ forall rep.+ (Aliased rep, Buildable rep, BuilderOps rep, HasSOAC rep) =>+ TopDownRuleOp rep+removeUnusedSOACInput _ pat aux op+ | Just (Screma w arrs form :: SOAC rep) <- asSOAC op,+ ScremaForm scan reduce map_lam <- form,+ (used, unused) <- partition (usedInput map_lam) (zip (lambdaParams map_lam) arrs), not (null unused) = Simplify $ do let (used_params, used_arrs) = unzip used map_lam' = map_lam {lambdaParams = used_params}- auxing aux $ letBind pat $ Op $ Screma w used_arrs (ScremaForm scan reduce map_lam')+ auxing aux $ letBind pat $ Op $ soacOp $ Screma w used_arrs (ScremaForm scan reduce map_lam') where- params_and_arrs = zip (lambdaParams map_lam) arrs- used_in_body = freeIn $ lambdaBody map_lam- usedInput (param, _) = paramName param `nameIn` used_in_body+ used_in_body map_lam = freeIn $ lambdaBody map_lam+ usedInput map_lam (param, _) = paramName param `nameIn` used_in_body map_lam removeUnusedSOACInput _ _ _ _ = Skip removeDeadMapping :: BottomUpRuleOp (Wise SOACS)
src/Futhark/IR/SegOp.hs view
@@ -56,6 +56,7 @@ import Control.Category import Control.Monad.Identity hiding (mapM_)+import Control.Monad.Reader hiding (mapM_) import Control.Monad.State.Strict import Control.Monad.Writer hiding (mapM_) import Data.Bifunctor (first)@@ -1178,6 +1179,8 @@ `Engine.orIf` Engine.isOp `Engine.orIf` par_blocker `Engine.orIf` Engine.isConsumed+ `Engine.orIf` Engine.isConsuming+ `Engine.orIf` Engine.isDeviceMigrated -- Ensure we do not try to use anything that is consumed in the result. (body_res, body_stms, hoisted) <-@@ -1201,6 +1204,12 @@ consumedInResult _ = [] +simplifyLambda ::+ Engine.SimplifiableRep rep =>+ Lambda (Wise rep) ->+ Engine.SimpleM rep (Lambda (Wise rep), Stms (Wise rep))+simplifyLambda = Engine.blockMigrated . Engine.simplifyLambda+ segSpaceSymbolTable :: ASTRep rep => SegSpace -> ST.SymbolTable rep segSpaceSymbolTable (SegSpace flat gtids_and_dims) = foldl' f (ST.fromScope $ M.singleton flat $ IndexName Int64) gtids_and_dims@@ -1214,7 +1223,7 @@ simplifySegBinOp (SegBinOp comm lam nes shape) = do (lam', hoisted) <- Engine.localVtable (\vtable -> vtable {ST.simplifyMemory = True}) $- Engine.simplifyLambda lam+ simplifyLambda lam shape' <- Engine.simplify shape nes' <- mapM Engine.simplify nes pure (SegBinOp comm lam' nes' shape', hoisted)@@ -1276,7 +1285,7 @@ (lam', op_hoisted) <- Engine.localVtable (<> scope_vtable) $ Engine.localVtable (\vtable -> vtable {ST.simplifyMemory = True}) $- Engine.simplifyLambda lam+ simplifyLambda lam pure ( HistOp w' rf' arrs' nes' dims' lam', op_hoisted@@ -1301,7 +1310,7 @@ -- | Simplification rules for simplifying 'SegOp's. segOpRules ::- (HasSegOp rep, BuilderOps rep, Buildable rep) =>+ (HasSegOp rep, BuilderOps rep, Buildable rep, Aliased rep) => RuleBook rep segOpRules = ruleBook [RuleOp segOpRuleTopDown] [RuleOp segOpRuleBottomUp]@@ -1316,7 +1325,7 @@ Skip segOpRuleBottomUp ::- (HasSegOp rep, BuilderOps rep) =>+ (HasSegOp rep, BuilderOps rep, Aliased rep) => BottomUpRuleOp rep segOpRuleBottomUp vtable pat dec op | Just op' <- asSegOp op =@@ -1431,7 +1440,7 @@ (kts, body, sum $ map (length . histDest) ops, SegHist lvl space ops) bottomUpSegOp ::- (HasSegOp rep, BuilderOps rep) =>+ (Aliased rep, HasSegOp rep, BuilderOps rep) => (ST.SymbolTable rep, UT.UsageTable) -> Pat (LetDec rep) -> StmAux (ExpDec rep) ->@@ -1454,15 +1463,16 @@ (kpes' == kpes) cannotSimplify - kbody <-+ kbody' <- localScope (scopeOfSegSpace space) $ mkKernelBodyM kstms' kres' - addStm $ Let (Pat kpes') dec $ Op $ segOp $ mk_segop kts' kbody+ addStm $ Let (Pat kpes') dec $ Op $ segOp $ mk_segop kts' kbody' where- (kts, KernelBody _ kstms kres, num_nonmap_results, mk_segop) =+ (kts, kbody@(KernelBody _ kstms kres), num_nonmap_results, mk_segop) = segOpGuts segop free_in_kstms = foldMap freeIn kstms+ consumed_in_segop = consumedInKernelBody kbody space = segSpace segop sliceWithGtidsFixed stm@@ -1494,6 +1504,7 @@ letBindNames [patElemName kpe'] . BasicOp . Index arr $ Slice $ outer_slice <> remaining_slice if patElemName kpe `UT.isConsumed` used+ || arr `nameIn` consumed_in_segop then do precopy <- newVName $ baseString (patElemName kpe) <> "_precopy" index kpe {patElemName = precopy}
src/Futhark/IR/Syntax.hs view
@@ -157,10 +157,12 @@ stmsFromList, stmsToList, stmsHead,+ stmsLast, subExpRes, subExpsRes, varRes, varsRes,+ subExpResVName, ) where @@ -245,6 +247,12 @@ stm Seq.:< stms' -> Just (stm, stms') Seq.EmptyL -> Nothing +-- | The last statement in the sequence, if any.+stmsLast :: Stms lore -> Maybe (Stms lore, Stm lore)+stmsLast stms = case Seq.viewr stms of+ stms' Seq.:> stm -> Just (stms', stm)+ Seq.EmptyR -> Nothing+ -- | A pairing of a subexpression and some certificates. data SubExpRes = SubExpRes { resCerts :: Certs,@@ -268,6 +276,11 @@ varsRes :: [VName] -> Result varsRes = map varRes +-- | The 'VName' of a 'SubExpRes', if it exists.+subExpResVName :: SubExpRes -> Maybe VName+subExpResVName (SubExpRes _ (Var v)) = Just v+subExpResVName _ = Nothing+ -- | The result of a body is a sequence of subexpressions. type Result = [SubExpRes] @@ -363,18 +376,14 @@ Update Safety VName (Slice SubExp) SubExp | FlatIndex VName (FlatSlice SubExp) | FlatUpdate VName (FlatSlice SubExp) VName- | -- | @- -- concat(0, [1] :| [[2, 3, 4], [5, 6]], 6) = [1, 2, 3, 4, 5, 6]@.- -- @+ | -- | @concat(0, [1] :| [[2, 3, 4], [5, 6]], 6) = [1, 2, 3, 4, 5, 6]@ -- -- Concatenates the non-empty list of 'VName' resulting in an -- array of length t'SubExp'. The 'Int' argument is used to -- specify the dimension along which the arrays are -- concatenated. For instance: --- -- @- -- concat(1, [[1,2], [3, 4]] :| [[[5,6]], [[7, 8]]], 4) = [[1, 2, 5, 6], [3, 4, 7, 8]]- -- @+ -- @concat(1, [[1,2], [3, 4]] :| [[[5,6]], [[7, 8]]], 4) = [[1, 2, 5, 6], [3, 4, 7, 8]]@ Concat Int (NonEmpty VName) SubExp | -- | Copy the given array. The result will not alias anything. Copy VName
src/Futhark/IR/Syntax/Core.hs view
@@ -43,6 +43,7 @@ oneAttr, inAttrs, withoutAttrs,+ mapAttrs, -- * Values PrimValue (..),@@ -553,3 +554,7 @@ -- | @x `withoutAttrs` y@ gives @x@ except for any attributes also in @y@. withoutAttrs :: Attrs -> Attrs -> Attrs withoutAttrs (Attrs x) (Attrs y) = Attrs $ x `S.difference` y++-- | Map a function over an attribute set.+mapAttrs :: (Attr -> a) -> Attrs -> [a]+mapAttrs f (Attrs attrs) = map f $ S.toList attrs
src/Futhark/Internalise/Defunctorise.hs view
@@ -129,9 +129,17 @@ lookupMod' :: QualName VName -> Scope -> Either String Mod lookupMod' mname scope = let (mname', scope') = lookupSubstInScope mname scope- in maybe (Left $ bad mname') Right $ M.lookup (qualLeaf mname') $ scopeMods scope'+ in maybe (Left $ bad mname') (Right . extend) $ M.lookup (qualLeaf mname') $ scopeMods scope' where bad mname' = "Unknown module: " ++ pretty mname ++ " (" ++ pretty mname' ++ ")"+ extend (ModMod (Scope inner_scope inner_mods)) =+ -- XXX: perhaps hacky fix for #1653. We need to impose the+ -- substitutions of abstract types from outside, because the+ -- inner module may have some incorrect substitutions in some+ -- cases. Our treatment of abstract types is completely whack+ -- and should be fixed.+ ModMod $ Scope (scopeSubsts scope <> inner_scope) inner_mods+ extend m = m lookupMod :: QualName VName -> TransformM Mod lookupMod mname = either error pure . lookupMod' mname =<< askScope
src/Futhark/Internalise/Exps.hs view
@@ -1578,6 +1578,7 @@ handleOps, handleSOACs, handleAccs,+ handleAD, handleRest ] msum [h args $ baseString $ qualLeaf qname | h <- handlers]@@ -1745,6 +1746,19 @@ vs <- internaliseExp "acc_v" v fmap pure $ letSubExp desc $ BasicOp $ UpdateAcc acc' [i'] vs handleAccs _ _ = Nothing++ handleAD [TupLit [f, x, v] _] fname+ | fname `elem` ["jvp2", "vjp2"] = Just $ \desc -> do+ x' <- internaliseExp "ad_x" x+ v' <- internaliseExp "ad_v" v+ xts <- mapM subExpType x'+ (ps, body, ret) <- internaliseLambda f xts+ let lam = I.Lambda ps body ret+ fmap (map I.Var) . letTupExp desc . Op $+ case fname of+ "jvp2" -> JVP lam x' v'+ _ -> VJP lam x' v'+ handleAD _ _ = Nothing handleRest [E.TupLit [a, si, v] _] "scatter" = Just $ scatterF 1 a si v handleRest [E.TupLit [a, si, v] _] "scatter_2d" = Just $ scatterF 2 a si v
src/Futhark/LSP/Compile.hs view
@@ -2,61 +2,88 @@ -- the Futhark program managed by the language server. The challenge -- here is that if the program becomes type-invalid, we want to keep -- the old state around.-module Futhark.LSP.Compile (tryTakeStateFromMVar, tryReCompile) where+module Futhark.LSP.Compile (tryTakeStateFromIORef, tryReCompile) where -import Control.Concurrent.MVar (MVar, putMVar, takeMVar) import Control.Monad.IO.Class (MonadIO (liftIO))+import Data.IORef (IORef, readIORef, writeIORef) import qualified Data.Map as M+import Data.Maybe (fromMaybe) import qualified Data.Text as T-import Futhark.Compiler.Program (LoadedProg, lpWarnings, noLoadedProg, reloadProg)+import Futhark.Compiler.Program (LoadedProg, lpFilePaths, lpWarnings, noLoadedProg, reloadProg) import Futhark.LSP.Diagnostic (diagnosticSource, maxDiagnostic, publishErrorDiagnostics, publishWarningDiagnostics)-import Futhark.LSP.State (State (..), emptyState)+import Futhark.LSP.State (State (..), emptyState, updateStaleContent, updateStaleMapping)+import Futhark.LSP.Tool (computeMapping) import Futhark.Util (debug) import Language.Futhark.Warnings (listWarnings)-import Language.LSP.Server (LspT, flushDiagnosticsBySource, getVirtualFiles)-import Language.LSP.Types (fromNormalizedFilePath, uriToNormalizedFilePath)+import Language.LSP.Server (LspT, flushDiagnosticsBySource, getVirtualFile, getVirtualFiles)+import Language.LSP.Types+ ( filePathToUri,+ fromNormalizedFilePath,+ toNormalizedUri,+ uriToNormalizedFilePath,+ ) import Language.LSP.VFS (VFS (vfsMap), virtualFileText) --- | Try to take state from MVar, if it's empty, try to compile.-tryTakeStateFromMVar :: MVar State -> Maybe FilePath -> LspT () IO State-tryTakeStateFromMVar state_mvar file_path = do- old_state <- liftIO $ takeMVar state_mvar+-- | Try to take state from IORef, if it's empty, try to compile.+tryTakeStateFromIORef :: IORef State -> Maybe FilePath -> LspT () IO State+tryTakeStateFromIORef state_mvar file_path = do+ old_state <- liftIO $ readIORef state_mvar case stateProgram old_state of Nothing -> do- new_state <- tryCompile file_path (State $ Just noLoadedProg)- liftIO $ putMVar state_mvar new_state+ new_state <- tryCompile old_state file_path noLoadedProg+ liftIO $ writeIORef state_mvar new_state pure new_state- Just _ -> do- liftIO $ putMVar state_mvar old_state- pure old_state+ Just prog -> do+ -- If this is in the context of some file that is not part of+ -- the program, try to reload the program from that file.+ let files = lpFilePaths prog+ state <- case file_path of+ Just file_path'+ | file_path' `notElem` files -> do+ debug $ "File not part of program: " <> show file_path'+ debug $ "Program contains: " <> show files+ tryCompile old_state file_path noLoadedProg+ _ -> pure old_state+ liftIO $ writeIORef state_mvar state+ pure state -- | Try to (re)-compile, replace old state if successful.-tryReCompile :: MVar State -> Maybe FilePath -> LspT () IO ()+tryReCompile :: IORef State -> Maybe FilePath -> LspT () IO () tryReCompile state_mvar file_path = do debug "(Re)-compiling ..."- old_state <- liftIO $ takeMVar state_mvar- new_state <- tryCompile file_path old_state+ old_state <- liftIO $ readIORef state_mvar+ let loaded_prog = getLoadedProg old_state+ new_state <- tryCompile old_state file_path loaded_prog case stateProgram new_state of Nothing -> do debug "Failed to (re)-compile, using old state or Nothing"- liftIO $ putMVar state_mvar old_state+ debug $ "Computing PositionMapping for: " <> show file_path+ mapping <- computeMapping old_state file_path+ liftIO $ writeIORef state_mvar $ updateStaleMapping file_path mapping old_state Just _ -> do debug "(Re)-compile successful"- liftIO $ putMVar state_mvar new_state+ liftIO $ writeIORef state_mvar new_state -- | Try to compile, publish diagnostics on warnings and errors, return newly compiled state. -- Single point where the compilation is done, and shouldn't be exported.-tryCompile :: Maybe FilePath -> State -> LspT () IO State-tryCompile Nothing _ = pure emptyState-tryCompile (Just path) state = do- let old_loaded_prog = getLoadedProg state+tryCompile :: State -> Maybe FilePath -> LoadedProg -> LspT () IO State+tryCompile _ Nothing _ = pure emptyState+tryCompile state (Just path) old_loaded_prog = do+ debug $ "Reloading program from " <> show path vfs <- getVirtualFiles- res <- liftIO $ reloadProg old_loaded_prog [path] (transformVFS vfs)+ res <- liftIO $ reloadProg old_loaded_prog [path] (transformVFS vfs) -- NOTE: vfs only keeps track of current opened files flushDiagnosticsBySource maxDiagnostic diagnosticSource case res of Right new_loaded_prog -> do publishWarningDiagnostics $ listWarnings $ lpWarnings new_loaded_prog- pure $ State (Just new_loaded_prog)+ maybe_virtual_file <- getVirtualFile $ toNormalizedUri $ filePathToUri path+ case maybe_virtual_file of+ Nothing -> pure $ State (Just new_loaded_prog) (staleData state) -- should never happen+ Just virtual_file ->+ pure $ updateStaleContent path virtual_file new_loaded_prog state+ -- Preserve files that have been opened should be enoguth.+ -- But still might need an update on re-compile logic, don't discard all state afterwards,+ -- try to compile from root file, if there is a depencency relatetion, improve performance and provide more dignostic. Left prog_error -> do debug "Compilation failed, publishing diagnostics" publishErrorDiagnostics prog_error@@ -77,5 +104,4 @@ (vfsMap vfs) getLoadedProg :: State -> LoadedProg-getLoadedProg (State (Just loaded_prog)) = loaded_prog-getLoadedProg (State Nothing) = noLoadedProg+getLoadedProg state = fromMaybe noLoadedProg (stateProgram state)
src/Futhark/LSP/Handlers.hs view
@@ -3,11 +3,11 @@ -- | The handlers exposed by the language server. module Futhark.LSP.Handlers (handlers) where -import Control.Concurrent.MVar (MVar) import Control.Lens ((^.)) import Data.Aeson.Types (Value (Array, String))+import Data.IORef import qualified Data.Vector as V-import Futhark.LSP.Compile (tryReCompile, tryTakeStateFromMVar)+import Futhark.LSP.Compile (tryReCompile, tryTakeStateFromIORef) import Futhark.LSP.State (State (..)) import Futhark.LSP.Tool (findDefinitionRange, getHoverInfoFromState) import Futhark.Util (debug)@@ -15,36 +15,35 @@ import Language.LSP.Types import Language.LSP.Types.Lens (HasUri (uri)) --- | Given an 'MVar' tracking the state, produce a set of handlers.+-- | Given an 'IORef' tracking the state, produce a set of handlers. -- When we want to add more features to the language server, this is -- the thing to change.-handlers :: MVar State -> Handlers (LspM ())+handlers :: IORef State -> Handlers (LspM ()) handlers state_mvar = mconcat [ onInitializeHandler,- onHoverHandler state_mvar, onDocumentOpenHandler state_mvar, onDocumentCloseHandler, onDocumentSaveHandler state_mvar, onDocumentChangeHandler state_mvar, onDocumentFocusHandler state_mvar, goToDefinitionHandler state_mvar,- onDocumentChangeHandler state_mvar+ onHoverHandler state_mvar ] onInitializeHandler :: Handlers (LspM ()) onInitializeHandler = notificationHandler SInitialized $ \_msg -> debug "Initialized" -onHoverHandler :: MVar State -> Handlers (LspM ())+onHoverHandler :: IORef State -> Handlers (LspM ()) onHoverHandler state_mvar = requestHandler STextDocumentHover $ \req responder -> do- debug "Got hover request" let RequestMessage _ _ _ (HoverParams doc pos _workDone) = req Position l c = pos file_path = uriToFilePath $ doc ^. uri- state <- tryTakeStateFromMVar state_mvar file_path+ debug $ "Got hover request: " <> show (file_path, pos)+ state <- tryTakeStateFromIORef state_mvar file_path responder $ Right $ getHoverInfoFromState state file_path (fromEnum l + 1) (fromEnum c + 1) -onDocumentFocusHandler :: MVar State -> Handlers (LspM ())+onDocumentFocusHandler :: IORef State -> Handlers (LspM ()) onDocumentFocusHandler state_mvar = notificationHandler (SCustomMethod "custom/onFocusTextDocument") $ \msg -> do debug "Got custom request: onFocusTextDocument" let NotificationMessage _ _ (Array vector_param) = msg@@ -52,33 +51,33 @@ debug $ show focused_uri tryReCompile state_mvar (uriToFilePath (Uri focused_uri)) -goToDefinitionHandler :: MVar State -> Handlers (LspM ())+goToDefinitionHandler :: IORef State -> Handlers (LspM ()) goToDefinitionHandler state_mvar = requestHandler STextDocumentDefinition $ \req responder -> do- debug "Got goto definition request" let RequestMessage _ _ _ (DefinitionParams doc pos _workDone _partial) = req Position l c = pos file_path = uriToFilePath $ doc ^. uri- state <- tryTakeStateFromMVar state_mvar file_path+ debug $ "Got goto definition: " <> show (file_path, pos)+ state <- tryTakeStateFromIORef state_mvar file_path case findDefinitionRange state file_path (fromEnum l + 1) (fromEnum c + 1) of Nothing -> responder $ Right $ InR $ InL $ List [] Just loc -> do debug $ show loc responder $ Right $ InL loc -onDocumentSaveHandler :: MVar State -> Handlers (LspM ())+onDocumentSaveHandler :: IORef State -> Handlers (LspM ()) onDocumentSaveHandler state_mvar = notificationHandler STextDocumentDidSave $ \msg -> do let NotificationMessage _ _ (DidSaveTextDocumentParams doc _text) = msg file_path = uriToFilePath $ doc ^. uri debug $ "Saved document: " ++ show doc tryReCompile state_mvar file_path -onDocumentChangeHandler :: MVar State -> Handlers (LspM ())+onDocumentChangeHandler :: IORef State -> Handlers (LspM ()) onDocumentChangeHandler state_mvar = notificationHandler STextDocumentDidChange $ \msg -> do let NotificationMessage _ _ (DidChangeTextDocumentParams doc _content) = msg file_path = uriToFilePath $ doc ^. uri tryReCompile state_mvar file_path -onDocumentOpenHandler :: MVar State -> Handlers (LspM ())+onDocumentOpenHandler :: IORef State -> Handlers (LspM ()) onDocumentOpenHandler state_mvar = notificationHandler STextDocumentDidOpen $ \msg -> do let NotificationMessage _ _ (DidOpenTextDocumentParams doc) = msg file_path = uriToFilePath $ doc ^. uri
+ src/Futhark/LSP/PositionMapping.hs view
@@ -0,0 +1,80 @@+-- | Provide mapping between position in stale content and current.+module Futhark.LSP.PositionMapping+ ( mappingFromDiff,+ PositionMapping,+ toStalePos,+ toCurrentLoc,+ StaleFile (..),+ )+where++import Data.Algorithm.Diff (Diff, PolyDiff (Both, First, Second), getDiff)+import Data.Bifunctor (Bifunctor (bimap, first, second))+import qualified Data.Text as T+import qualified Data.Vector as V+import Futhark.Util.Loc (Loc (Loc), Pos (Pos))+import Language.LSP.VFS (VirtualFile)++-- | A mapping between current file content and the stale (last successful compiled) file content.+-- Currently, only supports entire line mapping,+-- more detailed mapping might be achieved via referring to haskell-language-server@efb4b94+data PositionMapping = PositionMapping+ { -- | The mapping from stale position to current.+ -- e.g. staleToCurrent[2] = 4 means "line 2" in the stale file, corresponds to "line 4" in the current file.+ staleToCurrent :: V.Vector Int,+ -- | The mapping from current position to stale.+ currentToStale :: V.Vector Int+ }+ deriving (Show)++-- | Stale text document stored in state.+data StaleFile = StaleFile+ { -- | The last successfully compiled file content.+ -- Using VirtualFile for convenience, we can use anything with {version, content}+ staleContent :: VirtualFile,+ -- | PositionMapping between current and stale file content.+ -- Nothing if last type-check is successful.+ staleMapping :: Maybe PositionMapping+ }+ deriving (Show)++-- | Compute PositionMapping using the diff between two texts.+mappingFromDiff :: [T.Text] -> [T.Text] -> PositionMapping+mappingFromDiff stale current = do+ let (stale_to_current, current_to_stale) = rawMapping (getDiff stale current) 0 0+ PositionMapping (V.fromList stale_to_current) (V.fromList current_to_stale)+ where+ rawMapping :: [Diff T.Text] -> Int -> Int -> ([Int], [Int])+ rawMapping [] _ _ = ([], [])+ rawMapping (Both _ _ : xs) lold lnew = bimap (lnew :) (lold :) $ rawMapping xs (lold + 1) (lnew + 1)+ rawMapping (First _ : xs) lold lnew = first (-1 :) $ rawMapping xs (lold + 1) lnew+ rawMapping (Second _ : xs) lold lnew = second (-1 :) $ rawMapping xs lold (lnew + 1)++-- | Transform current Pos to the stale pos for query+-- Note: line and col in Pos is larger by one+toStalePos :: Maybe PositionMapping -> Pos -> Maybe Pos+toStalePos (Just (PositionMapping _ current_to_stale)) pos =+ if l > Prelude.length current_to_stale+ then Nothing+ else Just $ Pos file (V.unsafeIndex current_to_stale (l - 1) + 1) c o+ where+ Pos file l c o = pos+toStalePos Nothing pos = Just pos++-- some refactoring might be needed, same logic as toStalePos+toCurrentPos :: Maybe PositionMapping -> Pos -> Maybe Pos+toCurrentPos (Just (PositionMapping stale_to_current _)) pos =+ if l > Prelude.length stale_to_current+ then Nothing+ else Just $ Pos file (V.unsafeIndex stale_to_current (l - 1) + 1) c o+ where+ Pos file l c o = pos+toCurrentPos Nothing pos = Just pos++-- | Transform stale Loc gotten from stale AST to current Loc.+toCurrentLoc :: Maybe PositionMapping -> Loc -> Maybe Loc+toCurrentLoc mapping loc = do+ let Loc start end = loc+ current_start <- toCurrentPos mapping start+ current_end <- toCurrentPos mapping end+ Just $ Loc current_start current_end
src/Futhark/LSP/State.hs view
@@ -2,17 +2,53 @@ module Futhark.LSP.State ( State (..), emptyState,+ getStaleContent,+ getStaleMapping,+ updateStaleContent,+ updateStaleMapping, ) where +import qualified Data.Map as M import Futhark.Compiler.Program (LoadedProg)+import Futhark.LSP.PositionMapping (PositionMapping, StaleFile (..))+import Language.LSP.VFS (VirtualFile) -- | The state of the language server.-newtype State = State+data State = State { -- | The loaded program.- stateProgram :: Maybe LoadedProg+ stateProgram :: Maybe LoadedProg,+ -- | The stale data, stored to provide PositionMapping when requested.+ -- All files that have been opened have an entry.+ staleData :: M.Map FilePath StaleFile } -- | Initial state. emptyState :: State-emptyState = State Nothing+emptyState = State Nothing M.empty++-- | Get the contents of a stale (last successfully complied) file's contents.+getStaleContent :: State -> FilePath -> Maybe VirtualFile+getStaleContent state file_path = (Just . staleContent) =<< M.lookup file_path (staleData state)++-- | Get the PositionMapping for a file.+getStaleMapping :: State -> FilePath -> Maybe PositionMapping+getStaleMapping state file_path = staleMapping =<< M.lookup file_path (staleData state)++-- | Update the state with another pair of file_path and contents.+-- Could do a clean up becausae there is no need to store files that are not in lpFilePaths prog.+updateStaleContent :: FilePath -> VirtualFile -> LoadedProg -> State -> State+updateStaleContent file_path file_content loadedProg state =+ -- NOTE: insert will replace the old value if the key already exists.+ -- updateStaleContent is only called after a successful type-check,+ -- so the PositionsMapping should be Nothing here, it's calculated after failed type-check.+ State (Just loadedProg) (M.insert file_path (StaleFile file_content Nothing) (staleData state))++-- | Update the state with another pair of file_path and PositionMapping.+updateStaleMapping :: Maybe FilePath -> Maybe PositionMapping -> State -> State+updateStaleMapping (Just file_path) mapping state = do+ case M.lookup file_path (staleData state) of+ Nothing -> state -- Only happends when the file have never been successfully type-checked before.+ Just (StaleFile file_content _mapping) ->+ State (stateProgram state) (M.insert file_path (StaleFile file_content mapping) (staleData state))+updateStaleMapping _ _ state = state
src/Futhark/LSP/Tool.hs view
@@ -8,11 +8,19 @@ rangeFromSrcLoc, rangeFromLoc, posToUri,+ computeMapping, ) where +import qualified Data.Text as T import Futhark.Compiler.Program (lpImports)-import Futhark.LSP.State (State (..))+import Futhark.LSP.PositionMapping+ ( PositionMapping,+ mappingFromDiff,+ toCurrentLoc,+ toStalePos,+ )+import Futhark.LSP.State (State (..), getStaleContent, getStaleMapping) import Futhark.Util.Loc (Loc (Loc, NoLoc), Pos (Pos), SrcLoc, locOf) import Futhark.Util.Pretty (prettyText) import Language.Futhark.Prop (isBuiltinLoc)@@ -22,7 +30,9 @@ atPos, boundLoc, )+import Language.LSP.Server (LspM, getVirtualFile) import Language.LSP.Types+import Language.LSP.VFS (VirtualFile, virtualFileText, virtualFileVersion) -- | Retrieve hover info for the definition referenced at the given -- file at the given line and column number (the two 'Int's).@@ -53,11 +63,53 @@ else Just $ Location (filePathToUri file_path) (rangeFromLoc loc) findDefinitionRange _ _ _ _ = Nothing +-- | Query the AST for information at certain Pos. queryAtPos :: State -> Pos -> Maybe AtPos-queryAtPos state pos =- case stateProgram state of- Nothing -> Nothing- Just loaded_prog -> atPos (lpImports loaded_prog) pos+queryAtPos state pos = do+ let Pos path _ _ _ = pos+ mapping = getStaleMapping state path+ loaded_prog <- stateProgram state+ stale_pos <- toStalePos mapping pos+ query_result <- atPos (lpImports loaded_prog) stale_pos+ updateAtPos mapping query_result+ where+ -- Update the 'AtPos' with the current mapping.+ updateAtPos :: Maybe PositionMapping -> AtPos -> Maybe AtPos+ updateAtPos mapping (AtName qn (Just def) loc) = do+ let def_loc = boundLoc def+ Loc (Pos def_file _ _ _) _ = def_loc+ Pos current_file _ _ _ = pos+ current_loc <- toCurrentLoc mapping loc+ if def_file == current_file+ then do+ current_def_loc <- toCurrentLoc mapping def_loc+ Just $ AtName qn (Just (updateBoundLoc def current_def_loc)) current_loc+ else do+ -- Defined in another file, get the corresponding PositionMapping.+ let def_mapping = getStaleMapping state def_file+ current_def_loc <- toCurrentLoc def_mapping def_loc+ Just $ AtName qn (Just (updateBoundLoc def current_def_loc)) current_loc+ updateAtPos _ _ = Nothing++ updateBoundLoc :: BoundTo -> Loc -> BoundTo+ updateBoundLoc (BoundTerm t _loc) current_loc = BoundTerm t current_loc+ updateBoundLoc (BoundModule _loc) current_loc = BoundModule current_loc+ updateBoundLoc (BoundModuleType _loc) current_loc = BoundModuleType current_loc+ updateBoundLoc (BoundType _loc) current_loc = BoundType current_loc++-- | Entry point for computing PositionMapping.+computeMapping :: State -> Maybe FilePath -> LspM () (Maybe PositionMapping)+computeMapping state (Just file_path) = do+ virtual_file <- getVirtualFile $ toNormalizedUri $ filePathToUri file_path+ pure $ getMapping (getStaleContent state file_path) virtual_file+ where+ getMapping :: Maybe VirtualFile -> Maybe VirtualFile -> Maybe PositionMapping+ getMapping (Just stale_file) (Just current_file) =+ if virtualFileVersion stale_file == virtualFileVersion current_file+ then Nothing -- Happens when other files (e.g. dependencies) fail to type-check.+ else Just $ mappingFromDiff (T.lines $ virtualFileText stale_file) (T.lines $ virtualFileText current_file)+ getMapping _ _ = Nothing+computeMapping _ _ = pure Nothing -- | Convert a Futhark 'Pos' to an LSP 'Uri'. posToUri :: Pos -> Uri
src/Futhark/Optimise/BlkRegTiling.hs view
@@ -21,909 +21,1205 @@ import Control.Monad.Reader import qualified Data.List as L-import qualified Data.Map.Strict as M-import Data.Maybe-import qualified Data.Sequence as Seq-import Futhark.IR.GPU-import Futhark.MonadFreshNames-import Futhark.Optimise.TileLoops.Shared-import Futhark.Tools-import Futhark.Transform.Rename--mmBlkRegTiling :: Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))-mmBlkRegTiling (Let pat aux (Op (SegOp (SegMap SegThread {} seg_space ts old_kbody))))- | KernelBody () kstms [Returns ResultMaySimplify cs (Var res_nm)] <- old_kbody,- cs == mempty,- -- check kernel has one result of primitive type- [res_tp] <- ts,- primType res_tp,- -- build the variance table, that records, for- -- each variable name, the variables it depends on- initial_variance <- M.map mempty $ scopeOfSegSpace seg_space,- variance <- varianceInStms initial_variance kstms,- -- check that the code fits the pattern having:- -- some `code1`, followed by one Screma SOAC, followed by some `code2`- (code1, Just screma_stmt, code2) <- matchCodeStreamCode kstms,- Let pat_redomap _ (Op _) <- screma_stmt,- -- checks that the Screma SOAC is actually a redomap and normalizes it- Just (common_dim, arrs, (_, red_lam, red_nes, map_lam)) <- isTileableRedomap screma_stmt,- -- check that exactly two 1D arrays are streamed thorugh redomap,- -- and the result of redomap is one scalar- -- !!!I need to rearrange this whole thing!!! including inp_A and inp_B- length arrs == 2 && length red_nes == 1,- [map_t1t, map_t2t] <- map paramDec $ lambdaParams map_lam,- [red_t1, _] <- map paramDec $ lambdaParams red_lam,- primType map_t1t && primType map_t2t && primType red_t1,- map_t1 <- elemType map_t1t,- map_t2 <- elemType map_t2t,- -- checks that the input arrays to redomap are variant to- -- exactly one of the two innermost dimensions of the kernel- Just var_dims <- isInvarTo1of2InnerDims mempty seg_space variance arrs,- -- get the variables on which the first result of redomap depends on- [redomap_orig_res] <- patElems pat_redomap,- Just res_red_var <- M.lookup (patElemName redomap_orig_res) variance, -- variance of the reduce result-- -- we furthermore check that code1 is only formed by- -- 1. statements that slice some globally-declared arrays- -- to produce the input for the redomap, and- -- 2. potentially some statements on which the redomap- -- is independent; these are recorded in `code2''`- Just (code2'', tab_inv_stm) <-- foldl- (processIndirections (namesFromList arrs) res_red_var)- (Just (Seq.empty, M.empty))- code1,- -- identify load_A, load_B- tmp_stms <- mapMaybe (`M.lookup` tab_inv_stm) arrs,- length tmp_stms == length arrs,- -- [inp_A, inp_B] <- arrs,- zip_AB <- zip tmp_stms arrs,- [(load_A, inp_A), (load_B, inp_B)] <- if var_dims == [0, 1] then zip_AB else reverse zip_AB,- -- code1' <- stmsFromList $ stmsToList code1 \\ stmsToList code2'',- code2' <- code2'' <> code2,- -- we get the global-thread id for the two inner dimensions,- -- as we are probably going to use it in code generation- (gtid_x, width_B) : (gtid_y, height_A) : rem_outer_dims_rev <- reverse $ unSegSpace seg_space,- rem_outer_dims <- reverse rem_outer_dims_rev,- -- sanity check that the reduce part is not missing- not $ null red_nes = do- let red_ne : _ = red_nes- red_t <- subExpType red_ne-- ---- in this binder: host code and outer seggroup (ie. the new kernel) ----- (new_kernel, host_stms) <- runBuilder $ do- -- host code-- tk_name <- nameFromString . pretty <$> newVName "Tk"- tx_name <- nameFromString . pretty <$> newVName "Tx"- ty_name <- nameFromString . pretty <$> newVName "Ty"- rx_name <- nameFromString . pretty <$> newVName "Rx"- ry_name <- nameFromString . pretty <$> newVName "Ry"-- (ty, ry) <- getParTiles ("Ty", "Ry") (ty_name, ry_name) height_A- (tx, rx) <- getParTiles ("Tx", "Rx") (tx_name, rx_name) width_B- tk <- getSeqTile "Tk" tk_name common_dim ty tx-- tk_div_tx <- letSubExp "tk_div_tx" =<< ceilDiv tk tx- tk_div_ty <- letSubExp "tk_div_ty" =<< ceilDiv tk ty-- tx_rx <- letSubExp "TxRx" =<< toExp (pe64 tx * pe64 rx)- ty_ry <- letSubExp "TyRy" =<< toExp (pe64 ty * pe64 ry)-- a_loc_sz <-- letSubExp "a_loc_sz"- =<< toExp (pe64 ty * pe64 ry * pe64 tk)-- b_loc_sz <-- letSubExp "b_loc_sz"- =<< toExp (pe64 tk * pe64 tx * pe64 rx)-- gridDim_x <- letSubExp "gridDim_x" =<< ceilDiv width_B tx_rx- gridDim_y <- letSubExp "gridDim_y" =<< ceilDiv height_A ty_ry- let gridxy_pexp = pe64 gridDim_y * pe64 gridDim_x- let grid_pexp =- foldl (\x d -> pe64 d * x) gridxy_pexp $- map snd rem_outer_dims_rev- grid_size <- letSubExp "grid_size" =<< toExp grid_pexp- group_size <- letSubExp "group_size" =<< toExp (pe64 ty * pe64 tx)- let segthd_lvl = SegThread (Count grid_size) (Count group_size) (SegNoVirtFull (SegSeqDims []))-- gid_x <- newVName "gid_x"- gid_y <- newVName "gid_y"- gid_flat <- newVName "gid_flat"-- ---- in this binder: outer seggroup ----- (ret_seggroup, stms_seggroup) <- runBuilder $ do- iii <- letExp "iii" =<< toExp (le64 gid_y * pe64 ty_ry)- jjj <- letExp "jjj" =<< toExp (le64 gid_x * pe64 tx_rx)-- -- initialize register mem with neutral elements.- cssss_list <- segMap2D "cssss" segthd_lvl ResultPrivate (ty, tx) $ \_ -> do- css_init <- scratch "css_init" (elemType red_t) [ry, rx]- css <- forLoop ry [css_init] $ \i [css_merge] -> do- css' <- forLoop rx [css_merge] $ \j [css_merge'] -> do- css'' <- update' "css" css_merge' [i, j] red_ne- resultBodyM [Var css'']- resultBodyM [Var css']- pure [varRes css]- let [cssss] = cssss_list-- a_loc_init <- scratch "A_loc" map_t1 [a_loc_sz]- b_loc_init <- scratch "B_loc" map_t2 [b_loc_sz]-- let kkLoopBody tkind kk0 (thd_res_merge, a_loc_init', b_loc_init') epilogue = do- kk <- letExp "kk" =<< toExp (le64 kk0 * pe64 tk)- a_loc <- segScatter2D- "A_glb2loc"- a_loc_sz- a_loc_init'- segthd_lvl- [ry, tk_div_tx]- (ty, tx)- $ \[i0, k0] (thd_y, thd_x) -> do- k <- letExp "k" =<< toExp (le64 thd_x + le64 k0 * pe64 tx)- i <- letExp "i" =<< toExp (le64 thd_y + le64 i0 * pe64 ty)-- letBindNames [gtid_y] =<< toExp (le64 iii + le64 i)- a_col_idx <- letExp "A_col_idx" =<< toExp (le64 kk + le64 k)-- a_elem <-- letSubExp "A_elem"- =<< eIf- ( toExp $- le64 gtid_y .<. pe64 height_A- .&&. if epilogue- then le64 a_col_idx .<. pe64 common_dim- else true- )- ( do- addStm load_A- res <- index "A_elem" inp_A [a_col_idx]- resultBodyM [Var res]- )- (eBody [eBlank $ Prim map_t1])- a_loc_ind <-- letSubExp "a_loc_ind"- =<< eIf- (toExp $ le64 k .<. pe64 tk)- ( toExp (le64 k + le64 i * pe64 tk)- >>= letTupExp' "loc_fi"- >>= resultBodyM- )- (eBody [pure $ BasicOp $ SubExp $ intConst Int64 (-1)])- pure (a_elem, a_loc_ind)-- -- copy B from global to shared memory- b_loc <- segScatter2D- "B_glb2loc"- b_loc_sz- b_loc_init'- segthd_lvl- [tk_div_ty, rx]- (ty, tx)- $ \[k0, j0] (thd_y, thd_x) ->- do- k <- letExp "k" =<< toExp (le64 thd_y + le64 k0 * pe64 ty)- j <- letExp "j" =<< toExp (le64 thd_x + le64 j0 * pe64 tx)-- letBindNames [gtid_x] =<< toExp (le64 jjj + le64 j)- b_row_idx <- letExp "B_row_idx" =<< toExp (le64 kk + le64 k)-- b_elem <-- letSubExp "B_elem"- =<< eIf- ( toExp $- le64 gtid_x .<. pe64 width_B- .&&. if epilogue- then le64 b_row_idx .<. pe64 common_dim- else true- )- ( do- addStm load_B- res <- index "B_elem" inp_B [b_row_idx]- resultBodyM [Var res]- )- (eBody [eBlank $ Prim map_t2])-- b_loc_ind <-- letSubExp "b_loc_ind"- =<< eIf- (toExp $ le64 k .<. pe64 tk)- ( toExp (le64 j + le64 k * pe64 tx_rx)- >>= letTupExp' "loc_fi"- >>= resultBodyM- )- (eBody [pure $ BasicOp $ SubExp $ intConst Int64 (-1)])- pure (b_elem, b_loc_ind)-- -- inner loop updating this thread's accumulator (loop k in mmm_kernels).- thd_acc <- forLoop tk [thd_res_merge] $ \k [acc_merge] ->- resultBodyM =<< letTupExp' "foo"- =<< eIf- ( toExp $- if epilogue- then- le64 kk + le64 k- .<. pe64 common_dim- else true -- if in prologue, always compute redomap.- )- ( do- reg_mem <- segMap2D "reg_mem" segthd_lvl ResultPrivate (ty, tx) $- \(ltid_y, ltid_x) -> do- asss_init <- scratch "asss_init" map_t1 [ry]- bsss_init <- scratch "bsss_init" map_t2 [rx]-- asss <- forLoop ry [asss_init] $ \i [asss_merge] -> do- a_loc_ind <-- letExp "a_loc_ind"- =<< toExp- ( le64 k- + (le64 ltid_y * pe64 ry + le64 i) * pe64 tk- )-- asss <-- index "A_loc_elem" a_loc [a_loc_ind]- >>= update "asss" asss_merge [i]- resultBodyM [Var asss]-- bsss <- forLoop rx [bsss_init] $ \j [bsss_merge] -> do- b_loc_ind <-- letExp "b_loc_ind"- =<< toExp- ( le64 j- + le64 k * pe64 tx_rx- + le64 ltid_x * pe64 rx- )-- bsss <-- index "B_loc_elem" b_loc [b_loc_ind]- >>= update "bsss" bsss_merge [j]- resultBodyM [Var bsss]- pure $ varsRes [asss, bsss]-- let [asss, bsss] = reg_mem-- -- the actual redomap.- redomap_res <- segMap2D "redomap_res" segthd_lvl ResultPrivate (ty, tx) $- \(ltid_y, ltid_x) -> do- as <- index "as" asss [ltid_y, ltid_x]- bs <- index "bs" bsss [ltid_y, ltid_x]- css_init <- index "css_init" acc_merge [ltid_y, ltid_x]-- css <- forLoop ry [css_init] $ \i [css_merge] -> do- css <- forLoop rx [css_merge] $ \j [css_merge'] -> do- let cond =- toExp $ case tkind of- TileFull -> true- TilePartial ->- le64 iii + le64 i + pe64 ry * le64 ltid_y- .<. pe64 height_A- .&&. le64 jjj + le64 j + pe64 rx * le64 ltid_x- .<. pe64 width_B- resultBodyM =<< letTupExp' "foo"- =<< eIf- cond- ( do- a <- index "a" as [i]- b <- index "b" bs [j]- c <- index "c" css_merge' [i, j]-- map_res <- newVName "map_res"- map_lam' <- renameLambda map_lam- red_lam' <- renameLambda red_lam-- -- the inputs to map are supposed to be permutted with the- -- inverted permutation, so as to reach the original position;- -- it just so happens that the inverse of [a,b] is [b,a]- let map_inp_reg = if var_dims == [0, 1] then [a, b] else [b, a]-- addStms $- rebindLambda map_lam' map_inp_reg [map_res]- <> rebindLambda red_lam' [c, map_res] [c]-- css <- update "css" css_merge' [i, j] c-- resultBodyM [Var css]- )- (resultBodyM [Var css_merge'])- resultBodyM [Var css]- pure [varRes css]-- resultBodyM $ map Var redomap_res- )- (resultBodyM [Var acc_merge])- pure [thd_acc, a_loc, b_loc]-- -- build prologue.- full_tiles <-- letExp "full_tiles" $- BasicOp $ BinOp (SQuot Int64 Unsafe) common_dim tk- prologue_res_list <-- forLoop' (Var full_tiles) [cssss, a_loc_init, b_loc_init] $- \kk0 [thd_res_merge, a_loc_merge, b_loc_merge] -> do- process_full_tiles <-- kkLoopBody TileFull kk0 (thd_res_merge, a_loc_merge, b_loc_merge) False-- resultBodyM $ map Var process_full_tiles-- let prologue_res : a_loc_reuse : b_loc_reuse : _ = prologue_res_list-- -- build epilogue.- epilogue_res_list <- kkLoopBody TilePartial full_tiles (prologue_res, a_loc_reuse, b_loc_reuse) True-- let redomap_res : _ = epilogue_res_list-- -- support for non-empty code2'- -- segmap (ltid_y < ty, ltid_x < tx) {- -- for i < ry do- -- for j < rx do- -- res = if (iii+ltid_y*ry+i < height_A && jjj+ltid_x*rx+j < width_B)- -- then code2' else dummy- -- final_res[i,j] = res- epilogue_res <-- if patElemName redomap_orig_res == res_nm- then pure redomap_res -- epilogue_res_list- else do- rssss_list <- segMap2D "rssss" segthd_lvl ResultPrivate (ty, tx) $ \(ltid_y, ltid_x) -> do- rss_init <- scratch "rss_init" (elemType res_tp) [ry, rx]- css <- index "redomap_thd" redomap_res [ltid_y, ltid_x]- ii <- letExp "ii" =<< toExp (le64 iii + le64 ltid_y * pe64 ry)- jj <- letExp "jj" =<< toExp (le64 jjj + le64 ltid_x * pe64 rx)- rss <- forLoop ry [rss_init] $ \i [rss_merge] -> do- rss' <- forLoop rx [rss_merge] $ \j [rss_merge'] -> do- c <- index "redomap_elm" css [i, j]- cpy_stm <- mkLetNamesM [patElemName redomap_orig_res] $ BasicOp $ SubExp $ Var c- addStm cpy_stm- letBindNames [gtid_y] =<< toExp (le64 ii + le64 i)- letBindNames [gtid_x] =<< toExp (le64 jj + le64 j)-- res_el <-- letSubExp "res_elem"- =<< eIf- ( toExp $- le64 gtid_y .<. pe64 height_A- .&&. le64 gtid_x .<. pe64 width_B- )- ( do- addStms code2'- resultBodyM [Var res_nm]- )- (eBody [eBlank res_tp])- rss'' <- update' "rss" rss_merge' [i, j] res_el- resultBodyM [Var rss'']- resultBodyM [Var rss']- pure [varRes rss]- let rssss : _ = rssss_list- pure rssss-- let regtile_ret_dims =- map (\(_, sz) -> (sz, se1, se1)) rem_outer_dims- ++ [(height_A, ty, ry), (width_B, tx, rx)]-- -- Add dummy dimensions to tile to reflect the outer dimensions.- epilogue_res' <-- if null rem_outer_dims- then pure epilogue_res- else do- epilogue_t <- lookupType epilogue_res- let (block_dims, rest_dims) = splitAt 2 $ arrayDims epilogue_t- ones = map (const $ intConst Int64 1) rem_outer_dims- new_shape = concat [ones, block_dims, ones, rest_dims]- letExp "res_reshaped" $ BasicOp $ Reshape (map DimNew new_shape) epilogue_res-- pure [RegTileReturns mempty regtile_ret_dims epilogue_res']-- let level' = SegGroup (Count grid_size) (Count group_size) SegNoVirt- space' = SegSpace gid_flat (rem_outer_dims ++ [(gid_y, gridDim_y), (gid_x, gridDim_x)])- kbody' = KernelBody () stms_seggroup ret_seggroup- pure $ Let pat aux $ Op $ SegOp $ SegMap level' space' ts kbody'- pure $ Just (host_stms, new_kernel)-mmBlkRegTiling _ = pure Nothing--ceilDiv :: MonadBuilder m => SubExp -> SubExp -> m (Exp (Rep m))-ceilDiv x y = pure $ BasicOp $ BinOp (SDivUp Int64 Unsafe) x y--scratch :: MonadBuilder m => String -> PrimType -> [SubExp] -> m VName-scratch se_name t shape = letExp se_name $ BasicOp $ Scratch t shape---- index an array with indices given in outer_indices; any inner--- dims of arr not indexed by outer_indices are sliced entirely-index :: MonadBuilder m => String -> VName -> [VName] -> m VName-index se_desc arr outer_indices = do- arr_t <- lookupType arr- let shape = arrayShape arr_t- inner_dims = shapeDims $ stripDims (length outer_indices) shape- untouched d = DimSlice (intConst Int64 0) d (intConst Int64 1)- inner_slices = map untouched inner_dims- slice = Slice $ map (DimFix . Var) outer_indices ++ inner_slices- letExp se_desc $ BasicOp $ Index arr slice--update :: MonadBuilder m => String -> VName -> [VName] -> VName -> m VName-update se_desc arr indices new_elem = update' se_desc arr indices (Var new_elem)--update' :: MonadBuilder m => String -> VName -> [VName] -> SubExp -> m VName-update' se_desc arr indices new_elem =- letExp se_desc $ BasicOp $ Update Unsafe arr (Slice $ map (DimFix . Var) indices) new_elem--forLoop' ::- SubExp -> -- loop var- [VName] -> -- loop inits- ( VName ->- [VName] -> -- (loop var -> loop inits -> loop body)- Builder GPU (Body GPU)- ) ->- Builder GPU [VName]-forLoop' i_bound merge body = do- i <- newVName "i" -- could give this as arg to the function- let loop_form = ForLoop i Int64 i_bound []-- merge_ts <- mapM lookupType merge- loop_inits <- mapM (\merge_t -> newParam "merge" $ toDecl merge_t Unique) merge_ts-- loop_body <-- runBodyBuilder . inScopeOf loop_form . localScope (scopeOfFParams loop_inits) $- body i $ map paramName loop_inits-- letTupExp "loop" $- DoLoop (zip loop_inits $ map Var merge) loop_form loop_body--forLoop ::- SubExp ->- [VName] ->- (VName -> [VName] -> Builder GPU (Body GPU)) ->- Builder GPU VName-forLoop i_bound merge body = do- res_list <- forLoop' i_bound merge body- pure $ head res_list---- given a lambda "lam", a list "new_params" of new--- parameters which should be applied to the lambda,--- and a VName "res_name" which the lambda result should--- be bound to:--- creates Stms corresponding to binding of new_params,--- lambda body, and binding of lambda result to res_name.-rebindLambda ::- Lambda GPU ->- [VName] ->- [VName] ->- Stms GPU-rebindLambda lam new_params res_names =- stmsFromList- ( zipWith- (\ident new_param -> mkLet [ident] $ BasicOp $ SubExp $ Var new_param)- idents- new_params- )- <> bodyStms lam_body- <> stmsFromList res_cpy_stms- where- (lam_params, lam_body, lam_ret_type : _) =- (lambdaParams lam, lambdaBody lam, lambdaReturnType lam)- idents =- map- (\param -> Ident (paramName param) (paramDec param))- lam_params- res_cpy_stms =- zipWith- ( \res_name (SubExpRes cs lam_res) ->- certify cs $ mkLet [Ident res_name lam_ret_type] $ BasicOp $ SubExp lam_res- )- res_names- lam_ress- lam_ress = bodyResult lam_body---- | Tries to identify the following pattern:--- code followed by some Screma followed by more code.-matchCodeStreamCode ::- Stms GPU ->- (Stms GPU, Maybe (Stm GPU), Stms GPU)-matchCodeStreamCode kstms =- let (code1, screma, code2) =- foldl- ( \acc stmt ->- case (acc, stmt) of- ((cd1, Nothing, cd2), Let _ _ (Op (OtherOp Screma {}))) ->- (cd1, Just stmt, cd2)- ((cd1, Nothing, cd2), _) ->- (cd1 ++ [stmt], Nothing, cd2)- ((cd1, Just strm, cd2), _) ->- (cd1, Just strm, cd2 ++ [stmt])- )- ([], Nothing, [])- (stmsToList kstms)- in (stmsFromList code1, screma, stmsFromList code2)---- | Checks that all streamed arrays are variant to exacly one of--- the two innermost parallel dimensions, and conversely, for--- each of the two innermost parallel dimensions, there is at--- least one streamed array variant to it. The result is the--- number of the only variant parallel dimension for each array.-isInvarTo1of2InnerDims ::- Names ->- SegSpace ->- VarianceTable ->- [VName] ->- Maybe [Int]-isInvarTo1of2InnerDims branch_variant kspace variance arrs =- let inner_perm0 = map varToOnly1of2InnerDims arrs- inner_perm = catMaybes inner_perm0- ok1 = elem 0 inner_perm && elem 1 inner_perm- ok2 = length inner_perm0 == length inner_perm- in if ok1 && ok2 then Just inner_perm else Nothing- where- varToOnly1of2InnerDims :: VName -> Maybe Int- varToOnly1of2InnerDims arr = do- (j, _) : (i, _) : _ <- Just $ reverse $ unSegSpace kspace- let variant_to = M.findWithDefault mempty arr variance- branch_invariant =- not $ nameIn j branch_variant || nameIn i branch_variant- if not branch_invariant- then Nothing -- if i or j in branch_variant; return nothing- else- if nameIn i variant_to && not (nameIn j variant_to)- then Just 0- else- if nameIn j variant_to && not (nameIn i variant_to)- then Just 1- else Nothing--processIndirections ::- Names -> -- input arrays to redomap- Names -> -- variables on which the result of redomap depends on.- Maybe (Stms GPU, M.Map VName (Stm GPU)) ->- Stm GPU ->- Maybe (Stms GPU, M.Map VName (Stm GPU))-processIndirections arrs _ acc stm@(Let patt _ (BasicOp (Index _ _)))- | Just (ss, tab) <- acc,- [p] <- patElems patt,- p_nm <- patElemName p,- nameIn p_nm arrs =- Just (ss, M.insert p_nm stm tab)-processIndirections _ res_red_var acc stm'@(Let patt _ _)- | Just (ss, tab) <- acc,- ps <- patElems patt,- all (\p -> not (nameIn (patElemName p) res_red_var)) ps =- Just (ss Seq.|> stm', tab)- | otherwise = Nothing--se0 :: SubExp-se0 = intConst Int64 0--se1 :: SubExp-se1 = intConst Int64 1--se2 :: SubExp-se2 = intConst Int64 2--se4 :: SubExp-se4 = intConst Int64 4--se8 :: SubExp-se8 = intConst Int64 8--getParTiles :: (String, String) -> (Name, Name) -> SubExp -> Builder GPU (SubExp, SubExp)-getParTiles (t_str, r_str) (t_name, r_name) len_dim =- case len_dim of- Constant (IntValue (Int64Value 8)) ->- pure (se8, se1)- Constant (IntValue (Int64Value 16)) ->- pure (se8, se2)- Constant (IntValue (Int64Value 32)) ->- pure (se8, se4)- _ -> do- t <- letSubExp t_str $ Op $ SizeOp $ GetSize t_name SizeTile- r <- letSubExp r_str $ Op $ SizeOp $ GetSize r_name SizeRegTile- pure (t, r)--getSeqTile :: String -> Name -> SubExp -> SubExp -> SubExp -> Builder GPU SubExp-getSeqTile tk_str tk_name len_dim ty tx =- case (tx, ty) of- (Constant (IntValue (Int64Value v_x)), Constant (IntValue (Int64Value v_y))) ->- letSubExp tk_str . BasicOp . SubExp . constant $- case len_dim of- Constant (IntValue (Int64Value v_d)) -> min v_d $ min v_x v_y- _ -> min v_x v_y- _ ->- letSubExp tk_str $ Op $ SizeOp $ GetSize tk_name SizeTile---------------------------------------------------------------------------------------------------- 3D Tiling (RegTiling for the outermost dimension & Block tiling for the innermost two) ----------------------------------------------------------------------------------------------------maxRegTile :: Int64-maxRegTile = 30--mkRegTileSe :: Int64 -> SubExp-mkRegTileSe = constant--variantToDim :: VarianceTable -> VName -> VName -> Bool-variantToDim variance gid_outer nm =- gid_outer == nm || nameIn gid_outer (M.findWithDefault mempty nm variance)---- | Checks that all streamed arrays are variant to exacly one of--- the two innermost parallel dimensions, and conversely, for--- each of the two innermost parallel dimensions, there is at--- least one streamed array variant to it. The result is the--- number of the only variant parallel dimension for each array.-isInvarTo2of3InnerDims ::- Names ->- SegSpace ->- VarianceTable ->- [VName] ->- Maybe [Int]-isInvarTo2of3InnerDims branch_variant kspace variance arrs =- let inner_perm0 = map varToOnly1of3InnerDims arrs- inner_perm = catMaybes inner_perm0- ok1 = elem 0 inner_perm && elem 1 inner_perm && elem 2 inner_perm- ok2 = length inner_perm0 == length inner_perm- in if ok1 && ok2 then Just inner_perm else Nothing- where- varToOnly1of3InnerDims :: VName -> Maybe Int- varToOnly1of3InnerDims arr = do- (k, _) : (j, _) : (i, _) : _ <- Just $ reverse $ unSegSpace kspace- let variant_to = M.findWithDefault mempty arr variance- branch_invariant =- not $- nameIn k branch_variant- || nameIn j branch_variant- || nameIn i branch_variant- if not branch_invariant- then Nothing -- if i or j or k in branch_variant; return nothing- else- if nameIn i variant_to && not (nameIn j variant_to || nameIn k variant_to)- then Just 0- else- if nameIn j variant_to && not (nameIn i variant_to || nameIn k variant_to)- then Just 1- else- if nameIn k variant_to && not (nameIn i variant_to || nameIn j variant_to)- then Just 2- else Nothing---- | Expects a kernel statement as argument.--- CONDITIONS for 3D tiling optimization to fire are:--- 1. a) The kernel body can be broken into--- scalar-code-1 ++ [Redomap stmt] ++ scalar-code-2.--- b) The kernels has a per-thread result, and obviously--- the result is variant to the 3rd dimension--- (counted from innermost to outermost)--- 2. For the Redomap:--- a) the streamed arrays are one dimensional--- b) each of the array arguments of Redomap are variant--- to exactly one of the three innermost-parallel dimension--- of the kernel. This condition can be relaxed by interchanging--- kernel dimensions whenever possible.--- 3. For scalar-code-1:--- a) each of the statements is a slice that produces one of the--- streamed arrays------ mmBlkRegTiling :: Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))--- mmBlkRegTiling (Let pat aux (Op (SegOp (SegMap SegThread{} seg_space ts old_kbody))))-doRegTiling3D :: Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))-doRegTiling3D (Let pat aux (Op (SegOp old_kernel)))- | SegMap SegThread {} space kertp (KernelBody () kstms kres) <- old_kernel,- -- build the variance table, that records, for- -- each variable name, the variables it depends on- initial_variance <- M.map mempty $ scopeOfSegSpace space,- variance <- varianceInStms initial_variance kstms,- -- we get the global-thread id for the two inner dimensions,- -- as we are probably going to use it in code generation- (gtid_x, d_Kx) : (gtid_y, d_Ky) : (gtid_z, d_M) : rem_outer_dims_rev <- reverse $ unSegSpace space,- rem_outer_dims <- reverse rem_outer_dims_rev,- -- check that the code fits the pattern having:- -- some `code1`, followed by one Screma SOAC, followed by some `code2`- (code1, Just screma_stmt, code2) <- matchCodeStreamCode kstms,- Let pat_redomap _ (Op _) <- screma_stmt,- -- checks that the Screma SOAC is actually a redomap and normalize it- Just (common_dim, inp_soac_arrs, (_, red_lam, red_nes, map_lam)) <- isTileableRedomap screma_stmt,- not (null red_nes),- -- assuming we have a budget of maxRegTile registers, we distribute- -- that budget across the result of redomap and the kernel result- num_res <- max (length red_nes) (length kres),- reg_tile <- maxRegTile `quot` fromIntegral num_res,- reg_tile_se <- mkRegTileSe reg_tile,- -- check that the element-type of the map and reduce are scalars:- all (primType . paramDec) $ lambdaParams map_lam,- red_res_tps <- map paramDec $ take (length red_nes) $ lambdaParams red_lam,- all primType red_res_tps,- -- checks that the input arrays to redomap are variant to- -- exactly one of the two innermost dimensions of the kernel- Just _ <- isInvarTo2of3InnerDims mempty space variance inp_soac_arrs,- -- get the free variables on which the result of redomap depends on- redomap_orig_res <- patElems pat_redomap,- res_red_var <- -- variance of the reduce result- mconcat $ mapMaybe ((`M.lookup` variance) . patElemName) redomap_orig_res,- mempty /= res_red_var,- -- we furthermore check that code1 is only formed by- -- 1. statements that slice some globally-declared arrays- -- to produce the input for the redomap, and- -- 2. potentially some statements on which the redomap- -- is independent; these are recorded in `code2''`- Just (code2'', arr_tab0) <-- foldl- (processIndirections (namesFromList inp_soac_arrs) res_red_var)- (Just (Seq.empty, M.empty))- code1,- -- check that code1 contains exacly one slice for each of the input array to redomap- tmp_stms <- mapMaybe (`M.lookup` arr_tab0) inp_soac_arrs,- length tmp_stms == length inp_soac_arrs,- -- code1' <- stmsFromList $ stmsToList code1 \\ stmsToList code2'',- code2' <- code2'' <> code2,- -- we assume the kernel results are variant to the thrid-outer parallel dimension- -- (for sanity sake, they should be)- ker_res_nms <- mapMaybe getResNm kres,- length ker_res_nms == length kres,- all primType kertp,- all (variantToDim variance gtid_z) ker_res_nms = do- -- HERE STARTS THE IMPLEMENTATION:- (new_kernel, host_stms) <- runBuilder $ do- -- host code- -- process the z-variant arrays that need transposition;- -- these "manifest" statements should come before the kernel- (tab_inn, tab_out) <-- foldM- (insertTranspose variance (gtid_z, d_M))- (M.empty, M.empty)- $ M.toList arr_tab0-- tx_name <- nameFromString . pretty <$> newVName "Tx"- ty_name <- nameFromString . pretty <$> newVName "Ty"-- tx0 <- letSubExp "Tx" $ Op $ SizeOp $ GetSize tx_name SizeTile- ty0 <- letSubExp "Ty" $ Op $ SizeOp $ GetSize ty_name SizeTile- ty <- limitTile "Ty" ty0 d_Ky- tx <- limitTile "Tx" tx0 d_Kx- let rz = reg_tile_se-- gridDim_x <- letSubExp "gridDim_x" =<< ceilDiv d_Kx tx- gridDim_y <- letSubExp "gridDim_y" =<< ceilDiv d_Ky ty- gridDim_z <- letSubExp "gridDim_z" =<< ceilDiv d_M rz- let gridxyz_pexp = pe64 gridDim_z * pe64 gridDim_y * pe64 gridDim_x- let grid_pexp = product $ gridxyz_pexp : map (pe64 . snd) rem_outer_dims_rev- grid_size <- letSubExp "grid_size_tile3d" =<< toExp grid_pexp- group_size <- letSubExp "group_size_tile3d" =<< toExp (pe64 ty * pe64 tx)- let segthd_lvl = SegThread (Count grid_size) (Count group_size) (SegNoVirtFull (SegSeqDims []))-- count_shmem <- letSubExp "count_shmem" =<< ceilDiv rz group_size-- gid_x <- newVName "gid_x"- gid_y <- newVName "gid_y"- gid_z <- newVName "gid_z"- gid_flat <- newVName "gid_flat"-- ---- in this binder: outer seggroup ----- (ret_seggroup, stms_seggroup) <- runBuilder $ do- ii <- letExp "ii" =<< toExp (le64 gid_z * pe64 rz)- jj1 <- letExp "jj1" =<< toExp (le64 gid_y * pe64 ty)- jj2 <- letExp "jj2" =<< toExp (le64 gid_x * pe64 tx)-- -- initialize the register arrays corresponding to the result of redomap;- reg_arr_nms <- segMap2D "res" segthd_lvl ResultPrivate (ty, tx) $ \_ ->- forM (zip red_nes red_res_tps) $ \(red_ne, red_t) -> do- css_init <- scratch "res_init" (elemType red_t) [rz]- css <- forLoop rz [css_init] $ \i [css_merge] -> do- css' <- update' "css" css_merge [i] red_ne- resultBodyM [Var css']- pure $ varRes css-- -- scratch the shared-memory arrays corresponding to the arrays that are- -- input to the redomap and are invariant to the outermost parallel dimension.- loc_arr_nms <- forM (M.toList tab_out) $ \(nm, (ptp, _)) ->- scratch (baseString nm ++ "_loc") ptp [rz]-- prologue_res_list <-- forLoop' common_dim (reg_arr_nms ++ loc_arr_nms) $- \q var_nms -> do- let reg_arr_merge_nms = take (length red_nes) var_nms- let loc_arr_merge_nms = drop (length red_nes) var_nms-- -- collective copy from global to shared memory- loc_arr_nms' <-- forLoop' count_shmem loc_arr_merge_nms $ \tt loc_arr_merge2_nms -> do- loc_arr_merge2_nms' <-- forM (zip loc_arr_merge2_nms (M.toList tab_out)) $ \(loc_Y_nm, (glb_Y_nm, (ptp_Y, load_Y))) -> do- ltid_flat <- newVName "ltid_flat"- ltid <- newVName "ltid"- let segspace = SegSpace ltid_flat [(ltid, group_size)]- ((res_v, res_i), stms) <- runBuilder $ do- offs <- letExp "offs" =<< toExp (pe64 group_size * le64 tt)- loc_ind <- letExp "loc_ind" =<< toExp (le64 ltid + le64 offs)- letBindNames [gtid_z] =<< toExp (le64 ii + le64 loc_ind)- let glb_ind = gtid_z- y_elm <-- letSubExp "y_elem"- =<< eIf- (toExp $ le64 glb_ind .<. pe64 d_M)- ( do- addStm load_Y- res <- index "Y_elem" glb_Y_nm [q]- resultBodyM [Var res]- )- (eBody [eBlank $ Prim ptp_Y])- y_ind <-- letSubExp "y_loc_ind"- =<< eIf- (toExp $ le64 loc_ind .<. pe64 rz)- (toExp loc_ind >>= letTupExp' "loc_fi" >>= resultBodyM)- (eBody [pure $ BasicOp $ SubExp $ intConst Int64 (-1)])- -- y_tp <- subExpType y_elm- pure (y_elm, y_ind)-- let ret = WriteReturns mempty (Shape [rz]) loc_Y_nm [(Slice [DimFix res_i], res_v)]- let body = KernelBody () stms [ret]-- res_nms <-- letTupExp "Y_glb2loc" <=< renameExp $- Op $ SegOp $ SegMap segthd_lvl segspace [Prim ptp_Y] body- let res_nm : _ = res_nms- pure res_nm- resultBodyM $ map Var loc_arr_merge2_nms'-- redomap_res <-- segMap2D "redomap_res" segthd_lvl ResultPrivate (ty, tx) $- \(ltid_y, ltid_x) -> do- letBindNames [gtid_y] =<< toExp (le64 jj1 + le64 ltid_y)- letBindNames [gtid_x] =<< toExp (le64 jj2 + le64 ltid_x)- reg_arr_merge_nms_slc <- forM reg_arr_merge_nms $ \reg_arr_nm ->- index "res_reg_slc" reg_arr_nm [ltid_y, ltid_x]- fmap subExpsRes . letTupExp' "redomap_guarded"- =<< eIf- (toExp $ le64 gtid_y .<. pe64 d_Ky .&&. le64 gtid_x .<. pe64 d_Kx)- ( do- inp_scals_invar_outer <-- forM (M.toList tab_inn) $ \(inp_arr_nm, load_stm) -> do- addStm load_stm- index (baseString inp_arr_nm) inp_arr_nm [q]- -- build the loop of count R whose body is semantically the redomap code- reg_arr_merge_nms' <-- forLoop' rz reg_arr_merge_nms_slc $ \i reg_arr_mm_nms -> do- letBindNames [gtid_z] =<< toExp (le64 ii + le64 i)- resultBodyM =<< letTupExp' "redomap_lam"- =<< eIf- (toExp $ le64 gtid_z .<. pe64 d_M)- ( do- -- read from shared memory- ys <- forM loc_arr_nms' $ \loc_arr_nm ->- index "inp_reg_var2z" loc_arr_nm [i]- cs <- forM reg_arr_mm_nms $ \reg_arr_nm ->- index "res_reg_var2z" reg_arr_nm [i]- -- here we need to put in order the scalar inputs to map:- let tab_scals =- M.fromList $- zip (map fst $ M.toList tab_out) ys- ++ zip (map fst $ M.toList tab_inn) inp_scals_invar_outer- map_inp_scals <- forM inp_soac_arrs $ \arr_nm ->- case M.lookup arr_nm tab_scals of- Nothing -> error "Impossible case reached in tiling3D\n"- Just nm -> pure nm- map_res_scals <- forM (lambdaReturnType map_lam) $ \_ -> newVName "map_res"- map_lam' <- renameLambda map_lam- red_lam' <- renameLambda red_lam- addStms $- rebindLambda map_lam' map_inp_scals map_res_scals- <> rebindLambda red_lam' (cs ++ map_res_scals) cs- css <- forM (zip reg_arr_mm_nms cs) $ \(reg_arr_nm, c) ->- update (baseString reg_arr_nm) reg_arr_nm [i] c- resultBodyM $ map Var css- )- (resultBodyM $ map Var reg_arr_mm_nms)- resultBodyM $ map Var reg_arr_merge_nms'- )- (resultBodyM $ map Var reg_arr_merge_nms_slc)- resultBodyM $ map Var $ redomap_res ++ loc_arr_nms'-- -- support for non-empty code2'- -- segmap (ltid_y < ty, ltid_x < tx) {- -- for i < rz do- -- res = if (ii+i < d_M && jj1+ltid_y < d_Ky && jj2 + ltid_x < d_Kx)- -- then code2' else dummy- -- final_res[i] = res- let redomap_res = take (length red_nes) prologue_res_list- epilogue_res <-- if length redomap_orig_res == length ker_res_nms- && ker_res_nms == map patElemName redomap_orig_res- then -- all (\ (a,b) -> patElemName a == b ) $ zip redomap_orig_res ker_res_nms- segMap3D "rssss" segthd_lvl ResultPrivate (se1, ty, tx) $ \(_ltid_z, ltid_y, ltid_x) ->+import Data.List.NonEmpty (NonEmpty (..))+import qualified Data.Map.Strict as M+import Data.Maybe+import qualified Data.Sequence as Seq+import Futhark.IR.GPU+import qualified Futhark.IR.Mem.IxFun as IxFun+import Futhark.MonadFreshNames+import Futhark.Optimise.TileLoops.Shared+import Futhark.Tools+import Futhark.Transform.Rename+import Futhark.Transform.Substitute++se0 :: SubExp+se0 = intConst Int64 0++se1 :: SubExp+se1 = intConst Int64 1++se2 :: SubExp+se2 = intConst Int64 2++se4 :: SubExp+se4 = intConst Int64 4++se8 :: SubExp+se8 = intConst Int64 8++scratch :: MonadBuilder m => String -> PrimType -> [SubExp] -> m VName+scratch se_name t shape = letExp se_name $ BasicOp $ Scratch t shape++-- | Main helper function for Register-and-Block Tiling+kkLoopBody ::+ Env ->+ ( (SubExp, SubExp, SubExp, SubExp, SubExp, SubExp, SubExp, SubExp),+ SegLevel,+ [Int],+ (VName, SubExp, VName, SubExp, SubExp),+ (SubExp, SubExp),+ (VName, VName),+ (Stm GPU, VName, PrimType, Stm GPU, VName, PrimType),+ (Lambda GPU, Lambda GPU)+ ) ->+ VName ->+ (VName, VName, VName) ->+ Bool ->+ Builder GPU [VName]+kkLoopBody+ env+ ( (rx, ry, tx, ty, tk, tk_div_tx, _tk_div_ty, tx_rx),+ segthd_lvl,+ var_dims,+ (gtid_x, width_B, gtid_y, height_A, common_dim),+ (a_loc_sz, b_loc_sz),+ (iii, jjj),+ (load_A, inp_A, pt_A, load_B, inp_B, pt_B),+ (map_lam, red_lam)+ )+ kk0+ (thd_res_merge, a_loc_init', b_loc_init')+ epilogue = do+ let (map_t1, map_t2) = (pt_A, pt_B)+ kk <- letExp "kk" =<< toExp (le64 kk0 * pe64 tk)+ -- copy A to local memory+ (a_loc, aCopyLoc2Reg) <-+ copyGlb2ShMem kk (gtid_y, iii, map_t1, height_A, inp_A, load_A, a_loc_sz, a_loc_init')++ -- copy B from global to shared memory+ (b_loc, bCopyLoc2Reg) <-+ copyGlb2ShMem kk (gtid_x, jjj, map_t2, width_B, inp_B, load_B, b_loc_sz, b_loc_init')++ -- inner loop updating this thread's accumulator (loop k in mmm_kernels).+ thd_acc <- forLoop tk [thd_res_merge] $ \k [acc_merge] ->+ resultBodyM =<< letTupExp' "foo"+ =<< eIf+ ( toExp $+ if epilogue+ then le64 kk + le64 k .<. pe64 common_dim+ else true -- if in prologue, always compute redomap.+ )+ ( do+ reg_mem <- segMap2D "reg_mem" segthd_lvl ResultPrivate (ty, tx) $+ \(ltid_y, ltid_x) -> do+ -- copy A from local memory to registers+ asss <- aCopyLoc2Reg k ltid_y+ -- copy B from local memory to registers+ bsss <- bCopyLoc2Reg k ltid_x+ pure $ varsRes [asss, bsss]+ let [asss, bsss] = reg_mem+ mkRedomapOneTileBody acc_merge asss bsss True+ )+ (resultBodyM [Var acc_merge])+ pure [thd_acc, a_loc, b_loc]+ where+ mk_ik is_coal (thd_y, thd_x) (i0, k0)+ | is_coal = do+ -- not-transposed case (i.e., already coalesced)+ let (t_par, t_seq) = (tx, tk)+ k <- letExp "k" =<< toExp (le64 thd_x + le64 k0 * pe64 t_par)+ i <- letExp "i" =<< toExp (le64 thd_y + le64 i0 * pe64 t_par)+ -- we have padded to minimize bank conflicts,+ -- hence the length of inner dim is (t_seq + 1)+ let e = le64 k + le64 i * (pe64 t_seq + pe64 se1)+ pure (i, k, e)+ mk_ik _ (thd_y, thd_x) (i0, k0) = do+ -- matrix is transposed case (i.e., uncoalesced):+ let (t_par, tr_par) = (tx, tx_rx)+ k <- letExp "k" =<< toExp (le64 thd_y + le64 k0 * pe64 t_par)+ i <- letExp "i" =<< toExp (le64 thd_x + le64 i0 * pe64 t_par)+ -- we have padded to minimize bank conflicts,+ -- hence the length of inner dim is (tr_par + 1)+ let e = le64 i + le64 k * (pe64 tr_par + pe64 se1)+ pure (i, k, e)+ isInnerCoal :: Env -> VName -> Stm GPU -> Bool+ isInnerCoal (_, ixfn_env) slc_X (Let pat _ (BasicOp (Index x _)))+ | [slc_X'] <- patNames pat,+ slc_X == slc_X',+ Nothing <- M.lookup x ixfn_env =+ True -- if not in the table, we assume not-transposed!+ isInnerCoal (_, ixfn_env) slc_X (Let pat _ (BasicOp (Index x _)))+ | [slc_X'] <- patNames pat,+ slc_X == slc_X',+ Just ixf_fn <- M.lookup x ixfn_env,+ (IxFun.IxFun (lmad :| []) _ _) <- ixf_fn =+ let lmad_dims = IxFun.lmadDims lmad+ q = length lmad_dims+ last_perm = IxFun.ldPerm $ last lmad_dims+ stride = IxFun.ldStride $ last lmad_dims+ res = last_perm == q - 1 && (stride == pe64 (intConst Int64 1))+ in res+ isInnerCoal _ _ _ = error "TileLoops/Shared.hs: not an error, but I would like to know why!"+ --+ mkRedomapOneTileBody acc_merge asss bsss fits_ij = do+ -- the actual redomap.+ redomap_res <- segMap2D "redomap_res" segthd_lvl ResultPrivate (ty, tx) $+ \(ltid_y, ltid_x) -> do+ as <- index "as" asss [ltid_y, ltid_x]+ bs <- index "bs" bsss [ltid_y, ltid_x]+ css_init <- index "css_init" acc_merge [ltid_y, ltid_x]++ css <- forLoop ry [css_init] $ \i [css_merge] -> do+ css <- forLoop rx [css_merge] $ \j [css_merge'] ->+ resultBodyM =<< letTupExp' "foo"+ =<< eIf+ ( toExp $+ if fits_ij+ then true+ else -- this condition is never needed because+ -- if i and j are out of range than css[i,j]+ -- is garbage anyways and should not be written.+ -- so fits_ij should be always true!!!++ le64 iii + le64 i + pe64 ry * le64 ltid_y+ .<. pe64 height_A+ .&&. le64 jjj + le64 j + pe64 rx * le64 ltid_x+ .<. pe64 width_B+ )+ ( do+ a <- index "a" as [i]+ b <- index "b" bs [j]+ c <- index "c" css_merge' [i, j]++ map_lam' <- renameLambda map_lam+ red_lam' <- renameLambda red_lam++ -- the inputs to map are supposed to be permutted with the+ -- inverted permutation, so as to reach the original position;+ -- it just so happens that the inverse of [a,b] is [b,a]+ let map_inp_reg = if var_dims == [0, 1] then [a, b] else [b, a]++ map_res <- eLambda map_lam' (map (eSubExp . Var) map_inp_reg)+ ~[red_res] <- eLambda red_lam' (map eSubExp $ Var c : map resSubExp map_res)+ css <- update "css" css_merge' [i, j] (resSubExp red_res)++ resultBodyM [Var css]+ )+ (resultBodyM [Var css_merge'])+ resultBodyM [Var css]+ pure [varRes css]+ resultBodyM $ map Var redomap_res+ --+ copyGlb2ShMem ::+ VName ->+ (VName, VName, PrimType, SubExp, VName, Stm GPU, SubExp, VName) ->+ Builder GPU (VName, VName -> VName -> Builder GPU VName)+ copyGlb2ShMem kk (gtid, ii, ptp_X_el, parlen_X, inp_X, load_X, loc_sz_X, x_loc_init') = do+ let (t_par, r_par, tseq_div_tpar) = (tx, rx, tk_div_tx)+ is_inner_coal = isInnerCoal env inp_X load_X+ str_A = baseString inp_X+ x_loc <-+ segScatter2D (str_A ++ "_glb2loc") loc_sz_X x_loc_init' segthd_lvl [r_par, tseq_div_tpar] (t_par, t_par) $+ scatterFun is_inner_coal++ pure (x_loc, copyLoc2Reg is_inner_coal str_A x_loc)+ where+ copyLoc2Reg ::+ Bool ->+ String ->+ VName ->+ VName ->+ VName ->+ Builder GPU VName+ copyLoc2Reg is_inner_coal str_A x_loc k ltid_yx = do+ let (r_par, t_seq, tr_par) = (rx, tk, tx_rx)+ xsss_init <- scratch (str_A ++ "_init_regs") ptp_X_el [r_par]+ forLoop r_par [xsss_init] $ \ij [xsss_merge] -> do+ x_loc_ind <-+ letExp (str_A ++ "_loc_ind")+ =<< toExp+ ( if is_inner_coal+ then le64 k + (le64 ltid_yx * pe64 r_par + le64 ij) * (pe64 t_seq + pe64 se1)+ else le64 ij + le64 ltid_yx * pe64 r_par + le64 k * (pe64 tr_par + pe64 se1)+ )+ xsss <-+ update (str_A ++ "_regs") xsss_merge [ij] . Var+ =<< index (str_A ++ "_loc_elem") x_loc [x_loc_ind]+ resultBodyM [Var xsss]+ --+ scatterFun ::+ Bool ->+ [VName] ->+ (VName, VName) ->+ Builder GPU (SubExp, SubExp)+ scatterFun is_inner_coal [i0, k0] (thd_y, thd_x) = do+ let str_A = baseString inp_X+ t_seq = tk+ (i, k, epx_loc_fi) <- mk_ik is_inner_coal (thd_y, thd_x) (i0, k0)+ letBindNames [gtid] =<< toExp (le64 ii + le64 i)+ a_seqdim_idx <- letExp (str_A ++ "_seqdim_idx") =<< toExp (le64 kk + le64 k)++ a_elem <-+ letSubExp (str_A ++ "_elem")+ =<< eIf+ ( toExp $+ le64 gtid .<. pe64 parlen_X+ .&&. if epilogue+ then le64 a_seqdim_idx .<. pe64 common_dim+ else true+ )+ ( do+ addStm load_X+ res <- index "A_elem" inp_X [a_seqdim_idx]+ resultBodyM [Var res]+ )+ (eBody [eBlank $ Prim ptp_X_el])++ a_loc_ind <-+ letSubExp (str_A ++ "_loc_ind")+ =<< eIf+ (toExp $ le64 k .<. pe64 t_seq)+ (eBody [toExp epx_loc_fi])+ (eBody [eSubExp $ intConst Int64 (-1)])+ pure (a_elem, a_loc_ind)+ scatterFun _ _ _ = do+ error "Function scatterFun in Shared.hs: 2nd arg should be an array with 2 elements!"++-- ToDo: we need tx == ty (named t_par), and rx == ry (named r_par)+-- in order to handle all the cases without transpositions.+-- additionally, of course, we need that tk is a multiple of t_par.+mmBlkRegTiling :: Env -> Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))+mmBlkRegTiling env stm = do+ res <- mmBlkRegTilingAcc env stm+ case res of+ Nothing -> mmBlkRegTilingNrm env stm+ _ -> pure res++mmBlkRegTilingAcc :: Env -> Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))+mmBlkRegTilingAcc env (Let pat aux (Op (SegOp (SegMap SegThread {} seg_space ts old_kbody))))+ | KernelBody () kstms [Returns ResultMaySimplify cs (Var res_nm)] <- old_kbody,+ cs == mempty,+ -- check kernel has one result of primitive type+ [res_tp] <- ts,+ isAcc res_tp,+ -- we get the global-thread id for the two inner dimensions,+ -- as we are probably going to use it in code generation+ (gtid_x, width_B) : (gtid_y, height_A) : rem_outer_dims_rev <-+ reverse $ unSegSpace seg_space,+ rem_outer_dims <- reverse rem_outer_dims_rev,+ Just+ ( code2',+ (load_A, inp_A, map_t1, load_B, inp_B, map_t2),+ common_dim,+ var_dims,+ (map_lam, red_lam, red_ne, redomap_orig_res, red_t)+ ) <-+ matchesBlkRegTile seg_space kstms,+ checkAccumulatesRedomapRes res_nm code2' redomap_orig_res = do+ -- Here we start the implementation --+ ---- in this binder: host code and outer seggroup (ie. the new kernel) ----+ (new_kernel, host_stms) <- runBuilder $ do+ -- host code+ (rx, ry, tx, ty, tk, tk_div_tx, tk_div_ty, tx_rx, ty_ry, a_loc_sz, b_loc_sz) <-+ mkTileMemSizes height_A width_B common_dim++ rk <- letSubExp "rk" $ BasicOp $ SubExp $ intConst Int64 8 -- 16 and 8 seem good values+ tk_rk <- letSubExp "tk_rk" =<< toExp (pe64 tk * pe64 rk)++ gridDim_t <- letSubExp "gridDim_t" =<< ceilDiv common_dim tk_rk+ gridDim_y <- letSubExp "gridDim_y" =<< ceilDiv height_A ty_ry+ gridDim_x <- letSubExp "gridDim_x" =<< ceilDiv width_B tx_rx++ let gridxyt_pexp = pe64 gridDim_y * pe64 gridDim_x * pe64 gridDim_t+ grid_pexp =+ foldl (\x d -> pe64 d * x) gridxyt_pexp $+ map snd rem_outer_dims_rev++ (grid_size, group_size, segthd_lvl) <- mkNewSegthdLvl tx ty grid_pexp+ (gid_x, gid_y, gid_flat) <- mkGidsXYF+ gid_t <- newVName "gid_t"++ ---- in this binder: outer seggroup ----+ (ret_seggroup, stms_seggroup) <- runBuilder $ do+ iii <- letExp "iii" =<< toExp (le64 gid_y * pe64 ty_ry)+ jjj <- letExp "jjj" =<< toExp (le64 gid_x * pe64 tx_rx)+ ttt <- letExp "ttt" =<< toExp (le64 gid_t * pe64 tk_rk)++ -- initialize register mem with neutral elements and create shmem+ (cssss, a_loc_init, b_loc_init) <-+ initRegShmem+ (rx, tx, ry, ty, a_loc_sz, b_loc_sz)+ (map_t1, map_t2, red_t)+ segthd_lvl+ red_ne++ -- build prologue.+ elems_on_t <- letSubExp "elems_on_t" =<< toExp (pe64 common_dim - le64 ttt)+ tiles_on_t <- letSubExp "tiles_on_t" $ BasicOp $ BinOp (SQuot Int64 Unsafe) elems_on_t tk+ full_tiles <- letExp "full_tiles" $ BasicOp $ BinOp (SMin Int64) rk tiles_on_t++ let ct_arg =+ ( (rx, ry, tx, ty, tk, tk_div_tx, tk_div_ty, tx_rx),+ segthd_lvl,+ var_dims,+ (gtid_x, width_B, gtid_y, height_A, common_dim),+ (a_loc_sz, b_loc_sz),+ (iii, jjj),+ (load_A, inp_A, map_t1, load_B, inp_B, map_t2),+ (map_lam, red_lam)+ )++ prologue_res_list <-+ forLoop' (Var full_tiles) [cssss, a_loc_init, b_loc_init] $+ \kk0 [thd_res_merge, a_loc_merge, b_loc_merge] -> do+ off_t <- letExp "off_t" =<< toExp (pe64 rk * le64 gid_t + le64 kk0)+ process_full_tiles <-+ kkLoopBody env ct_arg off_t (thd_res_merge, a_loc_merge, b_loc_merge) False++ resultBodyM $ map Var process_full_tiles++ let prologue_res : a_loc_reuse : b_loc_reuse : _ = prologue_res_list++ redomap_res_lst <-+ letTupExp "redomap_res_if"+ =<< eIf+ ( toExp $+ le64 full_tiles .==. pe64 rk+ .||. pe64 common_dim .==. (pe64 tk * le64 full_tiles + le64 ttt)+ )+ (resultBodyM $ map Var prologue_res_list)+ ( do+ off_t <- letExp "off_t" =<< toExp (pe64 rk * le64 gid_t + le64 full_tiles)+ process_sprs_tile <-+ kkLoopBody env ct_arg off_t (prologue_res, a_loc_reuse, b_loc_reuse) True++ resultBodyM $ map Var process_sprs_tile+ )+ let redomap_res : _ = redomap_res_lst++ -- support for non-empty code2'+ -- segmap (ltid_y < ty, ltid_x < tx) {+ -- for i < ry do+ -- for j < rx do+ -- res = if (iii+ltid_y*ry+i < height_A && jjj+ltid_x*rx+j < width_B)+ -- then code2' else dummy+ -- final_res[i,j] = res+ mkEpilogueAccRes+ segthd_lvl+ (redomap_orig_res, redomap_res)+ (res_nm, res_tp)+ (ty, tx, ry, rx)+ (iii, jjj)+ (gtid_y, gtid_x)+ (height_A, width_B, rem_outer_dims)+ code2'++ let level' = SegGroup (Count grid_size) (Count group_size) SegNoVirt+ space' = SegSpace gid_flat (rem_outer_dims ++ [(gid_t, gridDim_t), (gid_y, gridDim_y), (gid_x, gridDim_x)])+ kbody' = KernelBody () stms_seggroup ret_seggroup+ pure $ Let pat aux $ Op $ SegOp $ SegMap level' space' ts kbody'+ pure $ Just (host_stms, new_kernel)+ where+ sameAccType acc_sglton (Acc sglton _ _ _) =+ acc_sglton == sglton+ sameAccType _ _ = False+ getAccumFV (Acc singleton _shp [_eltp] _) = do+ let fvs = namesToList $ freeIn old_kbody -- code+ tps <- localScope (scopeOfSegSpace seg_space) $ do+ mapM lookupType fvs+ let (acc_0s, _) = unzip $ filter (sameAccType singleton . snd) $ zip fvs tps+ case acc_0s of+ [acc_0] -> pure acc_0+ _ -> error "Impossible case reached when treating accumulators!"+ getAccumFV tp = error ("Should be an accumulator type at this point, given: " ++ pretty tp)+ --+ -- checks that the redomap result is used directly as the accumulated value,+ -- in which case it is safe to parallelize the innermost dimension (of tile tk)+ checkAccumulatesRedomapRes res_nm acc_code redomap_orig_res = do+ foldl getAccumStm False $ reverse $ stmsToList acc_code+ where+ getAccumStm True _ = True+ getAccumStm False (Let (Pat [pat_el]) _aux (BasicOp (UpdateAcc _acc_nm _ind vals)))+ | [v] <- vals,+ patElemName pat_el == res_nm =+ v == Var redomap_orig_res+ getAccumStm False _ = False+ --+ -- epilogue for accumulator result type+ mkEpilogueAccRes+ segthd_lvl+ (redomap_orig_res, redomap_res)+ (res_nm, res_tp)+ (ty, tx, ry, rx)+ (iii, jjj)+ (gtid_y, gtid_x)+ (height_A, width_B, _rem_outer_dims)+ code2' = do+ rss_init <- getAccumFV res_tp+ rssss_list <- segMap2D "rssss" segthd_lvl ResultMaySimplify (ty, tx) $ \(ltid_y, ltid_x) -> do+ (css, ii, jj) <- getThdRedomapRes (rx, ry) (ltid_x, ltid_y) (iii, jjj, redomap_res)+ rss <- forLoop ry [rss_init] $ \i [rss_merge] -> do+ rss' <- forLoop rx [rss_merge] $ \j [rss_merge'] -> do+ prereqAddCode2 (gtid_x, gtid_y) (ii, i, jj, j) (css, redomap_orig_res)+ let code2_subs = substituteNames (M.singleton rss_init rss_merge') code2'++ res_el <-+ letSubExp "res_elem"+ =<< eIf+ ( toExp $+ le64 gtid_y .<. pe64 height_A+ .&&. le64 gtid_x .<. pe64 width_B+ )+ ( do+ addStms code2_subs+ resultBodyM [Var res_nm]+ )+ (resultBodyM [Var rss_merge'])+ resultBodyM [res_el]+ resultBodyM [Var rss']+ pure [varRes rss]+ let epilogue_res_acc : _ = rssss_list+ pure [Returns ResultMaySimplify (Certs []) $ Var epilogue_res_acc]+mmBlkRegTilingAcc _ _ = pure Nothing++--------------------------+--------------------------++mmBlkRegTilingNrm :: Env -> Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))+mmBlkRegTilingNrm env (Let pat aux (Op (SegOp (SegMap SegThread {} seg_space ts old_kbody))))+ | KernelBody () kstms [Returns ResultMaySimplify cs (Var res_nm)] <- old_kbody,+ cs == mempty,+ -- check kernel has one result of primitive type+ [res_tp] <- ts,+ primType res_tp,+ -- we get the global-thread id for the two inner dimensions,+ -- as we are probably going to use it in code generation+ (gtid_x, width_B) : (gtid_y, height_A) : rem_outer_dims_rev <-+ reverse $ unSegSpace seg_space,+ rem_outer_dims <- reverse rem_outer_dims_rev,+ Just+ ( code2',+ (load_A, inp_A, map_t1, load_B, inp_B, map_t2),+ common_dim,+ var_dims,+ (map_lam, red_lam, red_ne, redomap_orig_res, red_t)+ ) <-+ matchesBlkRegTile seg_space kstms = do+ -- Here we start the implementation+ ---- in this binder: host code and outer seggroup (ie. the new kernel) ----+ (new_kernel, host_stms) <- runBuilder $ do+ -- host code+ (rx, ry, tx, ty, tk, tk_div_tx, tk_div_ty, tx_rx, ty_ry, a_loc_sz, b_loc_sz) <-+ mkTileMemSizes height_A width_B common_dim++ gridDim_x <- letSubExp "gridDim_x" =<< ceilDiv width_B tx_rx+ gridDim_y <- letSubExp "gridDim_y" =<< ceilDiv height_A ty_ry+ let gridxy_pexp = pe64 gridDim_y * pe64 gridDim_x+ let grid_pexp =+ foldl (\x d -> pe64 d * x) gridxy_pexp $+ map snd rem_outer_dims_rev+ (grid_size, group_size, segthd_lvl) <- mkNewSegthdLvl tx ty grid_pexp++ (gid_x, gid_y, gid_flat) <- mkGidsXYF++ ---- in this binder: outer seggroup ----+ (ret_seggroup, stms_seggroup) <- runBuilder $ do+ iii <- letExp "iii" =<< toExp (le64 gid_y * pe64 ty_ry)+ jjj <- letExp "jjj" =<< toExp (le64 gid_x * pe64 tx_rx)++ -- initialize register mem with neutral elements and create shmem+ (cssss, a_loc_init, b_loc_init) <-+ initRegShmem+ (rx, tx, ry, ty, a_loc_sz, b_loc_sz)+ (map_t1, map_t2, red_t)+ segthd_lvl+ red_ne++ -- build prologue.+ full_tiles <-+ letExp "full_tiles" $+ BasicOp $ BinOp (SQuot Int64 Unsafe) common_dim tk++ let ct_arg =+ ( (rx, ry, tx, ty, tk, tk_div_tx, tk_div_ty, tx_rx),+ segthd_lvl,+ var_dims,+ (gtid_x, width_B, gtid_y, height_A, common_dim),+ (a_loc_sz, b_loc_sz),+ (iii, jjj),+ (load_A, inp_A, map_t1, load_B, inp_B, map_t2),+ (map_lam, red_lam)+ )++ prologue_res_list <-+ forLoop' (Var full_tiles) [cssss, a_loc_init, b_loc_init] $+ \kk0 [thd_res_merge, a_loc_merge, b_loc_merge] -> do+ process_full_tiles <-+ kkLoopBody env ct_arg kk0 (thd_res_merge, a_loc_merge, b_loc_merge) False++ resultBodyM $ map Var process_full_tiles++ let prologue_res : a_loc_reuse : b_loc_reuse : _ = prologue_res_list++ -- build epilogue.+ epilogue_res_list <- kkLoopBody env ct_arg full_tiles (prologue_res, a_loc_reuse, b_loc_reuse) True++ let redomap_res : _ = epilogue_res_list++ -- support for non-empty code2'+ -- segmap (ltid_y < ty, ltid_x < tx) {+ -- for i < ry do+ -- for j < rx do+ -- res = if (iii+ltid_y*ry+i < height_A && jjj+ltid_x*rx+j < width_B)+ -- then code2' else dummy+ -- final_res[i,j] = res+ mkEpiloguePrimRes+ segthd_lvl+ (redomap_orig_res, redomap_res)+ (res_nm, res_tp)+ (ty, tx, ry, rx)+ (iii, jjj)+ (gtid_y, gtid_x)+ (height_A, width_B, rem_outer_dims)+ code2'++ let level' = SegGroup (Count grid_size) (Count group_size) SegNoVirt+ space' = SegSpace gid_flat (rem_outer_dims ++ [(gid_y, gridDim_y), (gid_x, gridDim_x)])+ kbody' = KernelBody () stms_seggroup ret_seggroup+ pure $ Let pat aux $ Op $ SegOp $ SegMap level' space' ts kbody'+ pure $ Just (host_stms, new_kernel)+ where+ mkEpiloguePrimRes+ segthd_lvl+ (redomap_orig_res, redomap_res)+ (res_nm, res_tp)+ (ty, tx, ry, rx)+ (iii, jjj)+ (gtid_y, gtid_x)+ (height_A, width_B, rem_outer_dims)+ code2' = do+ epilogue_res <-+ if redomap_orig_res == res_nm+ then pure redomap_res -- epilogue_res_list+ else do+ rssss_list <- segMap2D "rssss" segthd_lvl ResultPrivate (ty, tx) $ \(ltid_y, ltid_x) -> do+ rss_init <- scratch "rss_init" (elemType res_tp) [ry, rx]+ (css, ii, jj) <- getThdRedomapRes (rx, ry) (ltid_x, ltid_y) (iii, jjj, redomap_res)+ rss <- forLoop ry [rss_init] $ \i [rss_merge] -> do+ rss' <- forLoop rx [rss_merge] $ \j [rss_merge'] -> do+ prereqAddCode2 (gtid_x, gtid_y) (ii, i, jj, j) (css, redomap_orig_res)++ res_el <-+ letSubExp "res_elem"+ =<< eIf+ ( toExp $+ le64 gtid_y .<. pe64 height_A+ .&&. le64 gtid_x .<. pe64 width_B+ )+ ( do+ addStms code2'+ resultBodyM [Var res_nm]+ )+ (eBody [eBlank res_tp])+ rss'' <- update "rss" rss_merge' [i, j] res_el+ resultBodyM [Var rss'']+ resultBodyM [Var rss']+ pure [varRes rss]+ let rssss : _ = rssss_list+ pure rssss++ let regtile_ret_dims =+ map (\(_, sz) -> (sz, se1, se1)) rem_outer_dims+ ++ [(height_A, ty, ry), (width_B, tx, rx)]++ -- Add dummy dimensions to tile to reflect the outer dimensions.+ epilogue_res' <-+ if null rem_outer_dims+ then pure epilogue_res+ else do+ epilogue_t <- lookupType epilogue_res+ let (block_dims, rest_dims) = splitAt 2 $ arrayDims epilogue_t+ ones = map (const $ intConst Int64 1) rem_outer_dims+ new_shape = concat [ones, block_dims, ones, rest_dims]+ letExp "res_reshaped" $ BasicOp $ Reshape (map DimNew new_shape) epilogue_res+ pure [RegTileReturns mempty regtile_ret_dims epilogue_res']+mmBlkRegTilingNrm _ _ = pure Nothing++-- pattern match the properties of the code that we look to+-- tile: a redomap whose two input arrays are each invariant+-- to one of the last two (innermost) parallel dimensions.+matchesBlkRegTile ::+ SegSpace ->+ Stms GPU ->+ Maybe+ ( Stms GPU,+ (Stm GPU, VName, PrimType, Stm GPU, VName, PrimType),+ SubExp,+ [Int],+ (Lambda GPU, Lambda GPU, SubExp, VName, PrimType)+ )+matchesBlkRegTile seg_space kstms+ | -- build the variance table, that records, for+ -- each variable name, the variables it depends on+ initial_variance <- M.map mempty $ scopeOfSegSpace seg_space,+ variance <- varianceInStms initial_variance kstms,+ -- check that the code fits the pattern having:+ -- some `code1`, followed by one Screma SOAC, followed by some `code2`+ (code1, Just screma_stmt, code2) <- matchCodeStreamCode kstms,+ Let pat_redomap _ (Op _) <- screma_stmt,+ -- checks that the Screma SOAC is actually a redomap and normalizes it+ Just (common_dim, arrs, (_, red_lam, red_nes, map_lam)) <- isTileableRedomap screma_stmt,+ -- check that exactly two 1D arrays are streamed thorugh redomap,+ -- and the result of redomap is one scalar+ -- !!!I need to rearrange this whole thing!!! including inp_A and inp_B+ length arrs == 2,+ [red_ne] <- red_nes,+ [map_t1t, map_t2t] <- map paramDec $ lambdaParams map_lam,+ [red_t1, _] <- map paramDec $ lambdaParams red_lam,+ primType map_t1t && primType map_t2t && primType red_t1,+ map_t1_0 <- elemType map_t1t,+ map_t2_0 <- elemType map_t2t,+ -- checks that the input arrays to redomap are variant to+ -- exactly one of the two innermost dimensions of the kernel+ Just var_dims <- isInvarTo1of2InnerDims mempty seg_space variance arrs,+ -- get the variables on which the first result of redomap depends on+ [redomap_orig_res] <- patNames pat_redomap,+ Just res_red_var <- M.lookup redomap_orig_res variance, -- variance of the reduce result++ -- we furthermore check that code1 is only formed by+ -- 1. statements that slice some globally-declared arrays+ -- to produce the input for the redomap, and+ -- 2. potentially some statements on which the redomap+ -- is independent; these are recorded in `code2''`+ Just (code2'', tab_inv_stm) <-+ foldl+ (processIndirections (namesFromList arrs) res_red_var)+ (Just (Seq.empty, M.empty))+ code1,+ -- identify load_A, load_B+ tmp_stms <- mapMaybe (`M.lookup` tab_inv_stm) arrs,+ length tmp_stms == length arrs =+ let zip_AB = zip3 tmp_stms arrs [map_t1_0, map_t2_0]+ [(load_A, inp_A, map_t1), (load_B, inp_B, map_t2)] =+ if var_dims == [0, 1]+ then zip_AB+ else reverse zip_AB+ code2' = code2'' <> code2+ in Just+ ( code2',+ (load_A, inp_A, map_t1, load_B, inp_B, map_t2),+ common_dim,+ var_dims,+ (map_lam, red_lam, red_ne, redomap_orig_res, elemType red_t1)+ )+matchesBlkRegTile _ _ = Nothing++-- ceiled division expression+ceilDiv :: MonadBuilder m => SubExp -> SubExp -> m (Exp (Rep m))+ceilDiv x y = pure $ BasicOp $ BinOp (SDivUp Int64 Unsafe) x y++mkTileMemSizes ::+ SubExp ->+ SubExp ->+ SubExp ->+ Builder+ GPU+ ( SubExp,+ SubExp,+ SubExp,+ SubExp,+ SubExp,+ SubExp,+ SubExp,+ SubExp,+ SubExp,+ SubExp,+ SubExp+ )+mkTileMemSizes height_A width_B common_dim = do+ tk_name <- nameFromString . pretty <$> newVName "Tk"+ tx_name <- nameFromString . pretty <$> newVName "Tx"+ ty_name <- nameFromString . pretty <$> newVName "Ty"+ rx_name <- nameFromString . pretty <$> newVName "Rx"+ ry_name <- nameFromString . pretty <$> newVName "Ry"++ (ty, ry) <- getParTiles ("Ty", "Ry") (ty_name, ry_name) height_A+ (tx, rx) <- getParTiles ("Tx", "Rx") (tx_name, rx_name) width_B+ tk <- getSeqTile "Tk" tk_name common_dim ty tx++ tk_div_tx <- letSubExp "tk_div_tx" =<< ceilDiv tk tx+ tk_div_ty <- letSubExp "tk_div_ty" =<< ceilDiv tk ty++ tx_rx <- letSubExp "TxRx" =<< toExp (pe64 tx * pe64 rx)+ ty_ry <- letSubExp "TyRy" =<< toExp (pe64 ty * pe64 ry)++ let pad_term = sMax64 (pe64 tk) (pe64 ty * pe64 ry)+ -- if A not transposed, its shmem should be [ty*ry][tk]+ -- we pad to [ty*ry][tk+1] size to minimize bank conflicts+ a_loc_sz <-+ letSubExp "a_loc_sz"+ =<< toExp (pe64 ty * pe64 ry * pe64 tk + pad_term)+ -- if B is transposed, its shmem should be [tk][tx*rx]+ -- we pad as above, by assuming tx*rx == ty*ry >= tk+ -- ToDo: we can decrease the size by checking at this+ -- point whether A and B are transposed (or not).+ b_loc_sz <-+ letSubExp "b_loc_sz"+ =<< toExp (pe64 tx * pe64 rx * pe64 tk + pad_term) -- (pe64 tk * pe64 tx * pe64 rx)+ pure (rx, ry, tx, ty, tk, tk_div_tx, tk_div_ty, tx_rx, ty_ry, a_loc_sz, b_loc_sz)++mkNewSegthdLvl ::+ SubExp ->+ SubExp ->+ TPrimExp Int64 VName ->+ Builder GPU (SubExp, SubExp, SegLevel)+mkNewSegthdLvl tx ty grid_pexp = do+ grid_size <- letSubExp "grid_size" =<< toExp grid_pexp+ group_size <- letSubExp "group_size" =<< toExp (pe64 ty * pe64 tx)+ let segthd_lvl = SegThread (Count grid_size) (Count group_size) (SegNoVirtFull (SegSeqDims []))+ pure (grid_size, group_size, segthd_lvl)++mkGidsXYF :: Builder GPU (VName, VName, VName)+mkGidsXYF = do+ gid_y <- newVName "gid_y"+ gid_x <- newVName "gid_x"+ gid_flat <- newVName "gid_flat"+ pure (gid_x, gid_y, gid_flat)++initRegShmem ::+ (SubExp, SubExp, SubExp, SubExp, SubExp, SubExp) ->+ (PrimType, PrimType, PrimType) ->+ SegLevel ->+ SubExp ->+ Builder GPU (VName, VName, VName)+initRegShmem+ (rx, tx, ry, ty, a_loc_sz, b_loc_sz)+ (map_t1, map_t2, red_t)+ segthd_lvl+ red_ne = do+ -- initialize register mem with neutral elements.+ cssss_list <- segMap2D "cssss" segthd_lvl ResultPrivate (ty, tx) $ \_ -> do+ css_init <- scratch "css_init" red_t [ry, rx]+ css <- forLoop ry [css_init] $ \i [css_merge] -> do+ css' <- forLoop rx [css_merge] $ \j [css_merge'] -> do+ css'' <- update "css" css_merge' [i, j] red_ne+ resultBodyM [Var css'']+ resultBodyM [Var css']+ pure [varRes css]+ let [cssss] = cssss_list+ -- scratch shared memory+ a_loc_init <- scratch "A_loc" map_t1 [a_loc_sz]+ b_loc_init <- scratch "B_loc" map_t2 [b_loc_sz]+ pure (cssss, a_loc_init, b_loc_init)++getThdRedomapRes ::+ (SubExp, SubExp) ->+ (VName, VName) ->+ (VName, VName, VName) ->+ Builder GPU (VName, VName, VName)+getThdRedomapRes (rx, ry) (ltid_x, ltid_y) (iii, jjj, redomap_res) = do+ css <- index "redomap_thd" redomap_res [ltid_y, ltid_x]+ ii <- letExp "ii" =<< toExp (le64 iii + le64 ltid_y * pe64 ry)+ jj <- letExp "jj" =<< toExp (le64 jjj + le64 ltid_x * pe64 rx)+ pure (css, ii, jj)++prereqAddCode2 ::+ (VName, VName) ->+ (VName, VName, VName, VName) ->+ (VName, VName) ->+ Builder GPU ()+prereqAddCode2 (gtid_x, gtid_y) (ii, i, jj, j) (css, redomap_orig_res) = do+ c <- index "redomap_elm" css [i, j]+ cpy_stm <- mkLetNamesM [redomap_orig_res] $ BasicOp $ SubExp $ Var c+ addStm cpy_stm+ letBindNames [gtid_y] =<< toExp (le64 ii + le64 i)+ letBindNames [gtid_x] =<< toExp (le64 jj + le64 j)++-- | Tries to identify the following pattern:+-- code followed by some Screma followed by more code.+matchCodeStreamCode ::+ Stms GPU ->+ (Stms GPU, Maybe (Stm GPU), Stms GPU)+matchCodeStreamCode kstms =+ let (code1, screma, code2) =+ foldl+ ( \acc stmt ->+ case (acc, stmt) of+ ((cd1, Nothing, cd2), Let _ _ (Op (OtherOp Screma {}))) ->+ (cd1, Just stmt, cd2)+ ((cd1, Nothing, cd2), _) ->+ (cd1 ++ [stmt], Nothing, cd2)+ ((cd1, Just strm, cd2), _) ->+ (cd1, Just strm, cd2 ++ [stmt])+ )+ ([], Nothing, [])+ (stmsToList kstms)+ in (stmsFromList code1, screma, stmsFromList code2)++-- | Checks that all streamed arrays are variant to exacly one of+-- the two innermost parallel dimensions, and conversely, for+-- each of the two innermost parallel dimensions, there is at+-- least one streamed array variant to it. The result is the+-- number of the only variant parallel dimension for each array.+isInvarTo1of2InnerDims ::+ Names ->+ SegSpace ->+ VarianceTable ->+ [VName] ->+ Maybe [Int]+isInvarTo1of2InnerDims branch_variant kspace variance arrs =+ let inner_perm0 = map varToOnly1of2InnerDims arrs+ inner_perm = catMaybes inner_perm0+ ok1 = elem 0 inner_perm && elem 1 inner_perm+ ok2 = length inner_perm0 == length inner_perm+ in if ok1 && ok2 then Just inner_perm else Nothing+ where+ varToOnly1of2InnerDims :: VName -> Maybe Int+ varToOnly1of2InnerDims arr = do+ (j, _) : (i, _) : _ <- Just $ reverse $ unSegSpace kspace+ let variant_to = M.findWithDefault mempty arr variance+ branch_invariant =+ not $ nameIn j branch_variant || nameIn i branch_variant+ if not branch_invariant+ then Nothing -- if i or j in branch_variant; return nothing+ else+ if nameIn i variant_to && not (nameIn j variant_to)+ then Just 0+ else+ if nameIn j variant_to && not (nameIn i variant_to)+ then Just 1+ else Nothing++processIndirections ::+ Names -> -- input arrays to redomap+ Names -> -- variables on which the result of redomap depends on.+ Maybe (Stms GPU, M.Map VName (Stm GPU)) ->+ Stm GPU ->+ Maybe (Stms GPU, M.Map VName (Stm GPU))+processIndirections arrs _ acc stm@(Let patt _ (BasicOp (Index _ _)))+ | Just (ss, tab) <- acc,+ [p] <- patElems patt,+ p_nm <- patElemName p,+ nameIn p_nm arrs =+ Just (ss, M.insert p_nm stm tab)+processIndirections _ res_red_var acc stm'@(Let patt _ _)+ | Just (ss, tab) <- acc,+ ps <- patElems patt,+ all (\p -> not (nameIn (patElemName p) res_red_var)) ps =+ Just (ss Seq.|> stm', tab)+ | otherwise = Nothing++getParTiles :: (String, String) -> (Name, Name) -> SubExp -> Builder GPU (SubExp, SubExp)+getParTiles (t_str, r_str) (t_name, r_name) len_dim =+ case len_dim of+ Constant (IntValue (Int64Value 8)) ->+ pure (se8, se1)+ Constant (IntValue (Int64Value 16)) ->+ pure (se8, se2)+ Constant (IntValue (Int64Value 32)) ->+ pure (se8, se4)+ _ -> do+ t <- letSubExp t_str $ Op $ SizeOp $ GetSize t_name SizeTile+ r <- letSubExp r_str $ Op $ SizeOp $ GetSize r_name SizeRegTile+ pure (t, r)++getSeqTile :: String -> Name -> SubExp -> SubExp -> SubExp -> Builder GPU SubExp+getSeqTile tk_str tk_name len_dim ty tx =+ case (tx, ty) of+ (Constant (IntValue (Int64Value v_x)), Constant (IntValue (Int64Value v_y))) ->+ letSubExp tk_str . BasicOp . SubExp . constant $+ case len_dim of+ Constant (IntValue (Int64Value v_d)) -> min v_d $ min v_x v_y+ _ -> min v_x v_y+ _ ->+ letSubExp tk_str $ Op $ SizeOp $ GetSize tk_name SizeTile++----------------------------------------------------------------------------------------------+--- 3D Tiling (RegTiling for the outermost dimension & Block tiling for the innermost two) ---+----------------------------------------------------------------------------------------------++maxRegTile :: Int64+maxRegTile = 30++mkRegTileSe :: Int64 -> SubExp+mkRegTileSe = constant++variantToDim :: VarianceTable -> VName -> VName -> Bool+variantToDim variance gid_outer nm =+ gid_outer == nm || nameIn gid_outer (M.findWithDefault mempty nm variance)++-- | Checks that all streamed arrays are variant to exacly one of+-- the two innermost parallel dimensions, and conversely, for+-- each of the two innermost parallel dimensions, there is at+-- least one streamed array variant to it. The result is the+-- number of the only variant parallel dimension for each array.+isInvarTo2of3InnerDims ::+ Names ->+ SegSpace ->+ VarianceTable ->+ [VName] ->+ Maybe [Int]+isInvarTo2of3InnerDims branch_variant kspace variance arrs =+ let inner_perm0 = map varToOnly1of3InnerDims arrs+ inner_perm = catMaybes inner_perm0+ ok1 = elem 0 inner_perm && elem 1 inner_perm && elem 2 inner_perm+ ok2 = length inner_perm0 == length inner_perm+ in if ok1 && ok2 then Just inner_perm else Nothing+ where+ varToOnly1of3InnerDims :: VName -> Maybe Int+ varToOnly1of3InnerDims arr = do+ (k, _) : (j, _) : (i, _) : _ <- Just $ reverse $ unSegSpace kspace+ let variant_to = M.findWithDefault mempty arr variance+ branch_invariant =+ not $+ nameIn k branch_variant+ || nameIn j branch_variant+ || nameIn i branch_variant+ if not branch_invariant+ then Nothing -- if i or j or k in branch_variant; return nothing+ else+ if nameIn i variant_to && not (nameIn j variant_to || nameIn k variant_to)+ then Just 0+ else+ if nameIn j variant_to && not (nameIn i variant_to || nameIn k variant_to)+ then Just 1+ else+ if nameIn k variant_to && not (nameIn i variant_to || nameIn j variant_to)+ then Just 2+ else Nothing++-- | Expects a kernel statement as argument.+-- CONDITIONS for 3D tiling optimization to fire are:+-- 1. a) The kernel body can be broken into+-- scalar-code-1 ++ [Redomap stmt] ++ scalar-code-2.+-- b) The kernels has a per-thread result, and obviously+-- the result is variant to the 3rd dimension+-- (counted from innermost to outermost)+-- 2. For the Redomap:+-- a) the streamed arrays are one dimensional+-- b) each of the array arguments of Redomap are variant+-- to exactly one of the three innermost-parallel dimension+-- of the kernel. This condition can be relaxed by interchanging+-- kernel dimensions whenever possible.+-- 3. For scalar-code-1:+-- a) each of the statements is a slice that produces one of the+-- streamed arrays+--+-- mmBlkRegTiling :: Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))+-- mmBlkRegTiling (Let pat aux (Op (SegOp (SegMap SegThread{} seg_space ts old_kbody))))+doRegTiling3D :: Stm GPU -> TileM (Maybe (Stms GPU, Stm GPU))+doRegTiling3D (Let pat aux (Op (SegOp old_kernel)))+ | SegMap SegThread {} space kertp (KernelBody () kstms kres) <- old_kernel,+ -- build the variance table, that records, for+ -- each variable name, the variables it depends on+ initial_variance <- M.map mempty $ scopeOfSegSpace space,+ variance <- varianceInStms initial_variance kstms,+ -- we get the global-thread id for the two inner dimensions,+ -- as we are probably going to use it in code generation+ (gtid_x, d_Kx) : (gtid_y, d_Ky) : (gtid_z, d_M) : rem_outer_dims_rev <- reverse $ unSegSpace space,+ rem_outer_dims <- reverse rem_outer_dims_rev,+ -- check that the code fits the pattern having:+ -- some `code1`, followed by one Screma SOAC, followed by some `code2`+ (code1, Just screma_stmt, code2) <- matchCodeStreamCode kstms,+ Let pat_redomap _ (Op _) <- screma_stmt,+ -- checks that the Screma SOAC is actually a redomap and normalize it+ Just (common_dim, inp_soac_arrs, (_, red_lam, red_nes, map_lam)) <- isTileableRedomap screma_stmt,+ not (null red_nes),+ -- assuming we have a budget of maxRegTile registers, we distribute+ -- that budget across the result of redomap and the kernel result+ num_res <- max (length red_nes) (length kres),+ reg_tile <- maxRegTile `quot` fromIntegral num_res,+ reg_tile_se <- mkRegTileSe reg_tile,+ -- check that the element-type of the map and reduce are scalars:+ all (primType . paramDec) $ lambdaParams map_lam,+ red_res_tps <- map paramDec $ take (length red_nes) $ lambdaParams red_lam,+ all primType red_res_tps,+ -- checks that the input arrays to redomap are variant to+ -- exactly one of the two innermost dimensions of the kernel+ Just _ <- isInvarTo2of3InnerDims mempty space variance inp_soac_arrs,+ -- get the free variables on which the result of redomap depends on+ redomap_orig_res <- patElems pat_redomap,+ res_red_var <- -- variance of the reduce result+ mconcat $ mapMaybe ((`M.lookup` variance) . patElemName) redomap_orig_res,+ mempty /= res_red_var,+ -- we furthermore check that code1 is only formed by+ -- 1. statements that slice some globally-declared arrays+ -- to produce the input for the redomap, and+ -- 2. potentially some statements on which the redomap+ -- is independent; these are recorded in `code2''`+ Just (code2'', arr_tab0) <-+ foldl+ (processIndirections (namesFromList inp_soac_arrs) res_red_var)+ (Just (Seq.empty, M.empty))+ code1,+ -- check that code1 contains exacly one slice for each of the input array to redomap+ tmp_stms <- mapMaybe (`M.lookup` arr_tab0) inp_soac_arrs,+ length tmp_stms == length inp_soac_arrs,+ -- code1' <- stmsFromList $ stmsToList code1 \\ stmsToList code2'',+ code2' <- code2'' <> code2,+ -- we assume the kernel results are variant to the thrid-outer parallel dimension+ -- (for sanity sake, they should be)+ ker_res_nms <- mapMaybe getResNm kres,+ length ker_res_nms == length kres,+ all primType kertp,+ all (variantToDim variance gtid_z) ker_res_nms = do+ -- HERE STARTS THE IMPLEMENTATION:+ (new_kernel, host_stms) <- runBuilder $ do+ -- host code+ -- process the z-variant arrays that need transposition;+ -- these "manifest" statements should come before the kernel+ (tab_inn, tab_out) <-+ foldM+ (insertTranspose variance (gtid_z, d_M))+ (M.empty, M.empty)+ $ M.toList arr_tab0++ tx_name <- nameFromString . pretty <$> newVName "Tx"+ ty_name <- nameFromString . pretty <$> newVName "Ty"++ tx0 <- letSubExp "Tx" $ Op $ SizeOp $ GetSize tx_name SizeTile+ ty0 <- letSubExp "Ty" $ Op $ SizeOp $ GetSize ty_name SizeTile+ ty <- limitTile "Ty" ty0 d_Ky+ tx <- limitTile "Tx" tx0 d_Kx+ let rz = reg_tile_se++ gridDim_x <- letSubExp "gridDim_x" =<< ceilDiv d_Kx tx+ gridDim_y <- letSubExp "gridDim_y" =<< ceilDiv d_Ky ty+ gridDim_z <- letSubExp "gridDim_z" =<< ceilDiv d_M rz+ let gridxyz_pexp = pe64 gridDim_z * pe64 gridDim_y * pe64 gridDim_x+ let grid_pexp = product $ gridxyz_pexp : map (pe64 . snd) rem_outer_dims_rev+ grid_size <- letSubExp "grid_size_tile3d" =<< toExp grid_pexp+ group_size <- letSubExp "group_size_tile3d" =<< toExp (pe64 ty * pe64 tx)+ let segthd_lvl = SegThread (Count grid_size) (Count group_size) (SegNoVirtFull (SegSeqDims []))++ count_shmem <- letSubExp "count_shmem" =<< ceilDiv rz group_size++ gid_x <- newVName "gid_x"+ gid_y <- newVName "gid_y"+ gid_z <- newVName "gid_z"+ gid_flat <- newVName "gid_flat"++ ---- in this binder: outer seggroup ----+ (ret_seggroup, stms_seggroup) <- runBuilder $ do+ ii <- letExp "ii" =<< toExp (le64 gid_z * pe64 rz)+ jj1 <- letExp "jj1" =<< toExp (le64 gid_y * pe64 ty)+ jj2 <- letExp "jj2" =<< toExp (le64 gid_x * pe64 tx)++ -- initialize the register arrays corresponding to the result of redomap;+ reg_arr_nms <- segMap2D "res" segthd_lvl ResultPrivate (ty, tx) $ \_ ->+ forM (zip red_nes red_res_tps) $ \(red_ne, red_t) -> do+ css_init <- scratch "res_init" (elemType red_t) [rz]+ css <- forLoop rz [css_init] $ \i [css_merge] -> do+ css' <- update "css" css_merge [i] red_ne+ resultBodyM [Var css']+ pure $ varRes css++ -- scratch the shared-memory arrays corresponding to the arrays that are+ -- input to the redomap and are invariant to the outermost parallel dimension.+ loc_arr_nms <- forM (M.toList tab_out) $ \(nm, (ptp, _)) ->+ scratch (baseString nm ++ "_loc") ptp [rz]++ prologue_res_list <-+ forLoop' common_dim (reg_arr_nms ++ loc_arr_nms) $+ \q var_nms -> do+ let reg_arr_merge_nms = take (length red_nes) var_nms+ let loc_arr_merge_nms = drop (length red_nes) var_nms++ -- collective copy from global to shared memory+ loc_arr_nms' <-+ forLoop' count_shmem loc_arr_merge_nms $ \tt loc_arr_merge2_nms -> do+ loc_arr_merge2_nms' <-+ forM (zip loc_arr_merge2_nms (M.toList tab_out)) $ \(loc_Y_nm, (glb_Y_nm, (ptp_Y, load_Y))) -> do+ ltid_flat <- newVName "ltid_flat"+ ltid <- newVName "ltid"+ let segspace = SegSpace ltid_flat [(ltid, group_size)]+ ((res_v, res_i), stms) <- runBuilder $ do+ offs <- letExp "offs" =<< toExp (pe64 group_size * le64 tt)+ loc_ind <- letExp "loc_ind" =<< toExp (le64 ltid + le64 offs)+ letBindNames [gtid_z] =<< toExp (le64 ii + le64 loc_ind)+ let glb_ind = gtid_z+ y_elm <-+ letSubExp "y_elem"+ =<< eIf+ (toExp $ le64 glb_ind .<. pe64 d_M)+ ( do+ addStm load_Y+ res <- index "Y_elem" glb_Y_nm [q]+ resultBodyM [Var res]+ )+ (eBody [eBlank $ Prim ptp_Y])+ y_ind <-+ letSubExp "y_loc_ind"+ =<< eIf+ (toExp $ le64 loc_ind .<. pe64 rz)+ (toExp loc_ind >>= letTupExp' "loc_fi" >>= resultBodyM)+ (eBody [pure $ BasicOp $ SubExp $ intConst Int64 (-1)])+ -- y_tp <- subExpType y_elm+ pure (y_elm, y_ind)++ let ret = WriteReturns mempty (Shape [rz]) loc_Y_nm [(Slice [DimFix res_i], res_v)]+ let body = KernelBody () stms [ret]++ res_nms <-+ letTupExp "Y_glb2loc" <=< renameExp $+ Op $ SegOp $ SegMap segthd_lvl segspace [Prim ptp_Y] body+ let res_nm : _ = res_nms+ pure res_nm+ resultBodyM $ map Var loc_arr_merge2_nms'++ redomap_res <-+ segMap2D "redomap_res" segthd_lvl ResultPrivate (ty, tx) $+ \(ltid_y, ltid_x) -> do+ letBindNames [gtid_y] =<< toExp (le64 jj1 + le64 ltid_y)+ letBindNames [gtid_x] =<< toExp (le64 jj2 + le64 ltid_x)+ reg_arr_merge_nms_slc <- forM reg_arr_merge_nms $ \reg_arr_nm ->+ index "res_reg_slc" reg_arr_nm [ltid_y, ltid_x]+ fmap subExpsRes . letTupExp' "redomap_guarded"+ =<< eIf+ (toExp $ le64 gtid_y .<. pe64 d_Ky .&&. le64 gtid_x .<. pe64 d_Kx)+ ( do+ inp_scals_invar_outer <-+ forM (M.toList tab_inn) $ \(inp_arr_nm, load_stm) -> do+ addStm load_stm+ index (baseString inp_arr_nm) inp_arr_nm [q]+ -- build the loop of count R whose body is semantically the redomap code+ reg_arr_merge_nms' <-+ forLoop' rz reg_arr_merge_nms_slc $ \i reg_arr_mm_nms -> do+ letBindNames [gtid_z] =<< toExp (le64 ii + le64 i)+ resultBodyM =<< letTupExp' "redomap_lam"+ =<< eIf+ (toExp $ le64 gtid_z .<. pe64 d_M)+ ( do+ -- read from shared memory+ ys <- forM loc_arr_nms' $ \loc_arr_nm ->+ index "inp_reg_var2z" loc_arr_nm [i]+ cs <- forM reg_arr_mm_nms $ \reg_arr_nm ->+ index "res_reg_var2z" reg_arr_nm [i]+ -- here we need to put in order the scalar inputs to map:+ let tab_scals =+ M.fromList $+ zip (map fst $ M.toList tab_out) ys+ ++ zip (map fst $ M.toList tab_inn) inp_scals_invar_outer+ map_inp_scals <- forM inp_soac_arrs $ \arr_nm ->+ case M.lookup arr_nm tab_scals of+ Nothing -> error "Impossible case reached in tiling3D\n"+ Just nm -> pure nm+ map_lam' <- renameLambda map_lam+ red_lam' <- renameLambda red_lam+ map_res_scals <- eLambda map_lam' (map (eSubExp . Var) map_inp_scals)+ red_res <- eLambda red_lam' (map eSubExp (map Var cs ++ map resSubExp map_res_scals))+ css <- forM (zip reg_arr_mm_nms red_res) $ \(reg_arr_nm, c) ->+ update (baseString reg_arr_nm) reg_arr_nm [i] (resSubExp c)+ resultBodyM $ map Var css+ )+ (resultBodyM $ map Var reg_arr_mm_nms)+ resultBodyM $ map Var reg_arr_merge_nms'+ )+ (resultBodyM $ map Var reg_arr_merge_nms_slc)+ resultBodyM $ map Var $ redomap_res ++ loc_arr_nms'++ -- support for non-empty code2'+ -- segmap (ltid_y < ty, ltid_x < tx) {+ -- for i < rz do+ -- res = if (ii+i < d_M && jj1+ltid_y < d_Ky && jj2 + ltid_x < d_Kx)+ -- then code2' else dummy+ -- final_res[i] = res+ let redomap_res = take (length red_nes) prologue_res_list+ epilogue_res <-+ if length redomap_orig_res == length ker_res_nms+ && ker_res_nms == map patElemName redomap_orig_res+ then segMap3D "rssss" segthd_lvl ResultPrivate (se1, ty, tx) $ \(_ltid_z, ltid_y, ltid_x) -> forM (zip kertp redomap_res) $ \(res_tp, res) -> do rss_init <- scratch "rss_init" (elemType res_tp) [rz, se1, se1] fmap varRes $
src/Futhark/Optimise/CSE.hs view
@@ -42,6 +42,7 @@ import Futhark.IR import Futhark.IR.Aliases ( Aliases,+ consumedInStms, mkStmsAliases, removeFunDefAliases, removeProgAliases,@@ -55,9 +56,6 @@ import Futhark.Pass import Futhark.Transform.Substitute -consumedInStms :: Aliased rep => Stms rep -> Names-consumedInStms = snd . flip mkStmsAliases []- -- | Perform CSE on every function in a program. -- -- If the boolean argument is false, the pass will not perform CSE on@@ -213,6 +211,13 @@ | patElemName pe `nameIn` consumed = Consume | otherwise = Observe +-- A small amount of normalisation of expressions that otherwise would+-- be different for pointless reasons.+normExp :: Exp lore -> Exp lore+normExp (Apply fname args ret (safety, _, _)) =+ Apply fname args ret (safety, mempty, mempty)+normExp e = e+ cseInStm :: ASTRep rep => Names ->@@ -221,7 +226,7 @@ CSEM rep a cseInStm consumed (Let pat (StmAux cs attrs edec) e) m = do CSEState (esubsts, nsubsts) cse_arrays <- ask- let e' = substituteNames nsubsts e+ let e' = normExp $ substituteNames nsubsts e pat' = substituteNames nsubsts pat if any (bad cse_arrays) $ patElems pat then m [Let pat' (StmAux cs attrs edec) e']@@ -300,6 +305,8 @@ where cseInOp (GPU.SegOp op) = GPU.SegOp <$> cseInOp op cseInOp (GPU.OtherOp op) = GPU.OtherOp <$> cseInOp op+ cseInOp (GPU.GPUBody types body) =+ subCSE $ GPU.GPUBody types <$> cseInBody (map (const Observe) types) body cseInOp x = pure x instance
src/Futhark/Optimise/Fusion.hs view
@@ -844,10 +844,11 @@ fusionGatherExp _ (Op Futhark.Screma {}) = errorIllegal "screma" fusionGatherExp _ (Op Futhark.Scatter {}) = errorIllegal "write"----------------------------------------- Generic Traversal -----------------------------------------+fusionGatherExp fres (Op (Futhark.VJP lam _ _)) =+ snd <$> fusionGatherLam (mempty, fres) lam+fusionGatherExp fres (Op (Futhark.JVP lam _ _)) =+ snd <$> fusionGatherLam (mempty, fres) lam+-- fusionGatherExp fres e = addNamesToInfusible fres $ freeIn e fusionGatherSubExp :: FusedRes -> SubExp -> FusionGM FusedRes
+ src/Futhark/Optimise/GenRedOpt.hs view
@@ -0,0 +1,432 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeFamilies #-}++-- | Tries to turn a generalized reduction kernel into+-- a more specialized construct, for example:+-- (a) a map nest with a sequential redomap ripe for tiling+-- (b) a SegRed kernel followed by a smallish accumulation kernel.+-- (c) a histogram (for this we need to track the withAccs)+-- The idea is to identify the first accumulation and+-- to separate the initial kernels into two:+-- 1. the code up to and including the accumulation,+-- which is optimized to turn the accumulation either+-- into a map-reduce composition or a histogram, and+-- 2. the remaining code, which is recursively optimized.+-- Since this is mostly prototyping, when the accumulation+-- can be rewritten as a map-reduce, we sequentialize the+-- map-reduce, as to potentially enable tiling oportunities.+module Futhark.Optimise.GenRedOpt (optimiseGenRed) where++import Control.Monad.Reader+import Control.Monad.State+import qualified Data.List as L+import qualified Data.Map.Strict as M+import Data.Maybe+import Futhark.Builder+import Futhark.IR.GPU+import Futhark.Optimise.TileLoops.Shared+import Futhark.Pass+import Futhark.Tools+import Futhark.Transform.Rename++type GenRedM = ReaderT (Scope GPU) (State VNameSource)++-- | The pass definition.+optimiseGenRed :: Pass GPU GPU+optimiseGenRed =+ Pass "optimise generalized reductions" "Specializes generalized reductions into map-reductions or histograms" $+ intraproceduralTransformation onStms+ where+ onStms scope stms =+ modifyNameSource $+ runState $+ runReaderT (optimiseStms (M.empty, M.empty) stms) scope++optimiseBody :: Env -> Body GPU -> GenRedM (Body GPU)+optimiseBody env (Body () stms res) =+ Body () <$> optimiseStms env stms <*> pure res++optimiseStms :: Env -> Stms GPU -> GenRedM (Stms GPU)+optimiseStms env stms =+ localScope (scopeOf stms) $ do+ (_, stms') <- foldM foldfun (env, mempty) $ stmsToList stms+ pure stms'+ where+ foldfun :: (Env, Stms GPU) -> Stm GPU -> GenRedM (Env, Stms GPU)+ foldfun (e, ss) s = do+ (e', s') <- optimiseStm e s+ pure (e', ss <> s')++optimiseStm :: Env -> Stm GPU -> GenRedM (Env, Stms GPU)+optimiseStm env stm@(Let _ _ (Op (SegOp (SegMap SegThread {} _ _ _)))) = do+ res_genred_opt <- genRedOpts env stm+ let stms' =+ case res_genred_opt of+ Just stms -> stms+ Nothing -> oneStm stm+ pure (env, stms')+optimiseStm env (Let pat aux e) = do+ env' <- changeEnv env (head $ patNames pat) e+ e' <- mapExpM (optimise env') e+ pure (env', oneStm $ Let pat aux e')+ where+ optimise env' = identityMapper {mapOnBody = \scope -> localScope scope . optimiseBody env'}++------------------------++genRedOpts :: Env -> Stm GPU -> GenRedM (Maybe (Stms GPU))+genRedOpts env ker = do+ res_tile <- genRed2Tile2d env ker+ case res_tile of+ Nothing -> do+ res_sgrd <- genRed2SegRed env ker+ helperGenRed res_sgrd+ _ -> helperGenRed res_tile+ where+ helperGenRed Nothing = pure Nothing+ helperGenRed (Just (stms_before, ker_snd)) = do+ mb_stms_after <- genRedOpts env ker_snd+ case mb_stms_after of+ Just stms_after -> pure $ Just $ stms_before <> stms_after+ Nothing -> pure $ Just $ stms_before <> oneStm ker_snd++se1 :: SubExp+se1 = intConst Int64 1++genRed2Tile2d :: Env -> Stm GPU -> GenRedM (Maybe (Stms GPU, Stm GPU))+genRed2Tile2d env kerstm@(Let pat_ker aux (Op (SegOp (SegMap seg_thd seg_space kres_tps old_kbody))))+ | (SegThread _ seg_group_size _novirt) <- seg_thd,+ -- novirt == SegNoVirtFull || novirt == SegNoVirt,+ KernelBody () kstms kres <- old_kbody,+ Just (css, r_ses) <- allGoodReturns kres,+ null css,+ -- build the variance table, that records, for+ -- each variable name, the variables it depends on+ initial_variance <- M.map mempty $ scopeOfSegSpace seg_space,+ variance <- varianceInStms initial_variance kstms,+ -- check that the code fits the pattern having:+ -- some `code1`, followed by one accumulation, followed by some `code2`+ -- UpdateAcc VName [SubExp] [SubExp]+ (code1, Just accum_stmt, code2) <- matchCodeAccumCode kstms,+ Let pat_accum _aux_acc (BasicOp (UpdateAcc acc_nm acc_inds acc_vals)) <- accum_stmt,+ [pat_acc_nm] <- patNames pat_accum,+ -- check that the `acc_inds` are invariant to at least one+ -- parallel kernel dimensions, and return the innermost such one:+ Just (invar_gid, gid_ind) <- isInvarToParDim mempty seg_space variance acc_inds,+ gid_dims_new_0 <- filter (\x -> invar_gid /= fst x) (unSegSpace seg_space),+ -- reorder the variant dimensions such that inner(most) accum-indices+ -- correspond to inner(most) parallel dimensions, so that the babysitter+ -- does not introduce transpositions+ -- gid_dims_new <- gid_dims_new_0,+ gid_dims_new <- reorderParDims variance acc_inds gid_dims_new_0,+ -- check that all global-memory accesses in `code1` on which+ -- `accum_stmt` depends on are invariant to at least one of+ -- the remaining parallel dimensions (i.e., excluding `invar_gid`)+ all (isTileable invar_gid gid_dims_new variance pat_acc_nm) (stmsToList code1),+ -- need to establish a cost model for the stms that would now+ -- be redundantly executed by the two kernels. If any recurence+ -- is redundant than it is a no go. Otherwise we need to look at+ -- memory accesses: if more than two are re-executed, then we+ -- should abort.+ cost <- costRedundantExecution variance pat_acc_nm r_ses kstms,+ maxCost cost (Small 2) == Small 2 = do+ -- 1. create the first kernel+ acc_tp <- lookupType acc_nm+ let inv_dim_len = segSpaceDims seg_space !! gid_ind+ -- 1.1. get the accumulation operator+ ((redop0, neutral), el_tps) = getAccLambda acc_tp+ redop <- renameLambda redop0+ let red =+ Reduce+ { redComm = Commutative,+ redLambda = redop,+ redNeutral = neutral+ }+ -- 1.2. build the sequential map-reduce screma+ code1' =+ stmsFromList $+ filter (dependsOnAcc pat_acc_nm variance) $+ stmsToList code1+ (code1'', code1_tr_host) <- transposeFVs (freeIn kerstm) variance invar_gid code1'+ let map_lam_body = mkBody code1'' $ map (SubExpRes (Certs [])) acc_vals+ map_lam0 = Lambda [Param mempty invar_gid (Prim int64)] map_lam_body el_tps+ map_lam <- renameLambda map_lam0+ (k1_res, ker1_stms) <- runBuilderT' $ do+ iota <- letExp "iota" $ BasicOp $ Iota inv_dim_len (intConst Int64 0) (intConst Int64 1) Int64+ let op_exp = Op (OtherOp (Screma inv_dim_len [iota] (ScremaForm [] [red] map_lam)))+ res_redmap <- letTupExp "res_mapred" op_exp+ letSubExp (baseString pat_acc_nm ++ "_big_update") $+ BasicOp (UpdateAcc acc_nm acc_inds $ map Var res_redmap)++ -- 1.3. build the kernel expression and rename it!+ gid_flat_1 <- newVName "gid_flat"+ let space1 = SegSpace gid_flat_1 gid_dims_new++ (grid_size, host_stms1) <- runBuilder $ do+ let grid_pexp = foldl (\x d -> x * pe64 d) (pe64 se1) $ map snd gid_dims_new+ dim_prod <- letSubExp "dim_prod" =<< toExp grid_pexp+ letSubExp "grid_size" =<< ceilDiv dim_prod (unCount seg_group_size)+ let level1 = SegThread (Count grid_size) seg_group_size (SegNoVirtFull (SegSeqDims [])) -- novirt ?+ kbody1 = KernelBody () ker1_stms [Returns ResultMaySimplify (Certs []) k1_res]++ -- is it OK here to use the "aux" from the parrent kernel?+ ker_exp <- renameExp $ Op (SegOp (SegMap level1 space1 [acc_tp] kbody1))+ let ker1 = Let pat_accum aux ker_exp++ -- 2 build the second kernel+ let ker2_body = old_kbody {kernelBodyStms = code1 <> code2}+ ker2_exp <- renameExp $ Op (SegOp (SegMap seg_thd seg_space kres_tps ker2_body))+ let ker2 = Let pat_ker aux ker2_exp+ pure $+ Just (code1_tr_host <> host_stms1 <> oneStm ker1, ker2)+ where+ isIndVarToParDim _ (Constant _) _ = False+ isIndVarToParDim variance (Var acc_ind) par_dim =+ acc_ind == fst par_dim+ || nameIn (fst par_dim) (M.findWithDefault mempty acc_ind variance)+ foldfunReorder variance (unused_dims, inner_dims) acc_ind =+ case L.findIndex (isIndVarToParDim variance acc_ind) unused_dims of+ Nothing -> (unused_dims, inner_dims)+ Just i ->+ ( take i unused_dims ++ drop (i + 1) unused_dims,+ (unused_dims !! i) : inner_dims+ )+ reorderParDims variance acc_inds gid_dims_new_0 =+ let (invar_dims, inner_dims) =+ foldl+ (foldfunReorder variance)+ (gid_dims_new_0, [])+ (reverse acc_inds)+ in invar_dims ++ inner_dims+ --+ ceilDiv x y = pure $ BasicOp $ BinOp (SDivUp Int64 Unsafe) x y+ getAccLambda acc_tp =+ case acc_tp of+ (Acc tp_id _shp el_tps _) ->+ case M.lookup tp_id (fst env) of+ Just lam -> (lam, el_tps)+ _ -> error $ "Lookup in environment failed! " ++ pretty tp_id ++ " env: " ++ pretty (fst env)+ _ -> error "Illegal accumulator type!"+ -- is a subexp invariant to a gid of a parallel dimension?+ isSeInvar2 variance gid (Var x) =+ let x_deps = M.findWithDefault mempty x variance+ in gid /= x && not (nameIn gid x_deps)+ isSeInvar2 _ _ _ = True+ -- is a DimIndex invar to a gid of a parallel dimension?+ isDimIdxInvar2 variance gid (DimFix d) =+ isSeInvar2 variance gid d+ isDimIdxInvar2 variance gid (DimSlice d1 d2 d3) =+ all (isSeInvar2 variance gid) [d1, d2, d3]+ -- is an entire slice invariant to at least one gid of a parallel dimension+ isSliceInvar2 variance slc =+ any (\gid -> all (isDimIdxInvar2 variance gid) (unSlice slc))+ -- are all statements that touch memory invariant to at least one parallel dimension?+ isTileable :: VName -> [(VName, SubExp)] -> VarianceTable -> VName -> Stm GPU -> Bool+ isTileable seq_gid gid_dims variance acc_nm (Let (Pat [pel]) _ (BasicOp (Index _ slc)))+ | acc_deps <- M.findWithDefault mempty acc_nm variance,+ nameIn (patElemName pel) acc_deps =+ let invar_par = isSliceInvar2 variance slc (map fst gid_dims)+ invar_seq = isSliceInvar2 variance slc [seq_gid]+ in invar_par || invar_seq+ -- this relies on the cost model, that currently accepts only+ -- global-memory reads, and for example rejects in-place updates+ -- or loops inside the code that is transformed in a redomap.+ isTileable _ _ _ _ _ = True+ -- does the to-be-reduced accumulator depends on this statement?+ dependsOnAcc pat_acc_nm variance (Let pat _ _) =+ let acc_deps = M.findWithDefault mempty pat_acc_nm variance+ in any (`nameIn` acc_deps) $ patNames pat+genRed2Tile2d _ _ =+ pure Nothing++genRed2SegRed :: Env -> Stm GPU -> GenRedM (Maybe (Stms GPU, Stm GPU))+genRed2SegRed _ _ =+ pure Nothing++transposeFVs ::+ Names ->+ VarianceTable ->+ VName ->+ Stms GPU ->+ GenRedM (Stms GPU, Stms GPU)+transposeFVs fvs variance gid stms = do+ (tab, stms') <- foldM foldfun (M.empty, mempty) $ stmsToList stms+ let stms_host = M.foldr (\(_, _, s) ss -> ss <> s) mempty tab+ pure (stms', stms_host)+ where+ foldfun (tab, all_stms) stm = do+ (tab', stm') <- transposeFV (tab, stm)+ pure (tab', all_stms <> oneStm stm')+ -- ToDo: currently handles only 2-dim arrays, please generalize+ transposeFV (tab, Let pat aux (BasicOp (Index arr slc)))+ | dims <- unSlice slc,+ all isFixDim dims,+ nameIn arr fvs,+ iis <- L.findIndices depOnGid dims,+ [ii] <- iis,+ -- generalize below: treat any rearange and add to tab if not there.+ Nothing <- M.lookup arr tab,+ ii /= length dims - 1,+ perm <- [0 .. ii - 1] ++ [ii + 1 .. length dims - 1] ++ [ii] = do+ (arr_tr, stms_tr) <- runBuilderT' $ do+ arr' <- letExp (baseString arr ++ "_trsp") $ BasicOp $ Rearrange perm arr -- Manifest [1,0] arr+ letExp (baseString arr' ++ "_opaque") $ BasicOp $ Opaque OpaqueNil $ Var arr'+ let tab' = M.insert arr (perm, arr_tr, stms_tr) tab+ slc' = Slice $ map (dims !!) perm+ stm' = Let pat aux $ BasicOp $ Index arr_tr slc'+ pure (tab', stm')+ where+ isFixDim DimFix {} = True+ isFixDim _ = False+ depOnGid (DimFix (Var nm)) =+ gid == nm || nameIn gid (M.findWithDefault mempty nm variance)+ depOnGid _ = False+ transposeFV r = pure r++-- | Tries to identify the following pattern:+-- code followed by some UpdateAcc-statement+-- followed by more code.+matchCodeAccumCode ::+ Stms GPU ->+ (Stms GPU, Maybe (Stm GPU), Stms GPU)+matchCodeAccumCode kstms =+ let (code1, screma, code2) =+ foldl+ ( \acc stmt ->+ case (acc, stmt) of+ ((cd1, Nothing, cd2), Let _ _ (BasicOp UpdateAcc {})) ->+ (cd1, Just stmt, cd2)+ ((cd1, Nothing, cd2), _) ->+ (cd1 ++ [stmt], Nothing, cd2)+ ((cd1, Just strm, cd2), _) ->+ (cd1, Just strm, cd2 ++ [stmt])+ )+ ([], Nothing, [])+ (stmsToList kstms)+ in (stmsFromList code1, screma, stmsFromList code2)++-- | Checks that there exist a parallel dimension (among @kids@),+-- to which all the indices (@acc_inds@) are invariant to.+-- It returns the innermost such parallel dimension, as a tuple+-- of the pardim gid ('VName') and its index ('Int') in the+-- parallel space.+isInvarToParDim ::+ Names ->+ SegSpace ->+ VarianceTable ->+ [SubExp] ->+ Maybe (VName, Int)+isInvarToParDim branch_variant kspace variance acc_inds =+ let ker_gids = map fst $ unSegSpace kspace+ branch_invariant = not $ any (`nameIn` branch_variant) ker_gids+ allvar2 = allvariant2 acc_inds ker_gids+ last_invar_dim =+ foldl (lastNotIn allvar2) Nothing $+ zip ker_gids [0 .. length ker_gids - 1]+ in if branch_invariant+ then last_invar_dim+ else Nothing+ where+ variant2 (Var ind) kids =+ let variant_to =+ M.findWithDefault mempty ind variance+ <> (if ind `elem` kids then oneName ind else mempty)+ in filter (`nameIn` variant_to) kids+ variant2 _ _ = []+ allvariant2 ind_ses kids =+ namesFromList $ concatMap (`variant2` kids) ind_ses+ lastNotIn allvar2 acc (kid, k) =+ if nameIn kid allvar2 then acc else Just (kid, k)++allGoodReturns :: [KernelResult] -> Maybe ([VName], [SubExp])+allGoodReturns kres+ | all goodReturn kres = do+ Just $ foldl addCertAndRes ([], []) kres+ where+ goodReturn (Returns ResultMaySimplify _ _) = True+ goodReturn _ = False+ addCertAndRes (cs, rs) (Returns ResultMaySimplify c r_se) =+ (cs ++ unCerts c, rs ++ [r_se])+ addCertAndRes _ _ =+ error "Impossible case reached in GenRedOpt.hs, function allGoodReturns!"+allGoodReturns _ = Nothing++--------------------------+--- Cost Model Helpers ---+--------------------------++costRedundantExecution ::+ VarianceTable ->+ VName ->+ [SubExp] ->+ Stms GPU ->+ Cost+costRedundantExecution variance pat_acc_nm r_ses kstms =+ let acc_deps = M.findWithDefault mempty pat_acc_nm variance+ vartab_cut_acc = varianceInStmsWithout (oneName pat_acc_nm) mempty kstms+ res_deps = mconcat $ map (findDeps vartab_cut_acc) $ mapMaybe se2nm r_ses+ common_deps = namesIntersection res_deps acc_deps+ in foldl (addCostOfStmt common_deps) (Small 0) kstms+ where+ se2nm (Var nm) = Just nm+ se2nm _ = Nothing+ findDeps vartab nm = M.findWithDefault mempty nm vartab+ addCostOfStmt common_deps cur_cost stm =+ let pat_nms = patNames $ stmPat stm+ in if namesIntersect (namesFromList pat_nms) common_deps+ then addCosts cur_cost $ costRedundantStmt stm+ else cur_cost+ varianceInStmsWithout :: Names -> VarianceTable -> Stms GPU -> VarianceTable+ varianceInStmsWithout nms = L.foldl' (varianceInStmWithout nms)+ varianceInStmWithout cuts vartab stm =+ let pat_nms = patNames $ stmPat stm+ in if namesIntersect (namesFromList pat_nms) cuts+ then vartab+ else L.foldl' add vartab pat_nms+ where+ add variance' v = M.insert v binding_variance variance'+ look variance' v = oneName v <> M.findWithDefault mempty v variance'+ binding_variance = mconcat $ map (look vartab) $ namesToList (freeIn stm)++data Cost = Small Int | Big | Break+ deriving (Eq)++addCosts :: Cost -> Cost -> Cost+addCosts Break _ = Break+addCosts _ Break = Break+addCosts Big _ = Big+addCosts _ Big = Big+addCosts (Small c1) (Small c2) = Small (c1 + c2)++maxCost :: Cost -> Cost -> Cost+maxCost (Small c1) (Small c2) = Small (max c1 c2)+maxCost c1 c2 = addCosts c1 c2++costBody :: Body GPU -> Cost+costBody bdy =+ foldl addCosts (Small 0) $+ map costRedundantStmt $ stmsToList $ bodyStms bdy++costRedundantStmt :: Stm GPU -> Cost+costRedundantStmt (Let _ _ (Op _)) = Big+costRedundantStmt (Let _ _ DoLoop {}) = Big+costRedundantStmt (Let _ _ Apply {}) = Big+costRedundantStmt (Let _ _ WithAcc {}) = Big+costRedundantStmt (Let _ _ (If _cond b_then b_else _)) =+ maxCost (costBody b_then) (costBody b_else)+costRedundantStmt (Let _ _ (BasicOp (ArrayLit _ Array {}))) = Big+costRedundantStmt (Let _ _ (BasicOp (ArrayLit _ _))) = Small 1+costRedundantStmt (Let _ _ (BasicOp (Index _ slc))) =+ if all isFixDim (unSlice slc) then Small 1 else Small 0+ where+ isFixDim DimFix {} = True+ isFixDim _ = False+costRedundantStmt (Let _ _ (BasicOp FlatIndex {})) = Small 0+costRedundantStmt (Let _ _ (BasicOp Update {})) = Break+costRedundantStmt (Let _ _ (BasicOp FlatUpdate {})) = Break+costRedundantStmt (Let _ _ (BasicOp Concat {})) = Big+costRedundantStmt (Let _ _ (BasicOp Copy {})) = Big+costRedundantStmt (Let _ _ (BasicOp Manifest {})) = Big+costRedundantStmt (Let _ _ (BasicOp Replicate {})) = Big+costRedundantStmt (Let _ _ (BasicOp UpdateAcc {})) = Break+costRedundantStmt (Let _ _ (BasicOp _)) = Small 0
+ src/Futhark/Optimise/HistAccs.hs view
@@ -0,0 +1,163 @@+{-# LANGUAGE TypeFamilies #-}++-- | Turn certain uses of accumulators into SegHists.+module Futhark.Optimise.HistAccs (histAccsGPU) where++import Control.Monad.Reader+import Control.Monad.State+import qualified Data.Map.Strict as M+import Futhark.IR.GPU+import Futhark.MonadFreshNames+import Futhark.Pass+import Futhark.Tools+import Futhark.Transform.Rename+import Prelude hiding (quot)++-- | A mapping from accumulator variables to their source.+type Accs rep = M.Map VName (WithAccInput rep)++type OptM = ReaderT (Scope GPU) (State VNameSource)++optimiseBody :: Accs GPU -> Body GPU -> OptM (Body GPU)+optimiseBody accs body = mkBody <$> optimiseStms accs (bodyStms body) <*> pure (bodyResult body)++optimiseExp :: Accs GPU -> Exp GPU -> OptM (Exp GPU)+optimiseExp accs = mapExpM mapper+ where+ mapper =+ identityMapper+ { mapOnBody = \scope body -> localScope scope $ optimiseBody accs body+ }++extractUpdate ::+ Accs rep ->+ VName ->+ Stms rep ->+ Maybe ((WithAccInput rep, VName, [SubExp], [SubExp]), Stms rep)+extractUpdate accs v stms = do+ (stm, stms') <- stmsHead stms+ case stm of+ Let (Pat [PatElem pe_v _]) _ (BasicOp (UpdateAcc acc is vs))+ | pe_v == v -> do+ acc_input <- M.lookup acc accs+ Just ((acc_input, acc, is, vs), stms')+ _ -> do+ (x, stms'') <- extractUpdate accs v stms'+ pure (x, oneStm stm <> stms'')++mkHistBody :: Accs GPU -> KernelBody GPU -> Maybe (KernelBody GPU, WithAccInput GPU, VName)+mkHistBody accs (KernelBody () stms [Returns rm cs (Var v)]) = do+ ((acc_input, acc, is, vs), stms') <- extractUpdate accs v stms+ pure+ ( KernelBody () stms' $ map (Returns rm cs) is ++ map (Returns rm cs) vs,+ acc_input,+ acc+ )+mkHistBody _ _ = Nothing++withAccLamToHistLam :: MonadFreshNames m => Shape -> Lambda GPU -> m (Lambda GPU)+withAccLamToHistLam shape lam =+ renameLambda $ lam {lambdaParams = drop (shapeRank shape) (lambdaParams lam)}++addArrsToAcc ::+ (MonadBuilder m, Rep m ~ GPU) =>+ SegLevel ->+ Shape ->+ [VName] ->+ VName ->+ m (Exp GPU)+addArrsToAcc lvl shape arrs acc = do+ flat <- newVName "phys_tid"+ gtids <- replicateM (shapeRank shape) (newVName "gtid")+ let space = SegSpace flat $ zip gtids $ shapeDims shape++ (acc', stms) <- localScope (scopeOfSegSpace space) . collectStms $ do+ vs <- forM arrs $ \arr -> do+ arr_t <- lookupType arr+ letSubExp (baseString arr <> "_elem") $+ BasicOp $ Index arr $ fullSlice arr_t $ map (DimFix . Var) gtids+ letExp (baseString acc <> "_upd") $+ BasicOp $ UpdateAcc acc (map Var gtids) vs++ acc_t <- lookupType acc+ pure . Op . SegOp . SegMap lvl space [acc_t] $+ KernelBody () stms [Returns ResultMaySimplify mempty (Var acc')]++flatKernelBody ::+ MonadBuilder m =>+ SegSpace ->+ KernelBody (Rep m) ->+ m (SegSpace, KernelBody (Rep m))+flatKernelBody space kbody = do+ gtid <- newVName "gtid"+ dims_prod <-+ letSubExp "dims_prod"+ =<< foldBinOp (Mul Int64 OverflowUndef) (intConst Int64 1) (segSpaceDims space)++ let space' = SegSpace (segFlat space) [(gtid, dims_prod)]++ kbody_stms <- localScope (scopeOfSegSpace space') . collectStms_ $ do+ let new_inds =+ unflattenIndex (map pe64 (segSpaceDims space)) (pe64 $ Var gtid)+ zipWithM_ letBindNames (map (pure . fst) (unSegSpace space))+ =<< mapM toExp new_inds+ addStms $ kernelBodyStms kbody++ pure (space', kbody {kernelBodyStms = kbody_stms})++optimiseStm :: Accs GPU -> Stm GPU -> OptM (Stms GPU)+-- TODO: this is very restricted currently, but shows the idea.+optimiseStm accs (Let pat aux (WithAcc inputs lam)) = do+ localScope (scopeOfLParams (lambdaParams lam)) $ do+ body' <- optimiseBody accs' $ lambdaBody lam+ let lam' = lam {lambdaBody = body'}+ pure $ oneStm $ Let pat aux $ WithAcc inputs lam'+ where+ acc_names = map paramName $ drop (length inputs) $ lambdaParams lam+ accs' = M.fromList (zip acc_names inputs) <> accs+optimiseStm accs (Let pat aux (Op (SegOp (SegMap lvl space _ kbody))))+ | accs /= mempty,+ Just (kbody', (acc_shape, _, Just (acc_lam, acc_nes)), acc) <-+ mkHistBody accs kbody,+ all primType $ lambdaReturnType acc_lam = runBuilder_ $ do+ hist_dests <- forM acc_nes $ \ne ->+ letExp "hist_dest" $ BasicOp $ Replicate acc_shape ne++ acc_lam' <- withAccLamToHistLam acc_shape acc_lam++ let ts' =+ replicate (shapeRank acc_shape) (Prim int64)+ ++ lambdaReturnType acc_lam+ histop =+ HistOp+ { histShape = acc_shape,+ histRaceFactor = intConst Int64 1,+ histDest = hist_dests,+ histNeutral = acc_nes,+ histOpShape = mempty,+ histOp = acc_lam'+ }++ (space', kbody'') <- flatKernelBody space kbody'++ hist_dest_upd <-+ letTupExp "hist_dest_upd" $ Op $ SegOp $ SegHist lvl space' [histop] ts' kbody''++ addStm . Let pat aux =<< addArrsToAcc lvl acc_shape hist_dest_upd acc+optimiseStm accs (Let pat aux e) =+ oneStm . Let pat aux <$> optimiseExp accs e++optimiseStms :: Accs GPU -> Stms GPU -> OptM (Stms GPU)+optimiseStms accs stms =+ localScope (scopeOf stms) $+ mconcat <$> mapM (optimiseStm accs) (stmsToList stms)++-- | The pass for GPU kernels.+histAccsGPU :: Pass GPU GPU+histAccsGPU =+ Pass "hist accs" "Turn certain accumulations into histograms" $+ intraproceduralTransformation onStms+ where+ onStms scope stms =+ modifyNameSource . runState $+ runReaderT (optimiseStms mempty stms) scope
+ src/Futhark/Optimise/MergeGPUBodies.hs view
@@ -0,0 +1,634 @@+-- |+-- This module implements an optimization pass that merges 'GPUBody' kernels to+-- eliminate memory transactions and reduce the number of kernel launches.+-- This is useful because the "Futhark.Optimise.ReduceDeviceSyncs" pass introduces+-- 'GPUBody' kernels that only execute single statements.+--+-- To merge as many 'GPUBody' kernels as possible, this pass reorders statements+-- with the goal of bringing as many 'GPUBody' statements next to each other in+-- a sequence. Such sequence can then trivially be merged.+module Futhark.Optimise.MergeGPUBodies (mergeGPUBodies) where++import Control.Monad+import Control.Monad.Trans.Class+import Control.Monad.Trans.State.Strict hiding (State)+import Data.Foldable+import qualified Data.IntMap as IM+import Data.IntSet ((\\))+import qualified Data.IntSet as IS+import qualified Data.Map as M+import Data.Maybe (fromMaybe)+import Data.Sequence ((|>))+import qualified Data.Sequence as SQ+import Futhark.Analysis.Alias+import Futhark.Construct (sliceDim)+import Futhark.Error+import Futhark.IR.Aliases+import Futhark.IR.GPU+import Futhark.MonadFreshNames hiding (newName)+import Futhark.Pass++-- | An optimization pass that reorders and merges 'GPUBody' statements to+-- eliminate memory transactions and reduce the number of kernel launches.+mergeGPUBodies :: Pass GPU GPU+mergeGPUBodies =+ Pass+ "merge GPU bodies"+ "Reorder and merge GPUBody constructs to reduce kernels executions."+ $ intraproceduralTransformationWithConsts onStms onFunDef . aliasAnalysis+ where+ onFunDef _ (FunDef entry attrs name types params body) =+ FunDef entry attrs name types params . fst <$> transformBody mempty body+ onStms stms =+ fst <$> transformStms mempty stms++--------------------------------------------------------------------------------+-- COMMON - TYPES --+--------------------------------------------------------------------------------++-- | A set of 'VName' tags that denote all variables that some group of+-- statements depend upon. Those must be computed before the group statements.+type Dependencies = IS.IntSet++-- | A set of 'VName' tags that denote all variables that some group of+-- statements binds.+type Bindings = IS.IntSet++-- | A set of 'VName' tags that denote the root aliases of all arrays that some+-- statement consumes.+type Consumption = IS.IntSet++--------------------------------------------------------------------------------+-- COMMON - HELPERS --+--------------------------------------------------------------------------------++-- | All free variables of a construct as 'Dependencies'.+depsOf :: FreeIn a => a -> Dependencies+depsOf = namesToSet . freeIn++-- | Convert 'Names' to an integer set of name tags.+namesToSet :: Names -> IS.IntSet+namesToSet = IS.fromList . map baseTag . namesToList++--------------------------------------------------------------------------------+-- AD HOC OPTIMIZATION --+--------------------------------------------------------------------------------++-- | Optimize a lambda and determine its dependencies.+transformLambda ::+ AliasTable ->+ Lambda (Aliases GPU) ->+ PassM (Lambda GPU, Dependencies)+transformLambda aliases (Lambda params body types) = do+ (body', deps) <- transformBody aliases body+ pure (Lambda params body' types, deps)++-- | Optimize a body and determine its dependencies.+transformBody ::+ AliasTable ->+ Body (Aliases GPU) ->+ PassM (Body GPU, Dependencies)+transformBody aliases (Body _ stms res) = do+ grp <- evalStateT (foldM_ reorderStm aliases stms >> collapse) initialState++ let stms' = groupStms grp+ let deps = (groupDependencies grp <> depsOf res) \\ groupBindings grp++ pure (Body () stms' res, deps)++-- | Optimize a sequence of statements and determine their dependencies.+transformStms ::+ AliasTable ->+ Stms (Aliases GPU) ->+ PassM (Stms GPU, Dependencies)+transformStms aliases stms = do+ (Body _ stms' _, deps) <- transformBody aliases (Body mempty stms [])+ pure (stms', deps)++-- | Optimizes and reorders a single statement within a sequence while tracking+-- the declaration, observation, and consumption of its dependencies.+-- This creates sequences of GPUBody statements that can be merged into single+-- kernels.+reorderStm :: AliasTable -> Stm (Aliases GPU) -> ReorderM AliasTable+reorderStm aliases (Let pat (StmAux cs attrs _) e) = do+ (e', deps) <- lift (transformExp aliases e)+ let pat' = removePatAliases pat+ let stm' = Let pat' (StmAux cs attrs ()) e'+ let pes' = patElems pat'++ -- Array aliases can be seen as a directed graph where vertices are arrays+ -- (or the names that bind them) and an edge x -> y denotes that x aliases y.+ -- The root aliases of some array A is then the set of arrays that can be+ -- reached from A in graph and which have no edges themselves.+ --+ -- All arrays that share a root alias are considered aliases of each other+ -- and will be consumed if either of them is consumed.+ -- When reordering statements we must ensure that no statement that consumes+ -- an array is moved before any statement that observes one of its aliases.+ --+ -- That is to move statement X before statement Y the set of root aliases of+ -- arrays consumed by X must not overlap with the root aliases of arrays+ -- observed by Y.+ --+ -- We consider the root aliases of Y's observed arrays as part of Y's+ -- dependencies and simply say that the root aliases of arrays consumed by X+ -- must not overlap those.+ --+ -- To move X before Y then the dependencies of X must also not overlap with+ -- the variables bound by Y.++ let observed = namesToSet $ rootAliasesOf (fold $ expAliases e) aliases+ let consumed = namesToSet $ rootAliasesOf (consumedInExp e) aliases+ let usage =+ Usage+ { usageBindings = IS.fromList $ map (baseTag . patElemName) pes',+ usageDependencies = observed <> deps <> depsOf pat' <> depsOf cs+ }++ case e' of+ Op GPUBody {} ->+ moveGPUBody stm' usage consumed+ _ ->+ moveOther stm' usage consumed++ pure $ foldl recordAliases aliases (patElems pat)+ where+ rootAliasesOf names atable =+ let look n = M.findWithDefault (oneName n) n atable+ in foldMap look (namesToList names)++ recordAliases atable pe+ | aliasesOf pe == mempty =+ atable+ | otherwise =+ let root_aliases = rootAliasesOf (aliasesOf pe) atable+ in M.insert (patElemName pe) root_aliases atable++-- | Optimize a single expression and determine its dependencies.+transformExp ::+ AliasTable ->+ Exp (Aliases GPU) ->+ PassM (Exp GPU, Dependencies)+transformExp aliases e =+ case e of+ BasicOp {} -> pure (removeExpAliases e, depsOf e)+ Apply {} -> pure (removeExpAliases e, depsOf e)+ If c tbody fbody dec -> do+ (tbody', t_deps) <- transformBody aliases tbody+ (fbody', f_deps) <- transformBody aliases fbody+ let deps = depsOf c <> t_deps <> f_deps <> depsOf dec+ pure (If c tbody' fbody' dec, deps)+ DoLoop merge lform body -> do+ -- What merge and lform aliases outside the loop is irrelevant as those+ -- cannot be consumed within the loop.+ (body', body_deps) <- transformBody aliases body+ let (params, args) = unzip merge+ let deps = body_deps <> depsOf params <> depsOf args <> depsOf lform++ let scope = scopeOf lform <> scopeOfFParams params+ let bound = IS.fromList $ map baseTag (M.keys scope)+ let deps' = deps \\ bound++ let dummy = DoLoop merge lform (Body (bodyDec body) SQ.empty [])+ let DoLoop merge' lform' _ = removeExpAliases dummy++ pure (DoLoop merge' lform' body', deps')+ WithAcc inputs lambda -> do+ accs <- mapM (transformWithAccInput aliases) inputs+ let (inputs', input_deps) = unzip accs+ -- The lambda parameters are all unique and thus have no aliases.+ (lambda', deps) <- transformLambda aliases lambda+ pure (WithAcc inputs' lambda', deps <> fold input_deps)+ Op {} ->+ -- A GPUBody cannot be nested within other HostOp constructs.+ pure (removeExpAliases e, depsOf e)++-- | Optimize a single WithAcc input and determine its dependencies.+transformWithAccInput ::+ AliasTable ->+ WithAccInput (Aliases GPU) ->+ PassM (WithAccInput GPU, Dependencies)+transformWithAccInput aliases (shape, arrs, op) = do+ (op', deps) <- case op of+ Nothing -> pure (Nothing, mempty)+ Just (f, nes) -> do+ -- The lambda parameters have no aliases.+ (f', deps) <- transformLambda aliases f+ pure (Just (f', nes), deps <> depsOf nes)+ let deps' = deps <> depsOf shape <> depsOf arrs+ pure ((shape, arrs, op'), deps')++--------------------------------------------------------------------------------+-- REORDERING - TYPES --+--------------------------------------------------------------------------------++-- | The monad used to reorder statements within a sequence such that its+-- GPUBody statements can be merged into as few possible kernels.+type ReorderM = StateT State PassM++-- | The state used by a 'ReorderM' monad.+data State = State+ { -- | All statements that already have been processed from the sequence,+ -- divided into alternating groups of non-GPUBody and GPUBody statements.+ -- Groups at even indices only contain non-GPUBody statements. Groups at+ -- odd indices only contain GPUBody statements.+ stateGroups :: Groups,+ stateEquivalents :: EquivalenceTable+ }++-- | A map from variable tags to t'SubExp's returned from within GPUBodies.+type EquivalenceTable = IM.IntMap Entry++-- | An entry in an 'EquivalenceTable'.+data Entry = Entry+ { -- | A value returned from within a GPUBody kernel.+ -- In @let res = gpu { x }@ this is @x@.+ entryValue :: SubExp,+ -- | The type of the 'entryValue'.+ entryType :: Type,+ -- | The name of the variable that binds the return value for 'entryValue'.+ -- In @let res = gpu { x }@ this is @res@.+ entryResult :: VName,+ -- | The index of the group that `entryResult` is bound in.+ entryGroupIdx :: Int,+ -- | If 'False' then the entry key is a variable that binds the same value+ -- as the 'entryValue'. Otherwise it binds an array with an outer dimension+ -- of one whose row equals that value.+ entryStored :: Bool+ }++type Groups = SQ.Seq Group++-- | A group is a subsequence of statements, usually either only GPUBody+-- statements or only non-GPUBody statements. The 'Usage' statistics of those+-- statements are also stored.+data Group = Group+ { -- | The statements of the group.+ groupStms :: Stms GPU,+ -- | The usage statistics of the statements within the group.+ groupUsage :: Usage+ }++-- | Usage statistics for some set of statements.+data Usage = Usage+ { -- | The variables that the statements bind.+ usageBindings :: Bindings,+ -- | The variables that the statements depend upon, i.e. the free variables+ -- of each statement and the root aliases of every array that they observe.+ usageDependencies :: Dependencies+ }++instance Semigroup Group where+ (Group s1 u1) <> (Group s2 u2) = Group (s1 <> s2) (u1 <> u2)++instance Monoid Group where+ mempty = Group {groupStms = mempty, groupUsage = mempty}++instance Semigroup Usage where+ (Usage b1 d1) <> (Usage b2 d2) = Usage (b1 <> b2) (d1 <> d2)++instance Monoid Usage where+ mempty = Usage {usageBindings = mempty, usageDependencies = mempty}++--------------------------------------------------------------------------------+-- REORDERING - FUNCTIONS --+--------------------------------------------------------------------------------++-- | Return the usage bindings of the group.+groupBindings :: Group -> Bindings+groupBindings = usageBindings . groupUsage++-- | Return the usage dependencies of the group.+groupDependencies :: Group -> Dependencies+groupDependencies = usageDependencies . groupUsage++-- | An initial state to use when running a 'ReorderM' monad.+initialState :: State+initialState =+ State+ { stateGroups = SQ.singleton mempty,+ stateEquivalents = mempty+ }++-- | Modify the groups that the sequence has been split into so far.+modifyGroups :: (Groups -> Groups) -> ReorderM ()+modifyGroups f =+ modify $ \st -> st {stateGroups = f (stateGroups st)}++-- | Remove these keys from the equivalence table.+removeEquivalents :: IS.IntSet -> ReorderM ()+removeEquivalents keys =+ modify $ \st ->+ let eqs' = stateEquivalents st `IM.withoutKeys` keys+ in st {stateEquivalents = eqs'}++-- | Add an entry to the equivalence table.+recordEquivalent :: VName -> Entry -> ReorderM ()+recordEquivalent n entry =+ modify $ \st ->+ let eqs = stateEquivalents st+ eqs' = IM.insert (baseTag n) entry eqs+ in st {stateEquivalents = eqs'}++-- | Moves a GPUBody statement to the furthest possible group of the statement+-- sequence, possibly a new group at the end of sequence.+--+-- To simplify consumption handling a GPUBody is not allowed to merge with a+-- kernel whose result it consumes. Such GPUBody may therefore not be moved+-- into the same group as such kernel.+moveGPUBody :: Stm GPU -> Usage -> Consumption -> ReorderM ()+moveGPUBody stm usage consumed = do+ -- Replace dependencies with their GPUBody result equivalents.+ eqs <- gets stateEquivalents+ let g i = maybe i (baseTag . entryResult) (IM.lookup i eqs)+ let deps' = IS.map g (usageDependencies usage)+ let usage' = usage {usageDependencies = deps'}++ -- Move the GPUBody.+ grps <- gets stateGroups+ let f = groupBlocks usage' consumed+ let idx = fromMaybe 1 (SQ.findIndexR f grps)+ let idx' = case idx `mod` 2 of+ 0 -> idx + 1+ _ | consumes idx grps -> idx + 2+ _ -> idx+ modifyGroups $ moveToGrp (stm, usage) idx'++ -- Record the kernel equivalents of the bound results.+ let pes = patElems (stmPat stm)+ let Op (GPUBody _ (Body _ _ res)) = stmExp stm+ mapM_ (stores idx') (zip pes (map resSubExp res))+ where+ consumes idx grps+ | Just grp <- SQ.lookup idx grps =+ not $ IS.disjoint (groupBindings grp) consumed+ | otherwise =+ False++ stores idx (PatElem n t, se)+ | Just row_t <- peelArray 1 t =+ recordEquivalent n $ Entry se row_t n idx True+ | otherwise =+ recordEquivalent n $ Entry se t n idx False++-- | Moves a non-GPUBody statement to the furthest possible groups of the+-- statement sequence, possibly a new group at the end of sequence.+moveOther :: Stm GPU -> Usage -> Consumption -> ReorderM ()+moveOther stm usage consumed = do+ grps <- gets stateGroups+ let f = groupBlocks usage consumed+ let idx = fromMaybe 0 (SQ.findIndexR f grps)+ let idx' = ((idx + 1) `div` 2) * 2+ modifyGroups $ moveToGrp (stm, usage) idx'+ recordEquivalentsOf stm idx'++-- | @recordEquivalentsOf stm idx@ records the GPUBody result and/or return+-- value that @stm@ is equivalent to. @idx@ is the index of the group that @stm@+-- belongs to.+--+-- A GPUBody can have a dependency substituted with a result equivalent if it+-- merges with the source GPUBody, allowing it to be moved beyond the binding+-- site of that dependency.+--+-- To guarantee that a GPUBody which moves beyond a dependency also merges with+-- its source GPUBody, equivalents are only allowed to be recorded for results+-- bound within the group at index @idx-1@.+recordEquivalentsOf :: Stm GPU -> Int -> ReorderM ()+recordEquivalentsOf stm idx = do+ eqs <- gets stateEquivalents+ case stm of+ Let (Pat [PatElem x _]) _ (BasicOp (SubExp (Var n)))+ | Just entry <- IM.lookup (baseTag n) eqs,+ entryGroupIdx entry == idx - 1 ->+ recordEquivalent x entry+ Let (Pat [PatElem x _]) _ (BasicOp (Index arr slice))+ | Just entry <- IM.lookup (baseTag arr) eqs,+ entryGroupIdx entry == idx - 1,+ Slice (DimFix i : dims) <- slice,+ i == intConst Int64 0,+ dims == map sliceDim (arrayDims $ entryType entry) ->+ recordEquivalent x (entry {entryStored = False})+ _ -> pure ()++-- | Does this group block a statement with this usage/consumption statistics+-- from being moved past it?+groupBlocks :: Usage -> Consumption -> Group -> Bool+groupBlocks usage consumed grp =+ let bound = groupBindings grp+ deps = groupDependencies grp++ used = usageDependencies usage+ in not (IS.disjoint bound used && IS.disjoint deps consumed)++-- | @moveToGrp stm idx grps@ moves @stm@ into the group at index @idx@ of+-- @grps@.+moveToGrp :: (Stm GPU, Usage) -> Int -> Groups -> Groups+moveToGrp stm idx grps+ | idx >= SQ.length grps =+ moveToGrp stm idx (grps |> mempty)+ | otherwise =+ SQ.adjust' (stm `moveTo`) idx grps++-- | Adds the statement and its usage statistics to the group.+moveTo :: (Stm GPU, Usage) -> Group -> Group+moveTo (stm, usage) grp =+ grp+ { groupStms = groupStms grp |> stm,+ groupUsage = groupUsage grp <> usage+ }++--------------------------------------------------------------------------------+-- MERGING GPU BODIES - TYPES --+--------------------------------------------------------------------------------++-- | The monad used for rewriting a GPUBody to use the t'SubExp's that are+-- returned from kernels it is merged with rather than the results that they+-- bind.+--+-- The state is a prologue of statements to be added at the beginning of the+-- rewritten kernel body.+type RewriteM = StateT (Stms GPU) ReorderM++--------------------------------------------------------------------------------+-- MERGING GPU BODIES - FUNCTIONS --+--------------------------------------------------------------------------------++-- | Collapses the processed sequence of groups into a single group and returns+-- it, merging GPUBody groups into single kernels in the process.+collapse :: ReorderM Group+collapse = do+ grps <- zip (cycle [False, True]) . toList <$> gets stateGroups+ grp <- foldM clps mempty grps++ modify $ \st -> st {stateGroups = SQ.singleton grp}+ pure grp+ where+ clps grp0 (gpu_bodies, Group stms usage) = do+ grp1 <-+ if gpu_bodies+ then Group <$> mergeKernels stms <*> pure usage+ else pure (Group stms usage)+ -- Remove equivalents that no longer are relevant for rewriting GPUBody+ -- kernels. This ensures that they are not substituted in later kernels+ -- where the replacement variables might not be in scope.+ removeEquivalents (groupBindings grp1)+ pure (grp0 <> grp1)++-- | Merges a sequence of GPUBody statements into a single kernel.+mergeKernels :: Stms GPU -> ReorderM (Stms GPU)+mergeKernels stms+ | SQ.length stms < 2 =+ pure stms+ | otherwise =+ SQ.singleton <$> foldrM merge empty stms+ where+ empty = Let mempty (StmAux mempty mempty ()) noop+ noop = Op (GPUBody [] (Body () SQ.empty []))++ merge :: Stm GPU -> Stm GPU -> ReorderM (Stm GPU)+ merge stm0 stm1+ | Let pat0 (StmAux cs0 attrs0 _) (Op (GPUBody types0 body)) <- stm0,+ Let pat1 (StmAux cs1 attrs1 _) (Op (GPUBody types1 body1)) <- stm1 =+ do+ Body _ stms0 res0 <- execRewrite (rewriteBody body)+ let Body _ stms1 res1 = body1++ pat' = pat0 <> pat1+ aux' = StmAux (cs0 <> cs1) (attrs0 <> attrs1) ()+ types' = types0 ++ types1+ body' = Body () (stms0 <> stms1) (res0 <> res1)+ in pure (Let pat' aux' (Op (GPUBody types' body')))+ merge _ _ =+ compilerBugS "mergeGPUBodies: cannot merge non-GPUBody statements"++-- | Perform a rewrite and finish it by adding the rewrite prologue to the start+-- of the body.+execRewrite :: RewriteM (Body GPU) -> ReorderM (Body GPU)+execRewrite m = evalStateT m' SQ.empty+ where+ m' = do+ Body _ stms res <- m+ prologue <- get+ pure (Body () (prologue <> stms) res)++-- | Return the equivalence table.+equivalents :: RewriteM EquivalenceTable+equivalents = lift (gets stateEquivalents)++rewriteBody :: Body GPU -> RewriteM (Body GPU)+rewriteBody (Body _ stms res) =+ Body () <$> rewriteStms stms <*> rewriteResult res++rewriteStms :: Stms GPU -> RewriteM (Stms GPU)+rewriteStms = mapM rewriteStm++rewriteStm :: Stm GPU -> RewriteM (Stm GPU)+rewriteStm (Let (Pat pes) (StmAux cs attrs _) e) = do+ pat' <- Pat <$> mapM rewritePatElem pes+ cs' <- rewriteCerts cs+ e' <- rewriteExp e+ pure $ Let pat' (StmAux cs' attrs ()) e'++rewritePatElem :: PatElem Type -> RewriteM (PatElem Type)+rewritePatElem (PatElem n t) =+ PatElem n <$> rewriteType t++rewriteExp :: Exp GPU -> RewriteM (Exp GPU)+rewriteExp e = do+ eqs <- equivalents+ case e of+ BasicOp (Index arr slice)+ | Just entry <- IM.lookup (baseTag arr) eqs,+ DimFix idx : dims <- unSlice slice,+ idx == intConst Int64 0 ->+ let se = entryValue entry+ in pure . BasicOp $ case (dims, se) of+ ([], _) -> SubExp se+ (_, Var src) -> Index src (Slice dims)+ _ -> compilerBugS "rewriteExp: bad equivalence entry"+ _ -> mapExpM rewriter e+ where+ rewriter =+ Mapper+ { mapOnSubExp = rewriteSubExp,+ mapOnBody = const rewriteBody,+ mapOnVName = rewriteName,+ mapOnRetType = rewriteExtType,+ mapOnBranchType = rewriteExtType,+ mapOnFParam = rewriteParam,+ mapOnLParam = rewriteParam,+ mapOnOp = const opError+ }++ opError = compilerBugS "rewriteExp: unhandled HostOp in GPUBody"++rewriteResult :: Result -> RewriteM Result+rewriteResult = mapM rewriteSubExpRes++rewriteSubExpRes :: SubExpRes -> RewriteM SubExpRes+rewriteSubExpRes (SubExpRes cs se) =+ SubExpRes <$> rewriteCerts cs <*> rewriteSubExp se++rewriteCerts :: Certs -> RewriteM Certs+rewriteCerts (Certs cs) =+ Certs <$> mapM rewriteName cs++rewriteType :: TypeBase Shape u -> RewriteM (TypeBase Shape u)+-- Note: mapOnType also maps the VName token of accumulators+rewriteType = mapOnType rewriteSubExp++rewriteExtType :: TypeBase ExtShape u -> RewriteM (TypeBase ExtShape u)+-- Note: mapOnExtType also maps the VName token of accumulators+rewriteExtType = mapOnExtType rewriteSubExp++rewriteParam :: Param (TypeBase Shape u) -> RewriteM (Param (TypeBase Shape u))+rewriteParam (Param attrs n t) =+ Param attrs n <$> rewriteType t++rewriteSubExp :: SubExp -> RewriteM SubExp+rewriteSubExp (Constant c) = pure (Constant c)+rewriteSubExp (Var n) = do+ eqs <- equivalents+ case IM.lookup (baseTag n) eqs of+ Nothing -> pure (Var n)+ Just (Entry se _ _ _ False) -> pure se+ Just (Entry se t _ _ True) -> Var <$> asArray se t++rewriteName :: VName -> RewriteM VName+rewriteName n = do+ se <- rewriteSubExp (Var n)+ case se of+ Var n' -> pure n'+ Constant c -> referConst c++-- | @asArray se t@ adds @let x = [se]@ to the rewrite prologue and returns the+-- name of @x@. @t@ is the type of @se@.+asArray :: SubExp -> Type -> RewriteM VName+asArray se row_t = do+ name <- newName "arr"+ let t = row_t `arrayOfRow` intConst Int64 1++ let pat = Pat [PatElem name t]+ let aux = StmAux mempty mempty ()+ let e = BasicOp (ArrayLit [se] row_t)++ modify (|> Let pat aux e)+ pure name++-- | @referConst c@ adds @let x = c@ to the rewrite prologue and returns the+-- name of @x@.+referConst :: PrimValue -> RewriteM VName+referConst c = do+ name <- newName "cnst"+ let t = Prim (primValueType c)++ let pat = Pat [PatElem name t]+ let aux = StmAux mempty mempty ()+ let e = BasicOp (SubExp $ Constant c)++ modify (|> Let pat aux e)+ pure name++-- | Produce a fresh name, using the given string as a template.+newName :: String -> RewriteM VName+newName s = lift $ lift (newNameFromString s)
+ src/Futhark/Optimise/ReduceDeviceSyncs.hs view
@@ -0,0 +1,848 @@+-- | This module implements an optimization that migrates host+-- statements into 'GPUBody' kernels to reduce the number of+-- host-device synchronizations that occur when a scalar variable is+-- written to or read from device memory. Which statements that should+-- be migrated are determined by a 'MigrationTable' produced by the+-- "Futhark.Optimise.ReduceDeviceSyncs.MigrationTable" module; this module+-- merely performs the migration and rewriting dictated by that table.+module Futhark.Optimise.ReduceDeviceSyncs (reduceDeviceSyncs) where++import Control.Monad+import Control.Monad.Trans.Class+import qualified Control.Monad.Trans.Reader as R+import Control.Monad.Trans.State.Strict hiding (State)+import Control.Parallel.Strategies (parMap, rpar)+import Data.Foldable+import qualified Data.IntMap.Strict as IM+import Data.List (unzip4, zip4)+import qualified Data.Map.Strict as M+import Data.Sequence ((<|), (><), (|>))+import qualified Data.Text as T+import Futhark.Construct (fullSlice, sliceDim)+import Futhark.Error+import qualified Futhark.FreshNames as FN+import Futhark.IR.GPU+import Futhark.MonadFreshNames (VNameSource, getNameSource, putNameSource)+import Futhark.Optimise.ReduceDeviceSyncs.MigrationTable+import Futhark.Pass+import Futhark.Transform.Substitute++-- | An optimization pass that migrates host statements into 'GPUBody' kernels+-- to reduce the number of host-device synchronizations.+reduceDeviceSyncs :: Pass GPU GPU+reduceDeviceSyncs =+ Pass+ "reduce device synchronizations"+ "Move host statements to device to reduce blocking memory operations."+ run+ where+ run prog = do+ ns <- getNameSource+ let mt = analyseProg prog+ let st = initialState ns+ let (prog', st') = R.runReader (runStateT (optimizeProgram prog) st) mt+ putNameSource (stateNameSource st')+ pure prog'++--------------------------------------------------------------------------------+-- AD HOC OPTIMIZATION --+--------------------------------------------------------------------------------++-- | Optimize a whole program. The type signatures of top-level functions will+-- remain unchanged.+optimizeProgram :: Prog GPU -> ReduceM (Prog GPU)+optimizeProgram (Prog consts funs) = do+ consts' <- optimizeStms consts+ funs' <- sequence $ parMap rpar optimizeFunDef funs+ pure (Prog consts' funs')++-- | Optimize a function definition. Its type signature will remain unchanged.+optimizeFunDef :: FunDef GPU -> ReduceM (FunDef GPU)+optimizeFunDef fd = do+ let body = funDefBody fd+ stms' <- optimizeStms (bodyStms body)+ pure $ fd {funDefBody = body {bodyStms = stms'}}++-- | Optimize a body. Scalar results may be replaced with single-element arrays.+optimizeBody :: Body GPU -> ReduceM (Body GPU)+optimizeBody (Body _ stms res) = do+ stms' <- optimizeStms stms+ res' <- resolveResult res+ pure (Body () stms' res')++-- | Optimize a sequence of statements.+optimizeStms :: Stms GPU -> ReduceM (Stms GPU)+optimizeStms = foldM optimizeStm mempty++-- | Optimize a single statement, rewriting it into one or more statements to+-- be appended to the provided 'Stms'. Only variables with continued host usage+-- will remain in scope if their statement is migrated.+optimizeStm :: Stms GPU -> Stm GPU -> ReduceM (Stms GPU)+optimizeStm out stm = do+ move <- asks (shouldMoveStm stm)+ if move+ then moveStm out stm+ else case stmExp stm of+ BasicOp (Update safety arr slice (Var v))+ | Just _ <- sliceIndices slice -> do+ -- Rewrite the Update if its write value has been migrated. Copying+ -- is faster than doing a synchronous write, so we use the device+ -- array even if the value has been made available to the host.+ dev <- storedScalar (Var v)+ case dev of+ Nothing -> pure (out |> stm)+ Just dst -> do+ -- Transform the single element Update into a slice Update.+ let dims = unSlice slice+ let (outer, [DimFix i]) = splitAt (length dims - 1) dims+ let one = intConst Int64 1+ let slice' = Slice $ outer ++ [DimSlice i one one]+ let e = BasicOp (Update safety arr slice' (Var dst))+ let stm' = stm {stmExp = e}++ pure (out |> stm')+ BasicOp (Replicate (Shape dims) (Var v))+ | Pat [PatElem n arr_t] <- stmPat stm -> do+ -- A Replicate can be rewritten to not require its replication value+ -- to be available on host. If its value is migrated the Replicate+ -- thus needs to be transformed.+ --+ -- If the inner dimension of the replication array is one then the+ -- rewrite can be performed more efficiently than the general case.+ v' <- resolveName v+ let v_kept_on_device = v /= v'++ gpubody_ok <- gets stateGPUBodyOk++ case v_kept_on_device of+ False -> pure (out |> stm)+ True+ | all (== intConst Int64 1) dims,+ Just t' <- peelArray 1 arr_t,+ gpubody_ok -> do+ let n' = VName (baseName n `withSuffix` "_inner") 0+ let pat' = Pat [PatElem n' t']+ let e' = BasicOp $ Replicate (Shape $ tail dims) (Var v)+ let stm' = Let pat' (stmAux stm) e'++ -- `gpu { v }` is slightly faster than `replicate 1 v` and+ -- can merge with the GPUBody that v was computed by.+ gpubody <- inGPUBody (rewriteStm stm')+ pure (out |> gpubody {stmPat = stmPat stm})+ True+ | last dims == intConst Int64 1 ->+ let e' = BasicOp $ Replicate (Shape $ init dims) (Var v')+ stm' = stm {stmExp = e'}+ in pure (out |> stm')+ True -> do+ n' <- newName n+ -- v_kept_on_device implies that v is a scalar.+ let dims' = dims ++ [intConst Int64 1]+ let arr_t' = Array (elemType arr_t) (Shape dims') NoUniqueness+ let pat' = Pat [PatElem n' arr_t']+ let e' = BasicOp $ Replicate (Shape dims) (Var v')+ let repl = Let pat' (stmAux stm) e'++ let aux = StmAux mempty mempty ()+ let slice = map sliceDim (arrayDims arr_t)+ let slice' = slice ++ [DimFix $ intConst Int64 0]+ let idx = BasicOp $ Index n' (Slice slice')+ let index = Let (stmPat stm) aux idx++ pure (out |> repl |> index)+ BasicOp {} ->+ pure (out |> stm)+ Apply {} ->+ pure (out |> stm)+ If cond (Body _ tstms0 tres) (Body _ fstms0 fres) (IfDec btypes sort) ->+ do+ -- Rewrite branches.+ tstms1 <- optimizeStms tstms0+ fstms1 <- optimizeStms fstms0++ -- Ensure return values and types match if one or both branches+ -- return a result that now reside on device.+ let bmerge (res, tstms, fstms) (pe, tr, fr, bt) =+ do+ let onHost (Var v) = (v ==) <$> resolveName v+ onHost _ = pure True++ tr_on_host <- onHost (resSubExp tr)+ fr_on_host <- onHost (resSubExp fr)++ if tr_on_host && fr_on_host+ then -- No result resides on device ==> nothing to do.+ pure ((pe, tr, fr, bt) : res, tstms, fstms)+ else -- Otherwise, ensure both results are migrated.+ do+ let t = patElemType pe+ (tstms', tarr) <- storeScalar tstms (resSubExp tr) t+ (fstms', farr) <- storeScalar fstms (resSubExp fr) t++ pe' <- arrayizePatElem pe+ let bt' = staticShapes1 (patElemType pe')+ let tr' = tr {resSubExp = Var tarr}+ let fr' = fr {resSubExp = Var farr}+ pure ((pe', tr', fr', bt') : res, tstms', fstms')++ let pes = patElems (stmPat stm)+ let zipped = zip4 pes tres fres btypes+ (zipped', tstms2, fstms2) <- foldM bmerge ([], tstms1, fstms1) zipped+ let (pes', tres', fres', btypes') = unzip4 (reverse zipped')++ -- Rewrite statement.+ let tbranch' = Body () tstms2 tres'+ let fbranch' = Body () fstms2 fres'+ let e' = If cond tbranch' fbranch' (IfDec btypes' sort)+ let stm' = Let (Pat pes') (stmAux stm) e'++ -- Read migrated scalars that are used on host.+ foldM addRead (out |> stm') (zip pes pes')+ DoLoop ps lf b -> do+ -- Enable the migration of for-in loop variables.+ (params, lform, body) <- rewriteForIn (ps, lf, b)++ -- Update statement bound variables and parameters if their values+ -- have been migrated to device.+ let lmerge (res, stms) (pe, (Param _ pn pt, pval), MoveToDevice) = do+ -- Rewrite the bound variable.+ pe' <- arrayizePatElem pe++ -- Move the initial value to device if not already there.+ (stms', arr) <- storeScalar stms pval (fromDecl pt)++ -- Rewrite the parameter.+ pn' <- newName pn+ let pt' = toDecl (patElemType pe') Nonunique+ let pval' = Var arr+ let param' = (Param mempty pn' pt', pval')++ -- Record the migration.+ Ident pn (fromDecl pt) `movedTo` pn'++ pure ((pe', param') : res, stms')+ lmerge _ (_, _, UsedOnHost) =+ -- Initial loop parameter value and loop result should have+ -- been made available on host instead.+ compilerBugS "optimizeStm: unhandled host usage of loop param"+ lmerge (res, stms) (pe, param, StayOnHost) =+ pure ((pe, param) : res, stms)++ mt <- ask++ let pes = patElems (stmPat stm)+ let mss = map (\(Param _ n _, _) -> statusOf n mt) params+ (zipped', out') <- foldM lmerge ([], out) (zip3 pes params mss)+ let (pes', params') = unzip (reverse zipped')++ -- Rewrite statement.+ body' <- optimizeBody body+ let e' = DoLoop params' lform body'+ let stm' = Let (Pat pes') (stmAux stm) e'++ -- Read migrated scalars that are used on host.+ foldM addRead (out' |> stm') (zip pes pes')+ WithAcc inputs lmd -> do+ let getAcc (Acc a _ _ _) = a+ getAcc _ =+ compilerBugS+ "Type error: WithAcc expression did not return accumulator."++ let accs = zipWith (\t i -> (getAcc t, i)) (lambdaReturnType lmd) inputs+ inputs' <- mapM (uncurry optimizeWithAccInput) accs++ let body = lambdaBody lmd+ stms' <- optimizeStms (bodyStms body)++ let rewrite (SubExpRes certs se, t, pe) =+ do+ se' <- resolveSubExp se+ if se == se'+ then pure (SubExpRes certs se, t, pe)+ else do+ pe' <- arrayizePatElem pe+ let t' = patElemType pe'+ pure (SubExpRes certs se', t', pe')++ -- Rewrite non-accumulator results that have been migrated.+ --+ -- Accumulator return values do not map to arrays one-to-one but+ -- one-to-many. They are not transformed however and can be mapped+ -- as a no-op.+ let len = length inputs+ let (res0, res1) = splitAt len (bodyResult body)+ let (rts0, rts1) = splitAt len (lambdaReturnType lmd)+ let pes = patElems (stmPat stm)+ let (pes0, pes1) = splitAt (length pes - length res1) pes+ (res1', rts1', pes1') <- unzip3 <$> mapM rewrite (zip3 res1 rts1 pes1)+ let res' = res0 ++ res1'+ let rts' = rts0 ++ rts1'+ let pes' = pes0 ++ pes1'++ -- Rewrite statement.+ let body' = Body () stms' res'+ let lmd' = lmd {lambdaBody = body', lambdaReturnType = rts'}+ let e' = WithAcc inputs' lmd'+ let stm' = Let (Pat pes') (stmAux stm) e'++ -- Read migrated scalars that are used on host.+ foldM addRead (out |> stm') (zip pes pes')+ Op op -> do+ op' <- optimizeHostOp op+ pure (out |> stm {stmExp = Op op'})+ where+ addRead stms (pe@(PatElem n _), PatElem dev _)+ | n == dev = pure stms+ | otherwise = pe `migratedTo` (dev, stms)++-- | Rewrite a for-in loop such that relevant source array reads can be delayed.+rewriteForIn ::+ ([(FParam GPU, SubExp)], LoopForm GPU, Body GPU) ->+ ReduceM ([(FParam GPU, SubExp)], LoopForm GPU, Body GPU)+rewriteForIn loop@(_, WhileLoop {}, _) =+ pure loop+rewriteForIn (params, ForLoop i t n elems, body) = do+ mt <- ask+ let (elems', stms') = foldr (inline mt) ([], bodyStms body) elems+ pure (params, ForLoop i t n elems', body {bodyStms = stms'})+ where+ inline mt (x, arr) (arrs, stms)+ | pn <- paramName x,+ not (usedOnHost pn mt) =+ let pt = typeOf x+ stm = bind (PatElem pn pt) (BasicOp $ index arr pt)+ in (arrs, stm <| stms)+ | otherwise =+ ((x, arr) : arrs, stms)++ index arr of_type =+ Index arr $ Slice $ DimFix (Var i) : map sliceDim (arrayDims of_type)++-- | Optimize an accumulator input. The 'VName' is the accumulator token.+optimizeWithAccInput :: VName -> WithAccInput GPU -> ReduceM (WithAccInput GPU)+optimizeWithAccInput _ (shape, arrs, Nothing) = pure (shape, arrs, Nothing)+optimizeWithAccInput acc (shape, arrs, Just (op, nes)) = do+ device_only <- asks (shouldMove acc)+ if device_only+ then do+ op' <- addReadsToLambda op+ pure (shape, arrs, Just (op', nes))+ else do+ let body = lambdaBody op+ -- To pass type check neither parameters nor results can change.+ --+ -- op may be used on both host and device so we must avoid introducing+ -- any GPUBody statements.+ stms' <- noGPUBody $ optimizeStms (bodyStms body)+ let op' = op {lambdaBody = body {bodyStms = stms'}}+ pure (shape, arrs, Just (op', nes))++-- | Optimize a host operation. 'Index' statements are added to kernel code+-- that depends on migrated scalars.+optimizeHostOp :: HostOp GPU op -> ReduceM (HostOp GPU op)+optimizeHostOp (SegOp (SegMap lvl space types kbody)) =+ SegOp . SegMap lvl space types <$> addReadsToKernelBody kbody+optimizeHostOp (SegOp (SegRed lvl space ops types kbody)) = do+ ops' <- mapM addReadsToSegBinOp ops+ SegOp . SegRed lvl space ops' types <$> addReadsToKernelBody kbody+optimizeHostOp (SegOp (SegScan lvl space ops types kbody)) = do+ ops' <- mapM addReadsToSegBinOp ops+ SegOp . SegScan lvl space ops' types <$> addReadsToKernelBody kbody+optimizeHostOp (SegOp (SegHist lvl space ops types kbody)) = do+ ops' <- mapM addReadsToHistOp ops+ SegOp . SegHist lvl space ops' types <$> addReadsToKernelBody kbody+optimizeHostOp (SizeOp op) =+ pure (SizeOp op)+optimizeHostOp OtherOp {} =+ -- These should all have been taken care of in the unstreamGPU pass.+ compilerBugS "optimizeHostOp: unhandled OtherOp"+optimizeHostOp (GPUBody types body) =+ GPUBody types <$> addReadsToBody body++--------------------------------------------------------------------------------+-- COMMON HELPERS --+--------------------------------------------------------------------------------++-- | Append the given string to a name.+withSuffix :: Name -> String -> Name+withSuffix name sfx = nameFromText $ T.append (nameToText name) (T.pack sfx)++--------------------------------------------------------------------------------+-- MIGRATION - TYPES --+--------------------------------------------------------------------------------++-- | The monad used to perform migration-based synchronization reductions.+type ReduceM = StateT State (R.Reader MigrationTable)++-- | The state used by a 'ReduceM' monad.+data State = State+ { -- | A source to generate new 'VName's from.+ stateNameSource :: VNameSource,+ -- | A table of variables in the original program which have been migrated+ -- to device. Each variable maps to a tuple that describes:+ -- * 'baseName' of the original variable.+ -- * Type of the original variable.+ -- * Name of the single element array holding the migrated value.+ -- * Whether the original variable still can be used on the host.+ stateMigrated :: IM.IntMap (Name, Type, VName, Bool),+ -- | Whether non-migration optimizations may introduce 'GPUBody' kernels at+ -- the current location.+ stateGPUBodyOk :: Bool+ }++--------------------------------------------------------------------------------+-- MIGRATION - PRIMITIVES --+--------------------------------------------------------------------------------++-- | An initial state to use when running a 'ReduceM' monad.+initialState :: VNameSource -> State+initialState ns =+ State+ { stateNameSource = ns,+ stateMigrated = mempty,+ stateGPUBodyOk = True+ }++-- | Retrieve a function of the current environment.+asks :: (MigrationTable -> a) -> ReduceM a+asks = lift . R.asks++-- | Fetch the value of the environment.+ask :: ReduceM MigrationTable+ask = lift R.ask++-- | Perform non-migration optimizations without introducing any GPUBody+-- kernels.+noGPUBody :: ReduceM a -> ReduceM a+noGPUBody m = do+ prev <- gets stateGPUBodyOk+ modify $ \st -> st {stateGPUBodyOk = False}+ res <- m+ modify $ \st -> st {stateGPUBodyOk = prev}+ pure res++-- | Produce a fresh name, using the given name as a template.+newName :: VName -> ReduceM VName+newName n = do+ st <- get+ let ns = stateNameSource st+ let (n', ns') = FN.newName ns n+ put (st {stateNameSource = ns'})+ pure n'++-- | Create a 'PatElem' that binds the array of a migrated variable binding.+arrayizePatElem :: PatElem Type -> ReduceM (PatElem Type)+arrayizePatElem (PatElem n t) = do+ let name = baseName n `withSuffix` "_dev"+ dev <- newName (VName name 0)+ let dev_t = t `arrayOfRow` intConst Int64 1+ pure (PatElem dev dev_t)++-- | @x `movedTo` arr@ registers that the value of @x@ has been migrated to+-- @arr[0]@.+movedTo :: Ident -> VName -> ReduceM ()+movedTo = recordMigration False++-- | @x `aliasedBy` arr@ registers that the value of @x@ also is available on+-- device as @arr[0]@.+aliasedBy :: Ident -> VName -> ReduceM ()+aliasedBy = recordMigration True++-- | @recordMigration host x arr@ records the migration of variable @x@ to+-- @arr[0]@. If @host@ then the original binding can still be used on host.+recordMigration :: Bool -> Ident -> VName -> ReduceM ()+recordMigration host (Ident x t) arr =+ modify $ \st ->+ let migrated = stateMigrated st+ entry = (baseName x, t, arr, host)+ migrated' = IM.insert (baseTag x) entry migrated+ in st {stateMigrated = migrated'}++-- | @pe `migratedTo` (dev, stms)@ registers that the variable @pe@ in the+-- original program has been migrated to @dev@ and rebinds the variable if+-- deemed necessary, adding an index statement to the given statements.+migratedTo :: PatElem Type -> (VName, Stms GPU) -> ReduceM (Stms GPU)+migratedTo pe (dev, stms) = do+ used <- asks (usedOnHost $ patElemName pe)+ if used+ then patElemIdent pe `aliasedBy` dev >> pure (stms |> bind pe (eIndex dev))+ else patElemIdent pe `movedTo` dev >> pure stms++-- | @useScalar stms n@ returns a variable that binds the result bound by @n@+-- in the original program. If the variable has been migrated to device and have+-- not been copied back to host a new variable binding will be added to the+-- provided statements and be returned.+useScalar :: Stms GPU -> VName -> ReduceM (Stms GPU, VName)+useScalar stms n = do+ entry <- IM.lookup (baseTag n) <$> gets stateMigrated+ case entry of+ Nothing ->+ pure (stms, n)+ Just (_, _, _, True) ->+ pure (stms, n)+ Just (name, t, arr, _) ->+ do+ n' <- newName (VName name 0)+ let stm = bind (PatElem n' t) (eIndex arr)+ pure (stms |> stm, n')++-- | Create an expression that reads the first element of a 1-dimensional array.+eIndex :: VName -> Exp GPU+eIndex arr = BasicOp $ Index arr (Slice [DimFix $ intConst Int64 0])++-- | A shorthand for binding a single variable to an expression.+bind :: PatElem Type -> Exp GPU -> Stm GPU+bind pe = Let (Pat [pe]) (StmAux mempty mempty ())++-- | Returns the array alias of @se@ if it is a variable that has been migrated+-- to device. Otherwise returns @Nothing@.+storedScalar :: SubExp -> ReduceM (Maybe VName)+storedScalar (Constant _) = pure Nothing+storedScalar (Var n) = do+ entry <- IM.lookup (baseTag n) <$> gets stateMigrated+ pure $ fmap (\(_, _, arr, _) -> arr) entry++-- | @storeScalar stms se t@ returns a variable that binds a single element+-- array that contains the value of @se@ in the original program. If @se@ is a+-- variable that has been migrated to device, its existing array alias will be+-- used. Otherwise a new variable binding will be added to the provided+-- statements and be returned. @t@ is the type of @se@.+storeScalar :: Stms GPU -> SubExp -> Type -> ReduceM (Stms GPU, VName)+storeScalar stms se t = do+ entry <- case se of+ Var n -> IM.lookup (baseTag n) <$> gets stateMigrated+ _ -> pure Nothing+ case entry of+ Just (_, _, arr, _) -> pure (stms, arr)+ Nothing -> do+ -- How to most efficiently create an array containing the given value+ -- depends on whether it is a variable or a constant. Creating a constant+ -- array is a runtime copy of static memory, while creating an array that+ -- contains a variable results in a synchronous write. The latter is thus+ -- replaced with either a mergeable GPUBody kernel or a Replicate.+ --+ -- Whether it makes sense to hoist arrays out of bodies to enable CSE is+ -- left to the simplifier to figure out. Duplicates will be eliminated if+ -- a scalar is stored multiple times within a body.+ --+ -- TODO: Enable the simplifier to hoist non-consumed, non-returned arrays+ -- out of top-level function definitions. All constant arrays+ -- produced here are in principle top-level hoistable.+ gpubody_ok <- gets stateGPUBodyOk+ case se of+ Var n | gpubody_ok -> do+ n' <- newName n+ let stm = bind (PatElem n' t) (BasicOp $ SubExp se)++ gpubody <- inGPUBody (pure stm)+ let dev = patElemName $ head $ patElems (stmPat gpubody)++ pure (stms |> gpubody, dev)+ Var n -> do+ pe <- arrayizePatElem (PatElem n t)+ let shape = Shape [intConst Int64 1]+ let stm = bind pe (BasicOp $ Replicate shape se)+ pure (stms |> stm, patElemName pe)+ _ -> do+ let n = VName (nameFromString "const") 0+ pe <- arrayizePatElem (PatElem n t)+ let stm = bind pe (BasicOp $ ArrayLit [se] t)+ pure (stms |> stm, patElemName pe)++-- | Map a variable name to itself or, if the variable no longer can be used on+-- host, the name of a single element array containing its value.+resolveName :: VName -> ReduceM VName+resolveName n = do+ entry <- IM.lookup (baseTag n) <$> gets stateMigrated+ case entry of+ Nothing -> pure n+ Just (_, _, _, True) -> pure n+ Just (_, _, arr, _) -> pure arr++-- | Like 'resolveName' but for a t'SubExp'. Constants are mapped to themselves.+resolveSubExp :: SubExp -> ReduceM SubExp+resolveSubExp (Var n) = Var <$> resolveName n+resolveSubExp cnst = pure cnst++-- | Like 'resolveSubExp' but for a 'SubExpRes'.+resolveSubExpRes :: SubExpRes -> ReduceM SubExpRes+resolveSubExpRes (SubExpRes certs se) =+ -- Certificates are always read back to host.+ SubExpRes certs <$> resolveSubExp se++-- | Apply 'resolveSubExpRes' to a list of results.+resolveResult :: Result -> ReduceM Result+resolveResult = mapM resolveSubExpRes++-- | Migrate a statement to device, ensuring all its bound variables used on+-- host will remain available with the same names.+moveStm :: Stms GPU -> Stm GPU -> ReduceM (Stms GPU)+moveStm out (Let pat aux (BasicOp (ArrayLit [se] t')))+ | Pat [PatElem n _] <- pat =+ do+ -- Save an 'Index' by rewriting the 'ArrayLit' rather than migrating it.+ let n' = VName (baseName n `withSuffix` "_inner") 0+ let pat' = Pat [PatElem n' t']+ let e' = BasicOp (SubExp se)+ let stm' = Let pat' aux e'++ gpubody <- inGPUBody (rewriteStm stm')+ pure (out |> gpubody {stmPat = pat})+moveStm out stm = do+ -- Move the statement to device.+ gpubody <- inGPUBody (rewriteStm stm)++ -- Read non-scalars and scalars that are used on host.+ let arrs = zip (patElems $ stmPat stm) (patElems $ stmPat gpubody)+ foldM addRead (out |> gpubody) arrs+ where+ addRead stms (pe@(PatElem _ t), PatElem dev dev_t) =+ let add' e = pure $ stms |> bind pe e+ add = add' . BasicOp+ in case arrayRank dev_t of+ -- Alias non-arrays with their prior name.+ 0 -> add $ SubExp (Var dev)+ -- Read all certificates for free.+ 1 | t == Prim Unit -> add' (eIndex dev)+ -- Record the device alias of each scalar variable and read them+ -- if used on host.+ 1 -> pe `migratedTo` (dev, stms)+ -- Drop the added dimension of multidimensional arrays.+ _ -> add $ Index dev (fullSlice dev_t [DimFix $ intConst Int64 0])++-- | Create a GPUBody kernel that executes a single statement and stores its+-- results in single element arrays.+inGPUBody :: RewriteM (Stm GPU) -> ReduceM (Stm GPU)+inGPUBody m = do+ (stm, st) <- runStateT m initialRState+ let prologue = rewritePrologue st++ let pes = patElems (stmPat stm)+ pat <- Pat <$> mapM arrayizePatElem pes+ let aux = StmAux mempty mempty ()+ let types = map patElemType pes+ let res = map (SubExpRes mempty . Var . patElemName) pes+ let body = Body () (prologue |> stm) res+ let e = Op (GPUBody types body)+ pure (Let pat aux e)++--------------------------------------------------------------------------------+-- KERNEL REWRITING - TYPES --+--------------------------------------------------------------------------------++-- The monad used to rewrite (migrated) kernel code.+type RewriteM = StateT RState ReduceM++-- | The state used by a 'RewriteM' monad.+data RState = RState+ { -- | Maps variables in the original program to names to be used by rewrites.+ rewriteRenames :: IM.IntMap VName,+ -- | Statements to be added as a prologue before rewritten statements.+ rewritePrologue :: Stms GPU+ }++--------------------------------------------------------------------------------+-- KERNEL REWRITING - FUNCTIONS --+--------------------------------------------------------------------------------++-- | An initial state to use when running a 'RewriteM' monad.+initialRState :: RState+initialRState =+ RState+ { rewriteRenames = mempty,+ rewritePrologue = mempty+ }++-- | Rewrite 'SegBinOp' dependencies to scalars that have been migrated.+addReadsToSegBinOp :: SegBinOp GPU -> ReduceM (SegBinOp GPU)+addReadsToSegBinOp op = do+ f' <- addReadsToLambda (segBinOpLambda op)+ pure (op {segBinOpLambda = f'})++-- | Rewrite 'HistOp' dependencies to scalars that have been migrated.+addReadsToHistOp :: HistOp GPU -> ReduceM (HistOp GPU)+addReadsToHistOp op = do+ f' <- addReadsToLambda (histOp op)+ pure (op {histOp = f'})++-- | Rewrite generic lambda dependencies to scalars that have been migrated.+addReadsToLambda :: Lambda GPU -> ReduceM (Lambda GPU)+addReadsToLambda f = do+ body' <- addReadsToBody (lambdaBody f)+ pure (f {lambdaBody = body'})++-- | Rewrite generic body dependencies to scalars that have been migrated.+addReadsToBody :: Body GPU -> ReduceM (Body GPU)+addReadsToBody body = do+ (body', prologue) <- addReadsHelper body+ pure body' {bodyStms = prologue >< bodyStms body'}++-- | Rewrite kernel body dependencies to scalars that have been migrated.+addReadsToKernelBody :: KernelBody GPU -> ReduceM (KernelBody GPU)+addReadsToKernelBody kbody = do+ (kbody', prologue) <- addReadsHelper kbody+ pure kbody' {kernelBodyStms = prologue >< kernelBodyStms kbody'}++-- | Rewrite migrated scalar dependencies within anything. The returned+-- statements must be added to the scope of the rewritten construct.+addReadsHelper :: (FreeIn a, Substitute a) => a -> ReduceM (a, Stms GPU)+addReadsHelper x = do+ let from = namesToList (freeIn x)+ (to, st) <- runStateT (mapM rename from) initialRState+ let rename_map = M.fromList (zip from to)+ pure (substituteNames rename_map x, rewritePrologue st)++-- | Create a fresh name, registering which name it is a rewrite of.+rewriteName :: VName -> RewriteM VName+rewriteName n = do+ n' <- lift (newName n)+ modify $ \st -> st {rewriteRenames = IM.insert (baseTag n) n' (rewriteRenames st)}+ pure n'++-- | Rewrite all bindings introduced by a body (to ensure they are unique) and+-- fix any dependencies that are broken as a result of migration or rewriting.+rewriteBody :: Body GPU -> RewriteM (Body GPU)+rewriteBody (Body _ stms res) = do+ stms' <- rewriteStms stms+ res' <- renameResult res+ pure (Body () stms' res')++-- | Rewrite all bindings introduced by a sequence of statements (to ensure they+-- are unique) and fix any dependencies that are broken as a result of migration+-- or rewriting.+rewriteStms :: Stms GPU -> RewriteM (Stms GPU)+rewriteStms = foldM rewriteTo mempty+ where+ rewriteTo out stm = do+ stm' <- rewriteStm stm+ pure $ case stmExp stm' of+ Op (GPUBody _ (Body _ stms res)) ->+ let pes = patElems (stmPat stm')+ in foldl' bnd (out >< stms) (zip pes res)+ _ -> out |> stm'++ bnd :: Stms GPU -> (PatElem Type, SubExpRes) -> Stms GPU+ bnd out (pe, SubExpRes cs se)+ | Just t' <- peelArray 1 (typeOf pe) =+ out |> Let (Pat [pe]) (StmAux cs mempty ()) (BasicOp $ ArrayLit [se] t')+ | otherwise =+ out |> Let (Pat [pe]) (StmAux cs mempty ()) (BasicOp $ SubExp se)++-- | Rewrite all bindings introduced by a single statement (to ensure they are+-- unique) and fix any dependencies that are broken as a result of migration or+-- rewriting.+--+-- NOTE: GPUBody kernels must be rewritten through 'rewriteStms'.+rewriteStm :: Stm GPU -> RewriteM (Stm GPU)+rewriteStm (Let pat aux e) = do+ e' <- rewriteExp e+ pat' <- rewritePat pat+ aux' <- rewriteStmAux aux+ pure (Let pat' aux' e')++-- | Rewrite all bindings introduced by a pattern (to ensure they are unique)+-- and fix any dependencies that are broken as a result of migration or+-- rewriting.+rewritePat :: Pat Type -> RewriteM (Pat Type)+rewritePat pat = Pat <$> mapM rewritePatElem (patElems pat)++-- | Rewrite the binding introduced by a single pattern element (to ensure it is+-- unique) and fix any dependencies that are broken as a result of migration or+-- rewriting.+rewritePatElem :: PatElem Type -> RewriteM (PatElem Type)+rewritePatElem (PatElem n t) = do+ n' <- rewriteName n+ t' <- renameType t+ pure (PatElem n' t')++-- | Fix any 'StmAux' certificate references that are broken as a result of+-- migration or rewriting.+rewriteStmAux :: StmAux () -> RewriteM (StmAux ())+rewriteStmAux (StmAux certs attrs _) = do+ certs' <- renameCerts certs+ pure (StmAux certs' attrs ())++-- | Rewrite the bindings introduced by an expression (to ensure they are+-- unique) and fix any dependencies that are broken as a result of migration or+-- rewriting.+rewriteExp :: Exp GPU -> RewriteM (Exp GPU)+rewriteExp =+ mapExpM $+ Mapper+ { mapOnSubExp = renameSubExp,+ mapOnBody = const rewriteBody,+ mapOnVName = rename,+ mapOnRetType = renameExtType,+ mapOnBranchType = renameExtType,+ mapOnFParam = rewriteParam,+ mapOnLParam = rewriteParam,+ mapOnOp = const opError+ }+ where+ -- This indicates that something fundamentally is wrong with the migration+ -- table produced by the "Futhark.Analysis.MigrationTable" module.+ opError = compilerBugS "Cannot migrate a host-only operation to device."++-- | Rewrite the binding introduced by a single parameter (to ensure it is+-- unique) and fix any dependencies that are broken as a result of migration or+-- rewriting.+rewriteParam :: Param (TypeBase Shape u) -> RewriteM (Param (TypeBase Shape u))+rewriteParam (Param attrs n t) = do+ n' <- rewriteName n+ t' <- renameType t+ pure (Param attrs n' t')++-- | Return the name to use for a rewritten dependency.+rename :: VName -> RewriteM VName+rename n = do+ st <- get+ let renames = rewriteRenames st+ let idx = baseTag n+ case IM.lookup idx renames of+ Just n' -> pure n'+ _ ->+ do+ let stms = rewritePrologue st+ (stms', n') <- lift $ useScalar stms n+ modify $ \st' ->+ st'+ { rewriteRenames = IM.insert idx n' renames,+ rewritePrologue = stms'+ }+ pure n'++-- | Update the variable names within a 'Result' to account for migration and+-- rewriting.+renameResult :: Result -> RewriteM Result+renameResult = mapM renameSubExpRes++-- | Update the variable names within a 'SubExpRes' to account for migration and+-- rewriting.+renameSubExpRes :: SubExpRes -> RewriteM SubExpRes+renameSubExpRes (SubExpRes certs se) = do+ certs' <- renameCerts certs+ se' <- renameSubExp se+ pure (SubExpRes certs' se')++-- | Update the variable names of certificates to account for migration and+-- rewriting.+renameCerts :: Certs -> RewriteM Certs+renameCerts cs = Certs <$> mapM rename (unCerts cs)++-- | Update any variable name within a t'SubExp' to account for migration and+-- rewriting.+renameSubExp :: SubExp -> RewriteM SubExp+renameSubExp (Var n) = Var <$> rename n+renameSubExp se = pure se++-- | Update the variable names within a type to account for migration and+-- rewriting.+renameType :: TypeBase Shape u -> RewriteM (TypeBase Shape u)+-- Note: mapOnType also maps the VName token of accumulators+renameType = mapOnType renameSubExp++-- | Update the variable names within an existential type to account for+-- migration and rewriting.+renameExtType :: TypeBase ExtShape u -> RewriteM (TypeBase ExtShape u)+-- Note: mapOnExtType also maps the VName token of accumulators+renameExtType = mapOnExtType renameSubExp
+ src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable.hs view
@@ -0,0 +1,1620 @@+-- |+-- This module implements program analysis to determine which program statements+-- the "Futhark.Optimise.ReduceDeviceSyncs" pass should move into 'GPUBody' kernels+-- to reduce blocking memory transfers between host and device. The results of+-- the analysis is encoded into a 'MigrationTable' which can be queried.+--+-- To reduce blocking scalar reads the module constructs a data flow+-- dependency graph of program variables (see+-- "Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph") in which+-- it finds a minimum vertex cut that separates array reads of scalars+-- from transitive usage that cannot or should not be migrated to+-- device.+--+-- The variables of each partition are assigned a 'MigrationStatus' that states+-- whether the computation of those variables should be moved to device or+-- remain on host. Due to how the graph is built and the vertex cut is found all+-- variables bound by a single statement will belong to the same partition.+--+-- The vertex cut contains all variables that will reside in device memory but+-- are required by host operations. These variables must be read from device+-- memory and cannot be reduced further in number merely by migrating+-- statements (subject to the accuracy of the graph model). The model is built+-- to reduce the worst-case number of scalar reads; an optimal migration of+-- statements depends on runtime data.+--+-- Blocking scalar writes are reduced by either turning such writes into+-- asynchronous kernels, as is done with scalar array literals and accumulator+-- updates, or by transforming host-device writing into device-device copying.+--+-- For details on how the graph is constructed and how the vertex cut is found,+-- see the master thesis "Reducing Synchronous GPU Memory Transfers" by Philip+-- Børgesen (2022).+module Futhark.Optimise.ReduceDeviceSyncs.MigrationTable+ ( -- * Analysis+ analyseProg,++ -- * Types+ MigrationTable,+ MigrationStatus (..),++ -- * Query++ -- | These functions all assume that no parent statement should be migrated.+ -- That is @shouldMoveStm stm mt@ should return @False@ for every statement+ -- @stm@ with a body that a queried 'VName' or 'Stm' is nested within,+ -- otherwise the query result may be invalid.+ shouldMoveStm,+ shouldMove,+ usedOnHost,+ statusOf,+ )+where++import Control.Monad+import Control.Monad.Trans.Class+import qualified Control.Monad.Trans.Reader as R+import Control.Monad.Trans.State.Strict ()+import Control.Monad.Trans.State.Strict hiding (State)+import Control.Parallel.Strategies (parMap, rpar)+import Data.Bifunctor (first, second)+import Data.Foldable+import qualified Data.IntMap.Strict as IM+import qualified Data.IntSet as IS+import qualified Data.Map.Strict as M+import Data.Maybe (fromJust, fromMaybe, isJust, isNothing)+import qualified Data.Sequence as SQ+import Data.Set (Set, (\\))+import qualified Data.Set as S+import Futhark.Error+import Futhark.IR.GPU+import Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph+ ( EdgeType (..),+ Edges (..),+ Id,+ IdSet,+ Result (..),+ Routing (..),+ Vertex (..),+ )+import qualified Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph as MG++--------------------------------------------------------------------------------+-- MIGRATION TABLES --+--------------------------------------------------------------------------------++-- | Where the value bound by a name should be computed.+data MigrationStatus+ = -- | The statement that computes the value should be moved to device.+ -- No host usage of the value will be left after the migration.+ MoveToDevice+ | -- | As 'MoveToDevice' but host usage of the value will remain after+ -- migration.+ UsedOnHost+ | -- | The statement that computes the value should remain on host.+ StayOnHost+ deriving (Eq, Ord, Show)++-- | Identifies+--+-- (1) which statements should be moved from host to device to reduce the+-- worst case number of blocking memory transfers.+--+-- (2) which migrated variables that still will be used on the host after+-- all such statements have been moved.+newtype MigrationTable = MigrationTable (IM.IntMap MigrationStatus)++-- | Where should the value bound by this name be computed?+statusOf :: VName -> MigrationTable -> MigrationStatus+statusOf n (MigrationTable mt) =+ fromMaybe StayOnHost $ IM.lookup (baseTag n) mt++-- | Should this whole statement be moved from host to device?+shouldMoveStm :: Stm GPU -> MigrationTable -> Bool+shouldMoveStm (Let (Pat ((PatElem n _) : _)) _ (BasicOp (Index _ slice))) mt =+ statusOf n mt == MoveToDevice || any movedOperand slice+ where+ movedOperand (Var op) = statusOf op mt == MoveToDevice+ movedOperand _ = False+shouldMoveStm (Let (Pat ((PatElem n _) : _)) _ (BasicOp _)) mt =+ statusOf n mt /= StayOnHost+shouldMoveStm (Let (Pat ((PatElem n _) : _)) _ Apply {}) mt =+ statusOf n mt /= StayOnHost+shouldMoveStm (Let _ _ (If (Var n) _ _ _)) mt =+ statusOf n mt == MoveToDevice+shouldMoveStm (Let _ _ (DoLoop _ (ForLoop _ _ (Var n) _) _)) mt =+ statusOf n mt == MoveToDevice+shouldMoveStm (Let _ _ (DoLoop _ (WhileLoop n) _)) mt =+ statusOf n mt == MoveToDevice+-- BasicOp and Apply statements might not bind any variables (shouldn't happen).+-- If statements might use a constant branch condition.+-- For loop statements might use a constant number of iterations.+-- HostOp statements cannot execute on device.+-- WithAcc statements are never moved in their entirety.+shouldMoveStm _ _ = False++-- | Should the value bound by this name be computed on device?+shouldMove :: VName -> MigrationTable -> Bool+shouldMove n mt = statusOf n mt /= StayOnHost++-- | Will the value bound by this name be used on host?+usedOnHost :: VName -> MigrationTable -> Bool+usedOnHost n mt = statusOf n mt /= MoveToDevice++-- | Merges two migration tables that are assumed to be disjoint.+merge :: MigrationTable -> MigrationTable -> MigrationTable+merge (MigrationTable a) (MigrationTable b) = MigrationTable (a `IM.union` b)++--------------------------------------------------------------------------------+-- HOST-ONLY FUNCTION ANALYSIS --+--------------------------------------------------------------------------------++-- | Identifies top-level function definitions that cannot be run on the+-- device. The application of any such function is host-only.+type HostOnlyFuns = Set Name++-- | Returns the names of all top-level functions that cannot be called from the+-- device. The evaluation of such a function is host-only.+hostOnlyFunDefs :: [FunDef GPU] -> HostOnlyFuns+hostOnlyFunDefs funs =+ let names = map funDefName funs+ call_map = M.fromList $ zip names (map checkFunDef funs)+ in S.fromList names \\ keysToSet (removeHostOnly call_map)+ where+ keysToSet = S.fromAscList . M.keys++ removeHostOnly cm =+ let (host_only, cm') = M.partition isHostOnly cm+ in if M.null host_only+ then cm'+ else removeHostOnly $ M.map (checkCalls $ keysToSet host_only) cm'++ isHostOnly = isNothing++ -- A function that calls a host-only function is itself host-only.+ checkCalls hostOnlyFuns (Just calls)+ | hostOnlyFuns `S.disjoint` calls =+ Just calls+ checkCalls _ _ =+ Nothing++-- | 'checkFunDef' returns 'Nothing' if this function definition uses arrays or+-- HostOps. Otherwise it returns the names of all applied functions, which may+-- include user defined functions that could turn out to be host-only.+checkFunDef :: FunDef GPU -> Maybe (Set Name)+checkFunDef fun = do+ checkFParams (funDefParams fun)+ checkRetTypes (funDefRetType fun)+ checkBody (funDefBody fun)+ where+ hostOnly = Nothing+ ok = Just ()+ check isArr as = if any isArr as then hostOnly else ok++ checkFParams = check isArray++ checkLParams = check (isArray . fst)++ checkRetTypes = check isArrayType++ checkPats = check isArray++ checkLoopForm (ForLoop _ _ _ (_ : _)) = hostOnly+ checkLoopForm _ = ok++ checkBody = checkStms . bodyStms++ checkStms stms = S.unions <$> mapM checkStm stms++ checkStm (Let (Pat pats) _ e) = checkPats pats >> checkExp e++ -- Any expression that produces an array is caught by checkPats+ checkExp (BasicOp (Index _ _)) = hostOnly+ checkExp (WithAcc _ _) = hostOnly+ checkExp (Op _) = hostOnly+ checkExp (Apply fn _ _ _) = Just (S.singleton fn)+ checkExp (If _ tbranch fbranch _) = do+ calls1 <- checkBody tbranch+ calls2 <- checkBody fbranch+ pure (calls1 <> calls2)+ checkExp (DoLoop params lform body) = do+ checkLParams params+ checkLoopForm lform+ checkBody body+ checkExp _ = Just S.empty++--------------------------------------------------------------------------------+-- MIGRATION ANALYSIS --+--------------------------------------------------------------------------------++-- | HostUsage identifies scalar variables that are used on host.+type HostUsage = [Id]++nameToId :: VName -> Id+nameToId = baseTag++-- | Analyses a program to return a migration table that covers all its+-- statements and variables.+analyseProg :: Prog GPU -> MigrationTable+analyseProg (Prog consts funs) =+ let hof = hostOnlyFunDefs funs+ mt = analyseConsts hof funs consts+ mts = parMap rpar (analyseFunDef hof) funs+ in foldl' merge mt mts++-- | Analyses top-level constants.+analyseConsts :: HostOnlyFuns -> [FunDef GPU] -> Stms GPU -> MigrationTable+analyseConsts hof funs consts =+ let usage = M.foldlWithKey (f $ freeIn funs) [] (scopeOf consts)+ in analyseStms hof usage consts+ where+ f free usage n t+ | isScalar t,+ n `nameIn` free =+ nameToId n : usage+ | otherwise =+ usage++-- | Analyses a top-level function definition.+analyseFunDef :: HostOnlyFuns -> FunDef GPU -> MigrationTable+analyseFunDef hof fd =+ let body = funDefBody fd+ usage = foldl' f [] $ zip (bodyResult body) (funDefRetType fd)+ stms = bodyStms body+ in analyseStms hof usage stms+ where+ f usage (SubExpRes _ (Var n), t) | isScalarType t = nameToId n : usage+ f usage _ = usage++-- | Analyses statements. The 'HostUsage' list identifies which bound scalar+-- variables that subsequently may be used on host. All free variables such as+-- constants and function parameters are assumed to reside on host.+analyseStms :: HostOnlyFuns -> HostUsage -> Stms GPU -> MigrationTable+analyseStms hof usage stms =+ let (g, srcs, _) = buildGraph hof usage stms+ (routed, unrouted) = srcs+ (_, g') = MG.routeMany unrouted g -- hereby routed+ f st' = MG.fold g' visit st' Normal+ st = foldl' f (initial, MG.none) unrouted+ (vr, vn, tn) = fst $ foldl' f st routed+ in -- TODO: Delay reads into (deeper) branches++ MigrationTable $+ IM.unions+ [ IM.fromSet (const MoveToDevice) vr,+ IM.fromSet (const MoveToDevice) vn,+ -- Read by host if not reached by a reversed edge+ IM.fromSet (const UsedOnHost) tn+ ]+ where+ -- 1) Visited by reversed edge.+ -- 2) Visited by normal edge, no route.+ -- 3) Visited by normal edge, had route; will potentially be read by host.+ initial = (IS.empty, IS.empty, IS.empty)++ visit (vr, vn, tn) Reversed v =+ let vr' = IS.insert (vertexId v) vr+ in (vr', vn, tn)+ visit (vr, vn, tn) Normal v@Vertex {vertexRouting = NoRoute} =+ let vn' = IS.insert (vertexId v) vn+ in (vr, vn', tn)+ visit (vr, vn, tn) Normal v =+ let tn' = IS.insert (vertexId v) tn+ in (vr, vn, tn')++--------------------------------------------------------------------------------+-- TYPE HELPERS --+--------------------------------------------------------------------------------++isScalar :: Typed t => t -> Bool+isScalar = isScalarType . typeOf++isScalarType :: TypeBase shape u -> Bool+isScalarType (Prim Unit) = False+isScalarType (Prim _) = True+isScalarType _ = False++isArray :: Typed t => t -> Bool+isArray = isArrayType . typeOf++isArrayType :: ArrayShape shape => TypeBase shape u -> Bool+isArrayType = (0 <) . arrayRank++--------------------------------------------------------------------------------+-- GRAPH BUILDING --+--------------------------------------------------------------------------------++buildGraph :: HostOnlyFuns -> HostUsage -> Stms GPU -> (Graph, Sources, Sinks)+buildGraph hof usage stms =+ let (g, srcs, sinks) = execGrapher hof (graphStms stms)+ g' = foldl' (flip MG.connectToSink) g usage+ in (g', srcs, sinks)++-- | Graph a body.+graphBody :: Body GPU -> Grapher ()+graphBody body = do+ let res_ops = namesIntSet $ freeIn (bodyResult body)+ body_stats <-+ captureBodyStats $+ incBodyDepthFor (graphStms (bodyStms body) >> tellOperands res_ops)++ body_depth <- (1 +) <$> getBodyDepth+ let host_only = IS.member body_depth (bodyHostOnlyParents body_stats)+ modify $ \st ->+ let stats = stateStats st+ hops' = IS.delete body_depth (bodyHostOnlyParents stats)+ -- If body contains a variable that is required on host the parent+ -- statement that contains this body cannot be migrated as a whole.+ stats' = if host_only then stats {bodyHostOnly = True} else stats+ in st {stateStats = stats' {bodyHostOnlyParents = hops'}}++-- | Graph multiple statements.+graphStms :: Stms GPU -> Grapher ()+graphStms = mapM_ graphStm++-- | Graph a single statement.+graphStm :: Stm GPU -> Grapher ()+graphStm stm = do+ let bs = boundBy stm+ let e = stmExp stm+ -- IMPORTANT! It is generally assumed that all scalars within types and+ -- shapes are present on host. Any expression of a type wherein one of its+ -- scalar operands appears must therefore ensure that that scalar operand is+ -- marked as a size variable (see the 'hostSize' function).+ case e of+ BasicOp (SubExp se) -> do+ graphSimple bs e+ one bs `reusesSubExp` se+ BasicOp (Opaque _ se) -> do+ graphSimple bs e+ one bs `reusesSubExp` se+ BasicOp (ArrayLit arr t)+ | isScalar t,+ any (isJust . subExpVar) arr ->+ -- Migrating an array literal with free variables saves a write for+ -- every scalar it contains. Under some backends the compiler+ -- generates asynchronous writes for scalar constants but otherwise+ -- each write will be synchronous. If all scalars are constants then+ -- the compiler generates more efficient code that copies static+ -- device memory.+ graphAutoMove (one bs)+ BasicOp UnOp {} -> graphSimple bs e+ BasicOp BinOp {} -> graphSimple bs e+ BasicOp CmpOp {} -> graphSimple bs e+ BasicOp ConvOp {} -> graphSimple bs e+ BasicOp Assert {} ->+ -- == OpenCL =============================================================+ --+ -- The next read after the execution of a kernel containing an assertion+ -- will be made asynchronous, followed by an asynchronous read to check+ -- if any assertion failed. The runtime will then block for all enqueued+ -- operations to finish.+ --+ -- Since an assertion only binds a certificate of unit type, an assertion+ -- cannot increase the number of (read) synchronizations that occur. In+ -- this regard it is free to migrate. The synchronization that does occur+ -- is however (presumably) more expensive as the pipeline of GPU work will+ -- be flushed.+ --+ -- Since this cost is difficult to quantify and amortize over assertion+ -- migration candidates (cost depends on ordering of kernels and reads) we+ -- assume it is insignificant. This will likely hold for a system where+ -- multiple threads or processes schedules GPU work, as system-wide+ -- throughput only will decrease if the GPU utilization decreases as a+ -- result.+ --+ -- == CUDA ===============================================================+ --+ -- Under the CUDA backend every read is synchronous and is followed by+ -- a full synchronization that blocks for all enqueued operations to+ -- finish. If any enqueued kernel contained an assertion, another+ -- synchronous read is then made to check if an assertion failed.+ --+ -- Migrating an assertion to save a read may thus introduce new reads, and+ -- the total number of reads can hence either decrease, remain the same,+ -- or even increase, subject to the ordering of reads and kernels that+ -- perform assertions.+ --+ -- Since it is possible to implement the same failure checking scheme as+ -- OpenCL using asynchronous reads (and doing so would be a good idea!)+ -- we consider this to be acceptable.+ --+ -- TODO: Implement the OpenCL failure checking scheme under CUDA. This+ -- should reduce the number of synchronizations per read to one.+ graphSimple bs e+ BasicOp (Index _ slice)+ | isFixed slice ->+ graphRead (one bs)+ BasicOp {}+ | [(_, t)] <- bs,+ dims <- arrayDims t,+ dims /= [], -- i.e. produces an array+ all (== intConst Int64 1) dims ->+ -- An expression that produces an array that only contains a single+ -- primitive value is as efficient to compute and copy as a scalar,+ -- and introduces no size variables.+ --+ -- This is an exception to the inefficiency rules that comes next.+ graphSimple bs e+ -- Expressions with a cost sublinear to the size of their result arrays are+ -- risky to migrate as we cannot guarantee that their results are not+ -- returned from a GPUBody, which always copies its return values. Since+ -- this would make the effective asymptotic cost of such statements linear+ -- we block them from being migrated on their own.+ --+ -- The parent statement of an enclosing body may still be migrated as a+ -- whole given that each of its returned arrays either+ -- 1) is backed by memory used by a migratable statement within its body.+ -- 2) contains just a single element.+ -- An array matching either criterion is denoted "copyable memory" because+ -- the asymptotic cost of copying it is less than or equal to the statement+ -- that produced it. This makes the parent of statements with sublinear cost+ -- safe to migrate.+ BasicOp (Index arr s) -> do+ graphInefficientReturn (sliceDims s) e+ one bs `reuses` arr+ BasicOp (Update _ arr _ _) -> do+ graphInefficientReturn [] e+ one bs `reuses` arr+ BasicOp (FlatIndex arr s) -> do+ -- Migrating a FlatIndex leads to a memory allocation error.+ --+ -- TODO: Fix FlatIndex memory allocation error.+ --+ -- Can be replaced with 'graphHostOnly e' to disable migration.+ -- A fix can be verified by enabling tests/migration/reuse2_flatindex.fut+ graphInefficientReturn (flatSliceDims s) e+ one bs `reuses` arr+ BasicOp (FlatUpdate arr _ _) -> do+ graphInefficientReturn [] e+ one bs `reuses` arr+ BasicOp (Scratch _ s) ->+ -- Migrating a Scratch leads to a memory allocation error.+ --+ -- TODO: Fix Scratch memory allocation error.+ --+ -- Can be replaced with 'graphHostOnly e' to disable migration.+ -- A fix can be verified by enabling tests/migration/reuse4_scratch.fut+ graphInefficientReturn s e+ BasicOp (Reshape s arr) -> do+ graphInefficientReturn (newDims s) e+ one bs `reuses` arr+ BasicOp (Rearrange _ arr) -> do+ graphInefficientReturn [] e+ one bs `reuses` arr+ BasicOp (Rotate _ arr) -> do+ -- Migrating a Rotate leads to a memory allocation error.+ --+ -- TODO: Fix Rotate memory allocation error.+ --+ -- Can be replaced with 'graphHostOnly e' to disable migration.+ -- A fix can be verified by enabling tests/migration/reuse7_rotate.fut+ graphInefficientReturn [] e+ one bs `reuses` arr+ -- Expressions with a cost linear to the size of their result arrays are+ -- inefficient to migrate into GPUBody kernels as such kernels are single-+ -- threaded. For sufficiently large arrays the cost may exceed what is saved+ -- by avoiding reads. We therefore also block these from being migrated,+ -- as well as their parents.+ BasicOp ArrayLit {} ->+ -- An array literal purely of primitive constants can be hoisted out to be+ -- a top-level constant, unless it is to be returned or consumed.+ -- Otherwise its runtime implementation will copy a precomputed static+ -- array and thus behave like a 'Copy'.+ -- Whether the rows are primitive constants or arrays, without any scalar+ -- variable operands such ArrayLit cannot directly prevent a scalar read.+ graphHostOnly e+ BasicOp Concat {} ->+ -- Is unlikely to prevent a scalar read as the only SubExp operand in+ -- practice is a computation of host-only size variables.+ graphHostOnly e+ BasicOp Copy {} ->+ -- Only takes an array operand, so cannot directly prevent a scalar read.+ graphHostOnly e+ BasicOp Manifest {} ->+ -- Takes no scalar operands so cannot directly prevent a scalar read.+ -- It is introduced as part of the BlkRegTiling kernel optimization and+ -- is thus unlikely to prevent the migration of a parent which was not+ -- already blocked by some host-only operation.+ graphHostOnly e+ BasicOp Iota {} -> graphHostOnly e+ BasicOp Replicate {} -> graphHostOnly e+ -- END+ BasicOp UpdateAcc {} ->+ graphUpdateAcc (one bs) e+ Apply fn _ _ _ ->+ graphApply fn bs e+ If cond tbody fbody _ ->+ graphIf bs cond tbody fbody+ DoLoop params lform body ->+ graphLoop bs params lform body+ WithAcc inputs f ->+ graphWithAcc bs inputs f+ Op GPUBody {} ->+ -- A GPUBody can be migrated into a parent GPUBody by replacing it with+ -- its body statements and binding its return values inside 'ArrayLit's.+ tellGPUBody+ Op _ ->+ graphHostOnly e+ where+ one [x] = x+ one _ = compilerBugS "Type error: unexpected number of pattern elements."++ isFixed = isJust . sliceIndices++ -- new_dims may introduce new size variables which must be present on host+ -- when this expression is evaluated.+ graphInefficientReturn new_dims e = do+ mapM_ hostSize new_dims+ graphedScalarOperands e >>= addEdges ToSink++ hostSize (Var n) = hostSizeVar n+ hostSize _ = pure ()++ hostSizeVar = requiredOnHost . nameToId++-- | Bindings for all pattern elements bound by a statement.+boundBy :: Stm GPU -> [Binding]+boundBy = map (\(PatElem n t) -> (nameToId n, t)) . patElems . stmPat++-- | Graph a statement which in itself neither reads scalars from device memory+-- nor forces such scalars to be available on host. Such statement can be moved+-- to device to eliminate the host usage of its operands which transitively may+-- depend on a scalar device read.+graphSimple :: [Binding] -> Exp GPU -> Grapher ()+graphSimple bs e = do+ -- Only add vertices to the graph if they have a transitive dependency to+ -- an array read. Transitive dependencies through variables connected to+ -- sinks do not count.+ ops <- graphedScalarOperands e+ let edges = MG.declareEdges (map fst bs)+ unless (IS.null ops) (mapM_ addVertex bs >> addEdges edges ops)++-- | Graph a statement that reads a scalar from device memory.+graphRead :: Binding -> Grapher ()+graphRead b = do+ -- Operands are not important as the source will block routes through b.+ addSource b+ tellRead++-- | Graph a statement that always should be moved to device.+graphAutoMove :: Binding -> Grapher ()+graphAutoMove =+ -- Operands are not important as the source will block routes through b.+ addSource++-- | Graph a statement that is unfit for execution in a GPUBody and thus must+-- be executed on host, requiring all its operands to be made available there.+-- Parent statements of enclosing bodies are also blocked from being migrated.+graphHostOnly :: Exp GPU -> Grapher ()+graphHostOnly e = do+ -- Connect the vertices of all operands to sinks to mark that they are+ -- required on host. Transitive reads that they depend upon can be delayed+ -- no further, and any parent statements cannot be migrated.+ ops <- graphedScalarOperands e+ addEdges ToSink ops+ tellHostOnly++-- | Graph an 'UpdateAcc' statement.+graphUpdateAcc :: Binding -> Exp GPU -> Grapher ()+graphUpdateAcc b e | (_, Acc a _ _ _) <- b =+ -- The actual graphing is delayed to the corrensponding 'WithAcc' parent.+ modify $ \st ->+ let accs = stateUpdateAccs st+ accs' = IM.alter add (nameToId a) accs+ in st {stateUpdateAccs = accs'}+ where+ add Nothing = Just [(b, e)]+ add (Just xs) = Just $ (b, e) : xs+graphUpdateAcc _ _ =+ compilerBugS+ "Type error: UpdateAcc did not produce accumulator typed value."++-- | Graph a function application.+graphApply :: Name -> [Binding] -> Exp GPU -> Grapher ()+graphApply fn bs e = do+ hof <- isHostOnlyFun fn+ if hof+ then graphHostOnly e+ else graphSimple bs e++-- | Graph an if statement.+graphIf :: [Binding] -> SubExp -> Body GPU -> Body GPU -> Grapher ()+graphIf bs cond tbody fbody = do+ body_host_only <-+ incForkDepthFor+ ( do+ tstats <- captureBodyStats (graphBody tbody)+ fstats <- captureBodyStats (graphBody fbody)+ pure $ bodyHostOnly tstats || bodyHostOnly fstats+ )++ -- Record aliases for copyable memory backing returned arrays.+ may_copy_results <- reusesBranches bs (results tbody) (results fbody)+ let may_migrate = not body_host_only && may_copy_results++ cond_id <- case (may_migrate, cond) of+ (False, Var n) ->+ -- The migration status of the condition is what determines whether the+ -- statement may be migrated as a whole or not. See 'shouldMoveStm'.+ connectToSink (nameToId n) >> pure IS.empty+ (True, Var n) -> onlyGraphedScalar n+ (_, _) -> pure IS.empty++ tellOperands cond_id++ -- Connect branch results to bound variables to allow delaying reads out of+ -- branches. It might also be beneficial to move the whole statement to+ -- device, to avoid reading the branch condition value. This must be balanced+ -- against the need to read the values bound by the if statement.+ --+ -- By connecting the branch condition to each variable bound by the statement+ -- the condition will only stay on device if+ --+ -- (1) the if statement is not required on host, based on the statements+ -- within its body.+ --+ -- (2) no additional reads will be required to use the if statement bound+ -- variables should the whole statement be migrated.+ --+ -- If the condition is migrated to device and stays there, then the if+ -- statement must necessarily execute on device.+ --+ -- While the graph model built by this module generally migrates no more+ -- statements than necessary to obtain a minimum vertex cut, the branches+ -- of if statements are subject to an inaccuracy. Specifically model is not+ -- strong enough to capture their mutual exclusivity and thus encodes that+ -- both branches are taken. While this does not affect the resulting number+ -- of host-device reads it means that some reads may needlessly be delayed+ -- out of branches. The overhead as measured on futhark-benchmarks appears+ -- to be neglible though.+ ret <- zipWithM (comb cond_id) (bodyResult tbody) (bodyResult fbody)+ mapM_ (uncurry createNode) (zip bs ret)+ where+ results = map resSubExp . bodyResult++ comb ci a b = (ci <>) <$> onlyGraphedScalars (toSet a <> toSet b)++ toSet (SubExpRes _ (Var n)) = S.singleton n+ toSet _ = S.empty++-----------------------------------------------------+-- These type aliases are only used by 'graphLoop' --+-----------------------------------------------------+type ReachableBindings = IdSet++type ReachableBindingsCache = MG.Visited (MG.Result ReachableBindings)++type NonExhausted = [Id]++type LoopValue = (Binding, Id, SubExp, SubExp)++-----------------------------------------------------+-----------------------------------------------------++-- | Graph a loop statement.+graphLoop ::+ [Binding] ->+ [(FParam GPU, SubExp)] ->+ LoopForm GPU ->+ Body GPU ->+ Grapher ()+graphLoop [] _ _ _ =+ -- We expect each loop to bind a value or be eliminated.+ compilerBugS "Loop statement bound no variable; should have been eliminated."+graphLoop (b : bs) params lform body = do+ -- Graph loop params and body while capturing statistics.+ g0 <- getGraph+ stats <- captureBodyStats (subgraphId `graphIdFor` graphTheLoop)++ -- Record aliases for copyable memory backing returned arrays.+ -- Does the loop return any arrays which prevent it from being migrated?+ let args = map snd params+ let results = map resSubExp (bodyResult body)+ may_copy_results <- reusesBranches (b : bs) args results++ -- Connect loop condition to a sink if the loop cannot be migrated.+ -- The migration status of the condition is what determines whether the+ -- loop may be migrated as a whole or not. See 'shouldMoveStm'.+ let may_migrate = not (bodyHostOnly stats) && may_copy_results+ unless may_migrate $ case lform of+ ForLoop _ _ (Var n) _ -> connectToSink (nameToId n)+ WhileLoop n -> connectToSink (nameToId n)+ _ -> pure ()++ -- Connect graphed return values to their loop parameters.+ g1 <- getGraph+ mapM_ (mergeLoopParam g1) loopValues++ -- Route the sources within the loop body in isolation.+ -- The loop graph must not be altered after this point.+ srcs <- routeSubgraph subgraphId++ -- Graph the variables bound by the statement.+ forM_ loopValues $ \(bnd, p, _, _) -> createNode bnd (IS.singleton p)++ -- If a device read is delayed from one iteration to the next the+ -- corresponding variables bound by the statement must be treated as+ -- sources.+ g2 <- getGraph+ let (dbs, rbc) = foldl' (deviceBindings g2) (IS.empty, MG.none) srcs+ modifySources $ second (IS.toList dbs <>)++ -- Connect operands to sinks if they can reach a sink within the loop.+ -- Otherwise connect them to the loop bound variables that they can+ -- reach and exhaust their normal entry edges into the loop.+ -- This means a read can be delayed through a loop but not into it if+ -- that would increase the number of reads done by any given iteration.+ let ops = IS.filter (`MG.member` g0) (bodyOperands stats)+ foldM_ connectOperand rbc (IS.elems ops)++ -- It might be beneficial to move the whole loop to device, to avoid+ -- reading the (initial) loop condition value. This must be balanced+ -- against the need to read the values bound by the loop statement.+ --+ -- For more details see the similar description for if statements.+ when may_migrate $ case lform of+ ForLoop _ _ n _ ->+ onlyGraphedScalarSubExp n >>= addEdges (ToNodes bindings Nothing)+ WhileLoop n+ | (_, _, arg, _) <- loopValueFor n ->+ onlyGraphedScalarSubExp arg >>= addEdges (ToNodes bindings Nothing)+ where+ subgraphId :: Id+ subgraphId = fst b++ loopValues :: [LoopValue]+ loopValues =+ let tmp = zip3 (b : bs) params (bodyResult body)+ tmp' = flip map tmp $+ \(bnd, (p, arg), res) ->+ let i = nameToId (paramName p)+ in (bnd, i, arg, resSubExp res)+ in filter (\((_, t), _, _, _) -> isScalar t) tmp'++ bindings :: IdSet+ bindings = IS.fromList $ map (\((i, _), _, _, _) -> i) loopValues++ loopValueFor :: VName -> LoopValue+ loopValueFor n =+ fromJust $ find (\(_, p, _, _) -> p == nameToId n) loopValues++ graphTheLoop :: Grapher ()+ graphTheLoop = do+ mapM_ graphParam loopValues++ -- For simplicity we do not currently track memory reuse through merge+ -- parameters. A parameter does not simply reuse the memory of its+ -- argument; it must also consider the iteration return value, which in+ -- turn may depend on other merge parameters.+ --+ -- Situations that would benefit from this tracking is unlikely to occur+ -- at the time of writing, and if it occurs current compiler limitations+ -- will prevent successful compilation.+ -- Specifically it requires the merge parameter argument to reuse memory+ -- from an array literal, and both it and the loop must occur within an+ -- if statement branch. Array literals are generally hoisted out of if+ -- statements however, and when they are not, a memory allocation error+ -- occurs.+ --+ -- TODO: Track memory reuse through merge parameters.++ case lform of+ ForLoop _ _ n elems -> do+ onlyGraphedScalarSubExp n >>= tellOperands+ mapM_ graphForInElem elems+ WhileLoop _ -> pure ()+ graphBody body+ where+ graphForInElem (p, arr) = do+ when (isScalar p) $ addSource (nameToId $ paramName p, typeOf p)+ when (isArray p) $ (nameToId (paramName p), typeOf p) `reuses` arr++ graphParam ((_, t), p, arg, _) =+ do+ -- It is unknown whether a read can be delayed via the parameter+ -- from one iteration to the next, so we have to create a vertex+ -- even if the initial value never depends on a read.+ addVertex (p, t)+ ops <- onlyGraphedScalarSubExp arg+ addEdges (MG.oneEdge p) ops++ mergeLoopParam :: Graph -> LoopValue -> Grapher ()+ mergeLoopParam g (_, p, _, res)+ | Var n <- res,+ ret <- nameToId n,+ ret /= p =+ if MG.isSinkConnected p g+ then connectToSink ret+ else addEdges (MG.oneEdge p) (IS.singleton ret)+ | otherwise =+ pure ()+ deviceBindings ::+ Graph ->+ (ReachableBindings, ReachableBindingsCache) ->+ Id ->+ (ReachableBindings, ReachableBindingsCache)+ deviceBindings g (rb, rbc) i =+ let (r, rbc') = MG.reduce g bindingReach rbc Normal i+ in case r of+ Produced rb' -> (rb <> rb', rbc')+ _ ->+ compilerBugS+ "Migration graph sink could be reached from source after it\+ \ had been attempted routed."+ bindingReach ::+ ReachableBindings ->+ EdgeType ->+ Vertex Meta ->+ ReachableBindings+ bindingReach rb _ v+ | i <- vertexId v,+ IS.member i bindings =+ IS.insert i rb+ | otherwise =+ rb+ connectOperand ::+ ReachableBindingsCache ->+ Id ->+ Grapher ReachableBindingsCache+ connectOperand cache op = do+ g <- getGraph+ case MG.lookup op g of+ Nothing -> pure cache+ Just v ->+ case vertexEdges v of+ ToSink -> pure cache+ ToNodes es Nothing -> connectOp g cache op es+ ToNodes _ (Just nx) -> connectOp g cache op nx+ where+ connectOp ::+ Graph ->+ ReachableBindingsCache ->+ Id -> -- operand id+ IdSet -> -- its edges+ Grapher ReachableBindingsCache+ connectOp g rbc i es = do+ let (res, nx, rbc') = findBindings g (IS.empty, [], rbc) (IS.elems es)+ case res of+ FoundSink -> connectToSink i+ Produced rb -> modifyGraph $ MG.adjust (updateEdges nx rb) i+ pure rbc'+ updateEdges ::+ NonExhausted ->+ ReachableBindings ->+ Vertex Meta ->+ Vertex Meta+ updateEdges nx rb v+ | ToNodes es _ <- vertexEdges v =+ let nx' = IS.fromList nx+ es' = ToNodes (rb <> es) $ Just (rb <> nx')+ in v {vertexEdges = es'}+ | otherwise = v+ findBindings ::+ Graph ->+ (ReachableBindings, NonExhausted, ReachableBindingsCache) ->+ [Id] -> -- current non-exhausted edges+ (MG.Result ReachableBindings, NonExhausted, ReachableBindingsCache)+ findBindings _ (rb, nx, rbc) [] =+ (Produced rb, nx, rbc)+ findBindings g (rb, nx, rbc) (i : is)+ | Just v <- MG.lookup i g,+ Just gid <- metaGraphId (vertexMeta v),+ gid == subgraphId -- only search the subgraph+ =+ let (res, rbc') = MG.reduce g bindingReach rbc Normal i+ in case res of+ FoundSink -> (FoundSink, [], rbc')+ Produced rb' -> findBindings g (rb <> rb', nx, rbc') is+ | otherwise =+ -- don't exhaust+ findBindings g (rb, i : nx, rbc) is++-- | Graph a 'WithAcc' statement.+graphWithAcc ::+ [Binding] ->+ [WithAccInput GPU] ->+ Lambda GPU ->+ Grapher ()+graphWithAcc bs inputs f = do+ -- Graph the body, capturing 'UpdateAcc' statements for delayed graphing.+ graphBody (lambdaBody f)++ -- Graph each accumulator monoid and its associated 'UpdateAcc' statements.+ mapM_ graph $ zip (lambdaReturnType f) inputs++ -- Record aliases for the backing memory of each returned array.+ -- 'WithAcc' statements are never migrated as a whole and always returns+ -- arrays backed by memory allocated elsewhere.+ let arrs = concatMap (\(_, as, _) -> map Var as) inputs+ let res = drop (length inputs) (bodyResult $ lambdaBody f)+ _ <- reusesReturn bs (arrs ++ map resSubExp res)++ -- Connect return variables to bound values. No outgoing edge exists+ -- from an accumulator vertex so skip those. Note that accumulators do+ -- not map to returned arrays one-to-one but one-to-many.+ ret <- mapM (onlyGraphedScalarSubExp . resSubExp) res+ mapM_ (uncurry createNode) $ zip (drop (length arrs) bs) ret+ where+ graph (Acc a _ types _, (_, _, comb)) = do+ let i = nameToId a++ delayed <- fromMaybe [] <$> gets (IM.lookup i . stateUpdateAccs)+ modify $ \st -> st {stateUpdateAccs = IM.delete i (stateUpdateAccs st)}++ graphAcc i types (fst <$> comb) delayed++ -- Neutral elements must always be made available on host for 'WithAcc'+ -- to type check.+ mapM_ connectSubExpToSink $ maybe [] snd comb+ graph _ =+ compilerBugS "Type error: WithAcc expression did not return accumulator."++-- Graph the operator and all 'UpdateAcc' statements associated with an+-- accumulator.+--+-- The arguments are the 'Id' for the accumulator token, the element types of+-- the accumulator/operator, its combining function if any, and all associated+-- 'UpdateAcc' statements outside kernels.+graphAcc :: Id -> [Type] -> Maybe (Lambda GPU) -> [Delayed] -> Grapher ()+graphAcc i _ _ [] = addSource (i, Prim Unit) -- Only used on device.+graphAcc i types op delayed = do+ -- Accumulators are intended for use within SegOps but in principle the AST+ -- allows their 'UpdateAcc's to be used outside a kernel. This case handles+ -- that unlikely situation.++ env <- ask+ st <- get++ -- Collect statistics about the operator statements.+ let lambda = fromMaybe (Lambda [] (Body () SQ.empty []) []) op+ let m = graphBody (lambdaBody lambda)+ let stats = R.runReader (evalStateT (captureBodyStats m) st) env+ -- We treat GPUBody kernels as host-only to not bother rewriting them inside+ -- operators and to simplify the analysis. They are unlikely to occur anyway.+ --+ -- NOTE: Performance may degrade if a GPUBody is replaced with its contents+ -- but the containing operator is used on host.+ let host_only = bodyHostOnly stats || bodyHasGPUBody stats++ -- op operands are read from arrays and written back so if any of the operands+ -- are scalar then a read can be avoided by moving the UpdateAcc usages to+ -- device. If the op itself performs scalar reads its UpdateAcc usages should+ -- also be moved.+ let does_read = bodyReads stats || any isScalar types++ -- Determine which external variables the operator depends upon.+ -- 'bodyOperands' cannot be used as it might exclude operands that were+ -- connected to sinks within the body, so instead we create an artifical+ -- expression to capture graphed operands from.+ ops <- graphedScalarOperands (WithAcc [] lambda)++ case (host_only, does_read) of+ (True, _) -> do+ -- If the operator cannot run well in a GPUBody then all non-kernel+ -- UpdateAcc statements are host-only. The current analysis is ignorant+ -- of what happens inside kernels so we must assume that the operator+ -- is used within a kernel, meaning that we cannot migrate its statements.+ --+ -- TODO: Improve analysis if UpdateAcc ever is used outside kernels.+ mapM_ (graphHostOnly . snd) delayed+ addEdges ToSink ops+ (_, True) -> do+ -- Migrate all accumulator usage to device to avoid reads and writes.+ mapM_ (graphAutoMove . fst) delayed+ addSource (i, Prim Unit)+ _ -> do+ -- Only migrate operator and UpdateAcc statements if it can allow their+ -- operands to be migrated.+ createNode (i, Prim Unit) ops+ forM_ delayed $+ \(b, e) -> graphedScalarOperands e >>= createNode b . IS.insert i++-- Returns for an expression all scalar operands that must be made available+-- on host to execute the expression there.+graphedScalarOperands :: Exp GPU -> Grapher Operands+graphedScalarOperands e =+ let is = fst $ execState (collect e) initial+ in IS.intersection is <$> getGraphedScalars+ where+ initial = (IS.empty, S.empty) -- scalar operands, accumulator tokens+ captureName n = modify $ first $ IS.insert (nameToId n)+ captureAcc a = modify $ second $ S.insert a+ collectFree x = mapM_ captureName (namesToList $ freeIn x)++ collect b@BasicOp {} =+ collectBasic b+ collect (Apply _ params _ _) =+ mapM_ (collectSubExp . fst) params+ collect (If cond tbranch fbranch _) =+ collectSubExp cond >> collectBody tbranch >> collectBody fbranch+ collect (DoLoop params lform body) = do+ mapM_ (collectSubExp . snd) params+ collectLForm lform+ collectBody body+ collect (WithAcc accs f) =+ collectWithAcc accs f+ collect (Op op) =+ collectHostOp op++ collectBasic (BasicOp (Update _ _ slice _)) =+ -- Writing a scalar to an array can be replaced with copying a single-+ -- element slice. If the scalar originates from device memory its read+ -- can thus be prevented without requiring the 'Update' to be migrated.+ collectFree slice+ collectBasic (BasicOp (Replicate shape _)) =+ -- The replicate of a scalar can be rewritten as a replicate of a single+ -- element array followed by a slice index.+ collectFree shape+ collectBasic e' =+ -- Note: Plain VName values only refer to arrays.+ walkExpM (identityWalker {walkOnSubExp = collectSubExp}) e'++ collectSubExp (Var n) = captureName n+ collectSubExp _ = pure ()++ collectBody body = do+ collectStms (bodyStms body)+ collectFree (bodyResult body)+ collectStms = mapM_ collectStm++ collectStm (Let pat _ ua)+ | BasicOp UpdateAcc {} <- ua,+ Pat [pe] <- pat,+ Acc a _ _ _ <- typeOf pe =+ -- Capture the tokens of accumulators used on host.+ captureAcc a >> collectBasic ua+ collectStm stm = collect (stmExp stm)++ collectLForm (ForLoop _ _ b _) = collectSubExp b+ -- WhileLoop condition is declared as a loop parameter.+ collectLForm (WhileLoop _) = pure ()++ -- The collective operands of an operator lambda body are only used on host+ -- if the associated accumulator is used in an UpdateAcc statement outside a+ -- kernel.+ collectWithAcc inputs f = do+ collectBody (lambdaBody f)+ used_accs <- gets snd+ let accs = take (length inputs) (lambdaReturnType f)+ let used = map (\(Acc a _ _ _) -> S.member a used_accs) accs+ mapM_ collectAcc (zip used inputs)++ collectAcc (_, (_, _, Nothing)) = pure ()+ collectAcc (used, (_, _, Just (op, nes))) = do+ mapM_ collectSubExp nes+ when used $ collectBody (lambdaBody op)++ -- Does not collect named operands in+ --+ -- * types and shapes; size variables are assumed available to the host.+ --+ -- * use by a kernel body.+ --+ -- All other operands are conservatively collected even if they generally+ -- appear to be size variables or results computed by a SizeOp.+ collectHostOp (SegOp (SegMap lvl sp _ _)) = do+ collectSegLevel lvl+ collectSegSpace sp+ collectHostOp (SegOp (SegRed lvl sp ops _ _)) = do+ collectSegLevel lvl+ collectSegSpace sp+ mapM_ collectSegBinOp ops+ collectHostOp (SegOp (SegScan lvl sp ops _ _)) = do+ collectSegLevel lvl+ collectSegSpace sp+ mapM_ collectSegBinOp ops+ collectHostOp (SegOp (SegHist lvl sp ops _ _)) = do+ collectSegLevel lvl+ collectSegSpace sp+ mapM_ collectHistOp ops+ collectHostOp (SizeOp op) = collectFree op+ collectHostOp (OtherOp op) = collectFree op+ collectHostOp GPUBody {} = pure ()++ collectSegLevel (SegThread (Count num) (Count size) _) =+ collectSubExp num >> collectSubExp size+ collectSegLevel (SegGroup (Count num) (Count size) _) =+ collectSubExp num >> collectSubExp size++ collectSegSpace space =+ mapM_ collectSubExp (segSpaceDims space)++ collectSegBinOp (SegBinOp _ _ nes _) =+ mapM_ collectSubExp nes++ collectHistOp (HistOp _ rf _ nes _ _) = do+ collectSubExp rf+ mapM_ collectSubExp nes++--------------------------------------------------------------------------------+-- GRAPH BUILDING - PRIMITIVES --+--------------------------------------------------------------------------------++-- | Creates a vertex for the given binding, provided that the set of operands+-- is not empty.+createNode :: Binding -> Operands -> Grapher ()+createNode b ops =+ unless (IS.null ops) (addVertex b >> addEdges (MG.oneEdge $ fst b) ops)++-- | Adds a vertex to the graph for the given binding.+addVertex :: Binding -> Grapher ()+addVertex (i, t) = do+ meta <- getMeta+ let v = MG.vertex i meta+ when (isScalar t) $ modifyGraphedScalars (IS.insert i)+ when (isArray t) $ recordCopyableMemory i (metaBodyDepth meta)+ modifyGraph (MG.insert v)++-- | Adds a source connected vertex to the graph for the given binding.+addSource :: Binding -> Grapher ()+addSource b = do+ addVertex b+ modifySources $ second (fst b :)++-- | Adds the given edges to each vertex identified by the 'IdSet'. It is+-- assumed that all vertices reside within the body that currently is being+-- graphed.+addEdges :: Edges -> IdSet -> Grapher ()+addEdges ToSink is = do+ modifyGraph $ \g -> IS.foldl' (flip MG.connectToSink) g is+ modifyGraphedScalars (`IS.difference` is)+addEdges es is = do+ modifyGraph $ \g -> IS.foldl' (flip $ MG.addEdges es) g is+ tellOperands is++-- | Ensure that a variable (which is in scope) will be made available on host+-- before its first use.+requiredOnHost :: Id -> Grapher ()+requiredOnHost i = do+ mv <- MG.lookup i <$> getGraph+ case mv of+ Nothing -> pure ()+ Just v -> do+ connectToSink i+ tellHostOnlyParent (metaBodyDepth $ vertexMeta v)++-- | Connects the vertex of the given id to a sink.+connectToSink :: Id -> Grapher ()+connectToSink i = do+ modifyGraph (MG.connectToSink i)+ modifyGraphedScalars (IS.delete i)++-- | Like 'connectToSink' but vertex is given by a t'SubExp'. This is a no-op if+-- the t'SubExp' is a constant.+connectSubExpToSink :: SubExp -> Grapher ()+connectSubExpToSink (Var n) = connectToSink (nameToId n)+connectSubExpToSink _ = pure ()++-- | Routes all possible routes within the subgraph identified by this id.+-- Returns the ids of the source connected vertices that were attempted routed.+--+-- Assumption: The subgraph with the given id has just been created and no path+-- exists from it to an external sink.+routeSubgraph :: Id -> Grapher [Id]+routeSubgraph si = do+ st <- get+ let g = stateGraph st+ let (routed, unrouted) = stateSources st+ let (gsrcs, unrouted') = span (inSubGraph si g) unrouted+ let (sinks, g') = MG.routeMany gsrcs g+ put $+ st+ { stateGraph = g',+ stateSources = (gsrcs ++ routed, unrouted'),+ stateSinks = sinks ++ stateSinks st+ }+ pure gsrcs++-- | @inSubGraph si g i@ returns whether @g@ contains a vertex with id @i@ that+-- is declared within the subgraph with id @si@.+inSubGraph :: Id -> Graph -> Id -> Bool+inSubGraph si g i+ | Just v <- MG.lookup i g,+ Just mgi <- metaGraphId (vertexMeta v) =+ si == mgi+inSubGraph _ _ _ = False++-- | @b `reuses` n@ records that @b@ binds an array backed by the same memory+-- as @n@. If @b@ is not array typed or the backing memory is not copyable then+-- this does nothing.+reuses :: Binding -> VName -> Grapher ()+reuses (i, t) n+ | isArray t =+ do+ body_depth <- outermostCopyableArray n+ case body_depth of+ Just bd -> recordCopyableMemory i bd+ Nothing -> pure ()+ | otherwise =+ pure ()++reusesSubExp :: Binding -> SubExp -> Grapher ()+reusesSubExp b (Var n) = b `reuses` n+reusesSubExp _ _ = pure ()++-- @reusesReturn bs res@ records each array binding in @bs@ as reusing copyable+-- memory if the corresponding return value in @res@ is backed by copyable+-- memory.+--+-- If every array binding is registered as being backed by copyable memory then+-- the function returns @True@, otherwise it returns @False@.+reusesReturn :: [Binding] -> [SubExp] -> Grapher Bool+reusesReturn bs res = do+ body_depth <- metaBodyDepth <$> getMeta+ foldM (reuse body_depth) True (zip bs res)+ where+ reuse :: Int -> Bool -> (Binding, SubExp) -> Grapher Bool+ reuse body_depth onlyCopyable (b, se)+ | all (== intConst Int64 1) (arrayDims $ snd b) =+ -- Single element arrays are immediately recognizable as copyable so+ -- don't bother recording those. Note that this case also matches+ -- primitive return values.+ pure onlyCopyable+ | (i, t) <- b,+ isArray t,+ Var n <- se =+ do+ res_body_depth <- outermostCopyableArray n+ case res_body_depth of+ Just inner -> do+ recordCopyableMemory i (min body_depth inner)+ let returns_free_var = inner <= body_depth+ pure (onlyCopyable && not returns_free_var)+ _ ->+ pure False+ | otherwise =+ pure onlyCopyable++-- @reusesBranches bs b1 b2@ records each array binding in @bs@ as reusing+-- copyable memory if each corresponding return value in the lists @b1@ and @b2@+-- are backed by copyable memory.+--+-- If every array binding is registered as being backed by copyable memory then+-- the function returns @True@, otherwise it returns @False@.+reusesBranches :: [Binding] -> [SubExp] -> [SubExp] -> Grapher Bool+reusesBranches bs b1 b2 = do+ body_depth <- metaBodyDepth <$> getMeta+ foldM (reuse body_depth) True $ zip3 bs b1 b2+ where+ reuse :: Int -> Bool -> (Binding, SubExp, SubExp) -> Grapher Bool+ reuse body_depth onlyCopyable (b, se1, se2)+ | all (== intConst Int64 1) (arrayDims $ snd b) =+ -- Single element arrays are immediately recognizable as copyable so+ -- don't bother recording those. Note that this case also matches+ -- primitive return values.+ pure onlyCopyable+ | (i, t) <- b,+ isArray t,+ Var n1 <- se1,+ Var n2 <- se2 =+ do+ body_depth_1 <- outermostCopyableArray n1+ body_depth_2 <- outermostCopyableArray n2+ case (body_depth_1, body_depth_2) of+ (Just bd1, Just bd2) -> do+ let inner = min bd1 bd2+ recordCopyableMemory i (min body_depth inner)+ let returns_free_var = inner <= body_depth+ pure (onlyCopyable && not returns_free_var)+ _ ->+ pure False+ | otherwise =+ pure onlyCopyable++--------------------------------------------------------------------------------+-- GRAPH BUILDING - TYPES --+--------------------------------------------------------------------------------++type Grapher = StateT State (R.Reader Env)++data Env = Env+ { -- | See 'HostOnlyFuns'.+ envHostOnlyFuns :: HostOnlyFuns,+ -- | Metadata for the current body being graphed.+ envMeta :: Meta+ }++-- | A measurement of how many bodies something is nested within.+type BodyDepth = Int++-- | Metadata on the environment that a variable is declared within.+data Meta = Meta+ { -- | How many if statement branch bodies the variable binding is nested+ -- within. If a route passes through the edge u->v and the fork depth+ --+ -- 1) increases from u to v, then u is within a conditional branch.+ --+ -- 2) decreases from u to v, then v binds the result of two or more+ -- branches.+ --+ -- After the graph has been built and routed, this can be used to delay+ -- reads into deeper branches to reduce their likelihood of manifesting.+ metaForkDepth :: Int,+ -- | How many bodies the variable is nested within.+ metaBodyDepth :: BodyDepth,+ -- | An id for the subgraph within which the variable exists, defined at+ -- the body level. A read may only be delayed to a point within its own+ -- subgraph.+ metaGraphId :: Maybe Id+ }++-- | Ids for all variables used as an operand.+type Operands = IdSet++-- | Statistics on the statements within a body and their dependencies.+data BodyStats = BodyStats+ { -- | Whether the body contained any host-only statements.+ bodyHostOnly :: Bool,+ -- | Whether the body contained any GPUBody kernels.+ bodyHasGPUBody :: Bool,+ -- | Whether the body performed any reads.+ bodyReads :: Bool,+ -- | All scalar variables represented in the graph that have been used+ -- as return values of the body or as operands within it, including those+ -- that are defined within the body itself. Variables with vertices+ -- connected to sinks may be excluded.+ bodyOperands :: Operands,+ -- | Depth of parent bodies with variables that are required on host. Since+ -- the variables are required on host, the parent statements of these bodies+ -- cannot be moved to device as a whole. They are host-only.+ bodyHostOnlyParents :: IS.IntSet+ }++instance Semigroup BodyStats where+ (BodyStats ho1 gb1 r1 o1 hop1) <> (BodyStats ho2 gb2 r2 o2 hop2) =+ BodyStats+ { bodyHostOnly = ho1 || ho2,+ bodyHasGPUBody = gb1 || gb2,+ bodyReads = r1 || r2,+ bodyOperands = IS.union o1 o2,+ bodyHostOnlyParents = IS.union hop1 hop2+ }++instance Monoid BodyStats where+ mempty =+ BodyStats+ { bodyHostOnly = False,+ bodyHasGPUBody = False,+ bodyReads = False,+ bodyOperands = IS.empty,+ bodyHostOnlyParents = IS.empty+ }++type Graph = MG.Graph Meta++-- | All vertices connected from a source, partitioned into those that have+-- been attempted routed and those which have not.+type Sources = ([Id], [Id])++-- | All terminal vertices of routes.+type Sinks = [Id]++-- | A captured statement for which graphing has been delayed.+type Delayed = (Binding, Exp GPU)++-- | The vertex handle for a variable and its type.+type Binding = (Id, Type)++-- | Array variables backed by memory segments that may be copied, mapped to the+-- outermost known body depths that declares arrays backed by a superset of+-- those segments.+type CopyableMemoryMap = IM.IntMap BodyDepth++data State = State+ { -- | The graph being built.+ stateGraph :: Graph,+ -- | All known scalars that have been graphed.+ stateGraphedScalars :: IdSet,+ -- | All variables that directly bind scalars read from device memory.+ stateSources :: Sources,+ -- | Graphed scalars that are used as operands by statements that cannot be+ -- migrated. A read cannot be delayed beyond these, so if the statements+ -- that bind these variables are moved to device, the variables must be read+ -- from device memory.+ stateSinks :: Sinks,+ -- | Observed 'UpdateAcc' host statements to be graphed later.+ stateUpdateAccs :: IM.IntMap [Delayed],+ -- | A map of encountered arrays that are backed by copyable memory.+ -- Trivial instances such as single element arrays are excluded.+ stateCopyableMemory :: CopyableMemoryMap,+ -- | Information about the current body being graphed.+ stateStats :: BodyStats+ }++--------------------------------------------------------------------------------+-- GRAPHER OPERATIONS --+--------------------------------------------------------------------------------++execGrapher :: HostOnlyFuns -> Grapher a -> (Graph, Sources, Sinks)+execGrapher hof m =+ let s = R.runReader (execStateT m st) env+ in (stateGraph s, stateSources s, stateSinks s)+ where+ env =+ Env+ { envHostOnlyFuns = hof,+ envMeta =+ Meta+ { metaForkDepth = 0,+ metaBodyDepth = 0,+ metaGraphId = Nothing+ }+ }+ st =+ State+ { stateGraph = MG.empty,+ stateGraphedScalars = IS.empty,+ stateSources = ([], []),+ stateSinks = [],+ stateUpdateAccs = IM.empty,+ stateCopyableMemory = IM.empty,+ stateStats = mempty+ }++-- | Execute a computation in a modified environment.+local :: (Env -> Env) -> Grapher a -> Grapher a+local f = mapStateT (R.local f)++-- | Fetch the value of the environment.+ask :: Grapher Env+ask = lift R.ask++-- | Retrieve a function of the current environment.+asks :: (Env -> a) -> Grapher a+asks = lift . R.asks++-- | Register that the body contains a host-only statement. This means its+-- parent statement and any parent bodies themselves are host-only. A host-only+-- statement should not be migrated, either because it cannot run on device or+-- because it would be inefficient to do so.+tellHostOnly :: Grapher ()+tellHostOnly =+ modify $ \st -> st {stateStats = (stateStats st) {bodyHostOnly = True}}++-- | Register that the body contains a GPUBody kernel.+tellGPUBody :: Grapher ()+tellGPUBody =+ modify $ \st -> st {stateStats = (stateStats st) {bodyHasGPUBody = True}}++-- | Register that the current body contains a statement that reads device+-- memory.+tellRead :: Grapher ()+tellRead =+ modify $ \st -> st {stateStats = (stateStats st) {bodyReads = True}}++-- | Register that these variables are used as operands within the current body.+tellOperands :: IdSet -> Grapher ()+tellOperands is =+ modify $ \st ->+ let stats = stateStats st+ operands = bodyOperands stats+ in st {stateStats = stats {bodyOperands = operands <> is}}++-- | Register that the current statement with a body at the given body depth is+-- host-only.+tellHostOnlyParent :: BodyDepth -> Grapher ()+tellHostOnlyParent body_depth =+ modify $ \st ->+ let stats = stateStats st+ parents = bodyHostOnlyParents stats+ parents' = IS.insert body_depth parents+ in st {stateStats = stats {bodyHostOnlyParents = parents'}}++-- | Get the graph under construction.+getGraph :: Grapher Graph+getGraph = gets stateGraph++-- | All scalar variables with a vertex representation in the graph.+getGraphedScalars :: Grapher IdSet+getGraphedScalars = gets stateGraphedScalars++-- | Every known array that is backed by a memory segment that may be copied,+-- mapped to the outermost known body depth where an array is backed by a+-- superset of that segment.+--+-- A body where all returned arrays are backed by such memory and are written by+-- its own statements will retain its asymptotic cost if migrated as a whole.+getCopyableMemory :: Grapher CopyableMemoryMap+getCopyableMemory = gets stateCopyableMemory++-- | The outermost known body depth for an array backed by the same copyable+-- memory as the array with this name.+outermostCopyableArray :: VName -> Grapher (Maybe BodyDepth)+outermostCopyableArray n = IM.lookup (nameToId n) <$> getCopyableMemory++-- | Reduces the variables to just the 'Id's of those that are scalars and which+-- have a vertex representation in the graph, excluding those that have been+-- connected to sinks.+onlyGraphedScalars :: Foldable t => t VName -> Grapher IdSet+onlyGraphedScalars vs = do+ let is = foldl' (\s n -> IS.insert (nameToId n) s) IS.empty vs+ IS.intersection is <$> getGraphedScalars++-- | Like 'onlyGraphedScalars' but for a single 'VName'.+onlyGraphedScalar :: VName -> Grapher IdSet+onlyGraphedScalar n = do+ let i = nameToId n+ gss <- getGraphedScalars+ if IS.member i gss+ then pure (IS.singleton i)+ else pure IS.empty++-- | Like 'onlyGraphedScalars' but for a single t'SubExp'.+onlyGraphedScalarSubExp :: SubExp -> Grapher IdSet+onlyGraphedScalarSubExp (Constant _) = pure IS.empty+onlyGraphedScalarSubExp (Var n) = onlyGraphedScalar n++-- | Update the graph under construction.+modifyGraph :: (Graph -> Graph) -> Grapher ()+modifyGraph f =+ modify $ \st -> st {stateGraph = f (stateGraph st)}++-- | Update the contents of the graphed scalar set.+modifyGraphedScalars :: (IdSet -> IdSet) -> Grapher ()+modifyGraphedScalars f =+ modify $ \st -> st {stateGraphedScalars = f (stateGraphedScalars st)}++-- | Update the contents of the copyable memory map.+modifyCopyableMemory :: (CopyableMemoryMap -> CopyableMemoryMap) -> Grapher ()+modifyCopyableMemory f =+ modify $ \st -> st {stateCopyableMemory = f (stateCopyableMemory st)}++-- | Update the set of source connected vertices.+modifySources :: (Sources -> Sources) -> Grapher ()+modifySources f =+ modify $ \st -> st {stateSources = f (stateSources st)}++-- | Record that this variable binds an array that is backed by copyable+-- memory shared by an array at this outermost body depth.+recordCopyableMemory :: Id -> BodyDepth -> Grapher ()+recordCopyableMemory i bd =+ modifyCopyableMemory (IM.insert i bd)++-- | Increment the fork depth for variables graphed by this action.+incForkDepthFor :: Grapher a -> Grapher a+incForkDepthFor =+ local $ \env ->+ let meta = envMeta env+ fork_depth = metaForkDepth meta+ in env {envMeta = meta {metaForkDepth = fork_depth + 1}}++-- | Increment the body depth for variables graphed by this action.+incBodyDepthFor :: Grapher a -> Grapher a+incBodyDepthFor =+ local $ \env ->+ let meta = envMeta env+ body_depth = metaBodyDepth meta+ in env {envMeta = meta {metaBodyDepth = body_depth + 1}}++-- | Change the graph id for variables graphed by this action.+graphIdFor :: Id -> Grapher a -> Grapher a+graphIdFor i =+ local $ \env ->+ let meta = envMeta env+ in env {envMeta = meta {metaGraphId = Just i}}++-- | Capture body stats produced by the given action.+captureBodyStats :: Grapher a -> Grapher BodyStats+captureBodyStats m = do+ stats <- gets stateStats+ modify $ \st -> st {stateStats = mempty}++ _ <- m++ stats' <- gets stateStats+ modify $ \st -> st {stateStats = stats <> stats'}++ pure stats'++-- | Can applications of this function be moved to device?+isHostOnlyFun :: Name -> Grapher Bool+isHostOnlyFun fn = asks $ S.member fn . envHostOnlyFuns++-- | Get the 'Meta' corresponding to the current body.+getMeta :: Grapher Meta+getMeta = asks envMeta++-- | Get the body depth of the current body (its nesting level).+getBodyDepth :: Grapher BodyDepth+getBodyDepth = asks (metaBodyDepth . envMeta)
+ src/Futhark/Optimise/ReduceDeviceSyncs/MigrationTable/Graph.hs view
@@ -0,0 +1,569 @@+-- | This module contains the type definitions and basic operations+-- for the graph that+-- "Futhark.Optimise.ReduceDeviceSyncs.MigrationTable" internally uses+-- to construct a migration table. It is however completely+-- Futhark-agnostic and implements a generic graph abstraction.+--+-- = Overview+--+-- The 'Graph' type is a data flow dependency graph of program variables, each+-- variable represented by a 'Vertex'. A vertex may have edges to other vertices+-- or to a sink, which is a special vertex with no graph representation. Each+-- edge to a vertex is either from another vertex or from a source, which also+-- is a special vertex with no graph representation.+--+-- The primary graph operation provided by this module is 'route'. Given the+-- vertex that some unspecified source has an edge to, a path is attempted+-- found to a sink. If a sink can be reached from the source, all edges along+-- the path are reversed. The path in the opposite direction of reversed edges+-- from a source to some sink is a route.+--+-- Routes can be used to find a minimum vertex cut in the graph through what+-- amounts to a specialized depth-first search implementation of the+-- Ford-Fulkerson method. When viewed in this way each graph edge has a capacity+-- of 1 and the reversing of edges to create routes amounts to sending reverse+-- flow through a residual network (the current state of the graph).+-- The max-flow min-cut theorem allows one to determine a minimum edge cut that+-- separates the sources and sinks.+--+-- If each vertex @v@ in the graph is viewed as two vertices, @v_in@ and+-- @v_out@, with all ingoing edges to @v@ going to @v_in@, all outgoing edges+-- from @v@ going from @v_out@, and @v_in@ connected to @v_out@ with a single+-- edge, then the minimum edge cut of the view amounts to a minimum vertex cut+-- in the actual graph. The view need not be manifested as whether @v_in@ or+-- @v_out@ is reached by an edge to @v@ can be determined from whether that edge+-- is reversed or not. The presence of an outgoing, reversed edge also gives the+-- state of the virtual edge that connects @v_in@ to @v_out@.+--+-- When routing fails to find a sink in some subgraph reached via an edge then+-- that edge is marked exhausted. No sink can be reached via an exhausted edge,+-- and any subsequent routing attempt will skip pathfinding along such edge.+module Futhark.Optimise.ReduceDeviceSyncs.MigrationTable.Graph+ ( -- * Types+ Graph,+ Id,+ IdSet,+ Vertex (..),+ Routing (..),+ Exhaustion (..),+ Edges (..),+ EdgeType (..),+ Visited,+ Result (..),++ -- * Construction+ empty,+ vertex,+ declareEdges,+ oneEdge,+ none,++ -- * Insertion+ insert,++ -- * Update+ adjust,+ connectToSink,+ addEdges,++ -- * Query+ member,+ lookup,+ isSinkConnected,++ -- * Routing+ route,+ routeMany,++ -- * Traversal+ fold,+ reduce,+ )+where++import qualified Data.IntMap.Strict as IM+import qualified Data.IntSet as IS+import Data.List (foldl')+import qualified Data.Map.Strict as M+import Data.Maybe (fromJust)+import Prelude hiding (lookup)++--------------------------------------------------------------------------------+-- TYPES --+--------------------------------------------------------------------------------++-- | A data flow dependency graph of program variables, each variable+-- represented by a 'Vertex'.+newtype Graph m = Graph (IM.IntMap (Vertex m))++-- | A handle that identifies a specific 'Vertex'.+type Id = Int++-- | A set of 'Id's.+type IdSet = IS.IntSet++-- | A graph representation of some program variable.+data Vertex m = Vertex+ { -- | The handle for this vertex in the graph.+ vertexId :: Id,+ -- | Custom data associated with the variable.+ vertexMeta :: m,+ -- | Whether a route passes through this vertex, and from where.+ vertexRouting :: Routing,+ -- | Handles of vertices that this vertex has an edge to.+ vertexEdges :: Edges+ }++-- | Route tracking for some vertex.+-- If a route passes through the vertex then both an ingoing and an outgoing+-- edge to/from that vertex will have been reversed, and the vertex will in+-- effect have lost one edge and gained another. The gained edge will be to+-- the prior vertex along the route that passes through.+data Routing+ = -- | No route passes through the vertex, and no edges have been reversed,+ -- added, nor deleted compared to what was declared.+ NoRoute+ | -- | A route passes through the vertex, and the prior vertex is the source+ -- of that route. The edge gained by reversal is by definition exhausted.+ FromSource+ | -- | A route passes through the vertex, and this is the handle of the prior+ -- vertex. The edge gained by reversal may be exhausted. Routing assumes+ -- that at most one 'FromNode' routing exists to each vertex in a graph.+ FromNode Id Exhaustion+ deriving (Show, Eq, Ord)++-- | Whether some edge is exhausted or not. No sink can be reached via an+-- exhausted edge.+data Exhaustion = Exhausted | NotExhausted+ deriving (Show, Eq, Ord)++-- | All relevant edges that have been declared from some vertex, plus+-- bookkeeping to track their exhaustion and reversal.+data Edges+ = -- | The vertex has an edge to a sink; all other declared edges are+ -- irrelevant. The edge cannot become exhausted, and it is reversed if a+ -- route passes through the vertex (@vertexRouting v /= NoRoute@).+ ToSink+ | -- | All vertices that the vertex has a declared edge to, and which of+ -- those edges that are not exhausted nor reversed, if not all.+ ToNodes IdSet (Maybe IdSet)+ deriving (Show, Eq, Ord)++instance Semigroup Edges where+ ToSink <> _ = ToSink+ _ <> ToSink = ToSink+ (ToNodes a1 Nothing) <> (ToNodes a2 Nothing) =+ ToNodes (a1 <> a2) Nothing+ (ToNodes a1 (Just e1)) <> (ToNodes a2 Nothing) =+ ToNodes (a1 <> a2) $ Just (e1 <> IS.difference a2 a1)+ (ToNodes a1 Nothing) <> (ToNodes a2 (Just e2)) =+ ToNodes (a1 <> a2) $ Just (e2 <> IS.difference a1 a2)+ (ToNodes a1 (Just e1)) <> (ToNodes a2 (Just e2)) =+ let a = IS.difference e2 (IS.difference a1 e1)+ b = IS.difference e1 (IS.difference a2 e2)+ in ToNodes (a1 <> a2) $ Just (a <> b)++instance Monoid Edges where+ -- The empty set of edges.+ mempty = ToNodes IS.empty Nothing++-- | Whether a vertex is reached via a normal or reversed edge.+data EdgeType = Normal | Reversed+ deriving (Eq, Ord)++-- | State that tracks which vertices a traversal has visited, caching immediate+-- computations.+newtype Visited a = Visited {visited :: M.Map (EdgeType, Id) a}++-- | The result of a graph traversal that may abort early in case a sink is+-- reached.+data Result a+ = -- | The traversal finished without encountering a sink, producing this+ -- value.+ Produced a+ | -- | The traversal was aborted because a sink was reached.+ FoundSink+ deriving (Eq)++instance Semigroup a => Semigroup (Result a) where+ FoundSink <> _ = FoundSink+ _ <> FoundSink = FoundSink+ Produced x <> Produced y = Produced (x <> y)++--------------------------------------------------------------------------------+-- CONSTRUCTION --+--------------------------------------------------------------------------------++-- | The empty graph.+empty :: Graph m+empty = Graph IM.empty++-- | Constructs a 'Vertex' without any edges.+vertex :: Id -> m -> Vertex m+vertex i m =+ Vertex+ { vertexId = i,+ vertexMeta = m,+ vertexRouting = NoRoute,+ vertexEdges = mempty+ }++-- | Creates a set of edges where no edge is reversed or exhausted.+declareEdges :: [Id] -> Edges+declareEdges is = ToNodes (IS.fromList is) Nothing++-- | Like 'declareEdges' but for a single vertex.+oneEdge :: Id -> Edges+oneEdge i = ToNodes (IS.singleton i) Nothing++-- | Initial 'Visited' state before any vertex has been visited.+none :: Visited a+none = Visited M.empty++--------------------------------------------------------------------------------+-- INSERTION --+--------------------------------------------------------------------------------++-- | Insert a new vertex into the graph. If its variable already is represented+-- in the graph, the original graph is returned.+insert :: Vertex m -> Graph m -> Graph m+insert v (Graph m) = Graph $ IM.insertWith const (vertexId v) v m++--------------------------------------------------------------------------------+-- UPDATE --+--------------------------------------------------------------------------------++-- | Adjust the vertex with this specific id. When no vertex with that id is a+-- member of the graph, the original graph is returned.+adjust :: (Vertex m -> Vertex m) -> Id -> Graph m -> Graph m+adjust f i (Graph m) = Graph $ IM.adjust f i m++-- | Connect the vertex with this id to a sink. When no vertex with that id is a+-- member of the graph, the original graph is returned.+connectToSink :: Id -> Graph m -> Graph m+connectToSink = adjust $ \v -> v {vertexEdges = ToSink}++-- | Add these edges to the vertex with this id. When no vertex with that id is+-- a member of the graph, the original graph is returned.+addEdges :: Edges -> Id -> Graph m -> Graph m+addEdges es = adjust $ \v -> v {vertexEdges = es <> vertexEdges v}++--------------------------------------------------------------------------------+-- QUERY --+--------------------------------------------------------------------------------++-- | Does a vertex for the given id exist in the graph?+member :: Id -> Graph m -> Bool+member i (Graph m) = IM.member i m++-- | Returns the vertex with the given id.+lookup :: Id -> Graph m -> Maybe (Vertex m)+lookup i (Graph m) = IM.lookup i m++-- | Returns whether a vertex with the given id exists in the+-- graph and is connected directly to a sink.+isSinkConnected :: Id -> Graph m -> Bool+isSinkConnected i g =+ maybe False ((ToSink ==) . vertexEdges) (lookup i g)++-- ROUTING --+--------------------------------------------------------------------------------++-- | @route src g@ attempts to find a path in @g@ from the source connected+-- vertex with id @src@. If a sink is found, all edges along the path will be+-- reversed to create a route, and the id of the vertex that connects to the+-- sink is returned.+route :: Id -> Graph m -> (Maybe Id, Graph m)+route src g =+ case route' IM.empty 0 Nothing Normal src g of+ (DeadEnd, g') -> (Nothing, g')+ (SinkFound snk, g') -> (Just snk, g')+ (CycleDetected {}, _) ->+ error+ "Routing did not escape cycle in Futhark.Analysis.MigrationTable.Graph."++-- | @routeMany srcs g@ attempts to create a 'route' in @g@ from every vertex+-- in @srcs@. Returns the ids for the vertices connected to each found sink.+routeMany :: [Id] -> Graph m -> ([Id], Graph m)+routeMany srcs graph =+ foldl' f ([], graph) srcs+ where+ f (snks, g) src =+ case route src g of+ (Nothing, g') -> (snks, g')+ (Just snk, g') -> (snk : snks, g')++--------------------------------------------------------------------------------+-- TRAVERSAL --+--------------------------------------------------------------------------------++-- | @fold g f (a, vs) et i@ folds @f@ over the vertices in @g@ that can be+-- reached from the vertex with handle @i@ accessed via an edge of type @et@.+-- Each vertex @v@ may be visited up to two times, once for each type of edge+-- @e@ pointing to it, and each time @f a e v@ is evaluated to produce an+-- updated @a@ value to be used in future @f@ evaluations or to be returned.+-- The @vs@ set records which @f a e v@ evaluations already have taken place.+-- The function returns an updated 'Visited' set recording the evaluations it+-- has performed.+fold ::+ Graph m ->+ (a -> EdgeType -> Vertex m -> a) ->+ (a, Visited ()) ->+ EdgeType ->+ Id ->+ (a, Visited ())+fold g f (res, vs) et i+ | M.notMember (et, i) (visited vs),+ Just v <- lookup i g =+ let res' = f res et v+ vs' = Visited $ M.insert (et, i) () (visited vs)+ st = (res', vs')+ in case (et, vertexRouting v) of+ (Normal, FromSource) -> st+ (Normal, FromNode rev _) -> foldReversed st rev+ (Reversed, FromNode rev _) -> foldAll st rev (vertexEdges v)+ _ -> foldNormals st (vertexEdges v)+ | otherwise =+ (res, vs)+ where+ foldReversed st = fold g f st Reversed++ foldAll st rev es = foldReversed (foldNormals st es) rev++ foldNormals st ToSink = st+ foldNormals st (ToNodes es _) =+ IS.foldl' (\s -> fold g f s Normal) st es++-- | @reduce g r vs et i@ returns 'FoundSink' if a sink can be reached via the+-- vertex @v@ with id @i@ in @g@. Otherwise it returns 'Produced' @(r x et v)@+-- where @x@ is the '<>' aggregate of all values produced by reducing the+-- vertices that are available via the edges of @v@.+-- @et@ identifies the type of edge that @v@ is accessed by and thereby which+-- edges of @v@ that are available. @vs@ caches reductions of vertices that+-- previously have been visited in the graph.+--+-- The reduction of a cyclic reference resolves to 'mempty'.+reduce ::+ Monoid a =>+ Graph m ->+ (a -> EdgeType -> Vertex m -> a) ->+ Visited (Result a) ->+ EdgeType ->+ Id ->+ (Result a, Visited (Result a))+reduce g r vs et i+ | Just res <- M.lookup (et, i) (visited vs) =+ (res, vs)+ | Just v <- lookup i g =+ reduceVertex v+ | otherwise =+ (Produced mempty, vs) -- shouldn't happen+ where+ reduceVertex v =+ let (res, vs') = reduceEdges v+ in case res of+ Produced x -> cached (Produced $ r x et v) vs'+ FoundSink -> cached res vs'++ cached res vs0 =+ let vs1 = Visited (M.insert (et, i) res $ visited vs0)+ in (res, vs1)++ reduceEdges v =+ case (et, vertexRouting v) of+ (Normal, FromSource) -> (Produced mempty, vs)+ (Normal, FromNode rev _) -> entry (reduceReversed rev)+ (Reversed, FromNode rev _) -> entry (reduceAll rev $ vertexEdges v)+ _ -> entry (reduceNormals $ vertexEdges v)++ -- Handle cycles+ entry f = f $ Visited $ M.insert (et, i) (Produced mempty) (visited vs)++ reduceReversed rev vs' = reduce g r vs' Reversed rev++ reduceAll rev es vs0 =+ let (res, vs1) = reduceNormals es vs0+ in case res of+ Produced _ ->+ let (res', vs2) = reduceReversed rev vs1+ in (res <> res', vs2)+ FoundSink -> (res, vs1)++ reduceNormals ToSink vs' = (FoundSink, vs')+ reduceNormals (ToNodes es _) vs' = reduceNorms mempty (IS.elems es) vs'++ reduceNorms x [] vs0 = (Produced x, vs0)+ reduceNorms x (e : es) vs0 =+ let (res, vs1) = reduce g r vs0 Normal e+ in case res of+ Produced y -> reduceNorms (x <> y) es vs1+ FoundSink -> (res, vs1)++--------------------------------------------------------------------------------+-- ROUTING INTERNALS --+--------------------------------------------------------------------------------++-- | A set of vertices visited by a graph traversal, and at what depth they were+-- encountered. Used to detect cycles.+type Pending = IM.IntMap Depth++-- | Search depth. Distance to some vertex from some search root.+type Depth = Int++-- | The outcome of attempted to find a route through a vertex.+data RoutingResult a+ = -- | No sink could be reached through this vertex.+ DeadEnd+ | -- | A cycle was detected. A sink can be reached through this vertex if a+ -- sink can be reached from the vertex at this depth. If no sink can be+ -- reached from the vertex at this depth, then the graph should be updated+ -- by these actions. Until the vertex is reached, the status of these+ -- vertices are pending.+ CycleDetected Depth [Graph a -> Graph a] Pending+ | -- | A sink was found. This is the id of the vertex connected to it.+ SinkFound Id++instance Semigroup (RoutingResult a) where+ SinkFound i <> _ = SinkFound i+ _ <> SinkFound i = SinkFound i+ CycleDetected d1 as1 _ <> CycleDetected d2 as2 p2 =+ CycleDetected (min d1 d2) (as1 ++ as2) p2+ _ <> CycleDetected d as p = CycleDetected d as p+ CycleDetected d as p <> _ = CycleDetected d as p+ DeadEnd <> DeadEnd = DeadEnd++instance Monoid (RoutingResult a) where+ mempty = DeadEnd++route' ::+ Pending ->+ Depth ->+ Maybe Id ->+ EdgeType ->+ Id ->+ Graph m ->+ (RoutingResult m, Graph m)+route' p d prev et i g+ | Just d' <- IM.lookup i p =+ let found_cycle = (CycleDetected d' [] p, g)+ in case et of+ -- Accessing some vertex v via a normal edge corresponds to accessing+ -- v_in via a normal edge. If v_in has a reversed edge then that is+ -- the only outgoing edge that is available.+ -- All outgoing edges available via this ingoing edge were already+ -- available via the edge that first reached the vertex.+ Normal -> found_cycle+ -- Accessing some vertex v via a reversed edge corresponds to+ -- accessing v_out via a reversed edge. All other edges of v_out are+ -- available, and the edge from v_in to v_out has been reversed,+ -- implying that v_in has a single reversed edge that also is+ -- available.+ -- There exists at most one reversed edge to each vertex. Since this+ -- vertex was reached via one, and the vertex already have been+ -- reached, then the first reach must have been via a normal edge+ -- that only could traverse a reversed edge. The reversed edge from+ -- v_out to v_in thus completes a cycle, but a sink might be+ -- reachable via any of the other edges from v_out.+ -- The depth for the vertex need not be updated as this is the only+ -- edge to v_out and 'prev' is already in the 'Pending' map.+ -- It follows that no (new) cycle can start here.+ Reversed ->+ let (res, g') = routeNormals (fromJust $ lookup i g) g p+ in (fst found_cycle <> res, g')+ | Just v <- lookup i g =+ routeVertex v+ | otherwise =+ backtrack+ where+ backtrack = (DeadEnd, g)++ routeVertex v =+ case (et, vertexRouting v) of+ (Normal, FromSource) -> backtrack+ (Normal, FromNode _ Exhausted) -> backtrack+ (Normal, FromNode rev _) -> entry (routeReversed rev)+ (Reversed, FromNode rev _) -> entry (routeAll rev v)+ _ -> entry (routeNormals v)++ entry f =+ let (res, g0) = f g (IM.insert i d p)+ in case res of+ CycleDetected d' as _+ | d == d' -> (DeadEnd, foldl' (\g1 a -> a g1) g0 as)+ _ | otherwise -> (res, g0)++ routeAll rev v g0 p0 =+ let (res, g1) = routeNormals v g0 p0+ in case res of+ DeadEnd -> routeReversed rev g1 p0+ CycleDetected _ _ p1 ->+ let (res', g2) = routeReversed rev g1 p1+ in (res <> res', g2)+ SinkFound _ -> (res, g1)++ routeReversed rev g0 p0 =+ let (res, g') = route' p0 (d + 1) (Just i) Reversed rev g0+ exhaust = flip adjust i $+ \v -> v {vertexRouting = FromNode rev Exhausted}+ in case (res, et) of+ (DeadEnd, _) ->+ (res, exhaust g')+ (CycleDetected d' as p', _) ->+ (CycleDetected d' (exhaust : as) p', g')+ (SinkFound _, Normal) ->+ (res, setRoute g')+ (SinkFound _, Reversed) ->+ let f v =+ v+ { vertexEdges = withPrev (vertexEdges v),+ vertexRouting = NoRoute+ }+ in (res, adjust f i g')++ setRoute = adjust (\v -> v {vertexRouting = routing}) i++ routing =+ case prev of+ Nothing -> FromSource+ Just i' -> FromNode i' NotExhausted++ withPrev edges+ | Just i' <- prev,+ ToNodes es (Just es') <- edges =+ ToNodes es (Just $ IS.insert i' es')+ | otherwise =+ edges -- shouldn't happen+ routeNormals v g0 p0+ | ToSink <- vertexEdges v =+ -- There cannot be a reversed edge to a vertex with an edge to a sink.+ (SinkFound i, setRoute g0)+ | ToNodes es nx <- vertexEdges v =+ let (res, g', nx') =+ case nx of+ Just es' -> routeNorms (IS.toAscList es') g0 p0+ Nothing -> routeNorms (IS.toAscList es) g0 p0+ edges = ToNodes es (Just $ IS.fromDistinctAscList nx')+ exhaust = flip adjust i $ \v' ->+ v' {vertexEdges = ToNodes es (Just IS.empty)}+ in case (res, et) of+ (DeadEnd, _) -> (res, exhaust g')+ (CycleDetected d' as p', _) ->+ let res' = CycleDetected d' (exhaust : as) p'+ v' = v {vertexEdges = edges}+ in (res', insert v' g')+ (SinkFound _, Normal) ->+ let v' = v {vertexEdges = edges, vertexRouting = routing}+ in (res, insert v' g')+ (SinkFound _, Reversed) ->+ let v' = v {vertexEdges = withPrev edges}+ in (res, insert v' g')++ routeNorms [] g0 _ = (DeadEnd, g0, [])+ routeNorms (e : es) g0 p0 =+ let (res, g1) = route' p0 (d + 1) (Just i) Normal e g0+ in case res of+ DeadEnd -> routeNorms es g1 p0+ SinkFound _ -> (res, g1, es)+ CycleDetected _ _ p1 ->+ let (res', g2, es') = routeNorms es g1 p1+ in (res <> res', g2, e : es')
src/Futhark/Optimise/Simplify.hs view
@@ -45,7 +45,7 @@ -- We deepen the vtable so it will look like the constants are in an -- "outer loop"; this communicates useful information to some- -- simplification rules (e.g. seee issue #1302).+ -- simplification rules (e.g. see issue #1302). funs' <- parPass (simplifyFun' (ST.deepen consts_vtable) . informFunDef) funs let funs_uses = UT.usages $ foldMap freeIn funs'
src/Futhark/Optimise/Simplify/Engine.hs view
@@ -40,9 +40,11 @@ orIf, hasFree, isConsumed,+ isConsuming, isFalse, isOp, isNotSafe,+ isDeviceMigrated, asksEngineEnv, askVtable, localVtable,@@ -60,7 +62,9 @@ ST.SymbolTable, hoistStms, blockIf,+ blockMigrated, enterLoop,+ constructBody, module Futhark.Optimise.Simplify.Rep, ) where@@ -387,7 +391,7 @@ -- | Statements that are not worth hoisting out of loops, because they -- are unsafe, and added safety (by 'protectLoopHoisted') may inhibit--- further optimisation..+-- further optimisation. notWorthHoisting :: ASTRep rep => BlockPred rep notWorthHoisting _ _ (Let pat _ e) = not (safeExp e) && any ((> 0) . arrayRank) (patTypes pat)@@ -402,7 +406,7 @@ cs' <- simplify cs (pat', pat_cs) <- collectCerts $ simplifyPat $ removePatWisdom pat let aux' = StmAux (cs' <> pat_cs) attrs dec- mkWiseLetStm pat' aux' <$> simplifyExpBase e+ mkWiseStm pat' aux' <$> simplifyExpBase e -- Bottom-up simplify a statement. Recurses into sub-Bodies and Ops. -- Does not copy-propagate into the pattern and similar, as it is@@ -416,7 +420,7 @@ recSimplifyStm (Let pat (StmAux cs attrs (_, dec)) e) usage = do ((e', e_hoisted), e_cs) <- collectCerts $ simplifyExp usage pat e let aux' = StmAux (cs <> e_cs) attrs dec- pure (e_hoisted, mkWiseLetStm (removePatWisdom pat) aux' e')+ pure (e_hoisted, mkWiseStm (removePatWisdom pat) aux' e') hoistStms :: SimplifiableRep rep =>@@ -860,7 +864,7 @@ Nothing -> pure (Nothing, mempty) Just (op_lam, nes) -> do- (op_lam', op_lam_stms) <- simplifyLambda op_lam+ (op_lam', op_lam_stms) <- blockMigrated (simplifyLambda op_lam) nes' <- simplify nes pure (Just (op_lam', nes'), op_lam_stms) (,op_stms) <$> ((,,op') <$> simplify shape <*> simplify arrs)@@ -870,6 +874,36 @@ simplifyExp _ _ e = do e' <- simplifyExpBase e pure (e', mempty)++-- | Block hoisting of 'Index' statements introduced by migration.+blockMigrated ::+ SimplifiableRep rep =>+ SimpleM rep (Lambda (Wise rep), Stms (Wise rep)) ->+ SimpleM rep (Lambda (Wise rep), Stms (Wise rep))+blockMigrated = local withMigrationBlocker+ where+ withMigrationBlocker (ops, env) =+ let blockers = envHoistBlockers env+ par_blocker = blockHoistPar blockers++ blocker = par_blocker `orIf` isDeviceMigrated++ blockers' = blockers {blockHoistPar = blocker}+ env' = env {envHoistBlockers = blockers'}+ in (ops, env')++-- | Statement is a scalar read from a single element array of rank one.+isDeviceMigrated :: SimplifiableRep rep => BlockPred (Wise rep)+isDeviceMigrated vtable _ stm+ | BasicOp (Index arr slice) <- stmExp stm,+ [DimFix idx] <- unSlice slice,+ idx == intConst Int64 0,+ Just arr_t <- ST.lookupType arr vtable,+ [size] <- arrayDims arr_t,+ size == intConst Int64 1 =+ True+ | otherwise =+ False -- The simple nonrecursive case that we can perform without bottom-up -- information.
src/Futhark/Optimise/Simplify/Rep.hs view
@@ -22,7 +22,7 @@ addScopeWisdom, addWisdomToPat, mkWiseBody,- mkWiseLetStm,+ mkWiseStm, mkWiseExpDec, CanBeWise (..), @@ -31,6 +31,7 @@ informLambda, informFunDef, informStms,+ informBody, ) where @@ -240,13 +241,13 @@ (aliases, consumed) = Aliases.mkBodyAliasing stms res -- | Produce a statement with simplifier information.-mkWiseLetStm ::+mkWiseStm :: (ASTRep rep, CanBeWise (Op rep)) => Pat (LetDec rep) -> StmAux (ExpDec rep) -> Exp (Wise rep) -> Stm (Wise rep)-mkWiseLetStm pat (StmAux cs attrs dec) e =+mkWiseStm pat (StmAux cs attrs dec) e = let pat' = addWisdomToPat pat e in Let pat' (StmAux cs attrs $ mkWiseExpDec pat' dec e) e @@ -275,7 +276,7 @@ env <- asksScope removeScopeWisdom flip runReaderT env $ do Let pat dec _ <- mkLetNames names $ removeExpWisdom e- pure $ mkWiseLetStm pat dec e+ pure $ mkWiseStm pat dec e mkBody stms res = let Body bodyrep _ _ = mkBody (fmap removeStmWisdom stms) res@@ -298,7 +299,7 @@ -- | Construct a 'Wise' statement. informStm :: Informing rep => Stm rep -> Stm (Wise rep)-informStm (Let pat aux e) = mkWiseLetStm pat aux $ informExp e+informStm (Let pat aux e) = mkWiseStm pat aux $ informExp e -- | Construct 'Wise' statements. informStms :: Informing rep => Stms rep -> Stms (Wise rep)
src/Futhark/Optimise/Sink.hs view
@@ -49,8 +49,12 @@ import Data.Bifunctor import Data.List (foldl') import qualified Data.Map as M+import Data.Sequence ((<|))+import qualified Data.Sequence as SQ import qualified Futhark.Analysis.Alias as Alias import qualified Futhark.Analysis.SymbolTable as ST+import Futhark.Builder.Class+import Futhark.Construct (sliceDim) import Futhark.IR.Aliases import Futhark.IR.GPU import Futhark.IR.MC@@ -67,6 +71,7 @@ type Constraints rep = ( ASTRep rep, Aliased rep,+ Buildable rep, ST.IndexOp (Op rep) ) @@ -103,6 +108,39 @@ && all (`ST.available` vtable) (namesToList (freeIn stm)) sunk = namesFromList $ foldMap (patNames . stmPat) sunk_stms +optimiseLoop ::+ Constraints rep =>+ Sinker rep (Op rep) ->+ Sinker rep ([(FParam rep, SubExp)], LoopForm rep, Body rep)+optimiseLoop onOp vtable sinking (merge, form, body0)+ | WhileLoop {} <- form =+ let (body1, sunk) = optimiseBody onOp vtable' sinking body0+ in ((merge, form, body1), sunk)+ | ForLoop i it bound loop_vars <- form =+ let stms' = foldr (inline i) (bodyStms body0) loop_vars+ body1 = body0 {bodyStms = stms'}+ (body2, sunk) = optimiseBody onOp vtable' sinking body1+ notSunk (x, _) = not $ paramName x `nameIn` sunk+ loop_vars' = filter notSunk loop_vars+ form' = ForLoop i it bound loop_vars'+ body3 = body2 {bodyStms = SQ.drop (length loop_vars') (bodyStms body2)}+ in ((merge, form', body3), sunk)+ where+ (params, _) = unzip merge+ scope = case form of+ WhileLoop {} -> scopeOfFParams params+ ForLoop i it _ _ -> M.insert i (IndexName it) $ scopeOfFParams params+ vtable' = ST.fromScope scope <> vtable++ inline i (x, arr) stms =+ let pt = typeOf x+ slice = Slice $ DimFix (Var i) : map sliceDim (arrayDims pt)+ e = BasicOp (Index arr slice)+ pat = mkExpPat [Ident (paramName x) pt] e+ aux = StmAux mempty mempty (mkExpDec pat e)+ stm = Let pat aux e+ in stm <| stms+ optimiseStms :: Constraints rep => Sinker rep (Op rep) ->@@ -140,6 +178,15 @@ in ( stm {stmExp = If cond tbranch' fbranch' ret} : stms', tsunk <> fsunk <> sunk )+ | DoLoop merge lform body <- stmExp stm =+ let comps = (merge, lform, body)+ (comps', loop_sunk) = optimiseLoop onOp vtable sinking comps+ (merge', lform', body') = comps'++ (stms', stms_sunk) = optimiseStms' vtable' sinking stms+ in ( stm {stmExp = DoLoop merge' lform' body'} : stms',+ stms_sunk <> loop_sunk+ ) | Op op <- stmExp stm = let (op', op_sunk) = onOp vtable sinking op (stms', stms_sunk) = optimiseStms' vtable' sinking stms@@ -211,7 +258,7 @@ type SinkRep rep = Aliases rep sink ::- ( ASTRep rep,+ ( Buildable rep, CanBeAliased (Op rep), ST.IndexOp (OpWithAliases (Op rep)) ) =>@@ -241,6 +288,8 @@ onHostOp :: Sinker (SinkRep GPU) (Op (SinkRep GPU)) onHostOp vtable sinking (SegOp op) = first SegOp $ optimiseSegOp onHostOp vtable sinking op+ onHostOp vtable sinking (GPUBody types body) =+ first (GPUBody types) $ optimiseBody onHostOp vtable sinking body onHostOp _ _ op = (op, mempty) -- | Sinking for multicore.
src/Futhark/Optimise/TileLoops.hs view
@@ -31,35 +31,45 @@ onStms scope stms = modifyNameSource $ runState $- runReaderT (optimiseStms stms) scope+ runReaderT (optimiseStms (M.empty, M.empty) stms) scope -optimiseBody :: Body GPU -> TileM (Body GPU)-optimiseBody (Body () stms res) =- Body () <$> optimiseStms stms <*> pure res+optimiseBody :: Env -> Body GPU -> TileM (Body GPU)+optimiseBody env (Body () stms res) =+ Body () <$> optimiseStms env stms <*> pure res -optimiseStms :: Stms GPU -> TileM (Stms GPU)-optimiseStms stms =- localScope (scopeOf stms) $- mconcat <$> mapM optimiseStm (stmsToList stms)+optimiseStms :: Env -> Stms GPU -> TileM (Stms GPU)+optimiseStms env stms =+ localScope (scopeOf stms) $ do+ (_, stms') <- foldM foldfun (env, mempty) $ stmsToList stms+ pure stms'+ where+ foldfun :: (Env, Stms GPU) -> Stm GPU -> TileM (Env, Stms GPU)+ foldfun (e, ss) s = do+ (e', s') <- optimiseStm e s+ pure (e', ss <> s') -optimiseStm :: Stm GPU -> TileM (Stms GPU)-optimiseStm stm@(Let pat aux (Op (SegOp (SegMap lvl@SegThread {} space ts kbody)))) = do- res3dtiling <- doRegTiling3D stm- case res3dtiling of- Just (extra_stms, stmt') -> pure (extra_stms <> oneStm stmt')- Nothing -> do- blkRegTiling_res <- mmBlkRegTiling stm- case blkRegTiling_res of- Just (extra_stms, stmt') -> pure (extra_stms <> oneStm stmt')- Nothing -> localScope (scopeOfSegSpace space) $ do- (host_stms, (lvl', space', kbody')) <- tileInKernelBody mempty initial_variance lvl space ts kbody- pure $ host_stms <> oneStm (Let pat aux $ Op $ SegOp $ SegMap lvl' space' ts kbody')+optimiseStm :: Env -> Stm GPU -> TileM (Env, Stms GPU)+optimiseStm env stm@(Let pat aux (Op (SegOp (SegMap lvl@SegThread {} space ts kbody)))) = do+ res3dtiling <- localScope (scopeOfSegSpace space) $ doRegTiling3D stm+ stms' <-+ case res3dtiling of+ Just (extra_stms, stmt') -> pure (extra_stms <> oneStm stmt')+ Nothing -> do+ blkRegTiling_res <- mmBlkRegTiling env stm+ case blkRegTiling_res of+ Just (extra_stms, stmt') -> pure (extra_stms <> oneStm stmt')+ Nothing -> localScope (scopeOfSegSpace space) $ do+ (host_stms, (lvl', space', kbody')) <- tileInKernelBody mempty initial_variance lvl space ts kbody+ pure $ host_stms <> oneStm (Let pat aux $ Op $ SegOp $ SegMap lvl' space' ts kbody')+ pure (env, stms') where initial_variance = M.map mempty $ scopeOfSegSpace space-optimiseStm (Let pat aux e) =- pure <$> (Let pat aux <$> mapExpM optimise e)+optimiseStm env (Let pat aux e) = do+ env' <- changeEnv env (head $ patNames pat) e+ e' <- mapExpM (optimise env') e+ pure (env', oneStm $ Let pat aux e') where- optimise = identityMapper {mapOnBody = \scope -> localScope scope . optimiseBody}+ optimise env' = identityMapper {mapOnBody = \scope -> localScope scope . optimiseBody env'} tileInKernelBody :: Names ->@@ -618,9 +628,6 @@ <$> (zip <$> mapM lookupType m_body_free <*> pure m_body_free) let blank t = maybe (eBlank t) (pure . BasicOp . SubExp . Var) $ lookup t t_to_v letTupExp desc =<< eIf (toExp in_bounds) (pure m_body) (eBody $ map blank ts)- where- isAcc Acc {} = True- isAcc _ = False postludeGeneric :: Tiling ->
@@ -1,5 +1,12 @@+{-# LANGUAGE FlexibleContexts #-}+ module Futhark.Optimise.TileLoops.Shared ( TileM,+ Env,+ index,+ update,+ forLoop',+ forLoop, segMap1D, segMap2D, segMap3D,@@ -7,6 +14,7 @@ VarianceTable, varianceInStms, isTileableRedomap,+ changeEnv, TileKind (..), ) where@@ -16,12 +24,64 @@ import Data.List (foldl', zip4) import qualified Data.Map as M import Futhark.IR.GPU+import qualified Futhark.IR.Mem.IxFun as IxFun+import qualified Futhark.IR.SeqMem as ExpMem import Futhark.MonadFreshNames import Futhark.Tools import Futhark.Transform.Rename type TileM = ReaderT (Scope GPU) (State VNameSource) +-- | Are we working with full or partial tiles?+data TileKind = TilePartial | TileFull++-- index an array with indices given in outer_indices; any inner+-- dims of arr not indexed by outer_indices are sliced entirely+index :: MonadBuilder m => String -> VName -> [VName] -> m VName+index se_desc arr outer_indices = do+ arr_t <- lookupType arr+ let shape = arrayShape arr_t+ inner_dims = shapeDims $ stripDims (length outer_indices) shape+ untouched d = DimSlice (intConst Int64 0) d (intConst Int64 1)+ inner_slices = map untouched inner_dims+ slice = Slice $ map (DimFix . Var) outer_indices ++ inner_slices+ letExp se_desc $ BasicOp $ Index arr slice++update :: MonadBuilder m => String -> VName -> [VName] -> SubExp -> m VName+update se_desc arr indices new_elem =+ letExp se_desc $ BasicOp $ Update Unsafe arr (Slice $ map (DimFix . Var) indices) new_elem++forLoop' ::+ SubExp -> -- loop var+ [VName] -> -- loop inits+ ( VName ->+ [VName] -> -- (loop var -> loop inits -> loop body)+ Builder GPU (Body GPU)+ ) ->+ Builder GPU [VName]+forLoop' i_bound merge body = do+ i <- newVName "i" -- could give this as arg to the function+ let loop_form = ForLoop i Int64 i_bound []++ merge_ts <- mapM lookupType merge+ loop_inits <- mapM (\merge_t -> newParam "merge" $ toDecl merge_t Unique) merge_ts++ loop_body <-+ runBodyBuilder . inScopeOf loop_form . localScope (scopeOfFParams loop_inits) $+ body i $ map paramName loop_inits++ letTupExp "loop" $+ DoLoop (zip loop_inits $ map Var merge) loop_form loop_body++forLoop ::+ SubExp ->+ [VName] ->+ (VName -> [VName] -> Builder GPU (Body GPU)) ->+ Builder GPU VName+forLoop i_bound merge body = do+ res_list <- forLoop' i_bound merge body+ pure $ head res_list+ segMap1D :: String -> SegLevel ->@@ -41,8 +101,7 @@ let ret (SubExpRes cs se) = Returns manifest cs se letTupExp desc $- Op . SegOp $- SegMap lvl space ts $ KernelBody () stms' $ map ret res'+ Op . SegOp $ SegMap lvl space ts $ KernelBody () stms' $ map ret res' segMap2D :: String -> -- desc@@ -55,8 +114,8 @@ Builder GPU [VName] segMap2D desc lvl manifest (dim_y, dim_x) f = do ltid_xx <- newVName "ltid_x"- ltid_flat <- newVName "ltid_flat" ltid_yy <- newVName "ltid_y"+ ltid_flat <- newVName "ltid_flat" let segspace = SegSpace ltid_flat [(ltid_yy, dim_y), (ltid_xx, dim_x)] ((ts, res), stms) <- localScope (scopeOfSegSpace segspace) . runBuilder $ do@@ -66,8 +125,7 @@ let ret (SubExpRes cs se) = Returns manifest cs se letTupExp desc <=< renameExp $- Op . SegOp $- SegMap lvl segspace ts $ KernelBody () stms $ map ret res+ Op . SegOp $ SegMap lvl segspace ts $ KernelBody () stms $ map ret res segMap3D :: String -> -- desc@@ -79,10 +137,10 @@ ) -> Builder GPU [VName] segMap3D desc lvl manifest (dim_z, dim_y, dim_x) f = do- ltid_x <- newVName "ltid_x" ltid_flat <- newVName "ltid_flat"- ltid_y <- newVName "ltid_y" ltid_z <- newVName "ltid_z"+ ltid_y <- newVName "ltid_y"+ ltid_x <- newVName "ltid_x" let segspace = SegSpace ltid_flat [(ltid_z, dim_z), (ltid_y, dim_y), (ltid_x, dim_x)] ((ts, res), stms) <- localScope (scopeOfSegSpace segspace) . runBuilder $ do@@ -92,12 +150,11 @@ let ret (SubExpRes cs se) = Returns manifest cs se letTupExp desc <=< renameExp $- Op . SegOp $- SegMap lvl segspace ts $ KernelBody () stms $ map ret res+ Op . SegOp $ SegMap lvl segspace ts $ KernelBody () stms $ map ret res segScatter2D ::- String -> -- desc- SubExp -> -- arr_size+ String ->+ SubExp -> VName -> SegLevel -> -- lvl [SubExp] -> -- dims of sequential loop on top@@ -105,14 +162,14 @@ ([VName] -> (VName, VName) -> Builder GPU (SubExp, SubExp)) -> -- f Builder GPU VName segScatter2D desc arr_size updt_arr lvl seq_dims (dim_x, dim_y) f = do- ltid_x <- newVName "ltid_x"- ltid_y <- newVName "ltid_y" ltid_flat <- newVName "ltid_flat"+ ltid_y <- newVName "ltid_y"+ ltid_x <- newVName "ltid_x" seq_is <- replicateM (length seq_dims) (newVName "ltid_seq") let seq_space = zip seq_is seq_dims - let segspace = SegSpace ltid_flat $ seq_space ++ [(ltid_x, dim_x), (ltid_y, dim_y)]+ let segspace = SegSpace ltid_flat $ seq_space ++ [(ltid_y, dim_y), (ltid_x, dim_x)] lvl' = SegThread (segNumGroups lvl)@@ -122,7 +179,7 @@ ((t_v, res_v, res_i), stms) <- runBuilder $ do (res_v, res_i) <- localScope (scopeOfSegSpace segspace) $- f seq_is (ltid_x, ltid_y)+ f seq_is (ltid_y, ltid_x) t_v <- subExpType res_v pure (t_v, res_v, res_i) @@ -195,5 +252,71 @@ varianceInStms :: VarianceTable -> Stms GPU -> VarianceTable varianceInStms = foldl' varianceInStm --- | Are we working with full or partial tiles?-data TileKind = TilePartial | TileFull+----------------+---- Helpers for building the environment that binds array variable names to their index functions+----------------++type IxFun = IxFun.IxFun (TPrimExp Int64 VName)++-- | Map from array variable names to their corresponding index functions.+-- The info is not guaranteed to be exact, e.g., we assume ifs and loops+-- return arrays layed out in normalized (row-major) form in memory.+-- We only record aliasing statements, such as transposition, slice, etc.+type IxFnEnv = M.Map VName IxFun++type WithEnv = M.Map VName (Lambda GPU, [SubExp])++type Env = (WithEnv, IxFnEnv)++changeEnv :: Env -> VName -> Exp GPU -> TileM Env+changeEnv (with_env, ixfn_env) y e = do+ with_env' <- changeWithEnv with_env e+ ixfn_env' <- changeIxFnEnv ixfn_env y e+ pure (with_env', ixfn_env')++changeWithEnv :: WithEnv -> Exp GPU -> TileM WithEnv+changeWithEnv with_env (WithAcc accum_decs inner_lam) = do+ let bindings = map mapfun accum_decs+ par_tps = take (length bindings) $ map paramName $ lambdaParams inner_lam+ with_env' = M.union with_env $ M.fromList $ zip par_tps bindings+ pure with_env'+ where+ mapfun (_, _, Nothing) = error "What the hack is an accumulator without operator?"+ mapfun (shp, _, Just (lam_inds, ne)) =+ let len_inds = length $ shapeDims shp+ lam_op = lam_inds {lambdaParams = drop len_inds $ lambdaParams lam_inds}+ in (lam_op, ne)+changeWithEnv with_env _ = pure with_env++composeIxfuns :: IxFnEnv -> VName -> VName -> (IxFun -> IxFun) -> TileM IxFnEnv+composeIxfuns env y x ixf_fun =+ case M.lookup x env of+ Just ixf -> pure $ M.insert y (ixf_fun ixf) env+ Nothing -> do+ tp <- lookupType x+ case tp of+ Array _ptp shp _u -> do+ let shp' = map ExpMem.pe64 (shapeDims shp)+ pure $ M.insert y (ixf_fun $ IxFun.iota shp') env+ _ -> pure env++changeIxFnEnv :: IxFnEnv -> VName -> Exp GPU -> TileM IxFnEnv+changeIxFnEnv env y (BasicOp (Reshape shp_chg x)) =+ composeIxfuns env y x (`IxFun.reshape` map (fmap ExpMem.pe64) shp_chg)+changeIxFnEnv env y (BasicOp (Manifest perm x)) = do+ tp <- lookupType x+ case tp of+ Array _ptp shp _u -> do+ let shp' = map ExpMem.pe64 (shapeDims shp)+ let ixfn = IxFun.permute (IxFun.iota shp') perm+ pure $ M.insert y ixfn env+ _ -> error "In TileLoops/Shared.hs, changeIxFnEnv: manifest applied to a non-array!"+changeIxFnEnv env y (BasicOp (Rearrange perm x)) =+ composeIxfuns env y x (`IxFun.permute` perm)+changeIxFnEnv env y (BasicOp (Rotate rs x)) =+ composeIxfuns env y x (`IxFun.rotate` fmap ExpMem.pe64 rs)+changeIxFnEnv env y (BasicOp (Index x slc)) =+ composeIxfuns env y x (`IxFun.slice` (Slice $ map (fmap ExpMem.pe64) $ unSlice slc))+changeIxFnEnv env y (BasicOp (Opaque _ (Var x))) =+ composeIxfuns env y x id+changeIxFnEnv env _ _ = pure env
src/Futhark/Pass.hs view
@@ -95,9 +95,9 @@ let ((x', log), src') = runState (runPassM (f a)) src in (x', log, src') --- | Apply some operation to the top-level constants. Then applies an--- operation to all the function function definitions, which are also--- given the transformed constants so they can be brought into scope.+-- | Apply some operation to the top-level constants. Then applies an+-- operation to all the function definitions, which are also given the+-- transformed constants so they can be brought into scope. -- The function definition transformations are run in parallel (with -- 'parPass'), since they cannot affect each other. intraproceduralTransformationWithConsts ::
+ src/Futhark/Pass/AD.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE TypeFamilies #-}++-- | Apply all AD operators in the program, leaving AD-free code.+module Futhark.Pass.AD (applyAD, applyADInnermost) where++import Control.Monad+import Control.Monad.Reader+import Futhark.AD.Fwd (fwdJVP)+import Futhark.AD.Rev (revVJP)+import Futhark.Builder+import Futhark.IR.SOACS+import Futhark.IR.SOACS.Simplify (simplifyLambda)+import Futhark.Pass++-- | Whether we apply only the innermost AD operators, or all of them.+-- The former is very useful for debugging, but probably not useful+-- for actual compilation.+data Mode = Innermost | All+ deriving (Eq)++bindLambda ::+ (MonadBuilder m, Rep m ~ SOACS) =>+ Pat Type ->+ StmAux (ExpDec SOACS) ->+ Lambda SOACS ->+ [SubExp] ->+ m ()+bindLambda pat aux (Lambda params body _) args = do+ auxing aux . forM_ (zip params args) $ \(param, arg) ->+ letBindNames [paramName param] $+ BasicOp $ case (paramType param, arg) of+ (Array {}, Var v) -> Copy v+ _ -> SubExp arg+ res <- bodyBind body+ forM_ (zip (patNames pat) res) $ \(v, SubExpRes cs se) ->+ certifying cs $ letBindNames [v] $ BasicOp $ SubExp se++onStm :: Mode -> Scope SOACS -> Stm SOACS -> PassM (Stms SOACS)+onStm mode scope (Let pat aux (Op (VJP lam args vec))) = do+ lam' <- onLambda mode scope lam+ if mode == All || lam == lam'+ then do+ lam'' <- (`runReaderT` scope) . simplifyLambda =<< revVJP scope lam'+ runBuilderT_ (bindLambda pat aux lam'' $ args ++ vec) scope+ else pure $ oneStm $ Let pat aux $ Op $ VJP lam' args vec+onStm mode scope (Let pat aux (Op (JVP lam args vec))) = do+ lam' <- onLambda mode scope lam+ if mode == All || lam == lam'+ then do+ lam'' <- fwdJVP scope lam'+ runBuilderT_ (bindLambda pat aux lam'' $ args ++ vec) scope+ else pure $ oneStm $ Let pat aux $ Op $ JVP lam' args vec+onStm mode scope (Let pat aux e) = oneStm . Let pat aux <$> mapExpM mapper e+ where+ mapper =+ identityMapper+ { mapOnBody = \bscope -> onBody mode (bscope <> scope),+ mapOnOp = mapSOACM soac_mapper+ }+ soac_mapper = identitySOACMapper {mapOnSOACLambda = onLambda mode scope}++onStms :: Mode -> Scope SOACS -> Stms SOACS -> PassM (Stms SOACS)+onStms mode scope stms = mconcat <$> mapM (onStm mode scope') (stmsToList stms)+ where+ scope' = scopeOf stms <> scope++onBody :: Mode -> Scope SOACS -> Body SOACS -> PassM (Body SOACS)+onBody mode scope body = do+ stms <- onStms mode scope $ bodyStms body+ pure $ body {bodyStms = stms}++onLambda :: Mode -> Scope SOACS -> Lambda SOACS -> PassM (Lambda SOACS)+onLambda mode scope lam = do+ body <- onBody mode (scopeOfLParams (lambdaParams lam) <> scope) $ lambdaBody lam+ pure $ lam {lambdaBody = body}++onFun :: Mode -> Stms SOACS -> FunDef SOACS -> PassM (FunDef SOACS)+onFun mode consts fd = do+ body <- onBody mode (scopeOf consts <> scopeOf fd) $ funDefBody fd+ pure $ fd {funDefBody = body}++applyAD :: Pass SOACS SOACS+applyAD =+ Pass+ { passName = "ad",+ passDescription = "Apply AD operators",+ passFunction =+ intraproceduralTransformationWithConsts+ (onStms All mempty)+ (onFun All)+ }++applyADInnermost :: Pass SOACS SOACS+applyADInnermost =+ Pass+ { passName = "ad innermost",+ passDescription = "Apply innermost AD operators",+ passFunction =+ intraproceduralTransformationWithConsts+ (onStms Innermost mempty)+ (onFun Innermost)+ }
src/Futhark/Pass/ExpandAllocations.hs view
@@ -735,6 +735,7 @@ unAllocOp Alloc {} = Left "unAllocOp: unhandled Alloc" unAllocOp (Inner OtherOp {}) = Left "unAllocOp: unhandled OtherOp"+ unAllocOp (Inner GPUBody {}) = Left "unAllocOp: unhandled GPUBody" unAllocOp (Inner (SizeOp op)) = pure $ SizeOp op unAllocOp (Inner (SegOp op)) = SegOp <$> mapSegOpM mapper op where
src/Futhark/Pass/ExplicitAllocations/GPU.hs view
@@ -76,6 +76,8 @@ error $ "Cannot allocate memory in SOAC: " ++ pretty op handleHostOp (SegOp op) = Inner . SegOp <$> handleSegOp op+handleHostOp (GPUBody ts (Body _ stms res)) =+ fmap (Inner . GPUBody ts) . buildBody_ . allocInStms stms $ pure res kernelExpHints :: Exp GPUMem -> AllocM GPU GPUMem [ExpHint] kernelExpHints (BasicOp (Manifest perm v)) = do
src/Futhark/Pass/ExtractMulticore.hs view
@@ -284,6 +284,10 @@ pure (red_stms, op) transformSOAC :: Pat Type -> Attrs -> SOAC SOACS -> ExtractM (Stms MC)+transformSOAC _ _ JVP {} =+ error "transformSOAC: unhandled JVP"+transformSOAC _ _ VJP {} =+ error "transformSOAC: unhandled VJP" transformSOAC pat _ (Screma w arrs form) | Just lam <- isMapSOAC form = do seq_op <- transformMap DoNotRename sequentialiseBody w lam arrs
src/Futhark/Passes.hs view
@@ -17,18 +17,23 @@ import Futhark.IR.GPUMem (GPUMem) import Futhark.IR.MC (MC) import Futhark.IR.MCMem (MCMem)-import Futhark.IR.SOACS (SOACS)+import Futhark.IR.SOACS (SOACS, usesAD) import Futhark.IR.Seq (Seq) import Futhark.IR.SeqMem (SeqMem) import Futhark.Optimise.CSE import Futhark.Optimise.DoubleBuffer import Futhark.Optimise.Fusion+import Futhark.Optimise.GenRedOpt+import Futhark.Optimise.HistAccs import Futhark.Optimise.InPlaceLowering import Futhark.Optimise.InliningDeadFun import qualified Futhark.Optimise.MemoryBlockMerging as MemoryBlockMerging+import Futhark.Optimise.MergeGPUBodies+import Futhark.Optimise.ReduceDeviceSyncs import Futhark.Optimise.Sink import Futhark.Optimise.TileLoops import Futhark.Optimise.Unstream+import Futhark.Pass.AD import Futhark.Pass.ExpandAllocations import qualified Futhark.Pass.ExplicitAllocations.GPU as GPU import qualified Futhark.Pass.ExplicitAllocations.MC as MC@@ -62,7 +67,23 @@ simplifySOACS, removeDeadFunctions ]+ >>> condPipeline usesAD adPipeline +-- | This is the pipeline that applies the AD transformation and+-- subsequent interesting optimisations.+adPipeline :: Pipeline SOACS SOACS+adPipeline =+ passes+ [ applyAD,+ simplifySOACS,+ performCSE True,+ fuseSOACs,+ performCSE True,+ simplifySOACS,+ fuseSOACs,+ simplifySOACS+ ]+ -- | The pipeline used by the CUDA and OpenCL backends, but before -- adding memory information. Includes 'standardPipeline'. kernelsPipeline :: Pipeline SOACS GPU@@ -71,12 +92,23 @@ >>> onePass extractKernels >>> passes [ simplifyGPU,- babysitKernels,+ optimiseGenRed,+ simplifyGPU, tileLoops,+ simplifyGPU,+ histAccsGPU,+ babysitKernels,+ simplifyGPU, unstreamGPU, performCSE True, simplifyGPU,- sinkGPU,+ sinkGPU, -- Sink reads before migrating them.+ reduceDeviceSyncs,+ simplifyGPU, -- Simplify and hoist storages.+ performCSE True, -- Eliminate duplicate storages.+ mergeGPUBodies,+ simplifyGPU, -- Cleanup merged GPUBody kernels.+ sinkGPU, -- Sink reads within GPUBody kernels. inPlaceLoweringGPU ]
src/Futhark/Pipeline.hs view
@@ -22,6 +22,7 @@ module Futhark.Error, onePass, passes,+ condPipeline, runPipeline, ) where@@ -36,6 +37,7 @@ import qualified Data.Text.IO as T import Data.Time.Clock import qualified Futhark.Analysis.Alias as Alias+import Futhark.Compiler.Config (Verbosity (..)) import Futhark.Error import Futhark.IR (PrettyRep, Prog) import Futhark.IR.TypeCheck@@ -47,16 +49,6 @@ import Text.Printf import Prelude hiding (id, (.)) --- | How much information to print to stderr while the compiler is running.-data Verbosity- = -- | Silence is golden.- NotVerbose- | -- | Print messages about which pass is running.- Verbose- | -- | Also print logs from individual passes.- VeryVerbose- deriving (Eq, Ord)- newtype FutharkEnv = FutharkEnv {futharkVerbose :: Verbosity} data FutharkState = FutharkState@@ -153,6 +145,15 @@ Left err -> validationError pass prog'' $ show err Right () -> pure () pure prog'++-- | Conditionally run pipeline if predicate is true.+condPipeline ::+ (Prog rep -> Bool) -> Pipeline rep rep -> Pipeline rep rep+condPipeline cond (Pipeline f) =+ Pipeline $ \cfg prog ->+ if cond prog+ then f cfg prog+ else pure prog -- | Create a pipeline from a list of passes. passes ::
src/Futhark/Script.hs view
@@ -152,8 +152,8 @@ [ try $ inParens sep (mkTuple <$> (parseExp sep `sepBy` pComma)), inParens sep $ parseExp sep, inBraces sep (Record <$> (pField `sepBy` pComma)),- Const <$> V.parseValue sep, StringLit . T.pack <$> lexeme sep ("\"" *> manyTill charLiteral "\""),+ Const <$> V.parseValue sep, Call <$> parseFunc <*> pure [] ] @@ -378,7 +378,7 @@ evalExp' _ (StringLit s) = case V.putValue s of Just s' ->- pure $ V.ValueAtom $ SValue (V.valueTypeText (V.valueType s')) $ VVal s'+ pure $ V.ValueAtom $ SValue (V.valueTypeTextNoDims (V.valueType s')) $ VVal s' Nothing -> error $ "Unable to write value " ++ pretty s evalExp' _ (Const val) = pure $ V.ValueAtom $ SValue (V.valueTypeTextNoDims (V.valueType val)) $ VVal val
src/Futhark/Test.hs view
@@ -163,11 +163,15 @@ note (_, t) | "(" `T.isPrefixOf` t = Just $- "\nNote: expected type " <> prettyText t <> " is an opaque tuple that cannot be constructed\n"+ "\nNote: expected type "+ <> prettyText t+ <> " is an opaque tuple that cannot be constructed\n" <> "in FutharkScript. Consider using type annotations to give it a proper name." | "{" `T.isPrefixOf` t = Just $- "\nNote: expected type " <> prettyText t <> " is an opaque record that cannot be constructed\n"+ "\nNote: expected type "+ <> prettyText t+ <> " is an opaque record that cannot be constructed\n" <> "in FutharkScript. Consider using type annotations to give it a proper name." | otherwise = Nothing
src/Futhark/Transform/FirstOrderTransform.hs view
@@ -123,6 +123,10 @@ Pat (LetDec (Rep m)) -> SOAC (Rep m) -> m ()+transformSOAC _ JVP {} =+ error "transformSOAC: unhandled JVP"+transformSOAC _ VJP {} =+ error "transformSOAC: unhandled VJP" transformSOAC pat (Screma w arrs form@(ScremaForm scans reds map_lam)) = do -- See Note [Translation of Screma]. --
src/Futhark/Util.hs view
@@ -15,6 +15,8 @@ maxinum, chunk, chunks,+ pairs,+ unpairs, dropAt, takeLast, dropLast,@@ -55,13 +57,13 @@ trim, pmapIO, interactWithFileSafely,- readFileSafely, convFloat, UserString, EncodedString, zEncodeString, atMostChars, invertMap,+ traverseFold, fixPoint, ) where@@ -76,6 +78,7 @@ import qualified Data.ByteString.Base16 as Base16 import Data.Char import Data.Either+import Data.Foldable (fold) import Data.Function ((&)) import Data.List (foldl', genericDrop, genericSplitAt, sortBy) import qualified Data.List.NonEmpty as NE@@ -85,7 +88,6 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.Text.Encoding.Error as T-import qualified Data.Text.IO as T import Data.Time.Clock (UTCTime, getCurrentTime) import Data.Tuple (swap) import Numeric@@ -142,6 +144,18 @@ let (bef, aft) = splitAt n xs in bef : chunks ns aft +-- | @pairs l@ chunks the list into pairs of consecutive elements,+-- ignoring any excess element. Example: @pairs [a,b,c,d] ==+-- [(a,b),(c,d)]@.+pairs :: [a] -> [(a, a)]+pairs (a : b : l) = (a, b) : pairs l+pairs _ = []++-- | The opposite of 'pairs': @unpairs [(a,b),(c,d)] = [a,b,c,d]@.+unpairs :: [(a, a)] -> [a]+unpairs [] = []+unpairs ((a, b) : l) = a : b : unpairs l+ -- | Like 'maximum', but returns zero for an empty list. maxinum :: (Num a, Ord a, Foldable f) => f a -> a maxinum = foldl' max 0@@ -422,12 +436,6 @@ | otherwise = pure $ Just $ Left $ show e --- | Read a file, returning 'Nothing' if the file does not exist, and--- 'Left' if some other error occurs.-readFileSafely :: FilePath -> IO (Maybe (Either String T.Text))-readFileSafely filepath =- interactWithFileSafely $ T.readFile filepath- -- | Convert between different floating-point types, preserving -- infinities and NaNs. convFloat :: (RealFloat from, RealFloat to) => from -> to@@ -528,6 +536,10 @@ M.toList m & fmap (swap . first S.singleton) & foldr (uncurry $ M.insertWith (<>)) mempty++-- | Applicatively fold a traversable.+traverseFold :: (Monoid m, Traversable t, Applicative f) => (a -> f m) -> t a -> f m+traverseFold f = fmap fold . traverse f -- | Perform fixpoint iteration. fixPoint :: Eq a => (a -> a) -> a -> a
src/Futhark/Util/Console.hs view
@@ -2,6 +2,7 @@ module Futhark.Util.Console ( color, inRed,+ inYellow, inBold, ) where@@ -15,6 +16,10 @@ -- | Make the string red. inRed :: String -> String inRed s = setSGRCode [SetColor Foreground Vivid Red] ++ s ++ setSGRCode [Reset]++-- | Make the string yellow.+inYellow :: String -> String+inYellow s = setSGRCode [SetColor Foreground Vivid Yellow] ++ s ++ setSGRCode [Reset] -- | Make the string bold. inBold :: String -> String
src/Futhark/Util/IntegralExp.hs view
@@ -31,6 +31,7 @@ div :: e -> e -> e mod :: e -> e -> e sgn :: e -> Maybe Int+ pow :: e -> e -> e -- | Like 'Futhark.Util.IntegralExp.div', but rounds towards -- positive infinity.@@ -71,3 +72,4 @@ div = liftOp2 Prelude.div mod = liftOp2 Prelude.mod sgn = Just . fromIntegral . signum . toInteger . wrappedValue+ pow = liftOp2 (Prelude.^)
+ src/Futhark/Util/ProgressBar.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Facilities for generating and otherwise handling text-based progress bars.+module Futhark.Util.ProgressBar+ ( progressBar,+ ProgressBar (..),+ progressSpinner,+ )+where++import qualified Data.Text as T++-- | Information about a progress bar to render. The "progress space"+-- spans from 0 and up to the `progressBarBound`, but can be+-- visualised in any number of steps.+data ProgressBar = ProgressBar+ { -- | Number of steps in the visualisation.+ progressBarSteps :: Int,+ -- | The logical upper bound.+ progressBarBound :: Double,+ -- | The current position in the progress bar, relative to the+ -- upper bound.+ progressBarElapsed :: Double+ }++-- | Render the progress bar.+progressBar :: ProgressBar -> T.Text+progressBar (ProgressBar steps bound elapsed) =+ "|" <> T.pack (map cell [1 .. steps]) <> "| "+ where+ step_size :: Double+ step_size = bound / fromIntegral steps+ chars = " ▏▎▍▍▌▋▊▉█"+ num_chars = T.length chars+ char i+ | i >= 0 && i < num_chars = T.index chars i+ | otherwise = ' '++ cell :: Int -> Char+ cell i+ | i' * step_size <= elapsed = char 9+ | otherwise =+ char (floor (((elapsed - (i' - 1) * step_size) * fromIntegral num_chars) / step_size))+ where+ i' = fromIntegral i++-- | Render a spinner - a kind of progress bar where there is no upper+-- bound because we don't know how long it'll take. You certainly+-- know these from THE INTERNET. The non-negative integer is how many+-- "steps" have been taken. The spinner looks best if this is+-- incremented by one for every call.+progressSpinner :: Int -> T.Text+progressSpinner spin_idx =+ T.singleton $ T.index spin_load (spin_idx `rem` n)+ where+ spin_load = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"+ n = T.length spin_load
src/Language/Futhark/Interpreter.hs view
@@ -1933,6 +1933,10 @@ <> pretty (asInt64 m) <> "]" else pure $ toArray shape $ map (toArray rowshape) $ chunk (asInt m) xs'+ def "vjp2" = Just $+ fun3t $ \_ _ _ -> bad noLoc mempty "Interpreter does not support autodiff."+ def "jvp2" = Just $+ fun3t $ \_ _ _ -> bad noLoc mempty "Interpreter does not support autodiff." def "acc" = Nothing def s | nameFromString s `M.member` namesToPrimTypes = Nothing def s = error $ "Missing intrinsic: " ++ s
src/Language/Futhark/Prop.hs view
@@ -73,6 +73,7 @@ DimPos (..), mustBeExplicit, mustBeExplicitInType,+ determineSizeWitnesses, tupleRecord, isTupleRecord, areTupleFields,@@ -234,13 +235,22 @@ onDim _ _ _ = pure () +-- | Determine which of the sizes in a type are used as sizes outside+-- of functions in the type, and which are not. The former are said+-- to be "witnessed" by this type, while the latter are not. In+-- practice, the latter means that the actual sizes must come from+-- somewhere else.+determineSizeWitnesses :: StructType -> (S.Set VName, S.Set VName)+determineSizeWitnesses t =+ bimap (S.fromList . M.keys) (S.fromList . M.keys) $+ M.partition not $ mustBeExplicitAux t+ -- | Figure out which of the sizes in a parameter type must be passed -- explicitly, because their first use is as something else than just -- an array dimension. 'mustBeExplicit' is like this function, but -- first decomposes into parameter types. mustBeExplicitInType :: StructType -> S.Set VName-mustBeExplicitInType t =- S.fromList $ M.keys $ M.filter id $ mustBeExplicitAux t+mustBeExplicitInType = snd . determineSizeWitnesses -- | Figure out which of the sizes in a binding type must be passed -- explicitly, because their first use is as something else than just@@ -1068,6 +1078,24 @@ arr_b $ shape [n] ] $ RetType [] $ uarr_a $ shape [k]+ ),+ ( "jvp2",+ IntrinsicPolyFun+ [tp_a, tp_b]+ [ Scalar t_a `arr` Scalar t_b,+ Scalar t_a,+ Scalar t_a+ ]+ $ RetType [] $ Scalar $ tupleRecord [Scalar t_b, Scalar t_b]+ ),+ ( "vjp2",+ IntrinsicPolyFun+ [tp_a, tp_b]+ [ Scalar t_a `arr` Scalar t_b,+ Scalar t_a,+ Scalar t_b+ ]+ $ RetType [] $ Scalar $ tupleRecord [Scalar t_b, Scalar t_a] ) ] ++@@ -1315,34 +1343,62 @@ -- | The set of module types used in any exported (non-local) -- declaration.-progModuleTypes :: Ord vn => ProgBase f vn -> S.Set vn-progModuleTypes = mconcat . map onDec . progDecs+progModuleTypes :: ProgBase Info VName -> S.Set VName+progModuleTypes prog = foldMap reach mtypes_used where- onDec (OpenDec x _) = onModExp x- onDec (ModDec md) =- maybe mempty (onSigExp . fst) (modSignature md) <> onModExp (modExp md)- onDec SigDec {} = mempty- onDec TypeDec {} = mempty- onDec ValDec {} = mempty- onDec LocalDec {} = mempty- onDec ImportDec {} = mempty+ -- Fixed point iteration.+ reach v = S.singleton v <> maybe mempty (foldMap reach) (M.lookup v reachable_from_mtype) - onModExp ModVar {} = mempty- onModExp (ModParens p _) = onModExp p- onModExp ModImport {} = mempty- onModExp (ModDecs ds _) = mconcat $ map onDec ds- onModExp (ModApply me1 me2 _ _ _) = onModExp me1 <> onModExp me2- onModExp (ModAscript me se _ _) = onModExp me <> onSigExp se- onModExp (ModLambda p r me _) =- onModParam p <> maybe mempty (onSigExp . fst) r <> onModExp me+ reachable_from_mtype = foldMap onDec $ progDecs prog+ where+ onDec OpenDec {} = mempty+ onDec ModDec {} = mempty+ onDec (SigDec sb) =+ M.singleton (sigName sb) (onSigExp (sigExp sb))+ onDec TypeDec {} = mempty+ onDec ValDec {} = mempty+ onDec (LocalDec d _) = onDec d+ onDec ImportDec {} = mempty - onModParam = onSigExp . modParamType+ onSigExp (SigVar v _ _) = S.singleton $ qualLeaf v+ onSigExp (SigParens e _) = onSigExp e+ onSigExp (SigSpecs ss _) = foldMap onSpec ss+ onSigExp (SigWith e _ _) = onSigExp e+ onSigExp (SigArrow _ e1 e2 _) = onSigExp e1 <> onSigExp e2 - onSigExp (SigVar v _ _) = S.singleton $ qualLeaf v- onSigExp (SigParens e _) = onSigExp e- onSigExp SigSpecs {} = mempty- onSigExp (SigWith e _ _) = onSigExp e- onSigExp (SigArrow _ e1 e2 _) = onSigExp e1 <> onSigExp e2+ onSpec ValSpec {} = mempty+ onSpec TypeSpec {} = mempty+ onSpec TypeAbbrSpec {} = mempty+ onSpec (ModSpec vn e _ _) = S.singleton vn <> onSigExp e+ onSpec (IncludeSpec e _) = onSigExp e++ mtypes_used = foldMap onDec $ progDecs prog+ where+ onDec (OpenDec x _) = onModExp x+ onDec (ModDec md) =+ maybe mempty (onSigExp . fst) (modSignature md) <> onModExp (modExp md)+ onDec SigDec {} = mempty+ onDec TypeDec {} = mempty+ onDec ValDec {} = mempty+ onDec LocalDec {} = mempty+ onDec ImportDec {} = mempty++ onModExp ModVar {} = mempty+ onModExp (ModParens p _) = onModExp p+ onModExp ModImport {} = mempty+ onModExp (ModDecs ds _) = mconcat $ map onDec ds+ onModExp (ModApply me1 me2 _ _ _) = onModExp me1 <> onModExp me2+ onModExp (ModAscript me se _ _) = onModExp me <> onSigExp se+ onModExp (ModLambda p r me _) =+ onModParam p <> maybe mempty (onSigExp . fst) r <> onModExp me++ onModParam = onSigExp . modParamType++ onSigExp (SigVar v _ _) = S.singleton $ qualLeaf v+ onSigExp (SigParens e _) = onSigExp e+ onSigExp SigSpecs {} = mempty+ onSigExp (SigWith e _ _) = onSigExp e+ onSigExp (SigArrow _ e1 e2 _) = onSigExp e1 <> onSigExp e2 -- | Extract a leading @((name, namespace, file), remainder)@ from a -- documentation comment string. These are formatted as
src/Language/Futhark/Query.hs view
@@ -371,10 +371,7 @@ containingModule imports (Pos file _ _ _) = snd <$> find ((== file') . fst) imports where- file' =- includeToString $- mkInitialImport $- fst $ Posix.splitExtension file+ file' = includeToString $ mkInitialImport $ fst $ Posix.splitExtension file -- | Information about what is at the given source location. data AtPos = AtName (QualName VName) (Maybe BoundTo) Loc
src/Language/Futhark/Syntax.hs view
@@ -92,7 +92,6 @@ DecBase (..), -- * Miscellaneous- Showable, NoInfo (..), Info (..), Alias (..),@@ -125,38 +124,11 @@ import Language.Futhark.Core import Prelude --- | Convenience class for deriving 'Show' instances for the AST.-class- ( Show vn,- Show (f VName),- Show (f (Diet, Maybe VName)),- Show (f String),- Show (f [VName]),- Show (f ([VName], [VName])),- Show (f PatType),- Show (f (PatType, [VName])),- Show (f (StructType, [VName])),- Show (f (StructRetType, [VName])),- Show (f EntryPoint),- Show (f StructType),- Show (f StructRetType),- Show (f PatRetType),- Show (f (StructType, Maybe VName)),- Show (f (PName, StructType)),- Show (f (PName, StructType, Maybe VName)),- Show (f (Aliasing, StructRetType)),- Show (f (M.Map VName VName)),- Show (f AppRes)- ) =>- Showable f vn- -- | No information functor. Usually used for placeholder type- or -- aliasing information. data NoInfo a = NoInfo deriving (Eq, Ord, Show) -instance Show vn => Showable NoInfo vn- instance Functor NoInfo where fmap _ NoInfo = NoInfo @@ -170,8 +142,6 @@ newtype Info a = Info {unInfo :: a} deriving (Eq, Ord, Show) -instance Show vn => Showable Info vn- instance Functor Info where fmap f (Info x) = Info $ f x @@ -267,12 +237,10 @@ NamedDim (QualName vn) | -- | The size is a constant. ConstDim Int- | -- | No known size - but still possibly given a unique name, so we- -- can recognise e.g. @type square [n] = [n][n]i32@ and make- -- @square []@ do the right thing. If @Nothing@, then this is a- -- name distinct from any other. The type checker should _never_- -- produce these - they are a (hopefully temporary) thing- -- introduced by defunctorisation and monomorphisation.+ | -- | No known size. If @Nothing@, then this is a name distinct+ -- from any other. The type checker should _never_ produce these+ -- - they are a (hopefully temporary) thing introduced by+ -- defunctorisation and monomorphisation. AnyDim (Maybe vn) deriving (Show) @@ -553,8 +521,10 @@ expandedType :: f StructType } -deriving instance Showable f vn => Show (TypeDeclBase f vn)+deriving instance Show (TypeDeclBase Info VName) +deriving instance Show vn => Show (TypeDeclBase NoInfo vn)+ deriving instance Eq (TypeDeclBase NoInfo VName) deriving instance Ord (TypeDeclBase NoInfo VName)@@ -596,8 +566,10 @@ identSrcLoc :: SrcLoc } -deriving instance Showable f vn => Show (IdentBase f vn)+deriving instance Show (IdentBase Info VName) +deriving instance Show vn => Show (IdentBase NoInfo vn)+ instance Eq vn => Eq (IdentBase ty vn) where x == y = identName x == identName y @@ -678,8 +650,10 @@ (Maybe (ExpBase f vn)) (Maybe (ExpBase f vn)) -deriving instance Showable f vn => Show (DimIndexBase f vn)+deriving instance Show (DimIndexBase Info VName) +deriving instance Show vn => Show (DimIndexBase NoInfo vn)+ deriving instance Eq (DimIndexBase NoInfo VName) deriving instance Ord (DimIndexBase NoInfo VName)@@ -789,8 +763,10 @@ | -- | A match expression. Match (ExpBase f vn) (NE.NonEmpty (CaseBase f vn)) SrcLoc -deriving instance Showable f vn => Show (AppExpBase f vn)+deriving instance Show (AppExpBase Info VName) +deriving instance Show vn => Show (AppExpBase NoInfo vn)+ deriving instance Eq (AppExpBase NoInfo VName) deriving instance Ord (AppExpBase NoInfo VName)@@ -893,8 +869,10 @@ Ascript (ExpBase f vn) (TypeDeclBase f vn) SrcLoc | AppExp (AppExpBase f vn) (f AppRes) -deriving instance Showable f vn => Show (ExpBase f vn)+deriving instance Show (ExpBase Info VName) +deriving instance Show vn => Show (ExpBase NoInfo vn)+ deriving instance Eq (ExpBase NoInfo VName) deriving instance Ord (ExpBase NoInfo VName)@@ -933,8 +911,10 @@ = RecordFieldExplicit Name (ExpBase f vn) SrcLoc | RecordFieldImplicit vn (f PatType) SrcLoc -deriving instance Showable f vn => Show (FieldBase f vn)+deriving instance Show (FieldBase Info VName) +deriving instance Show vn => Show (FieldBase NoInfo vn)+ deriving instance Eq (FieldBase NoInfo VName) deriving instance Ord (FieldBase NoInfo VName)@@ -946,8 +926,10 @@ -- | A case in a match expression. data CaseBase f vn = CasePat (PatBase f vn) (ExpBase f vn) SrcLoc -deriving instance Showable f vn => Show (CaseBase f vn)+deriving instance Show (CaseBase Info VName) +deriving instance Show vn => Show (CaseBase NoInfo vn)+ deriving instance Eq (CaseBase NoInfo VName) deriving instance Ord (CaseBase NoInfo VName)@@ -961,8 +943,10 @@ | ForIn (PatBase f vn) (ExpBase f vn) | While (ExpBase f vn) -deriving instance Showable f vn => Show (LoopFormBase f vn)+deriving instance Show (LoopFormBase Info VName) +deriving instance Show vn => Show (LoopFormBase NoInfo vn)+ deriving instance Eq (LoopFormBase NoInfo VName) deriving instance Ord (LoopFormBase NoInfo VName)@@ -987,8 +971,10 @@ | PatConstr Name (f PatType) [PatBase f vn] SrcLoc | PatAttr (AttrInfo vn) (PatBase f vn) SrcLoc -deriving instance Showable f vn => Show (PatBase f vn)+deriving instance Show (PatBase Info VName) +deriving instance Show vn => Show (PatBase NoInfo vn)+ deriving instance Eq (PatBase NoInfo VName) deriving instance Ord (PatBase NoInfo VName)@@ -1057,8 +1043,10 @@ valBindLocation :: SrcLoc } -deriving instance Showable f vn => Show (ValBindBase f vn)+deriving instance Show (ValBindBase Info VName) +deriving instance Show (ValBindBase NoInfo Name)+ instance Located (ValBindBase f vn) where locOf = locOf . valBindLocation @@ -1073,8 +1061,10 @@ typeBindLocation :: SrcLoc } -deriving instance Showable f vn => Show (TypeBindBase f vn)+deriving instance Show (TypeBindBase Info VName) +deriving instance Show (TypeBindBase NoInfo Name)+ instance Located (TypeBindBase f vn) where locOf = locOf . typeBindLocation @@ -1133,8 +1123,10 @@ | ModSpec vn (SigExpBase f vn) (Maybe DocComment) SrcLoc | IncludeSpec (SigExpBase f vn) SrcLoc -deriving instance Showable f vn => Show (SpecBase f vn)+deriving instance Show (SpecBase Info VName) +deriving instance Show (SpecBase NoInfo Name)+ instance Located (SpecBase f vn) where locOf (ValSpec _ _ _ _ loc) = locOf loc locOf (TypeAbbrSpec tbind) = locOf tbind@@ -1150,13 +1142,17 @@ | SigWith (SigExpBase f vn) (TypeRefBase f vn) SrcLoc | SigArrow (Maybe vn) (SigExpBase f vn) (SigExpBase f vn) SrcLoc -deriving instance Showable f vn => Show (SigExpBase f vn)+deriving instance Show (SigExpBase Info VName) +deriving instance Show (SigExpBase NoInfo Name)+ -- | A type refinement. data TypeRefBase f vn = TypeRef (QualName vn) [TypeParamBase vn] (TypeDeclBase f vn) SrcLoc -deriving instance Showable f vn => Show (TypeRefBase f vn)+deriving instance Show (TypeRefBase Info VName) +deriving instance Show (TypeRefBase NoInfo Name)+ instance Located (TypeRefBase f vn) where locOf (TypeRef _ _ _ loc) = locOf loc @@ -1175,8 +1171,10 @@ sigLoc :: SrcLoc } -deriving instance Showable f vn => Show (SigBindBase f vn)+deriving instance Show (SigBindBase Info VName) +deriving instance Show (SigBindBase NoInfo Name)+ instance Located (SigBindBase f vn) where locOf = locOf . sigLoc @@ -1203,8 +1201,10 @@ (ModExpBase f vn) SrcLoc -deriving instance Showable f vn => Show (ModExpBase f vn)+deriving instance Show (ModExpBase Info VName) +deriving instance Show (ModExpBase NoInfo Name)+ instance Located (ModExpBase f vn) where locOf (ModVar _ loc) = locOf loc locOf (ModParens _ loc) = locOf loc@@ -1224,8 +1224,10 @@ modLocation :: SrcLoc } -deriving instance Showable f vn => Show (ModBindBase f vn)+deriving instance Show (ModBindBase Info VName) +deriving instance Show (ModBindBase NoInfo Name)+ instance Located (ModBindBase f vn) where locOf = locOf . modLocation @@ -1237,8 +1239,10 @@ modParamLocation :: SrcLoc } -deriving instance Showable f vn => Show (ModParamBase f vn)+deriving instance Show (ModParamBase Info VName) +deriving instance Show (ModParamBase NoInfo Name)+ instance Located (ModParamBase f vn) where locOf = locOf . modParamLocation @@ -1252,8 +1256,10 @@ | LocalDec (DecBase f vn) SrcLoc | ImportDec FilePath (f FilePath) SrcLoc -deriving instance Showable f vn => Show (DecBase f vn)+deriving instance Show (DecBase Info VName) +deriving instance Show (DecBase NoInfo Name)+ instance Located (DecBase f vn) where locOf (ValDec d) = locOf d locOf (TypeDec d) = locOf d@@ -1270,7 +1276,9 @@ progDecs :: [DecBase f vn] } -deriving instance Showable f vn => Show (ProgBase f vn)+deriving instance Show (ProgBase Info VName)++deriving instance Show (ProgBase NoInfo Name) --- Some prettyprinting definitions are here because we need them in --- the Attributes module.
src/Language/Futhark/TypeChecker/Modules.hs view
@@ -266,13 +266,13 @@ mismatchedLiftedness name_l (map qualLeaf $ M.keys mod_abs)- (qualLeaf name)+ name (mod_l, ps, t) | name_l < SizeLifted, not $ null $ retDims t -> anonymousSizes (map qualLeaf $ M.keys mod_abs)- (qualLeaf name)+ name (mod_l, ps, t) | Just (abs_name, _) <- M.lookup (fmap baseName name) abs_mapping -> pure (qualLeaf name, (abs_name, TypeAbbr name_l ps t))@@ -355,25 +355,26 @@ mismatchedType :: Loc -> [VName] ->+ [VName] -> VName -> (Liftedness, [TypeParam], StructRetType) -> (Liftedness, [TypeParam], StructRetType) -> Either TypeError b-mismatchedType loc abs name spec_t env_t =+mismatchedType loc abs quals name spec_t env_t = Left . TypeError loc mempty $ "Module defines"- </> indent 2 (ppTypeAbbr abs name env_t)+ </> indent 2 (ppTypeAbbr abs (QualName quals name) env_t) </> "but module type requires"- </> indent 2 (ppTypeAbbr abs name spec_t)+ </> indent 2 (ppTypeAbbr abs (QualName quals name) spec_t) -ppTypeAbbr :: [VName] -> VName -> (Liftedness, [TypeParam], StructRetType) -> Doc+ppTypeAbbr :: [VName] -> QualName VName -> (Liftedness, [TypeParam], StructRetType) -> Doc ppTypeAbbr abs name (l, ps, RetType [] (Scalar (TypeVar () _ tn args))) | typeLeaf tn `elem` abs, map typeParamToArg ps == args =- "type" <> ppr l <+> pprName name+ "type" <> ppr l <+> ppr name <+> spread (map ppr ps) ppTypeAbbr _ name (l, ps, t) =- "type" <> ppr l <+> pprName name+ "type" <> ppr l <+> ppr name <+> spread (map ppr ps) <+> equals <+/> nest 2 (align (ppr t))@@ -390,29 +391,31 @@ matchMTys orig_mty orig_mty_sig = matchMTys' (M.map (SizeSubst . NamedDim) $ resolveMTyNames orig_mty orig_mty_sig)+ [] orig_mty orig_mty_sig where matchMTys' :: M.Map VName (Subst StructRetType) ->+ [VName] -> MTy -> MTy -> Loc -> Either TypeError (M.Map VName VName) - matchMTys' _ (MTy _ ModFun {}) (MTy _ ModEnv {}) loc =+ matchMTys' _ _ (MTy _ ModFun {}) (MTy _ ModEnv {}) loc = Left $ TypeError loc mempty "Cannot match parametric module with non-parametric module type."- matchMTys' _ (MTy _ ModEnv {}) (MTy _ ModFun {}) loc =+ matchMTys' _ _ (MTy _ ModEnv {}) (MTy _ ModFun {}) loc = Left $ TypeError loc mempty "Cannot match non-parametric module with paramatric module type."- matchMTys' old_abs_subst_to_type (MTy mod_abs mod) (MTy sig_abs sig) loc = do+ matchMTys' old_abs_subst_to_type quals (MTy mod_abs mod) (MTy sig_abs sig) loc = do -- Check that abstract types in 'sig' have an implementation in -- 'mod'. This also gives us a substitution that we use to check -- the types of values.@@ -421,31 +424,33 @@ let abs_subst_to_type = old_abs_subst_to_type <> M.map (substFromAbbr . snd) abs_substs abs_name_substs = M.map (qualLeaf . fst) abs_substs- substs <- matchMods abs_subst_to_type mod sig loc+ substs <- matchMods abs_subst_to_type quals mod sig loc pure (substs <> abs_name_substs) matchMods :: M.Map VName (Subst StructRetType) ->+ [VName] -> Mod -> Mod -> Loc -> Either TypeError (M.Map VName VName)- matchMods _ ModEnv {} ModFun {} loc =+ matchMods _ _ ModEnv {} ModFun {} loc = Left $ TypeError loc mempty "Cannot match non-parametric module with parametric module type."- matchMods _ ModFun {} ModEnv {} loc =+ matchMods _ _ ModFun {} ModEnv {} loc = Left $ TypeError loc mempty "Cannot match parametric module with non-parametric module type."- matchMods abs_subst_to_type (ModEnv mod) (ModEnv sig) loc =- matchEnvs abs_subst_to_type mod sig loc+ matchMods abs_subst_to_type quals (ModEnv mod) (ModEnv sig) loc =+ matchEnvs abs_subst_to_type quals mod sig loc matchMods old_abs_subst_to_type+ quals (ModFun (FunSig mod_abs mod_pmod mod_mod)) (ModFun (FunSig sig_abs sig_pmod sig_mod)) loc = do@@ -453,17 +458,18 @@ let abs_subst_to_type = old_abs_subst_to_type <> M.map (substFromAbbr . snd) abs_substs abs_name_substs = M.map (qualLeaf . fst) abs_substs- pmod_substs <- matchMods abs_subst_to_type mod_pmod sig_pmod loc- mod_substs <- matchMTys' abs_subst_to_type mod_mod sig_mod loc+ pmod_substs <- matchMods abs_subst_to_type quals mod_pmod sig_pmod loc+ mod_substs <- matchMTys' abs_subst_to_type quals mod_mod sig_mod loc pure (pmod_substs <> mod_substs <> abs_name_substs) matchEnvs :: M.Map VName (Subst StructRetType) ->+ [VName] -> Env -> Env -> Loc -> Either TypeError (M.Map VName VName)- matchEnvs abs_subst_to_type env sig loc = do+ matchEnvs abs_subst_to_type quals env sig loc = do -- XXX: we only want to create substitutions for visible names. -- This must be wrong in some cases. Probably we need to -- rethink how we do shadowing for module types.@@ -476,7 +482,7 @@ \(name, TypeAbbr spec_l spec_ps spec_t) -> case findBinding envTypeTable Type (baseName name) env of Just (name', TypeAbbr l ps t) ->- matchTypeAbbr loc abs_subst_to_type name spec_l spec_ps spec_t name' l ps t+ matchTypeAbbr loc abs_subst_to_type quals name spec_l spec_ps spec_t name' l ps t Nothing -> missingType loc $ baseName name -- Check that all values are defined correctly, substituting the@@ -485,7 +491,7 @@ forM (M.toList $ envVtable sig) $ \(name, spec_bv) -> do let spec_bv' = substituteTypesInBoundV (`M.lookup` abs_subst_to_type) spec_bv case findBinding envVtable Term (baseName name) env of- Just (name', bv) -> matchVal loc name spec_bv' name' bv+ Just (name', bv) -> matchVal loc quals name spec_bv' name' bv _ -> missingVal loc (baseName name) -- Check for correct modules.@@ -493,7 +499,8 @@ forM (M.toList $ envModTable sig) $ \(name, modspec) -> case findBinding envModTable Term (baseName name) env of Just (name', mod) ->- M.insert name name' <$> matchMods abs_subst_to_type mod modspec loc+ M.insert name name'+ <$> matchMods abs_subst_to_type (quals ++ [name]) mod modspec loc Nothing -> missingMod loc $ baseName name @@ -502,6 +509,7 @@ matchTypeAbbr :: Loc -> M.Map VName (Subst StructRetType) ->+ [VName] -> VName -> Liftedness -> [TypeParam] ->@@ -511,11 +519,9 @@ [TypeParam] -> StructRetType -> Either TypeError (VName, VName)- matchTypeAbbr loc abs_subst_to_type spec_name spec_l spec_ps spec_t name l ps t = do- -- We have to create substitutions for the type parameters, too.+ matchTypeAbbr loc abs_subst_to_type quals spec_name spec_l spec_ps spec_t name l ps t = do+ -- Number of type parameters must match. unless (length spec_ps == length ps) $ nomatch spec_t- param_substs <-- mconcat <$> zipWithM (matchTypeParam (nomatch spec_t)) spec_ps ps -- Abstract types have a particular restriction to ensure that -- if we have a value of an abstract type 't [n]', then there is@@ -526,67 +532,60 @@ d : _ -> Left . TypeError loc mempty $ "Type"- </> indent 2 (ppTypeAbbr [] name (l, ps, t))+ </> indent 2 (ppTypeAbbr [] (QualName quals name) (l, ps, t)) </> textwrap "cannot be made abstract because size parameter" <+/> pquote (pprName d) <+/> textwrap "is not used as an array size in the definition." - let spec_t' = applySubst (`M.lookup` (param_substs <> abs_subst_to_type)) spec_t- if spec_t' == t- then pure (spec_name, name)- else nomatch spec_t'+ let spec_t' = applySubst (`M.lookup` abs_subst_to_type) spec_t+ nonrigid = ps <> map (`TypeParamDim` mempty) (retDims t)+ case doUnification loc spec_ps nonrigid (retType spec_t') (retType t) of+ Right t'+ | noSizes t' `subtypeOf` noSizes (retType spec_t') -> pure (spec_name, name)+ _ -> nomatch spec_t' where nomatch spec_t' = mismatchedType loc (M.keys abs_subst_to_type)+ quals spec_name (spec_l, spec_ps, spec_t') (l, ps, t) - matchTypeParam _ (TypeParamDim x _) (TypeParamDim y _) =- pure $ M.singleton x $ SizeSubst $ NamedDim $ qualName y- matchTypeParam _ (TypeParamType spec_l x _) (TypeParamType l y _)- | spec_l <= l =- pure . M.singleton x . Subst [] . RetType [] $- Scalar $ TypeVar () Nonunique (typeName y) []- matchTypeParam nomatch _ _ =- nomatch- matchVal :: Loc ->+ [VName] -> VName -> BoundV -> VName -> BoundV -> Either TypeError (VName, VName)- matchVal loc spec_name spec_v name v =+ matchVal loc quals spec_name spec_v name v = case matchValBinding loc spec_v v of Nothing -> pure (spec_name, name) Just problem -> Left $ TypeError loc mempty $ "Module type specifies"- </> indent 2 (ppValBind spec_name spec_v)+ </> indent 2 (ppValBind (QualName quals spec_name) spec_v) </> "but module provides"- </> indent 2 (ppValBind spec_name v)+ </> indent 2 (ppValBind (QualName quals spec_name) v) </> fromMaybe mempty problem matchValBinding :: Loc -> BoundV -> BoundV -> Maybe (Maybe Doc)- matchValBinding loc (BoundV _ orig_spec_t) (BoundV tps orig_t) =- case doUnification loc tps (toStruct orig_spec_t) (toStruct orig_t) of+ matchValBinding loc (BoundV spec_tps orig_spec_t) (BoundV tps orig_t) = do+ case doUnification loc spec_tps tps (toStruct orig_spec_t) (toStruct orig_t) of Left (TypeError _ notes msg) -> Just $ Just $ msg <> ppr notes -- Even if they unify, we still have to verify the uniqueness -- properties. Right t- | noSizes t- `subtypeOf` noSizes orig_spec_t ->- Nothing+ | noSizes t `subtypeOf` noSizes orig_spec_t -> Nothing | otherwise -> Just Nothing ppValBind v (BoundV tps t) =- "val" <+> pprName v <+> spread (map ppr tps) <+> colon+ "val" <+> ppr v <+> spread (map ppr tps) <+> colon </> indent 2 (align (ppr t)) -- | Apply a parametric module to an argument.
src/Language/Futhark/TypeChecker/Monad.hs view
@@ -287,7 +287,8 @@ Scalar (Prim (Signed Int64)) -> pure v' _ -> typeError loc mempty $- "Dimension declaration" <+> ppr v <+> "should be of type i64."+ "Sizes must have type i64, but" <+> pquote (ppr v) <+> "has type:"+ </> ppr t typeError :: Located loc => loc -> Notes -> Doc -> m a
src/Language/Futhark/TypeChecker/Terms.hs view
@@ -37,7 +37,7 @@ import Language.Futhark.TypeChecker.Terms.Monad import Language.Futhark.TypeChecker.Terms.Pat import Language.Futhark.TypeChecker.Types-import Language.Futhark.TypeChecker.Unify hiding (Usage)+import Language.Futhark.TypeChecker.Unify import Prelude hiding (mod) overloadedTypeVars :: Constraints -> Names@@ -1331,8 +1331,12 @@ "Type is ambiguous (must be a sum type with constructors:" <+> ppr (Sum cs) <> ")." </> "Add a type annotation to disambiguate the type."- fixOverloaded (v, Size Nothing usage) =- typeError usage mempty $ "Size" <+> pquote (pprName v) <+> "is ambiguous.\n"+ fixOverloaded (v, Size Nothing (Usage Nothing loc)) =+ typeError loc mempty . withIndexLink "ambiguous-size" $+ "Ambiguous size" <+> pquote (pprName v) <> "."+ fixOverloaded (v, Size Nothing (Usage (Just u) loc)) =+ typeError loc mempty . withIndexLink "ambiguous-size" $+ "Ambiguous size" <+> pquote (pprName v) <+> "arising from" <+> text u <> "." fixOverloaded _ = pure () hiddenParamNames :: [Pat] -> Names
src/Language/Futhark/TypeChecker/Terms/Monad.hs view
@@ -531,13 +531,14 @@ -- parameters. Returns the names of the fresh type variables, the -- instance list, and the instantiated type. instantiateTypeScheme ::+ QualName VName -> SrcLoc -> [TypeParam] -> PatType -> TermTypeM ([VName], PatType)-instantiateTypeScheme loc tparams t = do+instantiateTypeScheme qn loc tparams t = do let tnames = map typeParamName tparams- (tparam_names, tparam_substs) <- unzip <$> mapM (instantiateTypeParam loc) tparams+ (tparam_names, tparam_substs) <- unzip <$> mapM (instantiateTypeParam qn loc) tparams let substs = M.fromList $ zip tnames tparam_substs t' = applySubst (`M.lookup` substs) t pure (tparam_names, t')@@ -546,19 +547,22 @@ -- substitution map. instantiateTypeParam :: Monoid as =>+ QualName VName -> SrcLoc -> TypeParam -> TermTypeM (VName, Subst (RetTypeBase dim as))-instantiateTypeParam loc tparam = do+instantiateTypeParam qn loc tparam = do i <- incCounter let name = nameFromString (takeWhile isAscii (baseString (typeParamName tparam))) v <- newID $ mkTypeVarName name i case tparam of TypeParamType x _ _ -> do- constrain v $ NoConstraint x $ mkUsage' loc+ constrain v . NoConstraint x . mkUsage loc $+ "instantiated type parameter of " <> quote (pretty qn) <> "." pure (v, Subst [] $ RetType [] $ Scalar $ TypeVar mempty Nonunique (typeName v) []) TypeParamDim {} -> do- constrain v $ Size Nothing $ mkUsage' loc+ constrain v . Size Nothing . mkUsage loc $+ "instantiated size parameter of " <> quote (pretty qn) <> "." pure (v, SizeSubst $ NamedDim $ qualName v) checkQualNameWithEnv :: Namespace -> QualName Name -> SrcLoc -> TermTypeM (TermScope, QualName VName)@@ -652,7 +656,7 @@ Just (BoundV _ tparams t) | "_" `isPrefixOf` baseString name -> underscoreUse loc qn | otherwise -> do- (tnames, t') <- instantiateTypeScheme loc tparams t+ (tnames, t') <- instantiateTypeScheme qn' loc tparams t pure $ qualifyTypeVars outer_env tnames qs t' Just EqualityF -> do argtype <- newTypeVar loc "t"@@ -811,7 +815,7 @@ checkTypeExpNonrigid te = do (te', svars, RetType dims st) <- termCheckTypeExp te forM_ (svars ++ dims) $ \v ->- constrain v $ Size Nothing $ mkUsage' $ srclocOf te+ constrain v $ Size Nothing $ mkUsage (srclocOf te) "anonymous size in type expression" pure (te', st, svars ++ dims) checkTypeExpRigid ::@@ -839,6 +843,8 @@ maybeDimFromExp e = ConstDim . fromIntegral <$> isInt64 e dimFromExp :: (Exp -> SizeSource) -> Exp -> TermTypeM (DimDecl VName, Maybe VName)+dimFromExp rf (Attr _ e _) = dimFromExp rf e+dimFromExp rf (Assert _ e _ _) = dimFromExp rf e dimFromExp rf (Parens e _) = dimFromExp rf e dimFromExp rf (QualParens _ e _) = dimFromExp rf e dimFromExp rf e
src/Language/Futhark/TypeChecker/Terms/Pat.hs view
@@ -223,6 +223,9 @@ dimIdent _ NamedDim {} = Nothing patternDims _ = [] +sizeBinderToParam :: SizeBinder VName -> UncheckedTypeParam+sizeBinderToParam (SizeBinder v loc) = TypeParamDim (baseName v) loc+ -- | Check and bind a @let@-pattern. bindingPat :: [SizeBinder VName] ->@@ -231,7 +234,7 @@ (Pat -> TermTypeM a) -> TermTypeM a bindingPat sizes p t m = do- checkForDuplicateNames [p]+ checkForDuplicateNames (map sizeBinderToParam sizes) [p] checkPat sizes p t $ \p' -> binding (S.toList $ patIdents p') $ do -- Perform an observation of every declared dimension. This -- prevents unused-name warnings for otherwise unused dimensions.@@ -388,7 +391,7 @@ (Pat -> TermTypeM a) -> TermTypeM a checkPat sizes p t m = do- checkForDuplicateNames [p]+ checkForDuplicateNames (map sizeBinderToParam sizes) [p] p' <- onFailure (CheckingPat p t) $ checkPat' sizes p t let explicit = mustBeExplicitInType $ patternStructType p'@@ -408,7 +411,7 @@ ([TypeParam] -> [Pat] -> TermTypeM a) -> TermTypeM a bindingParams tps orig_ps m = do- checkForDuplicateNames orig_ps+ checkForDuplicateNames tps orig_ps checkTypeParams tps $ \tps' -> bindingTypeParams tps' $ do let descend ps' (p : ps) = checkPat [] p NoneInferred $ \p' ->
src/Language/Futhark/TypeChecker/Types.hs view
@@ -26,9 +26,10 @@ import Control.Monad.Reader import Control.Monad.State import Data.Bifunctor-import Data.List (foldl', sort, unzip4, (\\))+import Data.List (find, foldl', sort, unzip4, (\\)) import qualified Data.Map.Strict as M import Data.Maybe+import qualified Data.Set as S import Futhark.Util (nubOrd) import Futhark.Util.Pretty hiding ((<|>)) import Language.Futhark@@ -233,12 +234,19 @@ dims' <- mapM (flip (checkName Term) loc) dims bindDims dims' $ do (t', svars, RetType t_dims st, l) <- evalTypeExp t- pure- ( TEDim dims' t' loc,- svars,- RetType (dims' ++ t_dims) st,- max l SizeLifted- )+ let (witnessed, _) = determineSizeWitnesses st+ case find (`S.notMember` witnessed) dims' of+ Just d ->+ typeError loc mempty . withIndexLink "unused-existential" $+ "Existential size " <> pquote (pprName d)+ <> " not used as array size."+ Nothing ->+ pure+ ( TEDim dims' t' loc,+ svars,+ RetType (dims' ++ t_dims) st,+ max l SizeLifted+ ) where bindDims [] m = m bindDims (d : ds) m =@@ -341,26 +349,28 @@ checkForDuplicateNamesInType te evalTypeExp te --- | Check for duplication of names inside a pattern group. Produces--- a description of all names used in the pattern group.+-- | Check for duplication of names inside a binding group. checkForDuplicateNames ::- MonadTypeChecker m =>- [UncheckedPat] ->- m ()-checkForDuplicateNames = (`evalStateT` mempty) . mapM_ check+ MonadTypeChecker m => [UncheckedTypeParam] -> [UncheckedPat] -> m ()+checkForDuplicateNames tps pats = (`evalStateT` mempty) $ do+ mapM_ checkTypeParam tps+ mapM_ checkPat pats where- check (Id v _ loc) = seen v loc- check (PatParens p _) = check p- check (PatAttr _ p _) = check p- check Wildcard {} = pure ()- check (TuplePat ps _) = mapM_ check ps- check (RecordPat fs _) = mapM_ (check . snd) fs- check (PatAscription p _ _) = check p- check PatLit {} = pure ()- check (PatConstr _ _ ps _) = mapM_ check ps+ checkTypeParam (TypeParamType _ v loc) = seen Type v loc+ checkTypeParam (TypeParamDim v loc) = seen Term v loc - seen v loc = do- already <- gets $ M.lookup v+ checkPat (Id v _ loc) = seen Term v loc+ checkPat (PatParens p _) = checkPat p+ checkPat (PatAttr _ p _) = checkPat p+ checkPat Wildcard {} = pure ()+ checkPat (TuplePat ps _) = mapM_ checkPat ps+ checkPat (RecordPat fs _) = mapM_ (checkPat . snd) fs+ checkPat (PatAscription p _ _) = checkPat p+ checkPat PatLit {} = pure ()+ checkPat (PatConstr _ _ ps _) = mapM_ checkPat ps++ seen ns v loc = do+ already <- gets $ M.lookup (ns, v) case already of Just prev_loc -> lift $@@ -368,7 +378,7 @@ "Name" <+> pquote (ppr v) <+> "also bound at" <+> text (locStr prev_loc) <> "." Nothing ->- modify $ M.insert v loc+ modify $ M.insert (ns, v) loc -- | Check whether the type contains arrow types that define the same -- parameter. These might also exist further down, but that's not
src/Language/Futhark/TypeChecker/Unify.hs view
@@ -8,7 +8,7 @@ -- blocks. module Language.Futhark.TypeChecker.Unify ( Constraint (..),- Usage,+ Usage (..), mkUsage, mkUsage', Level,@@ -659,10 +659,18 @@ scopeCheck usage bcs vn lvl tp constraints <- getConstraints- let link =- let ext = filter (`S.member` typeDimNames tp) bound- in modifyConstraints $+ let link = do+ let (witnessed, not_witnessed) = determineSizeWitnesses tp+ used v = v `S.member` witnessed || v `S.member` not_witnessed+ ext = filter used bound+ case filter (`notElem` witnessed) ext of+ [] ->+ modifyConstraints $ M.insert vn (lvl, Constraint (RetType ext tp) usage)+ problems ->+ unifyError usage mempty bcs . withIndexLink "unify-param-existential" $+ "Parameter(s) " <> commasep (map (pquote . pprName) problems)+ <> " used as size(s) would go out of scope." case snd <$> M.lookup vn constraints of Just (NoConstraint Unlifted unlift_usage) -> do@@ -1164,25 +1172,34 @@ </> indent 2 (ppr t2) </> "do not match." -runUnifyM :: [TypeParam] -> UnifyM a -> Either TypeError a-runUnifyM tparams (UnifyM m) = runExcept $ evalStateT m (constraints, 0)+runUnifyM :: [TypeParam] -> [TypeParam] -> UnifyM a -> Either TypeError a+runUnifyM rigid_tparams nonrigid_tparams (UnifyM m) =+ runExcept $ evalStateT m (constraints, 0) where- constraints = M.fromList $ map f tparams- f (TypeParamDim p loc) = (p, (0, Size Nothing $ Usage Nothing loc))- f (TypeParamType l p loc) = (p, (0, NoConstraint l $ Usage Nothing loc))+ constraints =+ M.fromList $+ map nonrigid nonrigid_tparams <> map rigid rigid_tparams+ nonrigid (TypeParamDim p loc) = (p, (0, Size Nothing $ Usage Nothing loc))+ nonrigid (TypeParamType l p loc) = (p, (0, NoConstraint l $ Usage Nothing loc))+ rigid (TypeParamDim p loc) = (p, (0, ParamSize loc))+ rigid (TypeParamType l p loc) = (p, (0, ParamType l loc)) -- | Perform a unification of two types outside a monadic context.--- The type parameters are allowed to be instantiated; all other types--- are considered rigid.+-- The first list of type parameters are rigid but may have liftedness+-- constraints; the second list of type parameters are allowed to be+-- instantiated. All other types are considered rigid with no+-- constraints. doUnification :: Loc -> [TypeParam] ->+ [TypeParam] -> StructType -> StructType -> Either TypeError StructType-doUnification loc tparams t1 t2 = runUnifyM tparams $ do- expect (Usage Nothing (srclocOf loc)) t1 t2- normTypeFully t2+doUnification loc rigid_tparams nonrigid_tparams t1 t2 =+ runUnifyM rigid_tparams nonrigid_tparams $ do+ expect (Usage Nothing (srclocOf loc)) t1 t2+ normTypeFully t2 -- Note [Linking variables to sum types] --
src/Language/Futhark/Warnings.hs view
@@ -1,6 +1,6 @@ -- | A very simple representation of collections of warnings. -- Warnings have a position (so they can be ordered), and their--- 'Show'-instance produces a human-readable string.+-- 'Pretty'-instance produces a human-readable string. module Language.Futhark.Warnings ( Warnings, anyWarnings,
src/main.hs view
@@ -1,144 +1,8 @@-{-# LANGUAGE OverloadedStrings #-}---- | The @futhark@ command line program.+-- | The *actual* @futhark@ command line program, as seen by cabal. module Main (main) where -import Control.Exception-import Data.List (sortOn)-import Data.Maybe-import qualified Data.Text as T-import qualified Data.Text.IO as T-import qualified Futhark.CLI.Autotune as Autotune-import qualified Futhark.CLI.Bench as Bench-import qualified Futhark.CLI.C as C-import qualified Futhark.CLI.CUDA as CCUDA-import qualified Futhark.CLI.Check as Check-import qualified Futhark.CLI.Datacmp as Datacmp-import qualified Futhark.CLI.Dataset as Dataset-import qualified Futhark.CLI.Defs as Defs-import qualified Futhark.CLI.Dev as Dev-import qualified Futhark.CLI.Doc as Doc-import qualified Futhark.CLI.LSP as LSP-import qualified Futhark.CLI.Literate as Literate-import qualified Futhark.CLI.Misc as Misc-import qualified Futhark.CLI.Multicore as Multicore-import qualified Futhark.CLI.MulticoreWASM as MulticoreWASM-import qualified Futhark.CLI.OpenCL as OpenCL-import qualified Futhark.CLI.Pkg as Pkg-import qualified Futhark.CLI.PyOpenCL as PyOpenCL-import qualified Futhark.CLI.Python as Python-import qualified Futhark.CLI.Query as Query-import qualified Futhark.CLI.REPL as REPL-import qualified Futhark.CLI.Run as Run-import qualified Futhark.CLI.Test as Test-import qualified Futhark.CLI.WASM as WASM-import Futhark.Error-import Futhark.Util (maxinum)-import Futhark.Util.Options-import GHC.IO.Encoding (setLocaleEncoding)-import GHC.IO.Exception (IOErrorType (..), IOException (..))-import System.Environment-import System.Exit-import System.IO-import Prelude--type Command = String -> [String] -> IO ()--commands :: [(String, (Command, String))]-commands =- sortOn- fst- [ ("dev", (Dev.main, "Run compiler passes directly.")),- ("repl", (REPL.main, "Run interactive Read-Eval-Print-Loop.")),- ("run", (Run.main, "Run a program through the (slow!) interpreter.")),- ("c", (C.main, "Compile to sequential C.")),- ("opencl", (OpenCL.main, "Compile to C calling OpenCL.")),- ("cuda", (CCUDA.main, "Compile to C calling CUDA.")),- ("multicore", (Multicore.main, "Compile to multicore C.")),- ("python", (Python.main, "Compile to sequential Python.")),- ("pyopencl", (PyOpenCL.main, "Compile to Python calling PyOpenCL.")),- ("wasm", (WASM.main, "Compile to WASM with sequential C")),- ("wasm-multicore", (MulticoreWASM.main, "Compile to WASM with multicore C")),- ("test", (Test.main, "Test Futhark programs.")),- ("bench", (Bench.main, "Benchmark Futhark programs.")),- ("dataset", (Dataset.main, "Generate random test data.")),- ("datacmp", (Datacmp.main, "Compare Futhark data files for equality.")),- ("dataget", (Misc.mainDataget, "Extract test data.")),- ("doc", (Doc.main, "Generate documentation for Futhark code.")),- ("pkg", (Pkg.main, "Manage local packages.")),- ("check", (Check.main, "Type-check a program.")),- ("check-syntax", (Misc.mainCheckSyntax, "Syntax-check a program.")),- ("imports", (Misc.mainImports, "Print all non-builtin imported Futhark files.")),- ("hash", (Misc.mainHash, "Print hash of program AST.")),- ("autotune", (Autotune.main, "Autotune threshold parameters.")),- ("defs", (Defs.main, "Show location and name of all definitions.")),- ("query", (Query.main, "Query semantic information about program.")),- ("literate", (Literate.main, "Process a literate Futhark program.")),- ("lsp", (LSP.main, "Run LSP server.")),- ("thanks", (Misc.mainThanks, "Express gratitude."))- ]--msg :: String-msg =- unlines $- ["<command> options...", "Commands:", ""]- ++ [ " " <> cmd <> replicate (k - length cmd) ' ' <> desc- | (cmd, (_, desc)) <- commands- ]- where- k = maxinum (map (length . fst) commands) + 3---- | Catch all IO exceptions and print a better error message if they--- happen.-reportingIOErrors :: IO () -> IO ()-reportingIOErrors =- flip- catches- [ Handler onExit,- Handler onICE,- Handler onIOException,- Handler onError- ]- where- onExit :: ExitCode -> IO ()- onExit = throwIO-- onICE :: InternalError -> IO ()- onICE (Error CompilerLimitation s) = do- T.hPutStrLn stderr "Known compiler limitation encountered. Sorry."- T.hPutStrLn stderr "Revise your program or try a different Futhark compiler."- T.hPutStrLn stderr s- exitWith $ ExitFailure 1- onICE (Error CompilerBug s) = do- T.hPutStrLn stderr "Internal compiler error."- T.hPutStrLn stderr "Please report this at https://github.com/diku-dk/futhark/issues."- T.hPutStrLn stderr s- exitWith $ ExitFailure 1-- onError :: SomeException -> IO ()- onError e- | Just UserInterrupt <- asyncExceptionFromException e =- pure () -- This corresponds to CTRL-C, which is not an error.- | otherwise = do- T.hPutStrLn stderr "Internal compiler error (unhandled IO exception)."- T.hPutStrLn stderr "Please report this at https://github.com/diku-dk/futhark/issues"- T.hPutStrLn stderr $ T.pack $ show e- exitWith $ ExitFailure 1-- onIOException :: IOException -> IO ()- onIOException e- | ioe_type e == ResourceVanished =- exitWith $ ExitFailure 1- | otherwise = throw e+import qualified Futhark.CLI.Main +-- | This is the main function. main :: IO ()-main = reportingIOErrors $ do- hSetEncoding stdout utf8- hSetEncoding stderr utf8- setLocaleEncoding utf8- args <- getArgs- prog <- getProgName- case args of- cmd : args'- | Just (m, _) <- lookup cmd commands -> m (unwords [prog, cmd]) args'- _ -> mainWithOptions () [] msg (const . const Nothing) prog args+main = Futhark.CLI.Main.main
+ unittests/Futhark/AD/DerivativesTests.hs view
@@ -0,0 +1,50 @@+module Futhark.AD.DerivativesTests (tests) where++import qualified Data.Map as M+import Futhark.AD.Derivatives+import Futhark.Analysis.PrimExp+import Futhark.IR.Syntax.Core (nameFromString)+import Futhark.Util.Pretty (pretty)+import Test.Tasty+import Test.Tasty.HUnit++tests :: TestTree+tests =+ testGroup+ "Futhark.AD.DerivativesTests"+ [ testGroup "Primitive functions" $+ map primFunTest $+ filter (not . (`elem` missing_primfuns) . fst) $ M.toList primFuns,+ testGroup "BinOps" $ map binOpTest allBinOps,+ testGroup "UnOps" $ map unOpTest allUnOps+ ]+ where+ blank = ValueExp . blankPrimValue++ primFunTest (f, (ts, ret, _)) =+ testCase f $+ case pdBuiltin (nameFromString f) (map blank ts) of+ Nothing -> assertFailure "pdBuiltin gives Nothing"+ Just v -> map primExpType v @?= replicate (length ts) ret++ -- We know we have no derivatives for these... and they are not+ -- coming any time soon.+ missing_primfuns =+ [ "gamma16",+ "gamma32",+ "gamma64",+ "lgamma16",+ "lgamma32",+ "lgamma64"+ ]++ binOpTest bop =+ testCase (pretty bop) $+ let t = binOpType bop+ (dx, dy) = pdBinOp bop (blank t) (blank t)+ in (primExpType dx, primExpType dy) @?= (t, t)++ unOpTest bop =+ testCase (pretty bop) $+ let t = unOpType bop+ in primExpType (pdUnOp bop $ blank t) @?= t
unittests/Futhark/IR/Mem/IxFunTests.hs view
@@ -24,6 +24,7 @@ rem = P.rem div = P.div mod = P.mod+ pow = (P.^) sgn = Just . P.signum allPoints :: [Int] -> [[Int]]
unittests/Language/Futhark/TypeChecker/TypesTests.hs view
@@ -4,6 +4,7 @@ module Language.Futhark.TypeChecker.TypesTests (tests) where import Data.Bifunctor (first)+import Data.List (isInfixOf) import qualified Data.Map as M import Futhark.FreshNames import Futhark.Util.Pretty (prettyOneLine)@@ -16,13 +17,18 @@ import Test.Tasty import Test.Tasty.HUnit -evalTest :: TypeExp Name -> ([VName], StructRetType) -> TestTree+evalTest :: TypeExp Name -> Either String ([VName], StructRetType) -> TestTree evalTest te expected = testCase (pretty te) $- case fmap (extract . fst) (run (checkTypeExp te)) of- Left e -> assertFailure $ "Failed: " <> pretty e- Right actual ->- actual @?= expected+ case (fmap (extract . fst) (run (checkTypeExp te)), expected) of+ (Left got_e, Left expected_e) ->+ (expected_e `isInfixOf` pretty got_e) @? pretty got_e+ (Left got_e, Right _) ->+ assertFailure $ "Failed: " <> pretty got_e+ (Right actual_t, Right expected_t) ->+ actual_t @?= expected_t+ (Right actual_t, Left _) ->+ assertFailure $ "Expected error, got: " <> show actual_t where extract (_, svars, t, _) = (svars, t) run = snd . runTypeM env mempty (mkInitialImport "") blankNameSource@@ -68,67 +74,80 @@ evalTests = testGroup "Type expression elaboration"- [ evalTest- "[]i32"- ([], "?[d_0].[d_0]i32"),- evalTest- "[][]i32"- ([], "?[d_0][d_1].[d_0][d_1]i32"),- evalTest- "bool -> []i32"- ([], "bool -> ?[d_0].[d_0]i32"),- evalTest- "bool -> []f32 -> []i32"- (["d_0"], "bool -> [d_0]f32 -> ?[d_1].[d_1]i32"),- evalTest- "([]i32,[]i32)"- ([], "?[d_0][d_1].([d_0]i32, [d_1]i32)"),- evalTest- "{a:[]i32,b:[]i32}"- ([], "?[d_0][d_1].{a:[d_0]i32, b:[d_1]i32}"),- evalTest- "?[n].[n][n]bool"- ([], "?[n_0].[n_0][n_0]bool"),- evalTest- "([]i32 -> []i32) -> bool -> []i32"- (["d_0"], "([d_0]i32 -> ?[d_1].[d_1]i32) -> bool -> ?[d_2].[d_2]i32"),- evalTest- "((k: i64) -> [k]i32 -> [k]i32) -> []i32 -> bool"- (["d_1"], "((k_0: i64) -> [k_0]i32 -> [k_0]i32) -> [d_1]i32 -> bool"),- evalTest- "square [10]"- ([], "[10][10]i32"),- evalTest- "square []"- ([], "?[d_0].[d_0][d_0]i32"),- evalTest- "bool -> square []"- ([], "bool -> ?[d_0].[d_0][d_0]i32"),- evalTest- "(k: i64) -> square [k]"- ([], "(k_0: i64) -> [k_0][k_0]i32"),- evalTest- "fun i32 bool"- ([], "i32 -> bool"),- evalTest- "fun ([]i32) bool"- ([], "?[d_0].[d_0]i32 -> bool"),- evalTest- "fun bool ([]i32)"- ([], "?[d_0].bool -> [d_0]i32"),- evalTest- "bool -> fun ([]i32) bool"- ([], "bool -> ?[d_0].[d_0]i32 -> bool"),- evalTest- "bool -> fun bool ([]i32)"- ([], "bool -> ?[d_0].bool -> [d_0]i32"),- evalTest- "pair"- ([], "?[n_0][m_1].([n_0]i64, [m_1]i64)"),- evalTest- "(pair,pair)"- ([], "?[n_0][m_1][n_2][m_3].(([n_0]i64, [m_1]i64), ([n_2]i64, [m_3]i64))")+ [ testGroup "Positive tests" (map mkPos pos),+ testGroup "Negative tests" (map mkNeg neg) ]+ where+ mkPos (x, y) = evalTest x (Right y)+ mkNeg (x, y) = evalTest x (Left y)+ pos =+ [ ( "[]i32",+ ([], "?[d_0].[d_0]i32")+ ),+ ( "[][]i32",+ ([], "?[d_0][d_1].[d_0][d_1]i32")+ ),+ ( "bool -> []i32",+ ([], "bool -> ?[d_0].[d_0]i32")+ ),+ ( "bool -> []f32 -> []i32",+ (["d_0"], "bool -> [d_0]f32 -> ?[d_1].[d_1]i32")+ ),+ ( "([]i32,[]i32)",+ ([], "?[d_0][d_1].([d_0]i32, [d_1]i32)")+ ),+ ( "{a:[]i32,b:[]i32}",+ ([], "?[d_0][d_1].{a:[d_0]i32, b:[d_1]i32}")+ ),+ ( "?[n].[n][n]bool",+ ([], "?[n_0].[n_0][n_0]bool")+ ),+ ( "([]i32 -> []i32) -> bool -> []i32",+ (["d_0"], "([d_0]i32 -> ?[d_1].[d_1]i32) -> bool -> ?[d_2].[d_2]i32")+ ),+ ( "((k: i64) -> [k]i32 -> [k]i32) -> []i32 -> bool",+ (["d_1"], "((k_0: i64) -> [k_0]i32 -> [k_0]i32) -> [d_1]i32 -> bool")+ ),+ ( "square [10]",+ ([], "[10][10]i32")+ ),+ ( "square []",+ ([], "?[d_0].[d_0][d_0]i32")+ ),+ ( "bool -> square []",+ ([], "bool -> ?[d_0].[d_0][d_0]i32")+ ),+ ( "(k: i64) -> square [k]",+ ([], "(k_0: i64) -> [k_0][k_0]i32")+ ),+ ( "fun i32 bool",+ ([], "i32 -> bool")+ ),+ ( "fun ([]i32) bool",+ ([], "?[d_0].[d_0]i32 -> bool")+ ),+ ( "fun bool ([]i32)",+ ([], "?[d_0].bool -> [d_0]i32")+ ),+ ( "bool -> fun ([]i32) bool",+ ([], "bool -> ?[d_0].[d_0]i32 -> bool")+ ),+ ( "bool -> fun bool ([]i32)",+ ([], "bool -> ?[d_0].bool -> [d_0]i32")+ ),+ ( "pair",+ ([], "?[n_0][m_1].([n_0]i64, [m_1]i64)")+ ),+ ( "(pair,pair)",+ ([], "?[n_0][m_1][n_2][m_3].(([n_0]i64, [m_1]i64), ([n_2]i64, [m_3]i64))")+ )+ ]+ neg =+ [ ("?[n].bool", "Existential size \"n\""),+ ("?[n].bool -> [n]bool", "Existential size \"n\""),+ ("?[n].[n]bool -> [n]bool", "Existential size \"n\""),+ ("?[n].[n]bool -> bool", "Existential size \"n\"")+ ] substTest :: M.Map VName (Subst StructRetType) -> StructRetType -> StructRetType -> TestTree substTest m t expected =
unittests/futhark_tests.hs view
@@ -1,5 +1,6 @@ module Main (main) where +import qualified Futhark.AD.DerivativesTests import qualified Futhark.BenchTests import qualified Futhark.IR.Mem.IxFunTests import qualified Futhark.IR.PrimitiveTests@@ -16,6 +17,7 @@ testGroup "" [ Language.Futhark.SyntaxTests.tests,+ Futhark.AD.DerivativesTests.tests, Futhark.BenchTests.tests, Futhark.IR.PropTests.tests, Futhark.IR.Syntax.CoreTests.tests,