packages feed

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

{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-}

-- |
-- Module      : Language.Halide.LoopLevel
-- Copyright   : (c) Tom Westerhout, 2023
module Language.Halide.LoopLevel
  ( LoopLevel (..)
  , LoopLevelTy (..)
  , SomeLoopLevel (..)
  , LoopAlignStrategy (..)

    -- * Internal
  , CxxLoopLevel
  , withCxxLoopLevel
  , wrapCxxLoopLevel
  )
where

import Control.Exception (bracket)
import Data.Text (Text)
import Foreign.ForeignPtr
import Foreign.Marshal (toBool)
import Foreign.Ptr (Ptr)
import GHC.Records (HasField (..))
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.Type
import Language.Halide.Utils
import System.IO.Unsafe (unsafePerformIO)
import Prelude hiding (min, tail)

-- | Haskell counterpart of @Halide::LoopLevel@
data CxxLoopLevel

importHalide

data LoopLevelTy = InlinedTy | RootTy | LockedTy

-- | A reference to a site in a Halide statement at the top of the body of a particular for loop.
data LoopLevel (t :: LoopLevelTy) where
  InlinedLoopLevel :: LoopLevel 'InlinedTy
  RootLoopLevel :: LoopLevel 'RootTy
  LoopLevel :: !(ForeignPtr CxxLoopLevel) -> LoopLevel 'LockedTy

data SomeLoopLevel where
  SomeLoopLevel :: LoopLevel t -> SomeLoopLevel

deriving stock instance Show SomeLoopLevel

instance Eq SomeLoopLevel where
  (SomeLoopLevel InlinedLoopLevel) == (SomeLoopLevel InlinedLoopLevel) = True
  (SomeLoopLevel RootLoopLevel) == (SomeLoopLevel RootLoopLevel) = True
  (SomeLoopLevel a@(LoopLevel _)) == (SomeLoopLevel b@(LoopLevel _)) = a == b
  _ == _ = False

instance Eq (LoopLevel t) where
  level1 == level2 =
    toBool . unsafePerformIO $
      withCxxLoopLevel level1 $ \l1 ->
        withCxxLoopLevel level2 $ \l2 ->
          [CU.exp| bool { *$(const Halide::LoopLevel* l1) == *$(const Halide::LoopLevel* l2) } |]

instance Show (LoopLevel t) where
  showsPrec _ InlinedLoopLevel = showString "InlinedLoopLevel"
  showsPrec _ RootLoopLevel = showString "RootLoopLevel"
  showsPrec d level@(LoopLevel _) =
    showParen (d > 10) $
      showString "LoopLevel {func = "
        . shows (level.func :: Text)
        . showString ", var = "
        . shows (level.var :: Expr Int32)
        . showString "}"

-- desc
--   where
--     desc = unpack . unsafePerformIO $
--       withCxxLoopLevel level $ \l ->
--         peekAndDeleteCxxString
--           =<< [C.throwBlock| std::string* {
--                 return handle_halide_exceptions([=](){
--                   return new std::string{$(const Halide::LoopLevel* l)->to_string()};
--                 });
--               } |]

-- | Different ways to handle the case when the start/end of the loops of stages computed with (fused)
-- are not aligned.
data LoopAlignStrategy
  = -- | Shift the start of the fused loops to align.
    LoopAlignStart
  | -- | Shift the end of the fused loops to align.
    LoopAlignEnd
  | -- | 'computeWith' will make no attempt to align the start/end of the fused loops.
    LoopNoAlign
  | -- | By default, LoopAlignStrategy is set to 'LoopNoAlign'.
    LoopAlignAuto
  deriving stock (Show, Eq, Ord)

instance Enum LoopAlignStrategy where
  fromEnum =
    fromIntegral . \case
      LoopAlignStart -> [CU.pure| int { static_cast<int>(Halide::LoopAlignStrategy::AlignStart) } |]
      LoopAlignEnd -> [CU.pure| int { static_cast<int>(Halide::LoopAlignStrategy::AlignEnd) } |]
      LoopNoAlign -> [CU.pure| int { static_cast<int>(Halide::LoopAlignStrategy::NoAlign) } |]
      LoopAlignAuto -> [CU.pure| int { static_cast<int>(Halide::LoopAlignStrategy::Auto) } |]
  toEnum k
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::LoopAlignStrategy::AlignStart) } |] = LoopAlignStart
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::LoopAlignStrategy::AlignEnd) } |] = LoopAlignEnd
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::LoopAlignStrategy::NoAlign) } |] = LoopNoAlign
    | fromIntegral k == [CU.pure| int { static_cast<int>(Halide::LoopAlignStrategy::Auto) } |] = LoopAlignAuto
    | otherwise = error $ "invalid LoopAlignStrategy: " <> show k

isInlined :: LoopLevel t -> Bool
isInlined InlinedLoopLevel = True
isInlined _ = False

isRoot :: LoopLevel t -> Bool
isRoot RootLoopLevel = True
isRoot _ = False

instance HasField "func" (LoopLevel 'LockedTy) Text where
  getField level = unsafePerformIO $
    withCxxLoopLevel level $ \level' ->
      peekAndDeleteCxxString
        =<< [CU.exp| std::string* {
              new std::string{$(const Halide::LoopLevel* level')->func()} } |]

instance HasField "var" (LoopLevel 'LockedTy) (Expr Int32) where
  getField level = unsafePerformIO $
    withCxxLoopLevel level $ \level' ->
      wrapCxxVarOrRVar
        =<< [CU.exp| Halide::VarOrRVar* {
              new Halide::VarOrRVar{$(const Halide::LoopLevel* level')->var()} } |]

wrapCxxLoopLevel :: Ptr CxxLoopLevel -> IO SomeLoopLevel
wrapCxxLoopLevel p = do
  [C.throwBlock| void { handle_halide_exceptions([=]() { $(Halide::LoopLevel* p)->lock(); }); } |]
  inlined <-
    toBool
      <$> [C.throwBlock| bool {
            return handle_halide_exceptions([=](){
              return $(const Halide::LoopLevel* p)->is_inlined(); });
          } |]
  root <-
    toBool
      <$> [C.throwBlock| bool {
            return handle_halide_exceptions([=](){
              return $(const Halide::LoopLevel* p)->is_root(); });
          } |]
  let level
        | inlined = [CU.exp| void { delete $(Halide::LoopLevel *p) } |] >> pure (SomeLoopLevel InlinedLoopLevel)
        | root = [CU.exp| void { delete $(Halide::LoopLevel *p) } |] >> pure (SomeLoopLevel RootLoopLevel)
        | otherwise = do
            let deleter = [C.funPtr| void deleteLoopLevel(Halide::LoopLevel* p) { delete p; } |]
            SomeLoopLevel . LoopLevel <$> newForeignPtr deleter p
  level

withCxxLoopLevel :: LoopLevel t -> (Ptr CxxLoopLevel -> IO a) -> IO a
withCxxLoopLevel (LoopLevel fp) action = withForeignPtr fp action
withCxxLoopLevel level action = do
  let allocate
        | isInlined level = [CU.exp| Halide::LoopLevel* { new Halide::LoopLevel{Halide::LoopLevel::inlined()} } |]
        | isRoot level = [CU.exp| Halide::LoopLevel* { new Halide::LoopLevel{Halide::LoopLevel::root()} } |]
        | otherwise = error "this should never happen"
      destroy p = [CU.exp| void { delete $(Halide::LoopLevel *p) } |]
  bracket allocate destroy action