synthesizer-llvm-0.3: src/Synthesizer/LLVM/Causal/Process.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module Synthesizer.LLVM.Causal.Process where
import qualified Synthesizer.LLVM.Simple.Signal as Sig
import qualified Synthesizer.LLVM.Frame as Frame
import qualified Synthesizer.LLVM.Execution as Exec
import qualified LLVM.Extra.Arithmetic as A
import qualified LLVM.Extra.MaybeContinuation as Maybe
import qualified LLVM.Extra.ForeignPtr as ForeignPtr
import qualified LLVM.Extra.Memory as Memory
-- import qualified LLVM.Extra.Control as U
import LLVM.Extra.Class (MakeValueTuple, )
import qualified Data.StorableVector.Lazy as SVL
import qualified Data.StorableVector as SV
import qualified Data.StorableVector.Base as SVB
import qualified Synthesizer.LLVM.Frame.Stereo as Stereo
import LLVM.Core as LLVM
import LLVM.Util.Loop (Phi, )
import LLVM.ExecutionEngine (simpleFunction, )
import qualified Control.Arrow as Arr
import qualified Control.Category as Cat
import Control.Arrow ((<<<), (^<<), (&&&), )
import Control.Monad (liftM2, liftM3, )
import Control.Applicative (Applicative, pure, (<*>), )
import Data.Word (Word32, )
import Foreign.Storable (Storable, )
import Foreign.ForeignPtr (withForeignPtr, touchForeignPtr, )
import Foreign.Ptr (FunPtr, )
import Control.Exception (bracket, )
import System.IO.Unsafe (unsafePerformIO, unsafeInterleaveIO, )
import qualified Number.Ratio as Ratio
import qualified Algebra.Field as Field
import qualified Algebra.Ring as Ring
import qualified Algebra.Additive as Additive
import NumericPrelude.Numeric
import NumericPrelude.Base hiding (and, map, zip, zipWith, )
data T a b =
forall state packed size ioContext.
(Memory.C state packed, IsSized packed size) =>
Cons (forall r c.
(Phi c) =>
ioContext ->
a -> state -> Maybe.T r c (b, state))
-- compute next value
(forall r.
ioContext ->
CodeGenFunction r state)
-- initial state
(IO ioContext)
-- initialization from IO monad
(ioContext -> IO ())
-- finalization from IO monad
simple ::
(Memory.C state packed, IsSized packed size) =>
(forall r c.
(Phi c) =>
a -> state -> Maybe.T r c (b, state)) ->
(forall r. CodeGenFunction r state) ->
T a b
simple next start =
Cons
(const next)
(const start)
(return ())
(const $ return ())
toSignal :: T () a -> Sig.T a
toSignal (Cons next start createIOContext deleteIOContext) = Sig.Cons
(\ioContext -> next ioContext ())
start
createIOContext deleteIOContext
fromSignal :: Sig.T b -> T a b
fromSignal (Sig.Cons next start createIOContext deleteIOContext) = Cons
(\ioContext _ -> next ioContext)
start
createIOContext deleteIOContext
map ::
(forall r. a -> CodeGenFunction r b) ->
T a b
map f =
mapAccum (\a s -> fmap (flip (,) s) $ f a) (return ())
mapAccum ::
(Memory.C state packed, IsSized packed size) =>
(forall r.
a -> state -> CodeGenFunction r (b, state)) ->
(forall r. CodeGenFunction r state) ->
T a b
mapAccum next =
simple (\a s -> Maybe.lift $ next a s)
zipWith ::
(forall r. a -> b -> CodeGenFunction r c) ->
T (a,b) c
zipWith f = map (uncurry f)
apply ::
T a b -> Sig.T a -> Sig.T b
apply proc sig =
toSignal (proc <<< fromSignal sig)
feedFst :: Sig.T a -> T b (a,b)
feedFst sig =
fromSignal sig &&& Cat.id
feedSnd :: Sig.T a -> T b (b,a)
feedSnd sig =
Cat.id &&& fromSignal sig
applyFst :: T (a,b) c -> Sig.T a -> T b c
applyFst proc sig =
proc <<< feedFst sig
applySnd :: T (a,b) c -> Sig.T b -> T a c
applySnd proc sig =
proc <<< feedSnd sig
compose :: T a b -> T b c -> T a c
compose
(Cons nextA startA createIOContextA deleteIOContextA)
(Cons nextB startB createIOContextB deleteIOContextB) = Cons
(\(ioContextA, ioContextB) a (sa0,sb0) -> do
(b,sa1) <- nextA ioContextA a sa0
(c,sb1) <- nextB ioContextB b sb0
return (c, (sa1,sb1)))
(\(ioContextA, ioContextB) ->
liftM2 (,)
(startA ioContextA)
(startB ioContextB))
(liftM2 (,)
createIOContextA
createIOContextB)
(\(ca,cb) ->
deleteIOContextA ca >>
deleteIOContextB cb)
first :: T b c -> T (b, d) (c, d)
first (Cons next start createIOContext deleteIOContext) = Cons
(\ioContext (b,d) sa0 ->
fmap
(\(c,sa1) -> ((c,d), sa1))
(next ioContext b sa0))
start
createIOContext deleteIOContext
instance Cat.Category T where
id = map return
(.) = flip compose
instance Arr.Arrow T where
arr f = map (return . f)
first = first
instance Functor (T a) where
fmap = (^<<)
instance Applicative (T a) where
pure x = Arr.arr (const x)
f <*> x = uncurry ($) ^<< f&&&x
instance (A.Additive b) => Additive.C (T a b) where
zero = pure A.zero
negate x = map A.neg <<< x
x + y = zipWith A.add <<< x&&&y
x - y = zipWith A.sub <<< x&&&y
instance (A.PseudoRing b, A.IntegerConstant b) => Ring.C (T a b) where
one = pure A.one
fromInteger n = pure (A.fromInteger' n)
x * y = zipWith A.mul <<< x&&&y
instance (A.Field b, A.RationalConstant b) => Field.C (T a b) where
fromRational' x = pure (A.fromRational' $ Ratio.toRational98 x)
x / y = zipWith A.fdiv <<< x&&&y
mix ::
(A.Additive a) =>
T (a, a) a
mix = zipWith Frame.mix
envelope ::
(A.PseudoRing a) =>
T (a, a) a
envelope = zipWith Frame.amplifyMono
envelopeStereo ::
(A.PseudoRing a) =>
T (a, Stereo.T a) (Stereo.T a)
envelopeStereo = zipWith Frame.amplifyStereo
amplify ::
(IsArithmetic a, IsConst a) =>
a -> T (Value a) (Value a)
amplify x =
map (Frame.amplifyMono (valueOf x))
amplifyStereo ::
(IsArithmetic a, IsConst a) =>
a -> T (Stereo.T (Value a)) (Stereo.T (Value a))
amplifyStereo x =
map (Frame.amplifyStereo (valueOf x))
applyStorable ::
(Storable a, MakeValueTuple a valueA, Memory.C valueA structA,
Storable b, MakeValueTuple b valueB, Memory.C valueB structB) =>
T valueA valueB -> SV.Vector a -> SV.Vector b
applyStorable (Cons next start createIOContext deleteIOContext) as =
unsafePerformIO $
bracket createIOContext deleteIOContext $ \ ioContext ->
SVB.withStartPtr as $ \ aPtr len ->
SVB.createAndTrim len $ \ bPtr -> do
fill <-
simpleFunction $
createNamedFunction ExternalLinkage "fillprocessblock" $ \ size alPtr blPtr -> do
s <- start ioContext
(pos,_) <- Maybe.arrayLoop2 size alPtr blPtr s $
\ aPtri bPtri s0 -> do
a <- Maybe.lift $ Memory.load aPtri
(b,s1) <- next ioContext a s0
Maybe.lift $ Memory.store b bPtri
return s1
ret (pos :: Value Word32)
fmap (fromIntegral :: Word32 -> Int) $
fill (fromIntegral len)
(Memory.castStorablePtr aPtr)
(Memory.castStorablePtr bPtr)
foreign import ccall safe "dynamic" derefStartPtr ::
Exec.Importer (IO (Ptr stateStruct))
foreign import ccall safe "dynamic" derefStopPtr ::
Exec.Importer (Ptr stateStruct -> IO ())
foreign import ccall safe "dynamic" derefChunkPtr ::
Exec.Importer (Ptr stateStruct -> Word32 ->
Ptr aStruct -> Ptr bStruct -> IO Word32)
compileChunky ::
(Memory.C aValue aStruct,
Memory.C bValue bStruct,
Memory.C state stateStruct,
IsSized stateStruct stateSize) =>
(forall r.
aValue -> state ->
Maybe.T r (Value Bool, (Value (Ptr bStruct), state)) (bValue, state)) ->
(forall r.
CodeGenFunction r state) ->
IO (FunPtr (IO (Ptr stateStruct)),
FunPtr (Ptr stateStruct -> IO ()),
FunPtr (Ptr stateStruct -> Word32 -> Ptr aStruct -> Ptr bStruct -> IO Word32))
compileChunky next start =
Exec.compileModule $
liftM3 (,,)
(createNamedFunction ExternalLinkage "startprocess" $
do
pptr <- LLVM.malloc
flip Memory.store pptr =<< start
ret pptr)
(createNamedFunction ExternalLinkage "stopprocess" $
\ pptr -> LLVM.free pptr >> ret ())
(createNamedFunction ExternalLinkage "fillprocess" $
\ sptr loopLen aPtr bPtr -> do
sInit <- Memory.load sptr
(pos,sExit) <- Maybe.arrayLoop2 loopLen aPtr bPtr sInit $
\ aPtri bPtri s0 -> do
a <- Maybe.lift $ Memory.load aPtri
(b,s1) <- next a s0
Maybe.lift $ Memory.store b bPtri
return s1
Memory.store sExit sptr
ret (pos :: Value Word32))
{-# DEPRECATED runStorableChunky "this function will not work when the process itself depends on a lazy storable vector" #-}
{- |
This function will not work as expected,
since feeding a lazy storable vector to the causal process
means that createIOContext creates a StablePtr to an IORef refering to a chunk list.
The IORef will be created once for all uses of the generated function
of type @(SVL.Vector a -> SVL.Vector b)@.
This means that the pointer into the chunks list will conflict.
An alternative would be to create the StablePtr in a foreign function
that calls back to Haskell.
But this way is disallowed for foreign finalizers.
-}
runStorableChunky ::
(Storable a, MakeValueTuple a valueA, Memory.C valueA structA,
Storable b, MakeValueTuple b valueB, Memory.C valueB structB) =>
T valueA valueB -> IO (SVL.Vector a -> SVL.Vector b)
runStorableChunky
(Cons next start createIOContext deleteIOContext) = do
ioContext <- createIOContext
(startFunc, stopFunc, fill) <-
compileChunky (next ioContext) (start ioContext)
{-
This is a dummy pointer, that we need for correct finalization.
Concerning the live time the FunPtr 'fill' also has the live time
that we are after,
but it is unsafe to treat a FunPtr as a Ptr or ForeignPtr.
-}
ioContextPtr <- ForeignPtr.new (deleteIOContext ioContext) False
return $ \sig -> SVL.fromChunks $ unsafePerformIO $ do
statePtr <- ForeignPtr.newInit stopFunc startFunc
let go xt =
unsafeInterleaveIO $
case xt of
[] -> return []
x:xs -> SVB.withStartPtr x $ \aPtr size -> do
v <-
withForeignPtr statePtr $ \sptr ->
SVB.createAndTrim size $
fmap (fromIntegral :: Word32 -> Int) .
derefChunkPtr fill sptr (fromIntegral size)
(Memory.castStorablePtr aPtr) .
Memory.castStorablePtr
touchForeignPtr ioContextPtr
(if SV.length v > 0
then fmap (v:)
else id) $
(if SV.length v < size
then return []
else go xs)
go (SVL.chunks sig)
applyStorableChunky ::
(Storable a, MakeValueTuple a valueA, Memory.C valueA structA,
Storable b, MakeValueTuple b valueB, Memory.C valueB structB) =>
T valueA valueB -> SVL.Vector a -> SVL.Vector b
applyStorableChunky
(Cons next start createIOContext deleteIOContext) sig =
SVL.fromChunks $ unsafePerformIO $ do
ioContext <- createIOContext
(startFunc, stopFunc, fill) <-
compileChunky (next ioContext) (start ioContext)
statePtr <- ForeignPtr.newInit stopFunc startFunc
{-
This is a dummy pointer, that we need for correct finalization.
Concerning the live time the FunPtr 'fill' also has the live time
that we are after,
but it is unsafe to treat a FunPtr as a Ptr or ForeignPtr.
-}
ioContextPtr <- ForeignPtr.new (deleteIOContext ioContext) False
let go xt =
unsafeInterleaveIO $
case xt of
[] -> return []
x:xs -> SVB.withStartPtr x $ \aPtr size -> do
v <-
withForeignPtr statePtr $ \sptr ->
SVB.createAndTrim size $
fmap (fromIntegral :: Word32 -> Int) .
derefChunkPtr fill sptr (fromIntegral size)
(Memory.castStorablePtr aPtr) .
Memory.castStorablePtr
touchForeignPtr ioContextPtr
(if SV.length v > 0
then fmap (v:)
else id) $
(if SV.length v < size
then return []
else go xs)
go (SVL.chunks sig)