{-# LANGUAGE GHC2021 #-} {-# LANGUAGE MagicHash #-} {-# LANGUAGE UnboxedTuples #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE UnliftedNewtypes #-} module GHC.Internal.Wasm.Prim.Types ( JSVal# (..), JSVal (..), freeJSVal, JSString (..), fromJSString, toJSString, JSException (..), WouldBlockException (..), PromisePendingException (..) ) where import GHC.Internal.Base import GHC.Internal.Exception.Type import GHC.Internal.Exts import GHC.Internal.Foreign.C.String.Encoding import GHC.Internal.ForeignPtr import GHC.Internal.IO import GHC.Internal.IO.Encoding import GHC.Internal.Num import GHC.Internal.Show import GHC.Internal.Stable {- Note [JSVal representation for wasm] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ On wasm, the Haskell heap lives in the linear memory space, and it can only contain bit patterns, not opaque references of the host JavaScript heap. As long as we have two heaps that coexist in this way, the best we can do is representing JavaScript references as unique ids in the Haskell heap. In JavaScript, we have a JSValManager which exposes some interfaces as wasm imports. The JSValManager is in charge of allocating unique ids and managing the mapping from ids to the actual JavaScript values. In fact we can implement the entire JSValManager in wasm, using a wasm table with externref elements to hold the JavaScript values and a special allocator to manage free slots in the table. That'll take more work to implement though, with one more caveat: browsers typically limit max wasm table size to 10000000 which may not be large enough for some use cases. We can workaround the table size restriction by managing a pool or tree of wasm tables, but at this point we really should ditch the idea of doing everything in wasm just because we can. Next, we have the unlifted JSVal# type, defined in jsval.cmm and contains one non-pointer word which is the id allocated by JSValManager. On top of JSVal#, we have the user-facing lifted JSVal type, which carries the JSVal#, as well as a weak pointer and a stable pointer. The weak pointer is used to garbage collect JSVals. Its key is the JSVal# closure, and it has a C finalizer that tells the JSValManager to drop the mapping when the JSVal# closure is collected. Since we want to provide freeJSVal to allow eager freeing of JSVals, we need to carry it as a field of JSVal. The stable pointer field is NULL for normal JSVals created via foreign import results or foreign export arguments. But for JSFFI dynamic exports that wraps a Haskell function closure as a JavaScript callback and returns that callback's JSVal, it is a stable pointer that pins that Haskell function closure. If this JSVal is garbage collected, then we can only rely on a JavaScript FinalizerRegistry to free the stable pointer in the future, but if we eagerly free the callback with freeJSVal, then we can eagerly free this stable pointer as well. The lifted JSVal type is meant to be an abstract type. Its creation and consumption is mainly handled by the RTS API functions rts_mkJSVal and rts_getJSVal, which are used in C stub files generated when desugaring JSFFI foreign imports/exports. -} newtype JSVal# = JSVal# (Any :: UnliftedType) data JSVal = forall a . JSVal JSVal# (Weak# JSVal#) (StablePtr# a) freeJSVal :: JSVal -> IO () freeJSVal v@(JSVal _ w sp) = do case sp `eqStablePtr#` unsafeCoerce# nullAddr# of 0# -> do js_callback_unregister v freeStablePtr $ StablePtr sp _ -> pure () IO $ \s0 -> case finalizeWeak# w s0 of (# s1, _, _ #) -> (# s1, () #) foreign import javascript unsafe "if (!__ghc_wasm_jsffi_finalization_registry.unregister($1)) { throw new WebAssembly.RuntimeError('js_callback_unregister'); }" js_callback_unregister :: JSVal -> IO () newtype JSString = JSString JSVal fromJSString :: JSString -> String fromJSString s = unsafeDupablePerformIO $ do l <- js_stringLength s fp <- mallocPlainForeignPtrBytes $ l * 3 withForeignPtr fp $ \buf -> do l' <- js_encodeInto s buf $ l * 3 peekCStringLen utf8 (buf, l') foreign import javascript unsafe "$1.length" js_stringLength :: JSString -> IO Int foreign import javascript unsafe "(new TextEncoder()).encodeInto($1, new Uint8Array(__exports.memory.buffer, $2, $3)).written" js_encodeInto :: JSString -> Ptr a -> Int -> IO Int toJSString :: String -> JSString toJSString s = unsafeDupablePerformIO $ withCStringLen utf8 s $ \(buf, len) -> js_toJSString buf len foreign import javascript unsafe "(new TextDecoder('utf-8', {fatal: true})).decode(new Uint8Array(__exports.memory.buffer, $1, $2))" js_toJSString :: Ptr a -> Int -> IO JSString newtype JSException = JSException JSVal instance Show JSException where showsPrec p e = showParen (p >= 11) $ showString "JSException " . showsPrec 11 (jsErrorString e) jsErrorString :: JSException -> String jsErrorString e = unsafeDupablePerformIO $ do s@(JSString v) <- js_errorToString e r <- evaluate $ fromJSString s freeJSVal v pure r foreign import javascript unsafe "`${$1.stack ? $1.stack : $1}`" js_errorToString :: JSException -> IO JSString instance Exception JSException data WouldBlockException = WouldBlockException deriving (Show) instance Exception WouldBlockException data PromisePendingException = PromisePendingException deriving (Show) instance Exception PromisePendingException