futhark-0.22.2: src/Futhark/IR/GPU/Op.hs
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
module Futhark.IR.GPU.Op
( -- * Size operations
SizeOp (..),
-- * Host operations
HostOp (..),
traverseHostOpStms,
typeCheckHostOp,
-- * SegOp refinements
SegLevel (..),
-- * Reexports
module Futhark.IR.GPU.Sizes,
module Futhark.IR.SegOp,
)
where
import Control.Monad
import Data.Sequence qualified as SQ
import Data.Text qualified as T
import Futhark.Analysis.Alias qualified as Alias
import Futhark.Analysis.Metrics
import Futhark.Analysis.SymbolTable qualified as ST
import Futhark.IR
import Futhark.IR.Aliases (Aliases, removeBodyAliases)
import Futhark.IR.GPU.Sizes
import Futhark.IR.Prop.Aliases
import Futhark.IR.SegOp
import Futhark.IR.TypeCheck qualified as TC
import Futhark.Optimise.Simplify.Engine qualified as Engine
import Futhark.Optimise.Simplify.Rep
import Futhark.Transform.Rename
import Futhark.Transform.Substitute
import Futhark.Util.Pretty
( commasep,
parens,
ppTuple',
pretty,
(<+>),
)
import Futhark.Util.Pretty qualified as PP
-- | At which level the *body* of a t'SegOp' executes.
data SegLevel
= SegThread
{ segNumGroups :: Count NumGroups SubExp,
segGroupSize :: Count GroupSize SubExp,
segVirt :: SegVirt
}
| SegGroup
{ segNumGroups :: Count NumGroups SubExp,
segGroupSize :: Count GroupSize SubExp,
segVirt :: SegVirt
}
deriving (Eq, Ord, Show)
instance PP.Pretty SegLevel where
pretty lvl =
PP.parens
( lvl' <> PP.semi
<+> "#groups=" <> pretty (segNumGroups lvl) <> PP.semi
<+> "groupsize=" <> pretty (segGroupSize lvl) <> virt
)
where
lvl' = case lvl of
SegThread {} -> "thread"
SegGroup {} -> "group"
virt = case segVirt lvl of
SegNoVirt -> mempty
SegNoVirtFull dims -> PP.semi <+> "full" <+> pretty (segSeqDims dims)
SegVirt -> PP.semi <+> "virtualise"
instance Engine.Simplifiable SegLevel where
simplify (SegThread num_groups group_size virt) =
SegThread
<$> traverse Engine.simplify num_groups
<*> traverse Engine.simplify group_size
<*> pure virt
simplify (SegGroup num_groups group_size virt) =
SegGroup
<$> traverse Engine.simplify num_groups
<*> traverse Engine.simplify group_size
<*> pure virt
instance Substitute SegLevel where
substituteNames substs (SegThread num_groups group_size virt) =
SegThread
(substituteNames substs num_groups)
(substituteNames substs group_size)
virt
substituteNames substs (SegGroup num_groups group_size virt) =
SegGroup
(substituteNames substs num_groups)
(substituteNames substs group_size)
virt
instance Rename SegLevel where
rename = substituteRename
instance FreeIn SegLevel where
freeIn' (SegThread num_groups group_size _) =
freeIn' num_groups <> freeIn' group_size
freeIn' (SegGroup num_groups group_size _) =
freeIn' num_groups <> freeIn' group_size
-- | A simple size-level query or computation.
data SizeOp
= -- | Produce some runtime-configurable size.
GetSize Name SizeClass
| -- | The maximum size of some class.
GetSizeMax SizeClass
| -- | Compare size (likely a threshold) with some integer value.
CmpSizeLe Name SizeClass SubExp
| -- | @CalcNumGroups w max_num_groups group_size@ calculates the
-- number of GPU workgroups to use for an input of the given size.
-- The @Name@ is a size name. Note that @w@ is an i64 to avoid
-- overflow issues.
CalcNumGroups SubExp Name SubExp
deriving (Eq, Ord, Show)
instance Substitute SizeOp where
substituteNames substs (CmpSizeLe name sclass x) =
CmpSizeLe name sclass (substituteNames substs x)
substituteNames substs (CalcNumGroups w max_num_groups group_size) =
CalcNumGroups
(substituteNames substs w)
max_num_groups
(substituteNames substs group_size)
substituteNames _ op = op
instance Rename SizeOp where
rename (CmpSizeLe name sclass x) =
CmpSizeLe name sclass <$> rename x
rename (CalcNumGroups w max_num_groups group_size) =
CalcNumGroups <$> rename w <*> pure max_num_groups <*> rename group_size
rename x = pure x
instance IsOp SizeOp where
safeOp _ = True
cheapOp _ = True
instance TypedOp SizeOp where
opType (GetSize _ _) = pure [Prim int64]
opType (GetSizeMax _) = pure [Prim int64]
opType CmpSizeLe {} = pure [Prim Bool]
opType CalcNumGroups {} = pure [Prim int64]
instance AliasedOp SizeOp where
opAliases _ = [mempty]
consumedInOp _ = mempty
instance FreeIn SizeOp where
freeIn' (CmpSizeLe _ _ x) = freeIn' x
freeIn' (CalcNumGroups w _ group_size) = freeIn' w <> freeIn' group_size
freeIn' _ = mempty
instance PP.Pretty SizeOp where
pretty (GetSize name size_class) =
"get_size" <> parens (commasep [pretty name, pretty size_class])
pretty (GetSizeMax size_class) =
"get_size_max" <> parens (commasep [pretty size_class])
pretty (CmpSizeLe name size_class x) =
"cmp_size" <> parens (commasep [pretty name, pretty size_class])
<+> "<="
<+> pretty x
pretty (CalcNumGroups w max_num_groups group_size) =
"calc_num_groups" <> parens (commasep [pretty w, pretty max_num_groups, pretty group_size])
instance OpMetrics SizeOp where
opMetrics GetSize {} = seen "GetSize"
opMetrics GetSizeMax {} = seen "GetSizeMax"
opMetrics CmpSizeLe {} = seen "CmpSizeLe"
opMetrics CalcNumGroups {} = seen "CalcNumGroups"
typeCheckSizeOp :: TC.Checkable rep => SizeOp -> TC.TypeM rep ()
typeCheckSizeOp GetSize {} = pure ()
typeCheckSizeOp GetSizeMax {} = pure ()
typeCheckSizeOp (CmpSizeLe _ _ x) = TC.require [Prim int64] x
typeCheckSizeOp (CalcNumGroups w _ group_size) = do
TC.require [Prim int64] w
TC.require [Prim int64] group_size
-- | A host-level operation; parameterised by what else it can do.
data HostOp rep op
= -- | A segmented operation.
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'.
traverseHostOpStms ::
Monad m =>
OpStmsTraverser m op rep ->
OpStmsTraverser m (HostOp rep op) rep
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) =
SegOp $ substituteNames substs op
substituteNames substs (OtherOp op) =
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)
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
indexOp vtable k (OtherOp op) is = ST.indexOp vtable k op is
indexOp _ _ _ _ = Nothing
instance (PrettyRep rep, PP.Pretty op) => PP.Pretty (HostOp rep op) where
pretty (SegOp op) = pretty op
pretty (OtherOp op) = pretty op
pretty (SizeOp op) = pretty op
pretty (GPUBody ts body) =
"gpu" <+> PP.colon <+> ppTuple' (map pretty ts) <+> PP.nestedBlock "{" "}" (pretty 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 =>
Maybe SegLevel ->
SegLevel ->
TC.TypeM rep ()
checkSegLevel Nothing lvl = do
TC.require [Prim int64] $ unCount $ segNumGroups lvl
TC.require [Prim int64] $ unCount $ segGroupSize lvl
checkSegLevel (Just SegThread {}) _ =
TC.bad $ TC.TypeError "SegOps cannot occur when already at thread level."
checkSegLevel (Just x) y
| x == y = TC.bad $ TC.TypeError $ "Already at at level " <> prettyText x
| segNumGroups x /= segNumGroups y || segGroupSize x /= segGroupSize y =
TC.bad $ TC.TypeError "Physical layout for SegLevel does not match parent SegLevel."
| otherwise =
pure ()
typeCheckHostOp ::
TC.Checkable rep =>
(SegLevel -> OpWithAliases (Op rep) -> TC.TypeM rep ()) ->
Maybe SegLevel ->
(op -> TC.TypeM rep ()) ->
HostOp (Aliases rep) op ->
TC.TypeM rep ()
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 . T.unlines $
[ "Expected type: " <> prettyTuple ts,
"Got body type: " <> prettyTuple body_ts
]