synthesizer-llvm-0.2: src/Synthesizer/LLVM/Causal/Process.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module Synthesizer.LLVM.Causal.Process where
import qualified Synthesizer.LLVM.Simple.Signal as Sig
import qualified LLVM.Extra.Representation as Rep
import qualified Synthesizer.LLVM.Sample as Sample
import qualified Synthesizer.LLVM.Execution as Exec
import qualified LLVM.Extra.MaybeContinuation as Maybe
-- import qualified LLVM.Extra.Control as U
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
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 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 Data.Tuple.HT (swap, )
import NumericPrelude.Numeric
import NumericPrelude.Base hiding (and, map, zip, zipWith, )
data T a b =
forall state packed size ioContext.
(Rep.Memory 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 ::
(Rep.Memory 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 a -> T () a
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 ::
(Rep.Memory 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)
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 =
first (fromSignal sig) <<^ (\b -> ((),b))
feedSnd :: Sig.T a -> T b (b,a)
feedSnd sig =
swap ^<< feedFst 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
mix ::
(IsArithmetic a) =>
T (Value a, Value a) (Value a)
mix = map (uncurry Sample.mixMono)
mixStereo ::
(IsArithmetic a) =>
T (Stereo.T (Value a), Stereo.T (Value a)) (Stereo.T (Value a))
mixStereo = map (uncurry Sample.mixStereo)
envelope ::
(IsArithmetic a) =>
T (Value a, Value a) (Value a)
envelope = map (uncurry Sample.amplifyMono)
envelopeStereo ::
(IsArithmetic a) =>
T (Value a, Stereo.T (Value a)) (Stereo.T (Value a))
envelopeStereo = map (uncurry Sample.amplifyStereo)
amplify ::
(IsArithmetic a, IsConst a) =>
a -> T (Value a) (Value a)
amplify x =
map (Sample.amplifyMono (valueOf x))
amplifyStereo ::
(IsArithmetic a, IsConst a) =>
a -> T (Stereo.T (Value a)) (Stereo.T (Value a))
amplifyStereo x =
map (Sample.amplifyStereo (valueOf x))
applyStorable ::
(Storable a, MakeValueTuple a valueA, Rep.Memory valueA structA,
Storable b, MakeValueTuple b valueB, Rep.Memory 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 $
createFunction ExternalLinkage $ \ size alPtr blPtr -> do
s <- start ioContext
(pos,_) <- Maybe.arrayLoop2 size alPtr blPtr s $
\ aPtri bPtri s0 -> do
a <- Maybe.lift $ Rep.load aPtri
(b,s1) <- next ioContext a s0
Maybe.lift $ Rep.store b bPtri
return s1
ret (pos :: Value Word32)
fmap (fromIntegral :: Word32 -> Int) $
fill (fromIntegral len)
(Rep.castStorablePtr aPtr)
(Rep.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 ::
(Rep.Memory aValue aStruct,
Rep.Memory bValue bStruct,
Rep.Memory 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 (,,)
(createFunction ExternalLinkage $
do
-- FIXME: size computation in LLVM currently does not work for structs!
pptr <- Rep.malloc
flip Rep.store pptr =<< start
ret pptr)
(createFunction ExternalLinkage $
\ pptr -> Rep.free pptr >> ret ())
(createFunction ExternalLinkage $
\ sptr loopLen aPtr bPtr -> do
sInit <- Rep.load sptr
(pos,sExit) <- Maybe.arrayLoop2 loopLen aPtr bPtr sInit $
\ aPtri bPtri s0 -> do
a <- Maybe.lift $ Rep.load aPtri
(b,s1) <- next a s0
Maybe.lift $ Rep.store b bPtri
return s1
Rep.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, Rep.Memory valueA structA,
Storable b, MakeValueTuple b valueB, Rep.Memory 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 <- Rep.newForeignPtr (deleteIOContext ioContext) False
return $ \sig -> SVL.fromChunks $ unsafePerformIO $ do
statePtr <- Rep.newForeignPtrInit 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)
(Rep.castStorablePtr aPtr) .
Rep.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, Rep.Memory valueA structA,
Storable b, MakeValueTuple b valueB, Rep.Memory 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 <- Rep.newForeignPtrInit 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 <- Rep.newForeignPtr (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)
(Rep.castStorablePtr aPtr) .
Rep.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)