horde-ad-0.3.0.0: src/HordeAd/Core/AstFreshId.hs
-- | Operations that (impurely, via a strictly increasing thread-safe counter)
-- generate fresh variables and sometimes also produce AST terms
-- by applying functions to such variables. This module encapsulates
-- the impurity, though some functions are in IO and they are used
-- with @unsafePerformIO@ outside, so some of the impurity escapes
-- and is encapsulated elsewhere.
module HordeAd.Core.AstFreshId
( funToAstIO, funToAst
, funToAstIntIO, funToAstInt
, funToAstIntMaybeIO, funToAstIntMaybe
, funToAstAutoBoundsIO, funToAstNoBoundsIO
, funToAstRevIO, funToAstFwdIO
, funToVarsIxS
-- * Low level counter manipulation to be used only in sequential tests
, resetVarCounter
) where
import Prelude
import Control.Concurrent.Counter (Counter, add, new, set)
import Data.Type.Equality (testEquality, (:~:) (Refl))
import GHC.Exts (IsList (..))
import System.IO.Unsafe (unsafePerformIO)
import Type.Reflection (typeRep)
import Data.Array.Nested.Shaped.Shape
import HordeAd.Core.Ast
import HordeAd.Core.AstTools
import HordeAd.Core.TensorKind
import HordeAd.Core.Types
-- | A counter that is impure but only in the most trivial way
-- (only ever incremented by one).
unsafeAstVarCounter :: Counter
{-# NOINLINE unsafeAstVarCounter #-}
unsafeAstVarCounter = unsafePerformIO (new 100000001)
-- | Only for tests, e.g., to ensure `show` applied to terms has stable length.
-- Tests that use this tool need to be run sequentially
-- to avoid variable confusion.
resetVarCounter :: IO ()
resetVarCounter = set unsafeAstVarCounter 100000001
unsafeGetFreshAstVarId :: IO AstVarId
{-# INLINE unsafeGetFreshAstVarId #-}
unsafeGetFreshAstVarId =
intToAstVarId <$> add unsafeAstVarCounter 1
funToAstIO :: KnownSpan s
=> FullShapeTK y -> (AstTensor ms s y -> AstTensor ms s2 z)
-> IO (AstVarName '(s, y), AstTensor ms s2 z)
{-# INLINE funToAstIO #-}
funToAstIO ftk f = do
!freshId <- unsafeGetFreshAstVarId
let !var = mkAstVarName ftk freshId
x = f $ astVar var
return (var, x)
funToAst :: KnownSpan s
=> FullShapeTK y -> (AstTensor ms s y -> AstTensor ms s2 z)
-> (AstVarName '(s, y), AstTensor ms s2 z)
{-# NOINLINE funToAst #-}
funToAst ftk = unsafePerformIO . funToAstIO ftk
funToAstIntIO :: (Int, Int) -> (AstInt ms -> AstTensor ms s2 z)
-> IO (IntVarName, AstTensor ms s2 z)
{-# INLINE funToAstIntIO #-}
funToAstIntIO bds f = do
!freshId <- unsafeGetFreshAstVarId
let !var = mkAstVarNameBounds bds freshId
x = f $ astVar var
return (var, x)
funToAstInt :: (Int, Int) -> (AstInt ms -> AstTensor ms s2 z)
-> (IntVarName, AstTensor ms s2 z)
{-# NOINLINE funToAstInt #-}
funToAstInt bds = unsafePerformIO . funToAstIntIO bds
funToAstIntMaybeIO :: Maybe (Int, Int) -> ((IntVarName, AstInt ms) -> a)
-> IO a
{-# INLINE funToAstIntMaybeIO #-}
funToAstIntMaybeIO mbounds f = do
!freshId <- unsafeGetFreshAstVarId
let !var = case mbounds of
Nothing -> mkAstVarName FTKScalar freshId
Just bds -> mkAstVarNameBounds bds freshId
x = astVar var
return $! f (var, x)
funToAstIntMaybe :: Maybe (Int, Int) -> ((IntVarName, AstInt ms) -> a) -> a
{-# NOINLINE funToAstIntMaybe #-}
funToAstIntMaybe mbounds = unsafePerformIO . funToAstIntMaybeIO mbounds
funToAstAutoBoundsIO :: forall r s ms. KnownSpan s
=> FullShapeTK (TKScalar r) -> AstTensor ms s (TKScalar r)
-> IO (AstVarName '(s, TKScalar r))
{-# INLINE funToAstAutoBoundsIO #-}
funToAstAutoBoundsIO ftk@FTKScalar a = do
!freshId <- unsafeGetFreshAstVarId
case knownSpan @s of
SPlainSpan | Just Refl <- testEquality (typeRep @r) (typeRep @Int)
, Just bds <- intBounds a ->
pure $! mkAstVarNameBounds bds freshId
_ -> pure $! mkAstVarName ftk freshId
funToAstNoBoundsIO :: KnownSpan s
=> FullShapeTK y -> IO (AstVarName '(s, y))
{-# INLINE funToAstNoBoundsIO #-}
funToAstNoBoundsIO ftk = do
!freshId <- unsafeGetFreshAstVarId
pure $! mkAstVarName ftk freshId
funToAstRevIO :: forall x.
FullShapeTK x
-> IO ( AstTensor AstMethodShare FullSpan x
, AstVarName '(FullSpan, x)
, AstTensor AstMethodLet FullSpan x )
{-# INLINE funToAstRevIO #-}
funToAstRevIO ftk = do
!freshId <- unsafeGetFreshAstVarId
let var :: AstVarName '(FullSpan, x)
var = mkAstVarName ftk freshId
astVarPrimal :: AstTensor AstMethodShare FullSpan x
!astVarPrimal = astVar var
astVarD :: AstTensor AstMethodLet FullSpan x
!astVarD = astVar var
return (astVarPrimal, var, astVarD)
funToAstFwdIO :: forall x.
FullShapeTK x
-> IO ( AstVarName '(FullSpan, ADTensorKind x)
, AstTensor AstMethodShare FullSpan (ADTensorKind x)
, AstTensor AstMethodShare FullSpan x
, AstVarName '(FullSpan, x)
, AstTensor AstMethodLet FullSpan x )
{-# INLINE funToAstFwdIO #-}
funToAstFwdIO ftk = do
!freshIdD <- unsafeGetFreshAstVarId
!freshId <- unsafeGetFreshAstVarId
let varPrimalD :: AstVarName '(FullSpan, ADTensorKind x)
varPrimalD = mkAstVarName (adFTK ftk) freshIdD
var :: AstVarName '(FullSpan, x)
var = mkAstVarName ftk freshId
astVarPrimalD :: AstTensor AstMethodShare FullSpan (ADTensorKind x)
!astVarPrimalD = astVar varPrimalD
astVarPrimal :: AstTensor AstMethodShare FullSpan x
!astVarPrimal = astVar var
astVarD :: AstTensor AstMethodLet FullSpan x
!astVarD = astVar var
return (varPrimalD, astVarPrimalD, astVarPrimal, var, astVarD)
funToVarsIxIOS
:: ShS sh -> (AstVarListS sh -> AstIxS ms sh -> AstTensor ms s2 z)
-> IO (AstTensor ms s2 z)
{-# INLINE funToVarsIxIOS #-}
funToVarsIxIOS sh f = withKnownShS sh $ do
let unsafeGetFreshIntVarName :: Int -> IO IntVarName
unsafeGetFreshIntVarName n = do
freshId <- unsafeGetFreshAstVarId
return $! mkAstVarNameBounds (0, n - 1) freshId
varList <- mapM unsafeGetFreshIntVarName $ shsToList sh
let !ix = fromList varList
vars = AstVarListS ix
asts = fmap astVar ix
return $! f vars asts
funToVarsIxS
:: ShS sh -> (AstVarListS sh -> AstIxS ms sh -> AstTensor ms s2 z)
-> AstTensor ms s2 z
{-# NOINLINE funToVarsIxS #-}
funToVarsIxS sh = unsafePerformIO . funToVarsIxIOS sh