futhark-0.25.24: src/Futhark/Optimise/ArrayLayout/Layout.hs
module Futhark.Optimise.ArrayLayout.Layout
( layoutTableFromIndexTable,
Layout,
Permutation,
LayoutTable,
-- * Exposed for testing
commonPermutationEliminators,
)
where
import Control.Monad (join)
import Data.List qualified as L
import Data.Map.Strict qualified as M
import Data.Maybe
import Futhark.Analysis.AccessPattern
import Futhark.Analysis.PrimExp.Table (PrimExpTable)
import Futhark.IR.Aliases
import Futhark.IR.GPU
import Futhark.IR.MC
import Futhark.IR.MCMem
import Futhark.Util (mininum)
type Permutation = [Int]
type LayoutTable =
M.Map
SegOpName
( M.Map
ArrayName
(M.Map IndexExprName Permutation)
)
class Layout rep where
-- | Produce a coalescing permutation that will be used to create a
-- manifest of the array. Returns Nothing if the array is already in
-- the optimal layout or if the array access is too complex to
-- confidently determine the optimal layout. Map each list of
-- 'DimAccess' in the IndexTable to a permutation in a generic way
-- that can be handled uniquely by each backend.
permutationFromDimAccess ::
PrimExpTable ->
SegOpName ->
ArrayName ->
IndexExprName ->
[DimAccess rep] ->
Maybe Permutation
isInscrutableExp :: PrimExp VName -> Bool
isInscrutableExp (LeafExp _ _) = False
isInscrutableExp (ValueExp _) = False
isInscrutableExp (BinOpExp _ a b) =
isInscrutableExp a || isInscrutableExp b
isInscrutableExp (UnOpExp _ a) =
isInscrutableExp a
isInscrutableExp _ = True
isInscrutable :: PrimExp VName -> Bool -> Bool
isInscrutable op@(BinOpExp {}) counter =
if counter
then -- Calculate stride and offset for loop-counters and thread-IDs
case reduceStrideAndOffset op of
-- Maximum allowable stride, might need tuning.
Just (s, _) -> s > 8
Nothing -> isInscrutableExp op
else isInscrutableExp op
isInscrutable op _ = isInscrutableExp op
reduceStrideAndOffset :: PrimExp l -> Maybe (Int, Int)
reduceStrideAndOffset (LeafExp _ _) = Just (1, 0)
reduceStrideAndOffset (BinOpExp oper a b) = case (a, b) of
(ValueExp (IntValue v), _) -> reduce v b
(_, ValueExp (IntValue v)) -> reduce v a
_ -> Nothing
where
reduce v (LeafExp _ _) =
case oper of
Add _ _ -> Just (1, valueIntegral v)
Sub _ _ -> Just (1, -valueIntegral v)
Mul _ _ -> Just (valueIntegral v, 0)
_ -> Nothing
reduce v op@(BinOpExp {}) =
case reduceStrideAndOffset op of
Nothing -> Nothing
Just (s, o) -> case oper of
Add _ _ -> Just (s, o + valueIntegral v)
Sub _ _ -> Just (s, o - valueIntegral v)
Mul _ _ -> Just (s * valueIntegral v, o * valueIntegral v)
_ -> Nothing
reduce _ (UnOpExp (Neg Bool) _) = Nothing
reduce _ (UnOpExp (Complement _) _) = Nothing
reduce _ (UnOpExp (Abs _) _) = Nothing
reduce _ (UnOpExp _ sub_op) = reduceStrideAndOffset sub_op
reduce _ (ConvOpExp _ sub_op) = reduceStrideAndOffset sub_op
reduce _ _ = Nothing
reduceStrideAndOffset _ = Nothing
-- | Reasons common to all backends to not manifest an array.
commonPermutationEliminators :: [Int] -> [BodyType] -> Bool
commonPermutationEliminators perm nest = do
-- Don't manifest if the permutation is the permutation is invalid
let is_invalid_perm = not (L.sort perm `L.isPrefixOf` [0 ..])
-- Don't manifest if the permutation is the identity permutation
is_identity = perm `L.isPrefixOf` [0 ..]
-- or is not a transpose.
inefficient_transpose = isNothing $ isMapTranspose perm
-- or if the last idx remains last
static_last_idx = last perm == length perm - 1
-- Don't manifest if the array is defined inside a segOp
inside_undesired = any undesired nest
is_invalid_perm
|| is_identity
|| inefficient_transpose
|| static_last_idx
|| inside_undesired
where
undesired :: BodyType -> Bool
undesired bodyType = case bodyType of
SegOpName _ -> True
_ -> False
sortMC :: [(Int, DimAccess rep)] -> [(Int, DimAccess rep)]
sortMC =
L.sortBy dimdexMCcmp
where
dimdexMCcmp (ia, a) (ib, b) = do
let aggr1 =
foldl max' Nothing $ map (f ia . snd) $ M.toList $ dependencies a
aggr2 =
foldl max' Nothing $ map (f ib . snd) $ M.toList $ dependencies b
cmpIdxPat aggr1 aggr2
where
cmpIdxPat Nothing Nothing = EQ
cmpIdxPat (Just _) Nothing = GT
cmpIdxPat Nothing (Just _) = LT
cmpIdxPat
(Just (iterL, lvlL, original_lvl_L))
(Just (iterR, lvlR, original_lvl_R)) =
case (iterL, iterR) of
(ThreadID, ThreadID) -> (lvlL, original_lvl_L) `compare` (lvlR, original_lvl_R)
(ThreadID, _) -> LT
(_, ThreadID) -> GT
_ -> (lvlL, original_lvl_L) `compare` (lvlR, original_lvl_R)
max' lhs rhs =
case cmpIdxPat lhs rhs of
LT -> rhs
_ -> lhs
f og (Dependency lvl varType) = Just (varType, lvl, og)
multicorePermutation :: PrimExpTable -> SegOpName -> ArrayName -> IndexExprName -> [DimAccess rep] -> Maybe Permutation
multicorePermutation primExpTable _segOpName (_arr_name, nest, arr_layout) _idx_name dimAccesses = do
-- Dont accept indices where the last index is invariant
let lastIdxIsInvariant = isInvariant $ last dimAccesses
-- Check if any of the dependencies are too complex to reason about
let dimAccesses' = filter (isJust . originalVar) dimAccesses
deps = mapMaybe originalVar dimAccesses'
counters = concatMap (map (isCounter . varType . snd) . M.toList . dependencies) dimAccesses'
primExps = mapM (join . (`M.lookup` primExpTable)) deps
inscrutable = maybe True (any (uncurry isInscrutable) . flip zip counters) primExps
-- Create a candidate permutation
let perm = map fst $ sortMC (zip arr_layout dimAccesses)
-- Check if we want to manifest this array with the permutation
if lastIdxIsInvariant || inscrutable || commonPermutationEliminators perm nest
then Nothing
else Just perm
instance Layout MC where
permutationFromDimAccess = multicorePermutation
sortGPU :: [(Int, DimAccess rep)] -> [(Int, DimAccess rep)]
sortGPU =
L.sortBy dimdexGPUcmp
where
dimdexGPUcmp (ia, a) (ib, b) = do
let aggr1 =
foldl max' Nothing $ map (f ia . snd) $ M.toList $ dependencies a
aggr2 =
foldl max' Nothing $ map (f ib . snd) $ M.toList $ dependencies b
cmpIdxPat aggr1 aggr2
where
cmpIdxPat Nothing Nothing = EQ
cmpIdxPat (Just _) Nothing = GT
cmpIdxPat Nothing (Just _) = LT
cmpIdxPat
(Just (iterL, lvlL, original_lvl_L))
(Just (iterR, lvlR, original_lvl_R)) = case (iterL, iterR) of
(ThreadID, ThreadID) -> (lvlL, original_lvl_L) `compare` (lvlR, original_lvl_R)
(ThreadID, _) -> GT
(_, ThreadID) -> LT
_ -> (lvlL, original_lvl_L) `compare` (lvlR, original_lvl_R)
max' lhs rhs =
case cmpIdxPat lhs rhs of
LT -> rhs
_ -> lhs
f og (Dependency lvl varType) = Just (varType, lvl, og)
gpuPermutation :: PrimExpTable -> SegOpName -> ArrayName -> IndexExprName -> [DimAccess rep] -> Maybe Permutation
gpuPermutation primExpTable _segOpName (_arr_name, nest, arr_layout) _idx_name dimAccesses = do
-- Find the outermost parallel level. XXX: this is a bit hacky. Why
-- don't we simply know at this point the nest in which this index
-- occurs?
let outermost_par = mininum $ foldMap (map lvl . parDeps) dimAccesses
invariantToPar = (< outermost_par) . lvl
-- Do nothing if last index is invariant to segop.
let lastIdxIsInvariant = all invariantToPar $ dependencies $ last dimAccesses
-- Do nothing if any index is constant, because otherwise we can end
-- up transposing a too-large array.
let anyIsConstant = any (null . dependencies) dimAccesses
-- Check if any of the dependencies are too complex to reason about
let dimAccesses' = filter (isJust . originalVar) dimAccesses
deps = mapMaybe originalVar dimAccesses'
counters = concatMap (map (isCounter . varType . snd) . M.toList . dependencies) dimAccesses'
primExps = mapM (join . (`M.lookup` primExpTable)) deps
inscrutable = maybe True (any (uncurry isInscrutable) . flip zip counters) primExps
-- Create a candidate permutation
let perm = map fst $ sortGPU (zip arr_layout dimAccesses)
-- Check if we want to manifest this array with the permutation
if lastIdxIsInvariant
|| anyIsConstant
|| inscrutable
|| commonPermutationEliminators perm nest
then Nothing
else Just perm
where
parDeps = filter ((== ThreadID) . varType) . M.elems . dependencies
instance Layout GPU where
permutationFromDimAccess = gpuPermutation
-- | like mapMaybe, but works on nested maps. Eliminates "dangling"
-- maps / rows with missing (Nothing) values.
tableMapMaybe ::
(k0 -> k1 -> k2 -> a -> Maybe b) ->
M.Map k0 (M.Map k1 (M.Map k2 a)) ->
M.Map k0 (M.Map k1 (M.Map k2 b))
tableMapMaybe f =
M.mapMaybeWithKey $ \key0 -> mapToMaybe $ mapToMaybe . f key0
where
maybeMap :: M.Map k a -> Maybe (M.Map k a)
maybeMap val = if null val then Nothing else Just val
mapToMaybe g = maybeMap . M.mapMaybeWithKey g
-- | Given an ordering function for `DimAccess`, and an IndexTable,
-- return a LayoutTable. We remove entries with no results after
-- `permutationFromDimAccess`
layoutTableFromIndexTable ::
(Layout rep) =>
PrimExpTable ->
IndexTable rep ->
LayoutTable
layoutTableFromIndexTable = tableMapMaybe . permutationFromDimAccess