packages feed

futhark-0.25.37: src/Futhark/Tools.hs

{-# LANGUAGE TypeFamilies #-}

-- | An unstructured grab-bag of various tools and inspection
-- functions that didn't really fit anywhere else.
module Futhark.Tools
  ( module Futhark.Construct,
    redomapToMapAndReduce,
    scanomapToMapAndScan,
    maposcanomapToMapScanAndMap,
    dissectScrema,
    extractPostLambda,
    sequentialStreamWholeArray,
    partitionChunkedFoldParameters,
    withAcc,
    doScatter,

    -- * Primitive expressions
    module Futhark.Analysis.PrimExp.Convert,
  )
where

import Control.Monad
import Futhark.Analysis.PrimExp.Convert
import Futhark.Construct
import Futhark.IR
import Futhark.IR.SOACS.SOAC

-- | Turns a binding of a @redomap@ into two seperate bindings, a
-- @map@ binding and a @reduce@ binding (returned in that order).
--
-- Reuses the original pattern for the @reduce@, and creates a new
-- pattern with new 'Ident's for the result of the @map@.
redomapToMapAndReduce ::
  ( MonadFreshNames m,
    Buildable rep,
    ExpDec rep ~ (),
    Op rep ~ SOAC rep
  ) =>
  Pat (LetDec rep) ->
  ( SubExp,
    [Reduce rep],
    Lambda rep,
    [VName]
  ) ->
  m (Stm rep, Stm rep)
redomapToMapAndReduce (Pat pes) (w, reds, map_lam, arrs) = do
  (map_pat, red_pat, red_arrs) <-
    splitScanOrRedomap pes w map_lam $ map redNeutral reds
  map_stm <- mkLet map_pat . Op . Screma w arrs <$> mapSOAC map_lam
  red_stm <-
    Let red_pat (defAux ()) . Op
      <$> (Screma w red_arrs <$> reduceSOAC reds)
  pure (map_stm, red_stm)

scanomapToMapAndScan ::
  ( MonadFreshNames m,
    Buildable rep,
    ExpDec rep ~ (),
    Op rep ~ SOAC rep
  ) =>
  Pat (LetDec rep) ->
  ( SubExp,
    [Scan rep],
    Lambda rep,
    [VName]
  ) ->
  m (Stm rep, Stm rep)
scanomapToMapAndScan (Pat pes) (w, scans, map_lam, arrs) = do
  (map_pat, scan_pat, scan_arrs) <-
    splitScanOrRedomap pes w map_lam $ map scanNeutral scans
  map_stm <- mkLet map_pat . Op . Screma w arrs <$> mapSOAC map_lam
  scan_stm <-
    Let scan_pat (defAux ()) . Op
      <$> (Screma w scan_arrs <$> scanSOAC scans)
  pure (map_stm, scan_stm)

maposcanomapToMapScanAndMap ::
  ( MonadFreshNames m,
    Buildable rep,
    Op rep ~ SOAC rep
  ) =>
  Pat (LetDec rep) ->
  ( SubExp,
    Lambda rep,
    [Scan rep],
    Lambda rep,
    [VName]
  ) ->
  m (Stm rep, Stm rep, Stm rep)
maposcanomapToMapScanAndMap (Pat pes) (w, post_lam, scans, map_lam, arrs) = do
  map_res <- mapM tempRes $ lambdaReturnType map_lam
  map_stm <- mkLet map_res . Op . Screma w arrs <$> mapSOAC map_lam
  scan_res <- mapM tempRes $ take (scanResults scans) $ lambdaReturnType map_lam
  let (scan_arrs, map_arrs) = splitAt (scanResults scans) $ map identName map_res
  scan_stm <- mkLet scan_res . Op . Screma w scan_arrs <$> scanSOAC scans
  let post_arrs = map identName scan_res <> map_arrs
      res = patElemIdent <$> pes
  post_stm <- mkLet res . Op . Screma w post_arrs <$> mapSOAC post_lam
  pure (map_stm, scan_stm, post_stm)
  where
    tempRes res = newIdent "temp_res" $ res `arrayOfRow` w

splitScanOrRedomap ::
  (Typed dec, MonadFreshNames m) =>
  [PatElem dec] ->
  SubExp ->
  Lambda rep ->
  [[SubExp]] ->
  m ([Ident], Pat dec, [VName])
splitScanOrRedomap pes w map_lam nes = do
  let (acc_pes, arr_pes) =
        splitAt (length $ concat nes) pes
      (acc_ts, _arr_ts) =
        splitAt (length (concat nes)) $ lambdaReturnType map_lam
  map_accpat <- zipWithM accMapPatElem acc_pes acc_ts
  map_arrpat <- mapM arrMapPatElem arr_pes
  let map_pat = map_accpat ++ map_arrpat
  pure (map_pat, Pat acc_pes, map identName map_accpat)
  where
    accMapPatElem pe acc_t =
      newIdent (baseName (patElemName pe) <> "_map_acc") $ acc_t `arrayOfRow` w
    arrMapPatElem = pure . patElemIdent

-- | Turn a Screma into a maposcanomap (possibly with mapout parts) and a
-- Redomap.  This is used to handle Scremas that are so complicated
-- that we cannot directly generate efficient parallel code for them.
-- In essense, what happens is the opposite of horisontal fusion.
dissectScrema ::
  ( MonadBuilder m,
    Op (Rep m) ~ SOAC (Rep m),
    Buildable (Rep m)
  ) =>
  Pat (LetDec (Rep m)) ->
  SubExp ->
  ScremaForm (Rep m) ->
  [VName] ->
  m ()
dissectScrema pat w (ScremaForm map_lam scans reds post_lam) arrs = do
  let num_reds = redResults reds
      num_scans = scanResults scans
      reds_ts = concatMap (lambdaReturnType . redLambda) reds
      (red_res, scan_map_res) = splitAt num_reds $ patNames pat
      (scan_pars, map_pars) = splitAt num_scans $ lambdaParams post_lam
      post_res = bodyResult $ lambdaBody post_lam

  to_red <- replicateM num_reds $ newVName "to_red"
  red_pars <- mapM (newParam "x") reds_ts
  let red_post_res = paramName <$> red_pars

  let post_lam' =
        post_lam
          { lambdaParams = scan_pars <> red_pars <> map_pars,
            lambdaBody =
              (lambdaBody post_lam)
                { bodyResult = varsRes red_post_res <> post_res
                },
            lambdaReturnType = reds_ts <> lambdaReturnType post_lam
          }

  maposcanomap <- maposcanomapSOAC map_lam scans post_lam'
  letBindNames (to_red <> scan_map_res) $ Op (Screma w arrs maposcanomap)

  reduce <- reduceSOAC reds
  letBindNames red_res $ Op $ Screma w to_red reduce

-- | Remove the post lambda from a screma, producing the screma with an identity
-- post-lambda, and a new map screma that is just the post-lambda. You can apply
-- this indefinitely in case the post-lambda is already an identity lambda, so
-- be careful.
extractPostLambda ::
  ( MonadBuilder m,
    Op (Rep m) ~ SOAC (Rep m),
    Buildable (Rep m)
  ) =>
  Pat (LetDec (Rep m)) ->
  SubExp ->
  [VName] ->
  ScremaForm (Rep m) ->
  m ()
extractPostLambda pat w arrs (ScremaForm pre_lam scans reds post_lam) = do
  tmp_names <-
    mapM (newVName . (<> "_extract") . baseName . paramName) (lambdaParams post_lam)
  id_lam <- mkIdentityLambda $ map paramType $ lambdaParams post_lam
  letBindNames (map patElemName red_res <> tmp_names) $
    Op (Screma w arrs $ ScremaForm pre_lam scans reds id_lam)
  letBind (Pat nonred_res) . Op . Screma w tmp_names =<< mapSOAC post_lam
  where
    (red_res, nonred_res) = splitAt (redResults reds) (patElems pat)

-- | Turn a stream SOAC into statements that apply the stream lambda
-- to the entire input.
sequentialStreamWholeArray ::
  (MonadBuilder m, Buildable (Rep m)) =>
  Pat (LetDec (Rep m)) ->
  SubExp ->
  [SubExp] ->
  Lambda (Rep m) ->
  [VName] ->
  m ()
sequentialStreamWholeArray pat w nes lam arrs = do
  -- We just set the chunksize to w and inline the lambda body.  There
  -- is no difference between parallel and sequential streams here.
  let (chunk_size_param, fold_params, arr_params) =
        partitionChunkedFoldParameters (length nes) $ lambdaParams lam

  -- The chunk size is the full size of the array.
  letBindNames [paramName chunk_size_param] $ BasicOp $ SubExp w

  -- The accumulator parameters are initialised to the neutral element.
  forM_ (zip fold_params nes) $ \(p, ne) ->
    letBindNames [paramName p] $ BasicOp $ SubExp ne

  -- Finally, the array parameters are set to the arrays (but reshaped
  -- to make the types work out; this will be simplified rapidly).
  forM_ (zip arr_params arrs) $ \(p, arr) ->
    letBindNames [paramName p] $
      if null (arrayDims $ paramType p)
        then BasicOp $ SubExp $ Var arr
        else shapeCoerce (arrayDims $ paramType p) arr

  -- Then we just inline the lambda body.
  mapM_ addStm $ bodyStms $ lambdaBody lam

  -- The number of results in the body matches exactly the size (and
  -- order) of 'pat', so we bind them up here, again with a reshape to
  -- make the types work out.
  forM_ (zip (patElems pat) $ bodyResult $ lambdaBody lam) $ \(pe, SubExpRes cs se) ->
    certifying cs $ case (arrayDims $ patElemType pe, se) of
      (dims, Var v)
        | not $ null dims ->
            letBindNames [patElemName pe] $ shapeCoerce dims v
      _ -> letBindNames [patElemName pe] $ BasicOp $ SubExp se

-- | Split the parameters of a stream reduction lambda into the chunk
-- size parameter, the accumulator parameters, and the input chunk
-- parameters.  The integer argument is how many accumulators are
-- used.
partitionChunkedFoldParameters ::
  Int ->
  [Param dec] ->
  (Param dec, [Param dec], [Param dec])
partitionChunkedFoldParameters _ [] =
  error "partitionChunkedFoldParameters: lambda takes no parameters"
partitionChunkedFoldParameters num_accs (chunk_param : params) =
  let (acc_params, arr_params) = splitAt num_accs params
   in (chunk_param, acc_params, arr_params)

-- | Construct a one-dimensional scatter-like 'WithAcc'. The closure is invoked
-- with the accumulators.
withAcc ::
  (MonadBuilder m, LParam (Rep m) ~ Param Type) =>
  [VName] ->
  Int ->
  ([VName] -> m [SubExp]) ->
  m (Exp (Rep m))
withAcc dest rank mk = do
  cert_ps <- replicateM (length dest) $ newParam "acc_cert" $ Prim Unit
  dest_ts <- mapM lookupType dest
  let acc_shape = Shape $ take rank $ arrayDims $ head dest_ts
      mkT cert elem_t = Acc cert acc_shape [elem_t] NoUniqueness
      acc_ts =
        zipWith mkT (map paramName cert_ps) $
          map (stripArray rank) dest_ts
  acc_ps <- mapM (newParam "acc_p") acc_ts

  withacc_lam <- mkLambda (cert_ps <> acc_ps) $ subExpsRes <$> mk (map paramName acc_ps)

  pure $ WithAcc [(acc_shape, [v], Nothing) | v <- dest] withacc_lam

-- | Perform a scatter-like operation using accumulators and map.
doScatter ::
  (MonadBuilder m, Buildable (Rep m), Op (Rep m) ~ SOAC (Rep m)) =>
  Name ->
  Int ->
  [VName] ->
  [VName] ->
  ([LParam (Rep m)] -> m [SubExp]) ->
  m [VName]
doScatter desc rank dest arrs mk = do
  cert_ps <- replicateM (length dest) $ newParam "acc_cert" $ Prim Unit
  dest_ts <- mapM lookupType dest
  let acc_shape = Shape $ take rank $ arrayDims $ head dest_ts
      mkT cert elem_t = Acc cert acc_shape [elem_t] NoUniqueness
      acc_ts =
        zipWith mkT (map paramName cert_ps) $
          map (stripArray rank) dest_ts
  acc_ps <- mapM (newParam "acc_p") acc_ts
  arrs_ts <- mapM lookupType arrs

  withacc_lam <- mkLambda (cert_ps <> acc_ps) $ do
    acc_ps_inner <- mapM (newParam "acc_p") acc_ts
    params <- mapM (newParam "v" . stripArray 1) arrs_ts
    map_lam <-
      mkLambda (acc_ps_inner <> params) $ do
        (is, vs) <- splitAt rank <$> mk params
        fmap subExpsRes $ forM (zip acc_ps_inner vs) $ \(acc_p_inner, v) ->
          letSubExp "scatter_acc" . BasicOp $
            UpdateAcc Safe (paramName acc_p_inner) is [v]

    let w = arraysSize 0 arrs_ts
    (fmap varsRes . letTupExp "acc_res")
      . Op
      . Screma w (map paramName acc_ps <> arrs)
      =<< mapSOAC map_lam

  letTupExp desc $ WithAcc [(acc_shape, [v], Nothing) | v <- dest] withacc_lam