packages feed

halide-haskell-0.0.1.0: src/Language/Halide/Schedule.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}

module Language.Halide.Schedule
  ( Dim (..)
  , DimType (..)
  , ForType (..)
  , SplitContents (..)
  , FuseContents (..)
  , Split (..)
  , Bound (..)
  , StorageDim (..)
  , FusedPair (..)
  , FuseLoopLevel (..)
  , StageSchedule (..)
  , ReductionVariable (..)
  , PrefetchDirective (..)
  , getStageSchedule
  -- , getStageSchedule
  -- , getFusedPairs
  -- , getReductionVariables
  -- , getFuseLoopLevel
  -- , getDims
  -- , getSplits
  , AutoScheduler (..)
  , loadAutoScheduler
  , applyAutoScheduler
  , getHalideLibraryPath
  , applySplits
  , applyDims
  , applySchedule
  )
where

import Control.Monad (void)
import Data.Text (Text, pack, unpack)
import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8)
import Foreign.C.Types (CInt (..))
import Foreign.ForeignPtr
import Foreign.Marshal (allocaArray, peekArray, toBool)
import Foreign.Ptr (Ptr, nullPtr)
import Foreign.Storable
import GHC.TypeLits
import qualified Language.C.Inline as C
import qualified Language.C.Inline.Cpp.Exception as C
import qualified Language.C.Inline.Unsafe as CU
import Language.Halide.Context
import Language.Halide.Expr
import Language.Halide.Func
import Language.Halide.LoopLevel
import Language.Halide.Target
import Language.Halide.Type
import Language.Halide.Utils
import System.FilePath (takeDirectory)
import Prelude hiding (tail)

#if USE_DLOPEN
import qualified System.Posix.DynamicLinker as DL

loadLibrary :: Text -> IO ()
loadLibrary path = do
  _ <- DL.dlopen (unpack path) [DL.RTLD_LAZY]
  pure ()

#else
import qualified System.Win32.DLL as Win32

loadLibrary :: Text -> IO ()
loadLibrary path = do
  _ <- Win32.loadLibrary (unpack path)
  pure ()
#endif

-- | Type of dimension that tells which transformations are legal on it.
data DimType = DimPureVar | DimPureRVar | DimImpureRVar
  deriving stock (Show, Eq)

-- | Specifies how loop values are traversed.
data ForType
  = ForSerial
  | ForParallel
  | ForVectorized
  | ForUnrolled
  | ForExtern
  | ForGPUBlock
  | ForGPUThread
  | ForGPULane
  deriving stock (Show, Eq)

data Dim = Dim {var :: !Text, forType :: !ForType, deviceApi :: !DeviceAPI, dimType :: !DimType}
  deriving stock (Show, Eq)

data FuseContents = FuseContents
  { fuseOuter :: !Text
  , fuseInner :: !Text
  , fuseNew :: !Text
  }
  deriving stock (Show, Eq)

data SplitContents = SplitContents
  { splitOld :: !Text
  , splitOuter :: !Text
  , splitInner :: !Text
  , splitFactor :: !(Expr Int32)
  , splitExact :: !Bool
  , splitTail :: !TailStrategy
  }
  deriving stock (Show)

data Split
  = SplitVar !SplitContents
  | FuseVars !FuseContents
  deriving stock (Show)

data Bound = Bound
  { boundVar :: !Text
  , boundMin :: !(Maybe (Expr Int32))
  , boundExtent :: !(Expr Int32)
  , boundModulus :: !(Maybe (Expr Int32))
  , boundRemainder :: !(Maybe (Expr Int32))
  }
  deriving stock (Show)

data StorageDim = StorageDim
  { storageVar :: !Text
  , storageAlignment :: !(Maybe (Expr Int32))
  , storageBound :: !(Maybe (Expr Int32))
  , storageFold :: !(Maybe (Expr Int32, Bool))
  }
  deriving stock (Show)

data FusedPair = FusedPair !Text !(Text, Int) !(Text, Int)
  deriving stock (Show, Eq)

data FuseLoopLevel = FuseLoopLevel !SomeLoopLevel
  deriving stock (Show, Eq)

data ReductionVariable = ReductionVariable {varName :: !Text, minExpr :: !(Expr Int32), extentExpr :: !(Expr Int32)}
  deriving stock (Show)

data PrefetchBoundStrategy
  = PrefetchClamp
  | PrefetchGuardWithIf
  | PrefetchNonFaulting
  deriving stock (Show, Eq)

data PrefetchDirective = PrefetchDirective
  { prefetchFunc :: !Text
  , prefetchAt :: !Text
  , prefetchFrom :: !Text
  , prefetchOffset :: !(Expr Int32)
  , prefetchStrategy :: !PrefetchBoundStrategy
  , prefetchParameter :: !(Maybe (ForeignPtr CxxParameter))
  }
  deriving stock (Show)

data StageSchedule = StageSchedule
  { rvars :: ![ReductionVariable]
  , splits :: ![Split]
  , dims :: ![Dim]
  , prefetches :: ![PrefetchDirective]
  , fuseLevel :: !FuseLoopLevel
  , fusedPairs :: ![FusedPair]
  , allowRaceConditions :: !Bool
  , atomic :: !Bool
  , overrideAtomicAssociativityTest :: !Bool
  }
  deriving stock (Show)

importHalide

instanceHasCxxVector "Halide::Internal::Dim"
instanceHasCxxVector "Halide::Internal::Split"
instanceHasCxxVector "Halide::Internal::FusedPair"
instanceHasCxxVector "Halide::Internal::ReductionVariable"

instance Enum ForType where
  toEnum k
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::ForType::Serial) } |] =
        ForSerial
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::ForType::Parallel) } |] =
        ForParallel
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::ForType::Vectorized) } |] =
        ForVectorized
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::ForType::Unrolled) } |] =
        ForUnrolled
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::ForType::Extern) } |] =
        ForExtern
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::ForType::GPUBlock) } |] =
        ForGPUBlock
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::ForType::GPUThread) } |] =
        ForGPUThread
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::ForType::GPULane) } |] =
        ForGPULane
    | otherwise = error $ "invalid ForType: " <> show k
  fromEnum = error "Enum instance for ForType does not implement fromEnum"

instance Enum DimType where
  toEnum k
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::DimType::PureVar) } |] =
        DimPureVar
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::DimType::PureRVar) } |] =
        DimPureRVar
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::Internal::DimType::ImpureRVar) } |] =
        DimImpureRVar
    | otherwise = error $ "invalid DimType: " <> show k
  fromEnum = error "Enum instance for DimType does not implement fromEnum"

instance Enum PrefetchBoundStrategy where
  toEnum k
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::PrefetchBoundStrategy::Clamp) } |] =
        PrefetchClamp
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::PrefetchBoundStrategy::GuardWithIf) } |] =
        PrefetchGuardWithIf
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::PrefetchBoundStrategy::NonFaulting) } |] =
        PrefetchNonFaulting
    | otherwise = error $ "invalid PrefetchBoundStrategy: " <> show k
  fromEnum = error "Enum instance for ForType does not implement fromEnum"

instance Storable FusedPair where
  sizeOf _ = fromIntegral [CU.pure| size_t { sizeof(Halide::Internal::FusedPair) } |]
  alignment _ = fromIntegral [CU.pure| size_t { alignof(Halide::Internal::FusedPair) } |]
  peek p = do
    func1 <-
      peekCxxString
        =<< [CU.exp| const std::string* { &$(const Halide::Internal::FusedPair* p)->func_1 } |]
    func2 <-
      peekCxxString
        =<< [CU.exp| const std::string* { &$(const Halide::Internal::FusedPair* p)->func_2 } |]
    stage1 <-
      fromIntegral
        <$> [CU.exp| size_t { $(const Halide::Internal::FusedPair* p)->stage_1 } |]
    stage2 <-
      fromIntegral
        <$> [CU.exp| size_t { $(const Halide::Internal::FusedPair* p)->stage_2 } |]
    varName <-
      peekCxxString
        =<< [CU.exp| const std::string* { &$(const Halide::Internal::FusedPair* p)->var_name } |]
    pure $ FusedPair varName (func1, stage1) (func2, stage2)
  poke _ _ = error "Storable instance of FusedPair does not implement poke"

instance Storable ReductionVariable where
  sizeOf _ = fromIntegral [CU.pure| size_t { sizeof(Halide::Internal::ReductionVariable) } |]
  alignment _ = fromIntegral [CU.pure| size_t { alignof(Halide::Internal::ReductionVariable) } |]
  peek p = do
    varName <-
      peekCxxString
        =<< [CU.exp| const std::string* { &$(const Halide::Internal::ReductionVariable* p)->var } |]
    minExpr <-
      cxxConstructExpr $ \ptr ->
        [CU.exp| void { new ($(Halide::Expr* ptr)) Halide::Expr{
          $(const Halide::Internal::ReductionVariable* p)->min} } |]
    extentExpr <-
      cxxConstructExpr $ \ptr ->
        [CU.exp| void { new ($(Halide::Expr* ptr)) Halide::Expr{
          $(const Halide::Internal::ReductionVariable* p)->extent} } |]
    pure $ ReductionVariable varName minExpr extentExpr
  poke _ _ = error "Storable instance of ReductionVariable does not implement poke"

instance Storable PrefetchDirective where
  sizeOf _ = fromIntegral [CU.pure| size_t { sizeof(Halide::Internal::PrefetchDirective) } |]
  alignment _ = fromIntegral [CU.pure| size_t { alignof(Halide::Internal::PrefetchDirective) } |]
  peek p = do
    funcName' <-
      peekCxxString
        =<< [CU.exp| const std::string* { &$(const Halide::Internal::PrefetchDirective* p)->name } |]
    atVar' <-
      peekCxxString
        =<< [CU.exp| const std::string* { &$(const Halide::Internal::PrefetchDirective* p)->at } |]
    fromVar' <-
      peekCxxString
        =<< [CU.exp| const std::string* { &$(const Halide::Internal::PrefetchDirective* p)->from } |]
    offset' <-
      cxxConstructExpr $ \ptr ->
        [CU.exp| void { new ($(Halide::Expr* ptr)) Halide::Expr{
          $(const Halide::Internal::PrefetchDirective* p)->offset} } |]
    strategy' <-
      toEnum . fromIntegral
        <$> [CU.exp| int { static_cast<int>($(const Halide::Internal::PrefetchDirective* p)->strategy) } |]
    -- isDefined <-
    --   toBool
    --     <$> [CU.exp| bool { $(const Halide::Internal::PrefetchDirective* p)->param.defined() } |]
    -- param' <-
    --   if isDefined
    --     then
    --       fmap Just $
    --         wrapCxxParameter
    --           =<< [CU.exp| Halide::Internal::Parameter* {
    --                 new Halide::Internal::Parameter{$(const Halide::Internal::PrefetchDirective* p)->param} } |]
    --     else pure Nothing
    pure $ PrefetchDirective funcName' atVar' fromVar' offset' strategy' Nothing
  poke _ _ = error "Storable instance for PrefetchDirective does not implement poke"

getReductionVariables :: Ptr CxxStageSchedule -> IO [ReductionVariable]
getReductionVariables schedule =
  peekCxxVector
    =<< [CU.exp| const std::vector<Halide::Internal::ReductionVariable>* {
          &$(const Halide::Internal::StageSchedule* schedule)->rvars() } |]

getSplits :: Ptr CxxStageSchedule -> IO [Split]
getSplits schedule =
  peekCxxVector
    =<< [CU.exp| const std::vector<Halide::Internal::Split>* {
            &$(const Halide::Internal::StageSchedule* schedule)->splits() } |]

getDims :: Ptr CxxStageSchedule -> IO [Dim]
getDims schedule =
  peekCxxVector
    =<< [CU.exp| const std::vector<Halide::Internal::Dim>* {
          &$(const Halide::Internal::StageSchedule* schedule)->dims() } |]

getFuseLoopLevel :: Ptr CxxStageSchedule -> IO FuseLoopLevel
getFuseLoopLevel schedule =
  fmap FuseLoopLevel $
    wrapCxxLoopLevel
      =<< [CU.exp| Halide::LoopLevel* {
            new Halide::LoopLevel{$(const Halide::Internal::StageSchedule* schedule)->fuse_level().level}
          } |]

getFusedPairs :: Ptr CxxStageSchedule -> IO [FusedPair]
getFusedPairs schedule = do
  peekCxxVector
    =<< [CU.exp| const std::vector<Halide::Internal::FusedPair>* {
          &$(const Halide::Internal::StageSchedule* schedule)->fused_pairs() } |]

peekStageSchedule :: Ptr CxxStageSchedule -> IO StageSchedule
peekStageSchedule schedule = do
  rvars' <- getReductionVariables schedule
  splits' <- getSplits schedule
  dims' <- getDims schedule
  let prefetches' = []
  fuseLevel' <- getFuseLoopLevel schedule
  fusedPairs' <- getFusedPairs schedule
  allowRaceConditions' <-
    toBool
      <$> [CU.exp| bool { $(const Halide::Internal::StageSchedule* schedule)->allow_race_conditions() } |]
  atomic' <-
    toBool
      <$> [CU.exp| bool { $(const Halide::Internal::StageSchedule* schedule)->atomic() } |]
  overrideAtomicAssociativityTest' <-
    toBool
      <$> [CU.exp| bool { $(const Halide::Internal::StageSchedule* schedule)->override_atomic_associativity_test() } |]
  pure $
    StageSchedule
      { rvars = rvars'
      , splits = splits'
      , dims = dims'
      , prefetches = prefetches'
      , fuseLevel = fuseLevel'
      , fusedPairs = fusedPairs'
      , allowRaceConditions = allowRaceConditions'
      , atomic = atomic'
      , overrideAtomicAssociativityTest = overrideAtomicAssociativityTest'
      }

instance Storable Dim where
  sizeOf _ = fromIntegral [CU.pure| size_t { sizeof(Halide::Internal::Dim) } |]
  alignment _ = fromIntegral [CU.pure| size_t { alignof(Halide::Internal::Dim) } |]
  peek p = do
    name <- peekCxxString =<< [CU.exp| const std::string* { &$(Halide::Internal::Dim* p)->var } |]
    forType' <-
      toEnum . fromIntegral
        <$> [CU.exp| int { static_cast<int>($(Halide::Internal::Dim* p)->for_type) } |]
    device <-
      toEnum . fromIntegral
        <$> [CU.exp| int { static_cast<int>($(Halide::Internal::Dim* p)->device_api) } |]
    dimType' <-
      toEnum . fromIntegral
        <$> [CU.exp| int { static_cast<int>($(Halide::Internal::Dim* p)->dim_type) } |]
    pure $ Dim name forType' device dimType'
  poke _ = error "Storable instance for Dim does not implement poke"

peekOld :: Ptr Split -> IO Text
peekOld p = peekCxxString =<< [CU.exp| const std::string* { &$(const Halide::Internal::Split* p)->old_var } |]

peekOuter :: Ptr Split -> IO Text
peekOuter p = peekCxxString =<< [CU.exp| const std::string* { &$(const Halide::Internal::Split* p)->outer } |]

peekInner :: Ptr Split -> IO Text
peekInner p = peekCxxString =<< [CU.exp| const std::string* { &$(const Halide::Internal::Split* p)->inner } |]

peekFactor :: Ptr Split -> IO (Expr Int32)
peekFactor p =
  cxxConstructExpr $ \ptr ->
    [CU.exp| void { new ($(Halide::Expr* ptr)) Halide::Expr{
      $(const Halide::Internal::Split* p)->factor} } |]

instance Storable Split where
  sizeOf _ = fromIntegral [CU.pure| size_t { sizeof(Halide::Internal::Split) } |]
  alignment _ = fromIntegral [CU.pure| size_t { alignof(Halide::Internal::Split) } |]
  peek p = do
    isRename <- toBool <$> [CU.exp| bool { $(const Halide::Internal::Split* p)->is_rename() } |]
    isSplit <- toBool <$> [CU.exp| bool { $(const Halide::Internal::Split* p)->is_split() } |]
    isFuse <- toBool <$> [CU.exp| bool { $(const Halide::Internal::Split* p)->is_fuse() } |]
    isPurify <- toBool <$> [CU.exp| bool { $(const Halide::Internal::Split* p)->is_purify() } |]
    let r
          | isSplit =
              fmap SplitVar $
                SplitContents
                  <$> peekOld p
                  <*> peekOuter p
                  <*> peekInner p
                  <*> peekFactor p
                  <*> (toBool <$> [CU.exp| bool { $(const Halide::Internal::Split* p)->exact } |])
                  <*> fmap
                    (toEnum . fromIntegral)
                    [CU.exp| int { static_cast<int>($(const Halide::Internal::Split* p)->tail) } |]
          | isFuse =
              fmap FuseVars $
                FuseContents
                  <$> peekOuter p
                  <*> peekInner p
                  <*> peekOld p
          | isRename = error "renames are not yet implemented"
          | isPurify = error "purify is not yet implemented"
          | otherwise = error "invalid split type"
    r
  poke _ = error "Storable instance for Split does not implement poke"

-- wrapCxxStageSchedule :: Ptr CxxStageSchedule -> IO StageSchedule
-- wrapCxxStageSchedule = fmap StageSchedule . newForeignPtr deleter
--   where
--     deleter =
--       [C.funPtr| void deleteSchedule(Halide::Internal::StageSchedule* p) {
--         std::cout << "deleting ..." << std::endl;
--         delete p; } |]

getStageSchedule :: (KnownNat n, IsHalideType a) => Stage n a -> IO StageSchedule
getStageSchedule stage =
  withCxxStage stage $ \stage' ->
    peekStageSchedule
      =<< [CU.exp| const Halide::Internal::StageSchedule* {
            &$(const Halide::Stage* stage')->get_schedule() } |]

#if USE_DLOPEN

getHalideLibraryPath :: IO (Maybe Text)
getHalideLibraryPath = do
  ptr <-
    [CU.block| std::string* {
      Dl_info info;
      if (dladdr((void const*)&Halide::load_plugin, &info) != 0 && info.dli_sname != nullptr) {
        auto symbol = dlsym(RTLD_NEXT, info.dli_sname);
        if (dladdr(symbol, &info) != 0 && info.dli_fname != nullptr) {
          return new std::string{info.dli_fname};
        }
      }
      return nullptr;
    } |]
  if ptr == nullPtr
    then pure Nothing
    else Just . pack . takeDirectory . unpack <$> peekAndDeleteCxxString ptr

#else

getHalideLibraryPath :: IO (Maybe Text)
getHalideLibraryPath = pure Nothing

#endif

data AutoScheduler
  = Adams2019
  | Li2018
  | Mullapudi2016
  deriving stock (Eq, Show)

loadAutoScheduler :: AutoScheduler -> IO ()
loadAutoScheduler scheduler = do
  lib <- getHalideLibraryPath
  let prepare s
        | Just dir <- lib = dir <> "/lib" <> s <> ".so"
        | Nothing <- lib = "lib" <> s <> ".so"
      path = prepare $
        case scheduler of
          Adams2019 -> "autoschedule_adams2019"
          Li2018 -> "autoschedule_li2018"
          Mullapudi2016 -> "autoschedule_mullapudi2016"
  loadLibrary path

applyAutoScheduler :: (KnownNat n, IsHalideType a) => AutoScheduler -> Target -> Func t n a -> IO Text
applyAutoScheduler scheduler target func = do
  let s = encodeUtf8 . pack . show $ scheduler
  withFunc func $ \f ->
    withCxxTarget target $ \t -> do
      peekAndDeleteCxxString
        =<< [C.throwBlock| std::string* {
              return handle_halide_exceptions([=](){
                auto name = std::string{$bs-ptr:s, static_cast<size_t>($bs-len:s)};
                auto pipeline = Halide::Pipeline{*$(Halide::Func* f)};
                auto params = Halide::AutoschedulerParams{name};
                auto results = pipeline.apply_autoscheduler(*$(Halide::Target* t), params);
                return new std::string{std::move(results.schedule_source)};
              });
            } |]

makeUnqualified :: Text -> Text
makeUnqualified = snd . T.breakOnEnd "."

applySplit :: (KnownNat n, IsHalideType a) => Split -> Stage n a -> IO ()
applySplit (SplitVar x) stage = do
  oldVar <- mkVar (makeUnqualified x.splitOld)
  outerVar <- mkVar (makeUnqualified x.splitOuter)
  innerVar <- mkVar (makeUnqualified x.splitInner)
  void $ Language.Halide.Func.split x.splitTail oldVar (outerVar, innerVar) x.splitFactor stage
applySplit (FuseVars x) stage = do
  newVar <- mkVar (makeUnqualified x.fuseNew)
  innerVar <- mkVar (makeUnqualified x.fuseInner)
  outerVar <- mkVar (makeUnqualified x.fuseOuter)
  void $ Language.Halide.Func.fuse (innerVar, outerVar) newVar stage

applySplits :: (KnownNat n, IsHalideType a) => [Split] -> Stage n a -> IO ()
applySplits xs stage = mapM_ (`applySplit` stage) xs

applyDim :: (KnownNat n, IsHalideType a) => Dim -> Stage n a -> IO ()
applyDim x stage = do
  var' <- mkVar (makeUnqualified x.var)
  void $
    case x.forType of
      ForSerial -> pure stage
      ForParallel -> parallel var' stage
      ForVectorized -> vectorize var' stage
      ForUnrolled -> unroll var' stage
      ForExtern -> error "extern ForType is not yet supported by applyDim"
      ForGPUBlock -> gpuBlocks x.deviceApi var' stage
      ForGPUThread -> gpuThreads x.deviceApi var' stage
      ForGPULane -> gpuLanes x.deviceApi var' stage

applyDims :: (KnownNat n, IsHalideType a) => [Dim] -> Stage n a -> IO ()
applyDims xs stage = do
  mapM_ (`applyDim` stage) xs
  vars <- mapM (mkVar . makeUnqualified . (.var)) xs
  void $ reorder vars stage

applySchedule :: (KnownNat n, IsHalideType a) => StageSchedule -> Stage n a -> IO ()
applySchedule schedule stage = do
  applySplits schedule.splits stage
  applyDims schedule.dims stage

-- data SplitContents = SplitContents
--   { old :: !Text
--   , outer :: !Text
--   , inner :: !Text
--   , factor :: !(Maybe Int)
--   , exact :: !Bool
--   , tail :: !TailStrategy
--   }
--   deriving stock (Show, Eq)
--
--
-- applySplits :: [Split] -> Stage n a -> IO ()
-- applySplits splits stage =

-- v <-
--   [CU.exp| Halide::Internal::Dim* {
--        $(Halide::Internal::StageSchedule* schedule)->dims().data() } |]
-- putStrLn $ "n = " <> show n
-- mapM (\i -> print i >> peekElemOff v i) [0 .. n - 1]