synthesizer-llvm-1.0: src/Synthesizer/LLVM/Generator/Render.hs
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module Synthesizer.LLVM.Generator.Render where
import qualified Synthesizer.LLVM.Causal.Parameterized as Parameterized
import qualified Synthesizer.LLVM.Generator.Source as Source
import qualified Synthesizer.LLVM.Storable.ChunkIterator as ChunkIt
import qualified Synthesizer.LLVM.Storable.LazySizeIterator as SizeIt
import qualified Synthesizer.LLVM.EventIterator as EventIt
import Synthesizer.LLVM.Generator.Private (T(Cons))
import qualified Synthesizer.LLVM.Frame.Stereo as Stereo
import qualified Synthesizer.LLVM.Storable.Vector as SVU
import qualified Synthesizer.LLVM.ForeignPtr as ForeignPtr
import qualified Synthesizer.LLVM.ConstantPiece as Const
import qualified Synthesizer.PiecewiseConstant.Signal as PC
import qualified Synthesizer.Causal.Class as CausalClass
import qualified LLVM.DSL.Execution as Exec
import qualified LLVM.DSL.Expression as Expr
import LLVM.DSL.Expression (Exp(Exp))
import qualified LLVM.Extra.Multi.Value.Storable as Storable
import qualified LLVM.Extra.Multi.Value.Marshal as Marshal
import qualified LLVM.Extra.Multi.Value as MultiValue
import qualified LLVM.Extra.Memory as Memory
import qualified LLVM.Extra.MaybeContinuation as MaybeCont
import qualified LLVM.Extra.Maybe as Maybe
import qualified LLVM.Extra.Control as C
import qualified LLVM.Extra.Arithmetic as A
import qualified LLVM.Extra.Tuple as Tuple
import qualified LLVM.Core as LLVM
import qualified Type.Data.Num.Decimal as TypeNum
import qualified Data.StorableVector.Lazy as SVL
import qualified Data.StorableVector.Base as SVB
import qualified Data.StorableVector as SV
import qualified Data.EventList.Relative.BodyTime as EventList
import qualified Numeric.NonNegative.Wrapper as NonNeg
import qualified Numeric.NonNegative.Chunky as NonNegChunky
import Control.Monad (join)
import Control.Applicative (liftA3)
import Foreign.ForeignPtr (touchForeignPtr)
import Foreign.Ptr (Ptr)
import Data.Foldable (traverse_)
import Data.Tuple.Strict (mapPair, mapTriple)
import Data.Word (Word, Word8, Word32)
import qualified System.Unsafe as Unsafe
foreign import ccall safe "dynamic" derefFillPtr ::
Exec.Importer (LLVM.Ptr param -> Word -> Ptr struct -> IO Word)
compile ::
(Storable.C a, MultiValue.T a ~ value,
Marshal.C param, Marshal.Struct param ~ paramStruct) =>
(Exp param -> T value) ->
IO (LLVM.Ptr paramStruct -> Word -> Ptr a -> IO Word)
compile sig =
Exec.compile "signal" $
Exec.createFunction derefFillPtr "fill" $ \paramPtr size bPtr ->
case sig (Exp (Memory.load paramPtr)) of
Cons next start stop -> do
(global,s) <- start
local <- LLVM.alloca
(pos,_) <- Storable.arrayLoopMaybeCont size bPtr s $ \ ptri s0 -> do
(y,s1) <- next global local s0
MaybeCont.lift $ Storable.store y ptri
return s1
stop global
return pos
runAux ::
(Marshal.C p, Storable.C a, MultiValue.T a ~ value) =>
(Exp p -> T value) -> IO (IO () -> Int -> p -> IO (SV.Vector a))
runAux sig = do
fill <- compile sig
return $ \final len param ->
Marshal.with param $ \paramPtr ->
SVB.createAndTrim len $ \ptr -> do
n <- fill paramPtr (fromIntegral len) ptr
final
return $ fromIntegral n
run_ ::
(Marshal.C p, Storable.C a, MultiValue.T a ~ value) =>
(Exp p -> T value) -> IO (Int -> p -> IO (SV.Vector a))
run_ = fmap ($ return ()) . runAux
foreign import ccall safe "dynamic" derefStartPtr ::
Exec.Importer (LLVM.Ptr param -> IO (LLVM.Ptr globalState))
foreign import ccall safe "dynamic" derefStopPtr ::
Exec.Importer (LLVM.Ptr globalState -> IO ())
foreign import ccall safe "dynamic" derefChunkPtr ::
Exec.Importer (LLVM.Ptr globalState -> Word -> Ptr a -> IO Word)
type MemoryPtr a = LLVM.Ptr (Memory.Struct a)
type WithGlobalState param = LLVM.Struct (param, ())
type Pair a b = LLVM.Struct (a,(b,()))
type Triple a b c = LLVM.Struct (a,(b,(c,())))
tripleStruct ::
(LLVM.IsSized a, LLVM.IsSized b, LLVM.IsSized c) =>
LLVM.Value a -> LLVM.Value b -> LLVM.Value c ->
LLVM.CodeGenFunction r (LLVM.Value (Triple a b c))
tripleStruct a b c = do
s0 <- LLVM.insertvalue Tuple.undef a TypeNum.d0
s1 <- LLVM.insertvalue s0 b TypeNum.d1
LLVM.insertvalue s1 c TypeNum.d2
{- |
This is a pretty ugly hack, but its seems to be the least ugly one.
We need to solve the following problem:
We have a function of type @Exp param -> T value@.
This means that all methods in @T value@ depend on @Exp param@.
We need to choose one piece of LLVM code in @Exp param@
that generates appropriate code for all methods in @T value@.
If we access a function parameter via @Memory.load paramPtr@
this means that all methods must end up in the same LLVM function
in order to access this parameter.
Thus I have to put all functionality in one LLVM function
and then the three functions in 'compileChunky'
jump into the handler function with a 'Word8' code
specifying the actual sub-routine.
We need to squeeze all possible inputs and outputs
through one function interface.
However, since the handler is marked as internal
the optimizer inlines it in the three functions from 'compileChunky'
and eliminates dead code.
This way, we end up with the code that we would have written otherwise.
The alternative would be to construct @T value@ multiple times.
Due to existential quantification we cannot prove
that the pointer types of different methods match,
so we need to cast pointers.
However, with the current approach we also have to do that.
-}
compileHandler ::
(Marshal.C param, Marshal.Struct param ~ paramStruct,
Storable.C a, MultiValue.T a ~ value) =>
(Exp param -> T value) ->
LLVM.CodeGenModule
(LLVM.Function
(Word8 -> LLVM.Ptr paramStruct -> Word -> Ptr a ->
IO (Pair (LLVM.Ptr (WithGlobalState paramStruct)) Word)))
compileHandler sig =
LLVM.createNamedFunction LLVM.InternalLinkage "handlesignal" $
\phase paramPtr loopLen bufferPtr ->
case sig $ Exp (Memory.load paramPtr) of
Cons next start stop -> do
paramGlobalStatePtr <- LLVM.bitcast paramPtr
let create = do
newParamGlobalStatePtr <- LLVM.malloc
(global,state) <- start
flip LLVM.store newParamGlobalStatePtr =<<
join
(liftA3 tripleStruct
(LLVM.load paramPtr)
(Memory.compose global)
(Memory.compose state))
newOpaqueParamGlobalStatePtr <-
LLVM.bitcast
(newParamGlobalStatePtr `asTypeOf` paramGlobalStatePtr)
LLVM.insertvalue Tuple.undef
newOpaqueParamGlobalStatePtr TypeNum.d0
let delete = do
globalPtr <-
LLVM.getElementPtr0 paramGlobalStatePtr (TypeNum.d1, ())
stop =<< Memory.load globalPtr
LLVM.free paramGlobalStatePtr
return Tuple.undef
let fill = do
globalPtr <-
LLVM.getElementPtr0 paramGlobalStatePtr (TypeNum.d1, ())
statePtr <-
LLVM.getElementPtr0 paramGlobalStatePtr (TypeNum.d2, ())
global <- Memory.load globalPtr
sInit <- Memory.load statePtr
local <- LLVM.alloca
(pos,sExit) <-
Storable.arrayLoopMaybeCont loopLen bufferPtr sInit $
\ ptr s0 -> do
(y,s1) <- next global local s0
MaybeCont.lift $ Storable.store y ptr
return s1
Memory.store (Maybe.fromJust sExit) statePtr
LLVM.insertvalue Tuple.undef pos TypeNum.d1
doCreate <- A.cmp LLVM.CmpEQ (LLVM.valueOf 0) phase
doDelete <- A.cmp LLVM.CmpEQ (LLVM.valueOf 1) phase
C.ret =<<
(C.ifThenElse doCreate create $
C.ifThenElse doDelete delete fill)
compileChunky ::
(LLVM.IsSized paramStruct, LLVM.Value (LLVM.Ptr paramStruct) ~ pPtr,
Memory.C state, Memory.Struct state ~ stateStruct,
Memory.C global, Memory.Struct global ~ globalStruct,
Triple paramStruct globalStruct stateStruct ~ triple,
LLVM.IsSized local,
Storable.C a, MultiValue.T a ~ value) =>
(forall r z. (Tuple.Phi z) =>
pPtr -> global -> LLVM.Value (LLVM.Ptr local) ->
() -> state -> MaybeCont.T r z (value, state)) ->
(forall r. pPtr -> LLVM.CodeGenFunction r (global, state)) ->
(forall r. pPtr -> global -> LLVM.CodeGenFunction r ()) ->
IO (LLVM.Ptr paramStruct -> IO (LLVM.Ptr triple),
Exec.Finalizer triple,
LLVM.Ptr triple -> Word -> Ptr a -> IO Word)
compileChunky next start stop =
Exec.compile "signal-chunky" $
liftA3 (,,)
(Exec.createFunction derefStartPtr "startsignal" $
\paramPtr -> do
paramGlobalStatePtr <- LLVM.malloc
(global,state) <- start paramPtr
flip LLVM.store paramGlobalStatePtr =<<
join
(liftA3 tripleStruct
(LLVM.load paramPtr)
(Memory.compose global)
(Memory.compose state))
return paramGlobalStatePtr)
(Exec.createFinalizer derefStopPtr "stopsignal" $
\paramGlobalStatePtr -> do
paramPtr <-
LLVM.getElementPtr0 paramGlobalStatePtr (TypeNum.d0, ())
stop paramPtr =<<
Memory.load =<<
LLVM.getElementPtr0 paramGlobalStatePtr (TypeNum.d1, ())
LLVM.free paramGlobalStatePtr)
(Exec.createFunction derefChunkPtr "fillsignal" $
\paramGlobalStatePtr loopLen ptr -> do
paramPtr <-
LLVM.getElementPtr0 paramGlobalStatePtr (TypeNum.d0, ())
global <-
Memory.load =<<
LLVM.getElementPtr0 paramGlobalStatePtr (TypeNum.d1, ())
statePtr <-
LLVM.getElementPtr0 paramGlobalStatePtr (TypeNum.d2, ())
sInit <- Memory.load statePtr
local <- LLVM.alloca
(pos,sExit) <-
Storable.arrayLoopMaybeCont loopLen ptr sInit $
\ ptri s0 -> do
(y,s1) <- next paramPtr global local () s0
MaybeCont.lift $ Storable.store y ptri
return s1
Memory.store (Maybe.fromJust sExit) statePtr
return pos)
runChunkyAux ::
(Storable.C a, MultiValue.T a ~ value, Marshal.C p) =>
(Exp p -> T value) -> IO (IO () -> SVL.ChunkSize -> p -> IO (SVL.Vector a))
runChunkyAux sig = do
paramd <-
Parameterized.fromProcessPtr "Signal.run" (CausalClass.fromSignal . sig)
case paramd of
Parameterized.Cons next start stop -> do
(startFunc,stopFunc,fill) <- compileChunky next start stop
return $ \final (SVL.ChunkSize size) p -> do
statePtr <- ForeignPtr.newParamMV stopFunc startFunc p
let go =
Unsafe.interleaveIO $ do
v <-
ForeignPtr.with statePtr $ \sptr ->
SVB.createAndTrim size $
fmap (fromIntegral :: Word -> Int) .
fill sptr (fromIntegral size)
(if SV.length v > 0
then fmap (v:)
else id) $
(if SV.length v < size
then final >> return []
else go)
fmap SVL.fromChunks go
runChunky ::
(Storable.C a, MultiValue.T a ~ value, Marshal.C p) =>
(Exp p -> T value) -> IO (SVL.ChunkSize -> p -> IO (SVL.Vector a))
runChunky = fmap ($ return ()) . runChunkyAux
runChunkyOnVector ::
(Storable.C a, MultiValue.T a ~ al) =>
(Storable.C b, MultiValue.T b ~ bl) =>
(T al -> T bl) ->
IO (SVL.ChunkSize -> SV.Vector a -> IO (SVL.Vector b))
runChunkyOnVector sig = do
f <- runChunkyAux (sig . Source.storableVector)
return $ \chunkSize av -> do
let (fp,ptr,l) = SVU.unsafeToPointers av
f (touchForeignPtr fp) chunkSize (Source.consStorableVector ptr l)
class Run f where
type DSL f
type Shape f
build ::
(Marshal.C p) =>
(Exp p -> DSL f) -> IO (IO (p, IO ()) -> Shape f -> f)
instance (Storable.C a) => Run (SVL.Vector a) where
type DSL (SVL.Vector a) = T (MultiValue.T a)
type Shape (SVL.Vector a) = SVL.ChunkSize
build =
fmap (\f create shape -> Unsafe.performIO $ buildIOGen f create shape) .
runChunkyAux
instance (Storable.C a) => Run (SV.Vector a) where
type DSL (SV.Vector a) = T (MultiValue.T a)
type Shape (SV.Vector a) = Int
build =
fmap (\f create shape -> Unsafe.performIO $ buildIOGen f create shape) .
runAux
instance (RunIO a) => Run (IO a) where
type DSL (IO a) = T (DSL_IO a)
type Shape (IO a) = ShapeIO a
build = buildIO
instance (RunArg a, Run f) => Run (a -> f) where
type DSL (a -> f) = DSLArg a -> DSL f
type Shape (a -> f) = Shape f
build sig =
case buildArg of
BuildArg pass createA -> do
f <- build (Expr.uncurry $ \p -> sig p . pass)
return $ \createP shape av ->
f (do (p,finalP) <- createP
(pa,finalA) <- createA av
return ((p,pa), finalA >> finalP))
shape
class RunIO a where
type DSL_IO a
type ShapeIO a
buildIO ::
(Marshal.C p) =>
(Exp p -> T (DSL_IO a)) -> IO (IO (p, IO ()) -> ShapeIO a -> IO a)
instance (Storable.C a) => RunIO (SVL.Vector a) where
type DSL_IO (SVL.Vector a) = MultiValue.T a
type ShapeIO (SVL.Vector a) = SVL.ChunkSize
buildIO = fmap buildIOGen . runChunkyAux
instance (Storable.C a) => RunIO (SV.Vector a) where
type DSL_IO (SV.Vector a) = MultiValue.T a
type ShapeIO (SV.Vector a) = Int
buildIO = fmap buildIOGen . runAux
buildIOGen ::
(Monad m) => (final -> shape -> p -> m a) -> m (p, final) -> shape -> m a
buildIOGen f create shape = do (p,final) <- create; f final shape p
data BuildArg a =
forall al. Marshal.C al =>
BuildArg (Exp al -> DSLArg a) (a -> IO (al, IO ()))
class RunArg a where
type DSLArg a
buildArg :: BuildArg a
instance RunArg () where
type DSLArg () = ()
buildArg = BuildArg (\ _unit -> ()) (\() -> return ((), return ()))
instance (RunArg a, RunArg b) => RunArg (a,b) where
type DSLArg (a,b) = (DSLArg a, DSLArg b)
buildArg =
case (buildArg,buildArg) of
(BuildArg passA createA, BuildArg passB createB) ->
BuildArg
(mapPair (passA,passB) . Expr.unzip)
(\(a,b) -> do
(pa,finalA) <- createA a
(pb,finalB) <- createB b
return ((pa,pb), finalB>>finalA))
instance (RunArg a, RunArg b, RunArg c) => RunArg (a,b,c) where
type DSLArg (a,b,c) = (DSLArg a, DSLArg b, DSLArg c)
buildArg =
case (buildArg,buildArg,buildArg) of
(BuildArg passA createA, BuildArg passB createB,
BuildArg passC createC) ->
BuildArg
(mapTriple (passA,passB,passC) . Expr.unzip3)
(\(a,b,c) -> do
(pa,finalA) <- createA a
(pb,finalB) <- createB b
(pc,finalC) <- createC c
return ((pa,pb,pc), finalC>>finalB>>finalA))
primitiveArg :: (Marshal.C a, DSLArg a ~ Exp a) => BuildArg a
primitiveArg = BuildArg id (\a -> return (a, return ()))
instance RunArg Float where
type DSLArg Float = Exp Float
buildArg = primitiveArg
instance RunArg Int where
type DSLArg Int = Exp Int
buildArg = primitiveArg
instance RunArg Word where
type DSLArg Word = Exp Word
buildArg = primitiveArg
instance RunArg Word32 where
type DSLArg Word32 = Exp Word32
buildArg = primitiveArg
instance (RunArg a) => RunArg (Stereo.T a) where
type DSLArg (Stereo.T a) = Stereo.T (DSLArg a)
buildArg =
case buildArg of
BuildArg pass create ->
BuildArg
(fmap pass . Stereo.unExpression)
(\s -> do
pf <- traverse create s
return (fst<$>pf, traverse_ snd pf))
instance
(TypeNum.Natural n, Marshal.C a, LLVM.IsSized (Marshal.Struct a),
TypeNum.Natural (n TypeNum.:*: LLVM.SizeOf (Marshal.Struct a))) =>
RunArg (MultiValue.Array n a) where
type DSLArg (MultiValue.Array n a) = Exp (MultiValue.Array n a)
buildArg = primitiveArg
instance (Storable.C a) => RunArg (SV.Vector a) where
type DSLArg (SV.Vector a) = T (MultiValue.T a)
buildArg =
BuildArg
Source.storableVector
(\av -> do
let (fp,ptr,l) = SVU.unsafeToPointers av
return (Source.consStorableVector ptr l, touchForeignPtr fp))
newtype Buffer a = Buffer (SV.Vector a)
buffer :: SV.Vector a -> Buffer a
buffer = Buffer
instance (Storable.C a) => RunArg (Buffer a) where
type DSLArg (Buffer a) = Exp (Source.StorableVector a)
buildArg =
BuildArg id
(\(Buffer av) -> do
let (fp,ptr,l) = SVU.unsafeToPointers av
return (Source.consStorableVector ptr l, touchForeignPtr fp))
newDisposeArg ::
(Marshal.C handle) =>
(a -> IO handle) -> (handle -> IO ()) ->
(Exp handle -> DSLArg a) -> BuildArg a
newDisposeArg new dispose fetch =
BuildArg fetch
(\x -> do
it <- new x
return (it, dispose it))
instance (Storable.C a) => RunArg (SVL.Vector a) where
type DSLArg (SVL.Vector a) = T (MultiValue.T a)
buildArg =
newDisposeArg ChunkIt.new ChunkIt.dispose Source.storableVectorLazy
class TimeInteger int where
subdivideLong :: EventList.T (NonNeg.T int) a -> EventList.T NonNeg.Int a
instance TimeInteger Int where
subdivideLong = id
instance TimeInteger Integer where
subdivideLong = PC.subdivideLongStrict
instance
(time ~ NonNeg.T int, TimeInteger int, Marshal.C a) =>
RunArg (EventList.T time a) where
type DSLArg (EventList.T time a) = T (Const.T (MultiValue.T a))
buildArg =
newDisposeArg
(EventIt.new . subdivideLong) EventIt.dispose Source.eventList
instance (a ~ SVL.ChunkSize) => RunArg (NonNegChunky.T a) where
type DSLArg (NonNegChunky.T a) = T (Const.T ())
buildArg =
newDisposeArg SizeIt.new SizeIt.dispose Source.lazySize
{-
do f <- run (\n -> takeWhile (<*n) (iterate (1+) 0) <> takeWhile (<*n) (iterate (2+) 0)); f SVL.defaultChunkSize (12::Float) :: IO (SVL.Vector Float)
do f <- Sig.run (\n -> Sig.takeWhile (Expr.<*n) (Sig.iterate (1+) 0) <> Sig.takeWhile (Expr.<*n) (Sig.iterate (2+) 0)); f SVL.defaultChunkSize (12::Float) :: IO (SVL.Vector Float)
-}
run :: (Run f) => DSL f -> IO (Shape f -> f)
run sig = do
act <- build (const sig)
return $ act (return ((), return ()))