EmbeddingCore.hs (10825B)
1 {-# LANGUAGE ForeignFunctionInterface #-} 2 {-# LANGUAGE ScopedTypeVariables #-} 3 -- Shared core boundary. Imports only the reducer, value codecs and registry dependencies. 4 module EmbeddingCore where 5 6 import Control.Concurrent.MVar 7 import Control.Exception 8 import Control.Monad (unless) 9 import Control.Monad.State.Strict 10 import Data.IORef 11 import qualified Data.Map.Strict as M 12 import qualified Data.Map.Lazy as L 13 import qualified Data.ByteString as BS 14 import qualified Data.Text as Text 15 import qualified Data.Text.Encoding as Text 16 import Data.Word 17 import Foreign hiding (void, toBool) 18 import Foreign.C.Types 19 import System.IO.Unsafe (unsafePerformIO) 20 import Runtime.Tree 21 import Runtime.Value hiding (toBytes) 22 import qualified Runtime.Value as Value 23 24 -- A process-wide nonreusing namespace covers contexts AND roots. No raw stable 25 -- pointer crosses the boundary; released, foreign and stale IDs are map misses. 26 {-# NOINLINE identities #-} 27 identities :: IORef Integer 28 identities = unsafePerformIO (newIORef 1) 29 {-# NOINLINE contexts #-} 30 contexts :: MVar (M.Map Word64 (MVar (Maybe Context))) 31 contexts = unsafePerformIO (newMVar M.empty) 32 -- Force map updates while keeping their tree values lazy: release removes the 33 -- root now, and application publication preserves the suspended computation. 34 data Context = Context { roots :: !(L.Map Word64 T), lastError :: !BS.ByteString } 35 data Fault = Fault Word32 String deriving Show 36 instance Exception Fault 37 38 boundaryStatus :: SomeException -> IO Word32 39 boundaryStatus e = pure $ case fromException e of 40 Just (Fault code _) -> code 41 Nothing -> 5 42 43 type Call = StateT Context IO 44 fault :: Word32 -> String -> IO a 45 fault code message = throwIO (Fault code message) 46 require :: Bool -> String -> IO () 47 require condition message = unless condition (fault 3 message) 48 newIdentity :: IO Word64 49 newIdentity = do 50 n <- atomicModifyIORef' identities (\n -> (n+1,n)) 51 if n > toInteger (maxBound :: Word64) then fault 6 "identity namespace exhausted" 52 else pure (fromInteger n) 53 54 -- Context locking serializes lazy evaluation as well as registry changes. 55 -- Destroy marks the cell closed before removing its name; concurrent lookups 56 -- respect that closed state. Exceptions roll back root-table publication. 57 -- IO effects and evaluation already performed on shared Haskell thunks persist. 58 withContext :: Word64 -> Call (IO ()) -> IO Word32 59 withContext identity action = handle boundaryStatus $ mask $ \restore -> do 60 found <- withMVar contexts (pure . M.lookup identity) 61 case found of 62 Nothing -> pure 1 63 Just cell -> modifyMVar cell $ \current -> case current of 64 Nothing -> pure (Nothing, 1) 65 Just context -> do 66 result <- try $ do 67 (commit, updated) <- restore (runStateT action context) 68 commit 69 pure updated 70 case result of 71 Right updated -> pure (Just updated, 0) 72 Left (e :: SomeException) -> do 73 let (code, message) = case fromException e of 74 Just (Fault c m) -> (c,m) 75 Nothing -> (5, displayException e) 76 -- Force bounded diagnostic bytes inside the exception handler. 77 let bytes = Text.encodeUtf8 (Text.pack (take 4096 message)) 78 _ <- evaluate (BS.length bytes) 79 pure (Just context {lastError = bytes}, code) 80 81 lookupRoot :: Word64 -> Call T 82 lookupRoot identity = do 83 table <- gets roots 84 maybe (liftIO (fault 2 "invalid or foreign handle")) pure (L.lookup identity table) 85 publish :: Ptr Word64 -> T -> Call (IO ()) 86 publish out tree = do 87 liftIO (require (out /= nullPtr) "null output") 88 identity <- liftIO newIdentity 89 modify' (\c -> c { roots = L.insert identity tree (roots c) }) 90 pure (poke out identity) 91 readBytes :: Ptr Word8 -> CSize -> IO BS.ByteString 92 readBytes ptr len = do 93 require (toInteger len <= toInteger (maxBound :: Int)) "buffer too large" 94 require (ptr /= nullPtr || len == 0) "null input buffer" 95 if len == 0 then pure BS.empty else BS.packCStringLen (castPtr ptr, fromIntegral len) 96 checked :: Word32 -> Either String a -> IO a 97 checked code = either (fault code) pure 98 readText :: Ptr Word8 -> CSize -> IO Text.Text 99 readText ptr len = readBytes ptr len >>= checked 3 . either (Left . show) Right . Text.decodeUtf8' 100 outputBuffer :: Ptr (Ptr Word8) -> Ptr CSize -> BS.ByteString -> IO () 101 outputBuffer out len bytes = do 102 require (out /= nullPtr && len /= nullPtr && castPtr out /= len) "invalid buffer outputs" 103 -- Allocate even for empty output, giving every successful buffer one owner. 104 bracketOnError (mallocBytes (max 1 (BS.length bytes))) free $ \ptr -> do 105 BS.useAsCStringLen bytes $ \(source,n) -> copyBytes ptr (castPtr source) n 106 poke out ptr 107 poke len (fromIntegral (BS.length bytes)) 108 109 abiVersion :: IO Word32 110 abiVersion = pure 1 111 contextNew :: Ptr Word64 -> IO Word32 112 contextNew out = handle boundaryStatus $ mask_ $ do 113 if out == nullPtr then pure 3 else do 114 identity <- newIdentity 115 cell <- newMVar (Just (Context L.empty BS.empty)) 116 modifyMVar_ contexts (pure . M.insert identity cell) 117 poke out identity 118 pure 0 119 contextFree :: Word64 -> IO Word32 120 contextFree identity = handle boundaryStatus $ mask_ $ do 121 found <- withMVar contexts (pure . M.lookup identity) 122 case found of 123 Nothing -> pure 1 124 Just cell -> do 125 status <- modifyMVar cell $ \current -> pure (Nothing, maybe 1 (const 0) current) 126 modifyMVar_ contexts (pure . M.delete identity) 127 pure status 128 retain :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 129 retain ctx value out = withContext ctx (lookupRoot value >>= publish out) 130 release :: Word64 -> Word64 -> IO Word32 131 release ctx value = withContext ctx $ do 132 _ <- lookupRoot value 133 modify' (\c -> c {roots = L.delete value (roots c)}) 134 pure (pure ()) 135 leaf :: Word64 -> Ptr Word64 -> IO Word32 136 leaf ctx out = withContext ctx (publish out Leaf) 137 stem :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 138 stem ctx value out = withContext ctx (lookupRoot value >>= publish out . Stem) 139 binary :: (T -> T -> T) -> Word64 -> Word64 -> Word64 -> Ptr Word64 -> IO Word32 140 binary constructor ctx left right out = withContext ctx $ do 141 a <- lookupRoot left 142 b <- lookupRoot right 143 publish out (constructor a b) 144 fork, application :: Word64 -> Word64 -> Word64 -> Ptr Word64 -> IO Word32 145 fork = binary Fork 146 application = binary apply 147 reduce :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 148 reduce ctx value out = withContext ctx $ do 149 liftIO (require (out /= nullPtr) "null output") 150 tree <- lookupRoot value >>= liftIO . evaluate 151 publish out tree 152 kind :: Word64 -> Word64 -> Ptr Word32 -> IO Word32 153 kind ctx value out = withContext ctx $ do 154 liftIO (require (out /= nullPtr) "null output") 155 tree <- lookupRoot value 156 tag <- liftIO $ evaluate $ case tree of Leaf -> 0; Stem _ -> 1; Fork _ _ -> 2 157 pure (poke out tag) 158 child :: Word64 -> Word64 -> Word32 -> Ptr Word64 -> IO Word32 159 child ctx value index out = withContext ctx $ do 160 tree <- lookupRoot value 161 case (tree,index) of 162 (Stem a,0) -> publish out a 163 (Fork a _,0) -> publish out a 164 (Fork _ b,1) -> publish out b 165 _ -> liftIO (fault 4 "constructor has no such child") 166 fromU64 :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 167 fromU64 ctx n out = withContext ctx (publish out (ofNumber (toInteger n))) 168 toBool :: Word64 -> Word64 -> Ptr Word32 -> IO Word32 169 toBool ctx value out = withContext ctx $ do 170 liftIO (require (out /= nullPtr) "null output") 171 tree <- lookupRoot value 172 tag <- case tree of 173 Leaf -> pure 0 174 Stem Leaf -> pure 1 175 _ -> liftIO (fault 4 "invalid boolean") 176 pure (poke out tag) 177 toU64 :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 178 toU64 ctx value out = withContext ctx $ do 179 liftIO (require (out /= nullPtr) "null output") 180 tree <- lookupRoot value 181 n <- liftIO (checked 4 (toNumber tree)) 182 liftIO $ do 183 require (n <= toInteger (maxBound :: Word64)) "number exceeds uint64" 184 pure (poke out (fromInteger n)) 185 fromBytes, fromUtf8 :: Word64 -> Ptr Word8 -> CSize -> Ptr Word64 -> IO Word32 186 fromBytes ctx ptr len out = withContext ctx $ do 187 bytes <- liftIO (readBytes ptr len) 188 publish out (ofBytes bytes) 189 fromUtf8 ctx ptr len out = withContext ctx $ do 190 text <- liftIO (readText ptr len) 191 publish out (ofString (Text.unpack text)) 192 toBytes, toUtf8 :: Word64 -> Word64 -> Ptr (Ptr Word8) -> Ptr CSize -> IO Word32 193 toBytes ctx value out len = withContext ctx $ do 194 tree <- lookupRoot value 195 bytes <- liftIO (checked 4 (toBytesValue tree)) 196 _ <- liftIO (evaluate (BS.length bytes)) 197 pure (outputBuffer out len bytes) 198 where toBytesValue = Value.toBytes 199 toUtf8 ctx value out len = withContext ctx $ do 200 tree <- lookupRoot value 201 text <- liftIO (checked 4 (toString tree)) 202 let bytes = Text.encodeUtf8 (Text.pack text) 203 _ <- liftIO (evaluate (BS.length bytes)) 204 pure (outputBuffer out len bytes) 205 lastErrorBytes :: Word64 -> Ptr (Ptr Word8) -> Ptr CSize -> IO Word32 206 lastErrorBytes ctx out len = withContext ctx $ do 207 bytes <- gets lastError 208 pure (outputBuffer out len bytes) 209 210 foreign export ccall "tricu_abi_version" abiVersion :: IO Word32 211 foreign export ccall "tricu_context_new" contextNew :: Ptr Word64 -> IO Word32 212 foreign export ccall "tricu_context_free" contextFree :: Word64 -> IO Word32 213 foreign export ccall "tricu_retain" retain :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 214 foreign export ccall "tricu_release" release :: Word64 -> Word64 -> IO Word32 215 foreign export ccall "tricu_leaf" leaf :: Word64 -> Ptr Word64 -> IO Word32 216 foreign export ccall "tricu_stem" stem :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 217 foreign export ccall "tricu_fork" fork :: Word64 -> Word64 -> Word64 -> Ptr Word64 -> IO Word32 218 foreign export ccall "tricu_apply" application :: Word64 -> Word64 -> Word64 -> Ptr Word64 -> IO Word32 219 foreign export ccall "tricu_reduce" reduce :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 220 foreign export ccall "tricu_kind" kind :: Word64 -> Word64 -> Ptr Word32 -> IO Word32 221 foreign export ccall "tricu_child" child :: Word64 -> Word64 -> Word32 -> Ptr Word64 -> IO Word32 222 foreign export ccall "tricu_from_u64" fromU64 :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 223 foreign export ccall "tricu_to_bool" toBool :: Word64 -> Word64 -> Ptr Word32 -> IO Word32 224 foreign export ccall "tricu_to_u64" toU64 :: Word64 -> Word64 -> Ptr Word64 -> IO Word32 225 foreign export ccall "tricu_from_bytes" fromBytes :: Word64 -> Ptr Word8 -> CSize -> Ptr Word64 -> IO Word32 226 foreign export ccall "tricu_from_utf8" fromUtf8 :: Word64 -> Ptr Word8 -> CSize -> Ptr Word64 -> IO Word32 227 foreign export ccall "tricu_to_bytes" toBytes :: Word64 -> Word64 -> Ptr (Ptr Word8) -> Ptr CSize -> IO Word32 228 foreign export ccall "tricu_to_utf8" toUtf8 :: Word64 -> Word64 -> Ptr (Ptr Word8) -> Ptr CSize -> IO Word32 229 foreign export ccall "tricu_last_error" lastErrorBytes :: Word64 -> Ptr (Ptr Word8) -> Ptr CSize -> IO Word32