packages feed

futhark-0.27.1: src/Futhark/Pass/Flatten/General.hs

-- | Various general utilities used in flattening.
module Futhark.Pass.Flatten.General
  ( -- * Reading inputs
    readInputVar,
    readInputs,
    readInput,
    readNeutral,
    readTypeDims,

    -- * Building blocks
    ensureDenseIrregular,
    liftResult,
    liftDistResultRep,
    liftSubExp,
    liftSubExpPreserveRep,
    liftSubExpRegular,
    liftVarRegular,
    isRegularInputArr,
    liftParam,
    liftRegularParam,
    liftRegResult,
    needsIrregularRetType,
    mkIrregFromReg,
    flattenIrregularRep,
    distCerts,
    dataArr,
    getIrregRep,
    scatterIrregular,
    scatterRegular,
    module Futhark.Pass.Flatten.Monad,
    module Futhark.Pass.Flatten.Builtins,

    -- * Various
    scopeOfDistInputs,
    lookupInputType,
    subExpInputType,
    localiseInputs,
    replicateForDims,
    liftBodyWithDistResults,
    distResultsToResReps,
    resultToResReps,
    resultToResRepsByDistResult,
    irregularRepToFlatArrs,
    distributeAndFlattenBody,
    splitInput,
    isVariant,
    segmentDims,
    flattenDistStm,
    flattenScalarStm,
    distributeBodyWith,
    distributeMapWith,
    atSegLevel,
  )
where

import Control.Monad
import Data.List qualified as L
import Data.Map qualified as M
import Data.Maybe
import Data.Tuple.Solo
import Futhark.IR.GPU
import Futhark.IR.SOACS as SOACS
import Futhark.MonadFreshNames
import Futhark.Pass.Flatten.Builtins
import Futhark.Pass.Flatten.Distribute
import Futhark.Pass.Flatten.Monad
import Futhark.Tools
import Futhark.Transform.Rename (renameExp)
import Futhark.Util (mapAccumLM)
import Futhark.Util.IntegralExp
import Prelude hiding (div, rem)

-- | Write back the irregular results of a branch to a (partially) blank space
-- The `offsets` variable is the offsets of the final result, whereas `irregRep`
-- is the irregular representation of the result.
scatterIrregular ::
  SegLevel ->
  VName ->
  VName ->
  (VName, IrregularRep) ->
  FlattenM VName
scatterIrregular lvl offsets space (is, irregRep) = do
  let IrregularRep {irregularS = segs, irregularD = elems, irregularO = off, irregularK = kind} = irregRep
  (_, _, ii1) <- doRepIota lvl segs
  (_, _, ii2) <- doSegIota lvl segs
  m <- arraySize 0 <$> lookupType ii1
  letExp "irregular_scatter" <=< genScatter lvl space m $ \gtid -> do
    segment <- letSubExp "segment" =<< eIndex ii1 [eSubExp gtid]
    intra_segment <- letSubExp "segment" =<< eIndex ii2 [eSubExp gtid]
    x <- case kind of
      Dense -> letSubExp "x" =<< eIndex elems [eSubExp gtid]
      Replicated -> do
        o <- letSubExp "rep_O" =<< eIndex off [eSubExp segment]
        letSubExp "x" =<< eIndex elems [toExp $ pe64 o + pe64 intra_segment]
    offset <- letExp "offset" =<< eIndex offsets [eIndex is [eSubExp segment]]
    i <- letExp "i" =<< eBinOp (Add Int64 OverflowUndef) (toExp offset) (eSubExp intra_segment)
    pure (i, x)

-- | Write back the regular results to a (partially) blank space
scatterRegular ::
  SegLevel ->
  VName ->
  (VName, VName) ->
  FlattenM VName
scatterRegular lvl space (is, xs) = do
  dims <- arrayDims <$> lookupType xs
  letExp "regular_scatter" <=< genScatterND lvl space dims $ \(gtid : rest) -> do
    x <- letSubExp "x" =<< eIndex xs (map eSubExp (gtid : rest))
    i <- letSubExp "i" =<< eIndex is [eSubExp gtid]
    pure (i : rest, x)

ensureDenseIrregular :: SegLevel -> Name -> IrregularRep -> FlattenM IrregularRep
ensureDenseIrregular _ _ rep@IrregularRep {irregularK = Dense} =
  pure rep
ensureDenseIrregular lvl desc rep@IrregularRep {} = do
  (new_F, new_O, ii1) <- doRepIota lvl (irregularS rep)
  m <- arraySize 0 <$> lookupType ii1
  new_D <- letExp (desc <> "_dense_D") <=< segMap lvl (MkSolo m) $ \(MkSolo i) -> do
    seg <- letSubExp "seg" =<< eIndex ii1 [eSubExp i]
    old_off <- letSubExp "old_off" =<< eIndex (irregularO rep) [eSubExp seg]
    new_off <- letSubExp "new_off" =<< eIndex new_O [eSubExp seg]
    j <- letSubExp "j" <=< toExp $ pe64 i - pe64 new_off
    x <- letSubExp "x" =<< eIndex (irregularD rep) [toExp $ pe64 old_off + pe64 j]
    pure [subExpRes x]
  pure $
    IrregularRep
      { irregularS = irregularS rep,
        irregularF = new_F,
        irregularO = new_O,
        irregularD = new_D,
        irregularK = Dense
      }

-- Lift a result of a function.
liftResult :: SegLevel -> Segments -> DistInputs -> DistEnv -> SubExpRes -> FlattenM Result
liftResult lvl segments inps env res = map (SubExpRes mempty . Var) <$> vs
  where
    vs = do
      (_, rep) <- liftSubExp lvl segments inps env (resSubExp res)
      case rep of
        Regular v -> pure [v]
        Irregular irreg -> mkIrrep irreg
    mkIrrep
      ( IrregularRep
          { irregularS = segs,
            irregularF = flags,
            irregularO = offsets,
            irregularD = elems
          }
        ) = do
        flags_t <- lookupType flags
        t <- lookupType elems
        num_data <- letExp "num_data" =<< toExp (product $ map pe64 $ arrayDims t)
        let shape = Shape [Var num_data]
        flags' <- letExp "flags" $ BasicOp $ Reshape flags $ reshapeAll (arrayShape flags_t) shape
        elems' <- letExp "elems" $ BasicOp $ Reshape elems $ reshapeAll (arrayShape t) shape
        pure [num_data, segs, flags', offsets, elems']

needsIrregularRetType :: DistInputs -> RetType SOACS -> Bool
needsIrregularRetType inps = any needsIrregularDim . arrayDims
  where
    needsIrregularDim Ext {} = True
    needsIrregularDim (Free se) = isVariant inps se

liftRegResult :: SegLevel -> Segments -> SubExp -> DistInputs -> DistEnv -> RetType SOACS -> SubExpRes -> FlattenM Result
liftRegResult lvl segments num_segments inps env rettype res
  | needsIrregularRetType inps rettype = case resSubExp res of
      Var v -> do
        irreg <- getIrregRep lvl segments env inps v
        varsRes <$> irregularRepToFlatArrs num_segments irreg
      Constant {} ->
        error "liftRegResult: irregular result is not a variable"
  | otherwise = do
      let res_se = resSubExp res
      res_t <- subExpInputType inps res_se
      let expectedShape = segmentsShape segments <> arrayShape res_t
      lifted_res <- liftSubExpRegular lvl segments inps env expectedShape res_se
      pure [SubExpRes mempty (Var lifted_res)]

mkIrregFromReg ::
  SegLevel ->
  Segments ->
  VName ->
  FlattenM IrregularRep
mkIrregFromReg lvl segments arr = do
  arr_t <- lookupType arr
  num_segments <-
    letSubExp "reg_num_segments" <=< toExp $ product $ segmentDims segments
  segment_size <-
    letSubExp "reg_seg_size" <=< toExp . product . map pe64 $
      drop (segmentsRank segments) (arrayDims arr_t)
  arr_S <-
    letExp "reg_segments" . BasicOp $
      Replicate (Shape [num_segments]) segment_size
  num_elems <-
    letSubExp "reg_num_elems" <=< toExp $ product $ map pe64 $ arrayDims arr_t
  arr_D <-
    letExp "reg_D" . BasicOp $
      Reshape arr (reshapeAll (arrayShape arr_t) (Shape [num_elems]))
  arr_F <- letExp "reg_F" <=< segMap lvl (MkSolo num_elems) $ \(MkSolo i) -> do
    flag <- letSubExp "flag" <=< toExp $ (pe64 i `rem` pe64 segment_size) .==. 0
    pure [subExpRes flag]
  arr_O <- letExp "reg_O" <=< segMap lvl (MkSolo num_segments) $ \(MkSolo i) -> do
    offset <- letSubExp "offset" <=< toExp $ pe64 i * pe64 segment_size
    pure [subExpRes offset]
  pure $
    IrregularRep
      { irregularS = arr_S,
        irregularF = arr_F,
        irregularO = arr_O,
        irregularD = arr_D,
        irregularK = Dense
      }

readIrregularInput ::
  Segments ->
  [SubExp] ->
  VName ->
  Type ->
  IrregularRep ->
  FlattenM VName
readIrregularInput segments is v t (IrregularRep _ _ v_O v_D _) = do
  offset <- letSubExp "offset" =<< eIndex v_O [toExp $ flatSegmentIndex segments is]
  case arrayDims t of
    [] -> do
      letExp (baseName v <> "_inp") =<< eIndex v_D [eSubExp offset]
    [num_elems] -> do
      let slice = Slice [DimSlice offset num_elems (intConst Int64 1)]
      letExp (baseName v <> "_inp") $ BasicOp $ Index v_D slice
    _ -> do
      num_elems <-
        letSubExp "num_elems" =<< toExp (product $ map pe64 $ arrayDims t)
      let slice = Slice [DimSlice offset num_elems (intConst Int64 1)]
      v_flat <-
        letExp (baseName v <> "_flat") $ BasicOp $ Index v_D slice
      v_flat_t <- lookupType v_flat
      letExp (baseName v <> "_inp") . BasicOp $
        Reshape v_flat (reshapeAll (arrayShape v_flat_t) (arrayShape t))

readInputVar :: Segments -> DistEnv -> [SubExp] -> DistInputs -> VName -> FlattenM VName
readInputVar segments env is inputs v =
  case lookup v inputs of
    Nothing -> pure v
    Just (DistInputFree arr t)
      | isAcc t -> pure arr
      | otherwise -> letExp (baseName v) =<< eIndex arr (map eSubExp is)
    Just (DistInput rt t) -> do
      case resVar rt env of
        Regular arr
          | isAcc t -> pure arr
          | otherwise -> letExp (baseName v) =<< eIndex arr (map eSubExp is)
        Irregular irreg -> readIrregularInput segments is v t irreg

readInput :: Segments -> DistEnv -> [SubExp] -> DistInputs -> SubExp -> FlattenM SubExp
readInput _ _ _ _ (Constant x) =
  pure $ Constant x
readInput segments env is inputs (Var v) =
  Var <$> readInputVar segments env is inputs v

-- | Read the neutral element of a reduction or scan operator. The neutral
-- element of an operator is unique because we assume uniform operators, so a
-- valid program must have the same value in every segment, and we can read it
-- from the first one. This is the case even if the neutral element is
-- nonuniform, which is useless but allowed. The segment space may however be
-- empty, in which case we produce a blank value instead; it is never used then,
-- but an unconditional read would be out of bounds.
readNeutral :: Segments -> DistEnv -> DistInputs -> SubExp -> FlattenM SubExp
readNeutral segments env inps ne
  | Var v <- ne,
    Just _ <- lookup v inps = do
      ne_t <- subExpInputType inps ne
      n <- letSubExp "num_segments" =<< toExp (segmentCount segments)
      letSubExp (baseName v <> "_ne")
        =<< eIf
          (toExp $ pe64 n .==. 0)
          (eBody [eBlank ne_t])
          (eBody [eSubExp =<< readInput segments env zeros inps ne])
  | otherwise =
      readInput segments env zeros inps ne
  where
    zeros = replicate (segmentsRank segments) (intConst Int64 0)

readTypeDims ::
  Segments ->
  DistEnv ->
  [SubExp] ->
  DistInputs ->
  TypeBase Shape u ->
  FlattenM [SubExp]
readTypeDims segments env is inputs =
  mapM (readInput segments env is inputs) . arrayDims

segmentDims :: Segments -> [TPrimExp Int64 VName]
segmentDims = map pe64 . shapeDims . segmentsShape

flatSegmentIndex :: Segments -> [SubExp] -> TPrimExp Int64 VName
flatSegmentIndex segments = flattenIndex (segmentDims segments) . map pe64

readInputs :: Segments -> DistEnv -> [SubExp] -> DistInputs -> FlattenM ()
readInputs segments env is = mapM_ onInput
  where
    bindInputName v e
      | v `nameIn` freeIn e = do
          v' <- letExp (baseName v <> "_inp") e
          letBindNames [v] $ BasicOp $ SubExp $ Var v'
      | otherwise =
          letBindNames [v] e
    onInput (v, DistInputFree arr t) =
      bindInputName v
        =<< if isAcc t
          then eSubExp (Var arr)
          else eIndex arr (map eSubExp is)
    onInput (v, DistInput rt t) =
      case resVar rt env of
        Regular arr ->
          bindInputName v
            =<< if isAcc t
              then eSubExp $ Var arr
              else eIndex arr (map eSubExp is)
        Irregular irreg ->
          readIrregularInput segments is v t irreg >>= eSubExp . Var >>= bindInputName v

scopeOfDistInputs :: DistInputs -> Scope GPU
scopeOfDistInputs = scopeOfLParams . map f
  where
    f (v, inp) = Param mempty v (distInputType inp)

lookupInputType :: DistInputs -> VName -> FlattenM Type
lookupInputType inps v =
  case lookup v inps of
    Just inp -> pure $ distInputType inp
    Nothing -> lookupType v

subExpInputType :: DistInputs -> SubExp -> FlattenM Type
subExpInputType _ (Constant val) =
  pure $ Prim $ primValueType val
subExpInputType inps (Var v) =
  lookupInputType inps v

isVariant :: DistInputs -> SubExp -> Bool
isVariant _ (Constant _) = False
isVariant inps (Var v) = isJust $ lookup v inps

liftDistResultRep ::
  SegLevel ->
  Segments ->
  DistInputs ->
  DistEnv ->
  DistResult ->
  SubExpRes ->
  FlattenM ResRep
liftDistResultRep lvl segments inps env dist_res res
  | isRegularDistResult dist_res = do
      let (DistType _ _ t) = distResType dist_res
          expectedShape = segmentsShape segments <> arrayShape t
      Regular <$> liftSubExpRegular lvl segments inps env expectedShape (resSubExp res)
  | otherwise =
      case resSubExp res of
        Var v -> do
          rep <- getIrregRep lvl segments env inps v
          pure $ Irregular rep
        _ -> error "liftBranchResultRep: irregular result is not a variable"

liftDistResult :: SegLevel -> Segments -> DistInputs -> DistEnv -> DistResult -> SubExpRes -> FlattenM Result
liftDistResult lvl segments inps env dist_res res =
  if isRegularDistResult dist_res
    then do
      let (DistType _ _ t) = distResType dist_res
      let expectedShape = segmentsShape segments <> arrayShape t
      v <- liftSubExpRegular lvl segments inps env expectedShape (resSubExp res)
      pure [SubExpRes mempty (Var v)]
    else case resSubExp res of
      Var v -> do
        irreg <- getIrregRep lvl segments env inps v
        pure $ map (SubExpRes mempty . Var) [irregularS irreg, irregularF irreg, irregularO irreg, irregularD irreg]
      _ -> error "liftDistResult: irregular result is not a variable"

liftBodyWithDistResults :: FlattenOps -> Segments -> DistInputs -> DistEnv -> DistStms -> [DistResult] -> Result -> FlattenM Result
liftBodyWithDistResults ops segments inputs env dstms dist_res result = do
  env' <- foldM (flattenDistStm ops segments) env dstms
  result' <- zipWithM (liftDistResult (flattenSegLevel ops) segments inputs env') dist_res result
  pure $ concat result'

distResultsToResReps :: [DistResult] -> [VName] -> [ResRep]
distResultsToResReps dist_res results =
  snd $ L.mapAccumL f results dist_res
  where
    f rs dist_res' =
      if isRegularDistResult dist_res'
        then
          let (v : rs') = rs
           in (rs', Regular v)
        else
          let (segs : flags : offsets : elems : rs') = rs
           in (rs', Irregular $ IrregularRep segs flags offsets elems Dense)

-- | Convert an irregular representation to its flat constituents (number of
-- data elements, segments, flags, offsets, elements), with the structure arrays
-- reshaped to be one-dimensional of the given width. This is the form in which
-- irregular values are passed to lifted functions and carried through loops.
irregularRepToFlatArrs :: SubExp -> IrregularRep -> FlattenM [VName]
irregularRepToFlatArrs w (IrregularRep segs flags offsets elems _) = do
  t <- lookupType elems
  t_o <- lookupType offsets
  flags_t <- lookupType flags
  num_data <- letExp "num_data" =<< toExp (product $ map pe64 $ arrayDims t)
  let shape = Shape [Var num_data]
  flags' <- letExp "flags" $ BasicOp $ Reshape flags $ reshapeAll (arrayShape flags_t) shape
  elems' <- letExp "elems" $ BasicOp $ Reshape elems $ reshapeAll (arrayShape t) shape
  segs' <- letExp "segs" $ BasicOp $ Reshape segs $ reshapeAll (arrayShape t_o) (Shape [w])
  offsets' <- letExp "offsets" $ BasicOp $ Reshape offsets $ reshapeAll (arrayShape t_o) (Shape [w])
  pure [num_data, segs', flags', offsets', elems']

-- | Distribute a body and lift the distributed statements, giving
-- back representations of the body results.
distributeAndFlattenBody ::
  FlattenOps ->
  Segments ->
  Name ->
  DistEnv ->
  DistInputs ->
  [DistResult] ->
  Body SOACS ->
  FlattenM [ResRep]
distributeAndFlattenBody ops segments desc env inps res body = do
  scope <- askScope
  (inps_local, env_local, _) <- localiseInputs env inps
  let (inps_dist, dstms) = distributeBodyWith ops scope segments inps_local body
  lifted_res <- liftBodyWithDistResults ops segments inps_dist env_local dstms res (bodyResult body)
  lifted_vs <- mapM (letExp desc <=< toExp . resSubExp) lifted_res
  pure $ distResultsToResReps res lifted_vs

-- | Take the elements at index @is@ from an input @v@. The representation of
-- @v@ can be overridden through the provided mapping, which takes precedence
-- over what the environment says.
splitInput ::
  SegLevel ->
  Segments ->
  DistEnv ->
  DistInputs ->
  VName ->
  M.Map VName ResRep ->
  VName ->
  FlattenM (Type, VName, ResRep)
splitInput lvl segments env inps is acc_reps v = do
  (t, rep0) <- liftSubExpPreserveRep segments inps env (Var v)
  let rep = M.findWithDefault rep0 v acc_reps
  (t,v,) <$> case rep of
    Regular arr -> do
      if isAcc t
        then
          pure $ Regular arr
        else do
          -- In the regular case we just take the elements
          -- of the array given by `is`
          n <- letSubExp "n" =<< (toExp . arraySize 0 =<< lookupType is)
          inner_dims <- drop (segmentsRank segments) . arrayDims <$> lookupType arr
          -- Do the segMap over all dims, so the inner dimensions
          -- are gathered in parallel
          arr' <- letExp "split_arr" <=< segMap lvl (n : inner_dims) $ \(i : js) -> do
            idx <- letSubExp "idx" =<< eIndex is [eSubExp i]
            let arr_is = unflattenIndex (segmentDims segments) (pe64 idx)
            subExpsRes . pure <$> (letSubExp "arr" =<< eIndex arr (map toExp arr_is ++ map eSubExp js))
          pure $ Regular arr'
    Irregular (IrregularRep segs flags offsets elems _) -> do
      -- In the irregular case we take the elements
      -- of the `segs` array given by `is` like in the regular case
      n <- letSubExp "n" =<< (toExp . arraySize 0 =<< lookupType is)
      segs' <- letExp "split_segs" <=< segMap lvl (MkSolo n) $ \(MkSolo i) -> do
        idx <- letExp "idx" =<< eIndex is [eSubExp i]
        subExpsRes . pure <$> (letSubExp "segs" =<< eIndex segs [toExp idx])
      -- From this we calculate the offsets and number of elements
      (_, offsets', num_data) <- exScanAndSum lvl segs'
      (_, _, ii1) <- doRepIota lvl segs'
      (_, _, ii2) <- doSegIota lvl segs'
      -- We then take the elements we need from `elems` and `flags`
      -- For each index `i`, we roughly:
      -- Get the offset of the segment we want to copy by indexing
      -- `offsets` through `is` further through `ii1` i.e.
      -- `offset = offsets[is[ii1[i]]]`
      -- We then add `ii2[i]` to `offset`
      -- and use that to index into `elems` and `flags`.
      ~[flags', elems'] <- letTupExp "split_F_data" <=< segMap lvl (MkSolo num_data) $ \(MkSolo i) -> do
        offset <- letExp "offset" =<< eIndex offsets [eIndex is [eIndex ii1 [eSubExp i]]]
        idx <- letExp "idx" =<< eBinOp (Add Int64 OverflowUndef) (toExp offset) (eIndex ii2 [eSubExp i])
        flags_split <- letSubExp "flags" =<< eIndex flags [toExp idx]
        elems_split <- letSubExp "elems" =<< eIndex elems [toExp idx]
        pure $ subExpsRes [flags_split, elems_split]
      pure $
        Irregular $
          IrregularRep
            { irregularS = segs',
              irregularF = flags',
              irregularO = offsets',
              irregularD = elems',
              irregularK = Dense
            }

-- | Flatten the arrays of an IrregularRep to be entirely one-dimensional.
flattenIrregularRep :: SegLevel -> IrregularRep -> FlattenM IrregularRep
flattenIrregularRep lvl ir@(IrregularRep shape _ offsets elems kind) = do
  elems_t <- lookupType elems
  if arrayRank elems_t == 1
    then pure ir
    else do
      n <- arraySize 0 <$> lookupType shape
      m' <- letSubExp "flat_m" <=< toExp $ product $ map pe64 $ arrayDims elems_t
      elems' <-
        letExp (baseName elems <> "_flat") . BasicOp $
          Reshape elems (reshapeAll (arrayShape elems_t) (Shape [m']))
      let inner_size = product $ map pe64 $ tail $ arrayDims elems_t
      ~[shape', offsets'] <-
        letTupExp (baseName shape <> "_flat_metadata")
          <=< renameExp
          <=< segMap lvl (MkSolo n)
          $ \(MkSolo i) -> do
            old_shape <-
              letSubExp "old_shape" =<< eIndex shape [toExp i]
            old_offset <-
              letSubExp "old_offset" =<< eIndex offsets [toExp i]

            segment_shape <-
              letSubExp "segment_shape" <=< toExp $ (pe64 old_shape * inner_size)
            segment_offset <-
              letSubExp "segment_offset" <=< toExp $ (pe64 old_offset * inner_size)
            pure $ subExpsRes [segment_shape, segment_offset]
      flags' <- genFlags lvl m' offsets'
      pure $ IrregularRep shape' flags' offsets' elems' kind

-- If the sub-expression is a constant, replicate it to match the shape of `segments`
-- If it's a variable, lookup the variable in the dist inputs and dist env,
-- and if it can't be found it is a free variable, so we replicate it to match the shape of `segments`.
liftSubExp :: SegLevel -> Segments -> DistInputs -> DistEnv -> SubExp -> FlattenM (Type, ResRep)
liftSubExp lvl segments inps env se = case se of
  c@(Constant prim) ->
    let t = Prim $ primValueType prim
     in ((t,) . Regular <$> letExp "lifted_const" (BasicOp $ Replicate (segmentsShape segments) c))
  Var v -> case M.lookup v $ inputReps inps env of
    Just (t, Regular v') -> do
      (t,)
        <$> case t of
          Prim {} -> pure $ Regular v'
          Array {} -> Irregular <$> mkIrregFromReg lvl segments v'
          Acc {} -> pure $ Regular v'
          Mem {} -> error "liftSubExp: Mem"
    Just (t, Irregular irreg) -> do
      irreg' <- ensureDenseIrregular lvl "lifted_irreg" irreg
      (t,)
        <$> case t of
          Prim {} -> pure $ Regular $ irregularD irreg'
          Array {} -> pure $ Irregular irreg'
          Acc {} -> error "liftSubExp: Irregular Acc"
          Mem {} -> error "liftSubExp: Mem"
    Nothing -> do
      t <- lookupType v
      v' <- letExp "free_replicated" $ BasicOp $ Replicate (segmentsShape segments) (Var v)
      (t,)
        <$> case t of
          Prim {} -> pure $ Regular v'
          Array {} -> Irregular <$> mkIrregFromReg lvl segments v'
          Acc {} -> pure $ Regular v'
          Mem {} -> error "getRepSubExp: Mem"

liftSubExpPreserveRep :: Segments -> DistInputs -> DistEnv -> SubExp -> FlattenM (Type, ResRep)
liftSubExpPreserveRep segments inps env se = case se of
  c@(Constant prim) ->
    let t = Prim $ primValueType prim
     in do
          v <- letExp "lifted_const" $ BasicOp $ Replicate (segmentsShape segments) c
          pure (t, Regular v)
  Var v -> case M.lookup v $ inputReps inps env of
    Just (t, rep) -> pure (t, rep)
    Nothing -> do
      t <- lookupType v
      v' <- letExp "free_replicated" $ BasicOp $ Replicate (segmentsShape segments) (Var v)
      pure (t, Regular v')

-- | Like 'liftSubExp' but always returns a Regular result with the
-- given expected shape. Reshapes the underlying data if necessary.
liftSubExpRegular ::
  SegLevel ->
  Segments ->
  DistInputs ->
  DistEnv ->
  Shape ->
  SubExp ->
  FlattenM VName
liftSubExpRegular lvl segments inps env expectedShape se = do
  case se of
    c@(Constant _) ->
      letExp "lifted_const" (BasicOp $ Replicate (segmentsShape segments) c)
    Var v -> liftVarRegular lvl segments inps env expectedShape v

liftVarRegular ::
  SegLevel ->
  Segments ->
  DistInputs ->
  DistEnv ->
  Shape ->
  VName ->
  FlattenM VName
liftVarRegular lvl segments inps env expectedShape x = do
  v <- case M.lookup x $ inputReps inps env of
    Just (_, Regular v') -> pure v'
    Just (_, Irregular irreg) -> do
      rep_dense <- ensureDenseIrregular lvl "lifted_irreg" irreg
      pure $ irregularD rep_dense
    Nothing ->
      letExp "free_replicated" $ BasicOp $ Replicate (segmentsShape segments) (Var x)
  v_t <- lookupType v
  if isAcc v_t || arrayShape v_t == expectedShape
    then pure v
    else
      letExp "reg_lifted" . BasicOp $
        Reshape v (reshapeAll (arrayShape v_t) expectedShape)

-- | Can this input array be lifted to a regular array? This holds unless it is
-- represented irregularly. The uniform alternatives lift their inputs regularly
-- (via 'liftSubExpRegular'), which is only valid when the inputs are actually
-- regular.
isRegularInputArr :: DistEnv -> DistInputs -> VName -> Bool
isRegularInputArr env inps arr =
  case lookup arr inps of
    Just (DistInput rt _) ->
      case resVar rt env of
        Regular {} -> True
        Irregular {} -> False
    _ -> True

liftParam :: (MonadFreshNames m) => SubExp -> FParam SOACS -> m ([FParam GPU], ResRep)
liftParam w fparam =
  case declTypeOf fparam of
    Prim pt -> do
      p <-
        newParam
          (desc <> "_lifted")
          (arrayOf (Prim pt) (Shape [w]) Nonunique)
      pure ([p], Regular $ paramName p)
    Array pt _ u -> do
      num_data <-
        newParam (desc <> "_num_data") $ Prim int64
      segments <-
        newParam (desc <> "_segments") $
          arrayOf (Prim int64) (Shape [w]) Nonunique
      flags <-
        newParam (desc <> "_F") $
          arrayOf (Prim Bool) (Shape [Var (paramName num_data)]) Nonunique
      offsets <-
        newParam (desc <> "_O") $
          arrayOf (Prim int64) (Shape [w]) Nonunique
      elems <-
        newParam (desc <> "_data") $
          arrayOf (Prim pt) (Shape [Var (paramName num_data)]) u
      pure
        ( [num_data, segments, flags, offsets, elems],
          Irregular $
            IrregularRep
              { irregularS = paramName segments,
                irregularF = paramName flags,
                irregularO = paramName offsets,
                irregularD = paramName elems,
                irregularK = Dense
              }
        )
    Acc {} ->
      error "liftParam: Acc"
    Mem {} ->
      error "liftParam: Mem"
  where
    desc = baseName (paramName fparam)

liftRegularParam :: (MonadFreshNames m) => SubExp -> FParam SOACS -> m (FParam GPU, ResRep)
liftRegularParam w fparam =
  case declTypeOf fparam of
    Prim pt -> do
      p <-
        newParam
          (desc <> "_lifted")
          (arrayOf (Prim pt) (Shape [w]) Nonunique)
      pure (p, Regular $ paramName p)
    Array pt shape u -> do
      p <-
        newParam (desc <> "_lifted") $
          arrayOf (Prim pt) (Shape [w] <> shape) u
      pure (p, Regular $ paramName p)
    Acc {} ->
      error "liftParam: Acc"
    Mem {} ->
      error "liftParam: Mem"
  where
    desc = baseName (paramName fparam)

distCerts :: DistInputs -> StmAux a -> DistEnv -> Certs
distCerts inps aux env = Certs $ map f $ unCerts $ stmAuxCerts aux
  where
    f v = case lookup v inps of
      Nothing -> v
      Just (DistInputFree vs _) -> vs
      Just (DistInput rt _) ->
        case resVar rt env of
          Regular vs -> vs
          Irregular r -> irregularD r

flattenData :: VName -> FlattenM VName
flattenData vs = do
  t <- lookupType vs
  case arrayDims t of
    [_] -> pure vs
    dims -> do
      n <- toSubExp "num_data" $ product $ map pe64 dims
      letExp (baseName vs <> "_flat") . BasicOp $
        Reshape vs $
          reshapeAll (arrayShape t) (Shape [n])

-- | Only sensible for variables of uniform type.
dataArr :: SegLevel -> Segments -> DistEnv -> DistInputs -> SubExp -> FlattenM VName
dataArr lvl _segments env inps (Var v)
  | Just v_inp <- lookup v inps =
      case v_inp of
        DistInputFree vs _ -> flattenData vs
        DistInput rt _ -> case resVar rt env of
          Irregular r -> do
            rep_dense <- ensureDenseIrregular lvl "dataArr" r
            pure $ irregularD rep_dense
          Regular vs -> flattenData vs
dataArr _ segments _ _ se = do
  -- The result is a one-dimensional array with one element per segment. With no
  -- enclosing segments there is a single implicit segment, so we replicate over
  -- a unit dimension; replicating over the empty shape would instead yield a
  -- scalar.
  let rep_shape = case segmentsShape segments of
        Shape [] -> Shape [intConst Int64 1]
        shape -> shape
  rep <- letExp "rep" $ BasicOp $ Replicate rep_shape se
  rep_t <- lookupType rep
  let dims = arrayDims rep_t
  if length dims == 1
    then pure rep
    else do
      n <- toSubExp "n" $ product $ map pe64 dims
      letExp "reshape" $ BasicOp $ Reshape rep $ reshapeAll (arrayShape rep_t) (Shape [n])

-- | Get the irregular representation of a var.
getIrregRep :: SegLevel -> Segments -> DistEnv -> DistInputs -> VName -> FlattenM IrregularRep
getIrregRep lvl segments env inps v =
  case lookup v inps of
    Just v_inp -> case v_inp of
      DistInputFree arr _ -> mkIrregFromReg lvl segments arr
      DistInput rt _ -> case resVar rt env of
        Irregular r -> pure r
        Regular arr -> mkIrregFromReg lvl segments arr
    Nothing -> do
      v' <-
        letExp (baseName v <> "_rep") . BasicOp $
          Replicate (segmentsShape segments) (Var v)
      mkIrregFromReg lvl segments v'

-- | This function walks through the *unlifted* result types
-- and uses the *lifted* results to construct the corresponding res reps.
--
-- See the 'liftResult' function for the opposite process i.e.
-- turning 'ResRep's into results.
resultToResReps :: [TypeBase s u] -> [VName] -> [ResRep]
resultToResReps types results =
  snd $
    L.mapAccumL
      ( \rs t -> case t of
          Prim {} ->
            let (v : rs') = rs
                rep = Regular v
             in (rs', rep)
          Array {} ->
            let (_ : segs : flags : offsets : elems : rs') = rs
                rep = Irregular $ IrregularRep segs flags offsets elems Dense
             in (rs', rep)
          Acc {} -> error "resultToResReps: Illegal type 'Acc'"
          Mem {} -> error "resultToResReps: Illegal type 'Mem'"
      )
      results
      types

resultToResRepsByDistResult :: [DistResult] -> [VName] -> [ResRep]
resultToResRepsByDistResult dist_res results =
  snd $
    L.mapAccumL
      ( \rs dist_res' ->
          if isRegularDistResult dist_res'
            then
              let (v : rs') = rs
               in (rs', Regular v)
            else
              let (_ : segs : flags : offsets : elems : rs') = rs
               in (rs', Irregular $ IrregularRep segs flags offsets elems Dense)
      )
      results
      dist_res

-- helper to not mess up the tags when generating new ones for the loop parameters
-- probably won't be used in future
localiseInputs :: DistEnv -> DistInputs -> FlattenM (DistInputs, DistEnv, Int)
localiseInputs env_outer inps = do
  let step (i, env_acc) (v, inp) =
        case inp of
          DistInputFree arr t ->
            pure ((i, env_acc), (v, DistInputFree arr t))
          DistInput oldrt t -> do
            let newrt = ResTag i
                rep = resVar oldrt env_outer
            env_acc' <- insertRepM newrt rep env_acc
            pure ((i + 1, env_acc'), (v, DistInput newrt t))

  ((next, env_local), inps_local) <-
    mapAccumLM step (0, mempty) inps
  pure (inps_local, env_local, next)

-- | Replicate an array to insert new inner dimensions  after the
-- existing segment dimensions.
replicateForDims :: Segments -> Shape -> VName -> FlattenM VName
replicateForDims segments dims v = do
  v_t <- lookupType v
  let seg_rank = length segments
      v_rank = arrayRank v_t
      dims_rank = shapeRank dims
      perm = [dims_rank .. dims_rank + seg_rank - 1] ++ [0 .. dims_rank - 1] ++ [seg_rank + dims_rank .. dims_rank + v_rank - 1]
  v_rep <-
    letExp (baseName v <> "_reg_rep") . BasicOp $ Replicate dims (Var v)
  letExp (baseName v <> "_reg_rep_tr") . BasicOp $ Rearrange v_rep perm

-- | Flatten a single 'DistStm', producing an updated environment.
flattenDistStm ::
  FlattenOps ->
  Segments ->
  DistEnv ->
  DistStm ->
  FlattenM DistEnv
flattenDistStm ops = flattenDistStmWith ops ops

-- | Flatten a single scalar statement, producing an updated environment.
flattenScalarStm ::
  FlattenOps ->
  Segments ->
  DistEnv ->
  DistInputs ->
  [DistResult] ->
  Stm SOACS ->
  FlattenM DistEnv
flattenScalarStm ops = flattenScalarStmAt ops (flattenSegLevel ops)

-- | 'distributeBody' with the settings in the given 'FlattenOps'.
distributeBodyWith ::
  FlattenOps ->
  Scope rep ->
  Segments ->
  DistInputs ->
  Body SOACS ->
  (DistInputs, DistStms)
distributeBodyWith ops =
  distributeBody (flattenIrregularity ops) (flattenFunHasParallelism ops)

-- | 'distributeMap' with the settings in the given 'FlattenOps'.
distributeMapWith ::
  FlattenOps ->
  Scope rep ->
  Pat Type ->
  Segments ->
  [MapArray t] ->
  Lambda SOACS ->
  (Distributed, M.Map ResTag t)
distributeMapWith ops =
  distributeMap (flattenIrregularity ops) (flattenFunHasParallelism ops)

-- | Continue flattening at the given seg level, adjusting the irregularity
-- handling mode to match. Intrablock code cannot use the machinery for
-- flattening irregular arrays, as it produces SegOps whose sizes are bound
-- inside the enclosing kernel. 'SequentialiseIrregularAll' is requested by the
-- user rather than implied by the level, so we keep it if provided.
atSegLevel :: SegLevel -> FlattenOps -> FlattenOps
atSegLevel lvl ops =
  ops {flattenSegLevel = lvl, flattenIrregularity = irreg}
  where
    irreg = case (flattenIrregularity ops, lvl) of
      (SequentialiseIrregularAll, _) -> SequentialiseIrregularAll
      (_, SegThreadInBlock {}) -> SequentialiseIrregularBasicOps
      _ -> DistributeIrregular