tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

Reactor.hs (14387B)


      1 {-# LANGUAGE ForeignFunctionInterface #-}
      2 {-# LANGUAGE ScopedTypeVariables #-}
      3 module Reactor where
      4 
      5 import qualified CompilerProbe as C
      6 import qualified BrowserRuntime as R
      7 import qualified BrowserModules as M
      8 import Data.Array (Array, (!))
      9 import Text.Read (readMaybe)
     10 import Runtime.Tree
     11 import Runtime.Value
     12 import Control.Exception (SomeException, SomeAsyncException, catch, evaluate, fromException, displayException)
     13 import Control.Monad (unless)
     14 import Data.IORef
     15 import Data.List (nub)
     16 import qualified Data.ByteString as BS
     17 import qualified Data.Text as Text
     18 import qualified Data.Text.Encoding as Text
     19 import Foreign.Ptr (Ptr, castPtr)
     20 import Foreign.C.Types (CInt(..))
     21 import Foreign.Marshal.Utils (copyBytes)
     22 import System.IO.Unsafe (unsafePerformIO)
     23 import System.Mem (performGC)
     24 import System.IO.Error (isUserError, ioeGetErrorString)
     25 
     26 {-# NOINLINE context #-}
     27 context :: IORef (Maybe (T,T))
     28 context = unsafePerformIO (newIORef Nothing)
     29 {-# NOINLINE current #-}
     30 current :: IORef [(String,T)]
     31 current = unsafePerformIO (newIORef [])
     32 {-# NOINLINE compilerLibrary #-}
     33 compilerLibrary :: IORef M.Bindings
     34 compilerLibrary = unsafePerformIO (newIORef [])
     35 {-# NOINLINE expressionBindings #-}
     36 expressionBindings :: IORef M.Bindings
     37 expressionBindings = unsafePerformIO (newIORef [])
     38 {-# NOINLINE pendingModule #-}
     39 pendingModule :: IORef (Maybe (M.Stage,[(String,M.Bindings)]))
     40 pendingModule = unsafePerformIO (newIORef Nothing)
     41 {-# NOINLINE response #-}
     42 response :: IORef BS.ByteString
     43 response = unsafePerformIO (newIORef BS.empty)
     44 
     45 -- One owned response at a time; no tree handles or Haskell heap addresses cross
     46 -- the boundary. JS copies it out before another request. Inputs are copied in.
     47 reply :: String -> IO ()
     48 reply s = do
     49   let bounded=take (16777216+1) s
     50   unless (length bounded<=16777216) (fail "response limit exceeded")
     51   let bytes=Text.encodeUtf8 (Text.pack bounded)
     52   unless (BS.length bytes<=16777216) (fail "response byte limit exceeded")
     53   _<-evaluate (BS.length bytes)
     54   writeIORef response bytes
     55 
     56 guardRequest :: IO () -> IO CInt
     57 guardRequest action = (action >> pure 0) `catch` \(e::SomeException) -> do
     58   let message=case fromException e of
     59         Just ioError | isUserError ioError -> ioeGetErrorString ioError
     60         _ -> displayException e
     61   reply ("error\n"++take 2048 message)
     62   pure (case (fromException e :: Maybe SomeAsyncException) of Just _->2;Nothing->1)
     63 
     64 input :: Ptr () -> CInt -> IO String
     65 input ptr len = do
     66   unless (len>=0 && len<=16777216) (fail "input byte limit exceeded")
     67   bytes<-BS.packCStringLen (castPtr ptr,fromIntegral len)
     68   either (fail . show) (pure . Text.unpack) (Text.decodeUtf8' bytes)
     69 
     70 initialize :: IO CInt
     71 initialize = guardRequest $ do
     72   existing<-readIORef context
     73   case existing of Just _->fail "already initialized"; Nothing->pure ()
     74   (compiler,env,library)<-C.loadLibrary "/data"
     75   writeIORef context (Just (compiler,env))
     76   writeIORef compilerLibrary library
     77   reply "ok\nready"
     78 
     79 compileSource :: Ptr () -> CInt -> IO CInt
     80 compileSource ptr len = guardRequest $ do
     81   source<-input ptr len
     82   ctx<-readIORef context
     83   (compiler,env)<-maybe (fail "not initialized") pure ctx
     84   compileWith compiler env source
     85 
     86 compileWith :: T -> T -> String -> IO ()
     87 compileWith compiler env source = do
     88   requireIdle
     89   unless(not(null source)&&length source<=16384)(fail "source scalar limit exceeded")
     90   let argument=ofString source
     91   _<-evaluate(C.force argument)
     92   defs<-case C.definitions(C.compile compiler env argument) of
     93     Left message->fail message
     94     Right values->pure values
     95   let names=map fst(take 17 defs)
     96   unless(not(null names)&&length names<=16&&all (\n->not(null n)&&length n<=128) names&&length(nub names)==length names)
     97     (fail "invalid compiler export names")
     98   reply("ok\n"++unlines names)
     99   writeIORef current defs
    100   writeIORef expressionBindings defs
    101   writeIORef pendingModule Nothing
    102   writeIORef ioResult Nothing
    103 
    104 observe :: Ptr () -> CInt -> IO CInt
    105 observe ptr len = guardRequest $ do
    106   request<-input ptr len
    107   (name,args)<-case lines request of []->fail "missing export"; n:as->pure(n,as)
    108   unless (length args<=16 && all ((<=50000).length) args) (fail "argument limit exceeded")
    109   defs<-readIORef current
    110   term<-maybe (fail "missing export") pure (lookup name defs)
    111   let result=foldl apply term (map C.fromTernary args)
    112   reply ("ok\n"++C.ternary result)
    113 
    114 dump :: IO CInt
    115 dump = guardRequest $ do
    116   defs<-readIORef current
    117   reply ("ok\n"++unlines [name++"\t"++C.ternary term | (name,term)<-defs])
    118 
    119 reset :: IO CInt
    120 reset = guardRequest $ do
    121   writeIORef current []
    122   writeIORef expressionBindings []
    123   writeIORef pendingModule Nothing
    124   writeIORef interaction Nothing
    125   writeIORef ioResult Nothing
    126   writeIORef response BS.empty
    127   performGC
    128   reply "ok\nreset"
    129 
    130 collect :: IO CInt
    131 collect = guardRequest (performGC >> reply "ok\ncollected")
    132 
    133 responseSize :: IO CInt
    134 responseSize = fromIntegral . BS.length <$> readIORef response
    135 responseCopy :: Ptr () -> CInt -> IO CInt
    136 responseCopy ptr capacity = do
    137   bytes<-readIORef response
    138   if fromIntegral capacity<BS.length bytes then pure (-1) else do
    139     BS.useAsCStringLen bytes $ \(src,len)->copyBytes ptr (castPtr src) len
    140     writeIORef response BS.empty
    141     pure (fromIntegral (BS.length bytes))
    142 
    143 
    144 {-# NOINLINE interaction #-}
    145 interaction :: IORef (Maybe (Integer,R.Pending))
    146 interaction = unsafePerformIO (newIORef Nothing)
    147 {-# NOINLINE nextToken #-}
    148 nextToken :: IORef Integer
    149 nextToken = unsafePerformIO (newIORef 0)
    150 {-# NOINLINE ioResult #-}
    151 ioResult :: IORef (Maybe T)
    152 ioResult = unsafePerformIO (newIORef Nothing)
    153 
    154 requireIdle :: IO ()
    155 requireIdle = do
    156   active<-readIORef interaction
    157   case active of Nothing->pure ();Just _->fail "IO interaction is pending"
    158 
    159 emitEvent :: R.Pending -> IO ()
    160 emitEvent pending=do
    161   token<-(+1) <$> readIORef nextToken
    162   value<-R.event token pending
    163   reply ("ok\n"++R.json value)
    164   writeIORef nextToken token
    165   case pending of
    166     R.Pending (R.Done value)->do
    167       writeIORef ioResult (Just value)
    168       writeIORef interaction Nothing
    169     _->writeIORef interaction (Just(token,pending))
    170 
    171 select :: BS.ByteString -> BS.ByteString -> [BS.ByteString] -> IO T
    172 select nameBytes dag roots=do
    173   name<-R.utf8 nameBytes
    174   defs<-readIORef current
    175   worker<-maybe (fail "missing export") pure (lookup name defs)
    176   unless(length roots<=16)(fail "argument limit exceeded")
    177   (trees,n)<-R.loadDAG dag
    178   indices<-mapM (R.index n) roots
    179   pure(foldl apply worker [trees!i | i<-indices])
    180 
    181 bindings :: Array Int T -> Int -> [BS.ByteString] -> IO [(String,T)]
    182 bindings trees n entries=do
    183   unless(length entries<=8192&&even(length entries))(fail "invalid binding table")
    184   let pairs []=pure []
    185       pairs (nameBytes:rootBytes:rest)=do
    186         name<-R.utf8 nameBytes
    187         unless(not(null name)&&length name<=4096)(fail "invalid binding name")
    188         root<-R.index n rootBytes
    189         ((name,trees!root):) <$> pairs rest
    190       pairs _=fail "invalid binding table"
    191   defs<-pairs entries
    192   unless(length(nub(map fst defs))==length defs)(fail "duplicate binding name")
    193   pure defs
    194 
    195 request :: Ptr () -> CInt -> IO CInt
    196 request ptr len = guardRequest $ do
    197   unless(len>=0 && len<=16777216)(fail "input byte limit exceeded")
    198   bytes<-BS.packCStringLen(castPtr ptr,fromIntegral len)
    199   fs<-R.fields bytes
    200   (op,arguments)<-case fs of
    201     tag:rest->do name<-R.utf8 tag;pure(name,rest)
    202     _->fail "missing operation"
    203   case (op,arguments) of
    204     ("module-header",[sourceBytes])->do
    205       requireIdle
    206       initialized<-readIORef context
    207       case initialized of Nothing->fail "compiler not initialized"; Just _->pure ()
    208       library<-readIORef compilerLibrary
    209       source<-R.utf8 sourceBytes
    210       staged<-M.stage library source
    211       reply("ok\n"++R.json(M.stageJSON staged))
    212     ("module-stage",[sourceBytes])->do
    213       requireIdle
    214       writeIORef current []
    215       writeIORef expressionBindings []
    216       writeIORef pendingModule Nothing
    217       writeIORef ioResult Nothing
    218       initialized<-readIORef context
    219       case initialized of Nothing->fail "compiler not initialized"; Just _->pure ()
    220       library<-readIORef compilerLibrary
    221       source<-R.utf8 sourceBytes
    222       staged<-M.stage library source
    223       reply("ok\n"++R.json(M.stageJSON staged))
    224       writeIORef pendingModule (Just (staged,[]))
    225     ("module-dependency",refBytes:dag:entries)->do
    226       requireIdle
    227       saved<-readIORef pendingModule
    228       (staged,modules)<-maybe (fail "No pending module") pure saved
    229       ref<-R.utf8 refBytes
    230       unless(length ref==64 && all (`elem` ("0123456789abcdef"::String)) ref && not(ref `elem` map fst modules))
    231         (fail "Invalid or duplicate module reference")
    232       (trees,n)<-R.loadDAG dag
    233       values<-bindings trees n entries
    234       writeIORef pendingModule (Just(staged,modules++[(ref,values)]))
    235       reply "ok\nloaded"
    236     ("module-compile",[])->do
    237       requireIdle
    238       saved<-readIORef pendingModule
    239       (staged,modules)<-maybe (fail "No pending module") pure saved
    240       ctx<-readIORef context
    241       (compiler,_)<-maybe (fail "compiler not initialized") pure ctx
    242       library<-readIORef compilerLibrary
    243       (public,scope)<-M.compile library compiler staged modules
    244       reply("ok\n"++unlines(map fst public))
    245       writeIORef current public
    246       writeIORef expressionBindings scope
    247       writeIORef pendingModule Nothing
    248     ("module-scope",[])->do
    249       scope<-readIORef expressionBindings
    250       reply("ok\n"++R.json(R.A(map (R.S . fst) scope)))
    251     ("module-abort",[])->do
    252       requireIdle
    253       writeIORef current []
    254       writeIORef expressionBindings []
    255       writeIORef pendingModule Nothing
    256       writeIORef ioResult Nothing
    257       reply "ok\naborted"
    258     ("compile",sourceBytes:dag:entries)->do
    259       requireIdle
    260       ctx<-readIORef context
    261       (compiler,_)<-maybe (fail "compiler not initialized") pure ctx
    262       source<-R.utf8 sourceBytes
    263       (trees,n)<-R.loadDAG dag
    264       defs<-bindings trees n entries
    265       compileWith compiler (ofList [Fork (ofString name) tree | (name,tree)<-defs]) source
    266     -- Compile against existing named trees without serializing, evaluating or
    267     -- replacing them. The temporary result lives only in this call/IO session.
    268     ("expression",[nameBytes,sourceBytes,modeBytes,codec])->do
    269       requireIdle
    270       ctx<-readIORef context
    271       (compiler,_)<-maybe (fail "compiler not initialized") pure ctx
    272       name<-R.utf8 nameBytes
    273       source<-R.utf8 sourceBytes
    274       mode<-R.utf8 modeBytes
    275       kind<-R.utf8 codec
    276       unless(not(null source)&&length source<=16384)(fail "source scalar limit exceeded")
    277       defs<-readIORef expressionBindings
    278       let env=ofList [Fork (ofString key) tree | (key,tree)<-defs]
    279       values<-either fail pure (C.definitions(C.compile compiler env (ofString source)))
    280       term<-case values of
    281         [(key,value)] | key==name->pure value
    282         _->fail "Enter one expression, not a source unit"
    283       case mode of
    284         "pure"->do
    285           value<-R.view kind term
    286           reply("ok\n"++R.json value)
    287         "io"->do
    288           phase<-R.beginIO term
    289           emitEvent(R.Pending phase)
    290           writeIORef ioResult Nothing
    291         _->fail "invalid expression mode"
    292     ("load",dag:entries)->do
    293       requireIdle
    294       (trees,n)<-R.loadDAG dag
    295       unless(not(null entries)&&length entries<=8192&&even(length entries))(fail "invalid export table")
    296       defs<-bindings trees n entries
    297       reply("ok\n"++R.json(R.A(map (R.S . fst) defs)))
    298       writeIORef current defs
    299       writeIORef expressionBindings defs
    300       writeIORef pendingModule Nothing
    301       writeIORef ioResult Nothing
    302     ("evaluate",name:codec:dag:roots)->do
    303       kind<-R.utf8 codec
    304       term<-select name dag roots
    305       value<-R.view kind term
    306       reply("ok\n"++R.json value)
    307     ("preview",name:limit:dag:roots)->do
    308       size<-R.index 257 limit
    309       term<-select name dag roots
    310       value<-R.preview size term
    311       reply("ok\n"++R.json value)
    312     ("module-snapshot",[])->do
    313       requireIdle
    314       defs<-readIORef current
    315       unless(not(null defs) && length defs<=4096)(fail "No compiled module")
    316       value<-R.preview 100000 (foldr (Fork . snd) Leaf defs)
    317       reply("ok\n"++R.json(R.O [("names",R.A(map (R.S . fst) defs)),("snapshot",value)]))
    318     ("snapshot",name:limit:dag:roots)->do
    319       size<-R.index 100001 limit
    320       term<-select name dag roots
    321       value<-R.preview size term
    322       reply("ok\n"++R.json value)
    323     ("io-start",name:dag:roots)->do
    324       requireIdle
    325       term<-select name dag roots
    326       phase<-R.beginIO term
    327       emitEvent(R.Pending phase)
    328       writeIORef ioResult Nothing
    329     ("io-resume",tokenBytes:valueBytes:[])->do
    330       tokenText<-R.utf8 tokenBytes
    331       supplied<-maybe (fail "invalid resume token") pure (readMaybe tokenText)
    332       saved<-readIORef interaction
    333       (token,pending)<-maybe (fail "no IO interaction") pure saved
    334       unless(supplied==token)(fail "stale IO resume token")
    335       value<-R.utf8 valueBytes
    336       next<-R.resumeIO pending value
    337       emitEvent next
    338     ("io-cancel",[])->do
    339       writeIORef interaction Nothing
    340       writeIORef ioResult Nothing
    341       reply("ok\n"++R.json(R.O[("kind",R.S "cancelled")]))
    342     ("io-snapshot",[limit])->do
    343       saved<-readIORef ioResult
    344       result<-maybe (fail "no completed IO result") pure saved
    345       size<-R.index 100001 limit
    346       value<-R.preview size result
    347       reply("ok\n"++R.json value)
    348     ("io-view",[codec])->do
    349       saved<-readIORef ioResult
    350       result<-maybe (fail "no completed IO result") pure saved
    351       kind<-R.utf8 codec
    352       value<-R.view kind result
    353       reply("ok\n"++R.json value)
    354     _->fail "invalid operation fields"
    355 
    356 initializeEmpty :: IO CInt
    357 initializeEmpty = guardRequest (reply "ok\nready")
    358 
    359 foreign export ccall "tricu_request" request :: Ptr () -> CInt -> IO CInt
    360 foreign export ccall "tricu_initialize_empty" initializeEmpty :: IO CInt
    361 
    362 foreign export ccall "tricu_initialize" initialize :: IO CInt
    363 foreign export ccall "tricu_compile" compileSource :: Ptr () -> CInt -> IO CInt
    364 foreign export ccall "tricu_observe" observe :: Ptr () -> CInt -> IO CInt
    365 foreign export ccall "tricu_dump" dump :: IO CInt
    366 foreign export ccall "tricu_reset" reset :: IO CInt
    367 foreign export ccall "tricu_collect" collect :: IO CInt
    368 foreign export ccall "tricu_response_size" responseSize :: IO CInt
    369 foreign export ccall "tricu_response_copy" responseCopy :: Ptr () -> CInt -> IO CInt