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 ()
|