packages feed

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

-- | Flattening of 'WithAcc'.
--
-- The basic idea is that in the nonuniform case, we change the 'WithAcc' to be
-- over the data array of the irregular representation. We then update all the
-- 'UpdateAcc' operations to compute flat indexes, via the usual metadata
-- arrays.
module Futhark.Pass.Flatten.WithAcc
  ( flattenWithAcc,
  )
where

import Control.Monad
import Data.Foldable
import Data.List qualified as L
import Data.Map qualified as M
import Futhark.IR.GPU
import Futhark.IR.SOACS
import Futhark.MonadFreshNames
import Futhark.Pass.Flatten.Distribute
import Futhark.Pass.Flatten.General
import Futhark.Tools
import Futhark.Transform.ToGPU (soacsLambdaToGPU)
import Prelude hiding (div, rem)

indexIrreg ::
  (MonadBuilder m, BranchType (Rep m) ~ ExtType) =>
  Segments ->
  DistEnv ->
  IrregularRep ->
  SubExp ->
  Safety ->
  ShapeBase SubExp ->
  [SubExp] ->
  m SubExp
indexIrreg _segments _env rep is safety shape js = do
  offset <- letSubExp "uacc_segment_offset" =<< eIndex (irregularO rep) [eSubExp is]
  flat <-
    letSubExp "flat_uacc_idx" <=< toExp $
      pe64 offset
        + flattenIndex
          (map pe64 (shapeDims shape))
          (map pe64 js)
  case safety of
    Unsafe -> pure flat
    -- The original per-segment index 'js' may be out of bounds, which for a
    -- safe 'UpdateAcc' is a deliberate no-op. Adding the segment offset might
    -- turn such an out-of-bounds index into an in-bounds index into a
    -- neighbouring segment, so we must preserve the out-of-boundedness
    -- explicitly.
    Safe -> do
      inbounds <- letSubExp "uacc_inbounds" =<< eShapeInBounds shape (map eSubExp js)
      letSubExp "uacc_flat_idx"
        =<< eIf
          (eSubExp inbounds)
          (eBody [eSubExp flat])
          (eBody [eSubExp (intConst Int64 (-1))])

-- If just one input is nonuniform, we treat them all as nonuniform.
flattenWithAcc ::
  FlattenOps ->
  Segments ->
  DistEnv ->
  DistInputs ->
  [DistResult] ->
  Pat Type ->
  StmAux () ->
  [WithAccInput SOACS] ->
  Lambda SOACS ->
  FlattenM DistEnv
flattenWithAcc ops segments env inps distres _withacc_pat withacc_aux withacc_inputs acc_lam = do
  lam_params' <- newAccLamParams $ lambdaParams acc_lam

  iota_w <- genShapeIota (flattenSegLevel ops) $ segmentsShape segments

  iota_p <- newParam "iota_p" $ Prim int64
  -- Type in DistInputFree is parameter type
  let iota_w_t = Prim int64
  let iota_se = Var (paramName iota_p)

  -- Potentially change to distres option.
  let nonuniform =
        not $
          all (\(_, arrs, _) -> all (isRegularInputArr env inps) arrs) withacc_inputs

  (withacc_inputs', trAccIndex, non_uniform_reps) <-
    if nonuniform
      then do
        (withacc_inputs', input_reps) <-
          mapAndUnzipM onNonuniformInput withacc_inputs
        let trAccIndex c safety is = do
              ((shape, _, _), rep : _) <-
                L.lookup c $
                  zip (map paramName lam_params') $
                    zip withacc_inputs input_reps
              Just $ L.singleton <$> indexIrreg segments env rep iota_se safety shape is
        pure (withacc_inputs', trAccIndex, concat input_reps)
      else do
        withacc_inputs' <- mapM onUniformInput withacc_inputs
        let trAccIndex c _safety is = do
              _ <- L.lookup c $ zip (map paramName lam_params') withacc_inputs
              Just $ do
                iota_se_unflat <-
                  mapM (letSubExp "iota_idx" <=< toExp) $
                    unflattenIndex (segmentDims segments) (pe64 iota_se)
                pure $ iota_se_unflat ++ is
        pure (withacc_inputs', trAccIndex, [])
  let trAccShape c = do
        (ispace, _, _) <- L.lookup c $ zip (map paramName lam_params') withacc_inputs'
        pure ispace
      sf = (trAccShape, trAccIndex)

  acc_lam_body <-
    runBodyBuilder $
      localScope (scopeOfLParams lam_params') $
        bodyBind . lambdaBody =<< trLam sf acc_lam

  scope <- askScope
  let orig_acc_params = drop num_accs $ lambdaParams acc_lam
      lam_params_tr = map (trParam sf) lam_params'
      acc_params_tr = drop num_accs lam_params_tr
      interchanged_inps =
        (paramName iota_p, DistInputFree iota_w iota_w_t)
          : [ (paramName p, DistInputFree (paramName acc) (paramType acc))
            | -- This could potentially be wrong but since it's acc type it should be fine.
              (p, acc) <- zip orig_acc_params acc_params_tr
            ]
          ++ inps

  let (withacc_new_inputs, withacc_dstms) =
        distributeBodyWith ops scope segments interchanged_inps acc_lam_body

  withacc_lam' <- mkLambda (map (trParam sf) lam_params') $ do
    env' <- foldM (flattenDistStm ops segments) env withacc_dstms
    reps <-
      mapM
        (liftWithAccResult (flattenSegLevel ops) segments withacc_new_inputs env')
        (zip distres (bodyResult $ lambdaBody acc_lam))
    concat <$> mapM repToResults reps

  withacc_out_vs <-
    certifying (distCerts inps withacc_aux env) $
      letTupExp "withacc_flatten_out" (WithAcc withacc_inputs' withacc_lam')

  -- The accumulator results are handled differently in the nonuniform case,
  -- since we do not have metadata for them and since all of them are turned
  -- flat even when they might actually be regular. We can still turn the
  -- distres that are regular into Regulars here.
  let num_acc_results = sum [length arrs | (_, arrs, _) <- withacc_inputs]
      (withacc_out_vs_wo, withacc_out_vs_no) = splitAt num_acc_results withacc_out_vs
      (distres_withacc, distres_normal) = splitAt num_acc_results distres

  let out_reps_normal = resultToResRepsByDistResult distres_normal withacc_out_vs_no
  out_reps_withacc <-
    if nonuniform
      then mapM mkNonuniformWithAccRep (zip3 withacc_out_vs_wo non_uniform_reps distres_withacc)
      else pure $ map Regular withacc_out_vs_wo
  insertRepsM (zip (map distResTag $ distres_withacc ++ distres_normal) (out_reps_withacc ++ out_reps_normal)) env
  where
    newAccLamParams ps = do
      let (cert_ps, acc_ps) = splitAt num_accs ps
      -- Should not rename the certificates.
      acc_ps' <- forM acc_ps $ \(Param attrs v t) ->
        Param attrs <$> newName v <*> pure t
      pure $ cert_ps <> acc_ps'

    num_accs = length withacc_inputs

    onOpWithIndexRank index_rank (op_lam, nes) = do
      -- We need to add an additional index parameter because we are extending
      -- the index space of the accumulator. In the uniform case we have the
      -- full index space of the segments, while in the nonuniform case we only
      -- have one additional dimension.
      idx_ps <- replicateM index_rank $ newParam "idx" $ Prim int64
      pure
        ( soacsLambdaToGPU $
            op_lam {lambdaParams = idx_ps <> lambdaParams op_lam},
          nes
        )

    -- Let's use liftSubExpRegular here
    onUniformInput (shape, arrs, op) =
      (segmentsShape segments <> shape,,)
        <$> mapM onArr arrs
        <*> traverse (onOpWithIndexRank (segmentsRank segments)) op
      where
        onArr arr = do
          arr_t <- lookupInputType inps arr
          let arr_shape = arrayShape arr_t
              expected_shape = segmentsShape segments <> arr_shape
          liftSubExpRegular (flattenSegLevel ops) segments inps env expected_shape (Var arr)

    onNonuniformOp rank (op, nes) = do
      let (old_idx_ps, value_ps) = splitAt rank $ lambdaParams op
          old_indices_used =
            any (\p -> paramName p `nameIn` freeIn (lambdaBody op)) old_idx_ps
      -- XXX: It is possible to change the lambda body to restore the indices based on flat_idx_p and handle this.
      when old_indices_used $
        error "flattenWithAcc: accumulator operator uses nonuniform indices"
      flat_idx_p <- newParam "flat_idx" $ Prim int64
      pure
        ( soacsLambdaToGPU $
            op
              { lambdaParams = flat_idx_p : value_ps
              },
          nes
        )
    onNonuniformInput (shape, arrs, op) = do
      reps <- mapM (getIrregRep (flattenSegLevel ops) segments env inps) arrs
      -- We need to ensure that the irregular arrays are dense
      reps_dense <- mapM (ensureDenseIrregular (flattenSegLevel ops) "withacc_input") reps
      let arrs' = map irregularD reps_dense
      w <- fmap (arraySize 0) . lookupType $ head arrs'
      -- We need to reshape to make sure all of the inputs have the same shape
      arrs'' <- forM arrs' $ \v -> do
        v_t <- lookupType v
        letExp (baseName v <> "_withacc_input_reshaped") . BasicOp $
          Reshape v $
            reshapeAll (arrayShape v_t) (Shape [w])
      (,reps_dense) . (Shape [w],arrs'',) <$> traverse (onNonuniformOp (shapeRank shape)) op

    -- The irregular kind is not carried through the results of the
    -- WithAcc, and 'mkNormalResReps' reconstructs the rep as Dense, so
    -- any irregular rep must actually be made dense before it crosses
    -- the WithAcc boundary.
    ensureDenseRep lvl (Irregular irreg) =
      Irregular <$> ensureDenseIrregular lvl "withacc_result" irreg
    ensureDenseRep _ rep = pure rep

    liftWithAccResult lvl segs inputs env' (dist_res, res) =
      case resSubExp res of
        Var v -> do
          let (Just (t, rep)) = M.lookup v $ inputReps inputs env'
          if isAcc t
            then
              pure rep
            else
              ensureDenseRep lvl =<< liftDistResultRep lvl segs inputs env' dist_res res
        Constant _ -> ensureDenseRep lvl =<< liftDistResultRep lvl segs inputs env' dist_res res

    repToResults (Regular v) =
      pure [SubExpRes mempty $ Var v]
    repToResults (Irregular irreg) =
      map (SubExpRes mempty . Var) <$> irregResults irreg

    irregResults
      ( 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']

    mkNonuniformWithAccRep (v, rep, dist_res)
      | isRegularDistResult dist_res = do
          let DistType _ _ t = distResType dist_res
              expectedShape = segmentsShape segments <> arrayShape t
          v_t <- lookupType v
          v_reshaped <-
            letExp "actual_regular_with_acc_res" . BasicOp $
              Reshape v (reshapeAll (arrayShape v_t) expectedShape)
          pure $ Regular v_reshaped
      | otherwise =
          pure $ Irregular $ rep {irregularD = v}

    trType ::
      (VName -> Maybe Shape, VName -> Safety -> [SubExp] -> Maybe (Builder SOACS [SubExp])) ->
      TypeBase shape u ->
      TypeBase shape u
    trType sf (Acc acc _ ts u)
      | Just shape <- fst sf acc =
          Acc acc shape ts u
    trType _ t = t

    trParam ::
      (VName -> Maybe Shape, VName -> Safety -> [SubExp] -> Maybe (Builder SOACS [SubExp])) ->
      Param (TypeBase Shape u) ->
      Param (TypeBase Shape u)
    trParam sf = fmap $ trType sf

    trBody sf (Body dec stms res) =
      Body dec <$> collectStms_ (traverse_ onStm stms) <*> pure res
      where
        onStm (Let pat aux e) =
          addStm . Let (fmap (trType sf) pat) aux =<< trExp sf pat e

    trLam sf (Lambda params ret body) =
      Lambda (map (trParam sf) params) (map (trType sf) ret) <$> trBody sf body

    trSOAC sf = mapSOACM mapper
      where
        mapper =
          identitySOACMapper {mapOnSOACLambda = trLam sf}

    trExp sf _ (WithAcc acc_inputs lam) =
      WithAcc acc_inputs <$> trLam sf lam
    trExp sf (Pat [PatElem _ acc_t]) (BasicOp (UpdateAcc safety acc is ses)) = do
      case acc_t of
        Acc cert _ _ _
          | Just mk <- snd sf cert safety is -> do
              is' <- mk
              pure $ BasicOp $ UpdateAcc safety acc is' ses
        _ ->
          pure $ BasicOp $ UpdateAcc safety acc is ses
    trExp sf _ e = mapExpM mapper e
      where
        mapper =
          identityMapper
            { mapOnBody = \_ -> trBody sf,
              mapOnRetType = pure . trType sf,
              mapOnBranchType = pure . trType sf,
              mapOnFParam = pure . trParam sf,
              mapOnLParam = pure . trParam sf,
              mapOnOp = trSOAC sf
            }