Matthew Pickering pushed to branch wip/wasm-bytearray-marshall at Glasgow Haskell Compiler / GHC
Commits:
-
96d12714
by Matthew Pickering at 2025-06-09T09:58:59+01:00
3 changed files:
- libraries/ghc-experimental/ghc-experimental.cabal.in
- + libraries/ghc-experimental/src/GHC/Wasm/Uint8Array.hs
- testsuite/tests/jsffi/textconv.hs
Changes:
| ... | ... | @@ -40,6 +40,7 @@ library |
| 40 | 40 | Prelude.Experimental
|
| 41 | 41 | if arch(wasm32)
|
| 42 | 42 | exposed-modules: GHC.Wasm.Prim
|
| 43 | + GHC.Wasm.Uint8Array
|
|
| 43 | 44 | other-extensions:
|
| 44 | 45 | build-depends: base >=4.20 && < 4.22,
|
| 45 | 46 | ghc-internal == @ProjectVersionForLib@.*
|
| 1 | +{-# LANGUAGE MagicHash #-}
|
|
| 2 | +{-# LANGUAGE UnboxedTuples #-}
|
|
| 3 | +-- | Utilities to marshall a javascript 'Uint8Array' to a 'ByteArray'
|
|
| 4 | +module GHC.Wasm.Uint8Array
|
|
| 5 | + ( toJSUint8Array, fromJSUint8Array, JSUint8Array(..), byteLength
|
|
| 6 | + ) where
|
|
| 7 | + |
|
| 8 | +import Data.Array.Byte
|
|
| 9 | +import GHC.Wasm.Prim
|
|
| 10 | +import GHC.Ptr
|
|
| 11 | +import GHC.Exts
|
|
| 12 | +import System.IO.Unsafe
|
|
| 13 | +import GHC.IO
|
|
| 14 | + |
|
| 15 | +newtype JSUint8Array = JSUint8Array { unJSUint8Array :: JSVal }
|
|
| 16 | + |
|
| 17 | +foreign import javascript unsafe
|
|
| 18 | + "$1.byteLength"
|
|
| 19 | + byteLength :: JSUint8Array -> Int
|
|
| 20 | + |
|
| 21 | +withByteArrayContents :: ByteArray -> (Ptr () -> Int -> IO a) -> IO a
|
|
| 22 | +withByteArrayContents (ByteArray arr#) f = IO $ \s0 ->
|
|
| 23 | + keepAlive# arr# s0 (unIO $ f (Ptr (byteArrayContents# arr#)) (I# (sizeofByteArray# arr#) ))
|
|
| 24 | + |
|
| 25 | +fromJSUint8ArrayLen :: Int -> JSUint8Array -> ByteArray
|
|
| 26 | +fromJSUint8ArrayLen (I# len) arr = unsafePerformIO $ IO $
|
|
| 27 | + \s -> case newByteArray# len s of
|
|
| 28 | + (# s', mb #) ->
|
|
| 29 | + let a = mutableByteArrayContents# mb
|
|
| 30 | + in case unIO (memorySetUint8Array (Ptr a) (I# len) arr) s' of
|
|
| 31 | + (# s'', () #) -> case unsafeFreezeByteArray# mb s'' of
|
|
| 32 | + (# s''', arr' #) -> (# s''', ByteArray arr' #)
|
|
| 33 | + |
|
| 34 | +toJSUint8Array :: ByteArray -> JSUint8Array
|
|
| 35 | +toJSUint8Array ba =
|
|
| 36 | + unsafePerformIO $ withByteArrayContents ba uint8ArrayFromMemory
|
|
| 37 | + |
|
| 38 | +fromJSUint8Array :: JSUint8Array -> ByteArray
|
|
| 39 | +fromJSUint8Array src_buf = fromJSUint8ArrayLen (byteLength src_buf) src_buf
|
|
| 40 | + |
|
| 41 | +foreign import javascript unsafe
|
|
| 42 | + "(new Uint8Array(__exports.memory.buffer, $1, $2)).set($3)"
|
|
| 43 | + memorySetUint8Array :: Ptr a -> Int -> JSUint8Array -> IO ()
|
|
| 44 | + |
|
| 45 | +foreign import javascript unsafe
|
|
| 46 | + "new Uint8Array(new Uint8Array(__exports.memory.buffer, $1, $2))"
|
|
| 47 | + uint8ArrayFromMemory :: Ptr a -> Int -> IO JSUint8Array
|
|
| 48 | + |
| ... | ... | @@ -14,36 +14,24 @@ import qualified Data.Text.IO.Utf8 as T |
| 14 | 14 | import GHC.Exts
|
| 15 | 15 | import GHC.IO
|
| 16 | 16 | import GHC.Wasm.Prim
|
| 17 | - |
|
| 18 | -newtype JSUint8Array = JSUint8Array JSVal
|
|
| 17 | +import GHC.Wasm.Uint8Array
|
|
| 19 | 18 | |
| 20 | 19 | foreign import javascript unsafe "(new TextEncoder()).encode($1)"
|
| 21 | - js_str_encode :: JSString -> IO JSUint8Array
|
|
| 22 | - |
|
| 23 | -foreign import javascript unsafe "$1.byteLength"
|
|
| 24 | - js_buf_len :: JSUint8Array -> IO Int
|
|
| 25 | - |
|
| 26 | -foreign import javascript unsafe "(new Uint8Array(__exports.memory.buffer, $2, $1.byteLength)).set($1)"
|
|
| 27 | - js_from_buf :: JSUint8Array -> Ptr a -> IO ()
|
|
| 20 | + js_str_encode :: JSString -> JSUint8Array
|
|
| 28 | 21 | |
| 29 | -foreign import javascript unsafe "(new TextDecoder('utf-8', {fatal: true})).decode(new Uint8Array(__exports.memory.buffer, $1, $2))"
|
|
| 30 | - js_to_str :: Ptr a -> Int -> IO JSString
|
|
| 22 | +foreign import javascript unsafe "(new TextDecoder('utf-8', {fatal: true})).decode($1)"
|
|
| 23 | + js_to_str :: JSUint8Array -> JSString
|
|
| 31 | 24 | |
| 32 | 25 | textFromJSString :: JSString -> Text
|
| 33 | -textFromJSString str = unsafeDupablePerformIO $ do
|
|
| 34 | - buf <- js_str_encode str
|
|
| 35 | - I# len# <- js_buf_len buf
|
|
| 36 | - IO $ \s0 -> case newByteArray# len# s0 of
|
|
| 37 | - (# s1, mba# #) -> case unIO (js_from_buf buf (Ptr (mutableByteArrayContents# mba#))) s1 of
|
|
| 38 | - (# s2, _ #) -> case unIO (freeJSVal (coerce buf)) s2 of
|
|
| 39 | - (# s3, _ #) -> case unsafeFreezeByteArray# mba# s3 of
|
|
| 40 | - (# s4, ba# #) -> (# s4, Text (ByteArray ba#) 0 (I# len#) #)
|
|
| 26 | +textFromJSString str =
|
|
| 27 | + let buf = js_str_encode str
|
|
| 28 | + in Text (fromJSUint8Array buf) 0 (byteLength buf)
|
|
| 41 | 29 | |
| 30 | +-- NOTE: Do not copy this into your program, you need to take into account the
|
|
| 31 | +-- offset to do this converson properly
|
|
| 42 | 32 | textToJSString :: Text -> JSString
|
| 43 | -textToJSString (Text (ByteArray ba#) (I# off#) (I# len#)) = unsafeDupablePerformIO $
|
|
| 44 | - IO $ \s0 -> case newPinnedByteArray# len# s0 of
|
|
| 45 | - (# s1, mba# #) -> case copyByteArray# ba# off# mba# 0# len# s1 of
|
|
| 46 | - s2 -> keepAlive# mba# s2 $ unIO $ js_to_str (Ptr (mutableByteArrayContents# mba#)) $ I# len#
|
|
| 33 | +textToJSString (Text ba 0 len) = js_to_str (toJSUint8Array ba)
|
|
| 34 | +textToJSString _ = error "non-zero offset not supported"
|
|
| 47 | 35 | |
| 48 | 36 | foreign export javascript "main sync"
|
| 49 | 37 | main :: IO ()
|