fix: Go-wasm host memory writes (live mem.buffer alias) + TextDecoder DataView - #17
Closed
e-fu wants to merge 2 commits into
Closed
fix: Go-wasm host memory writes (live mem.buffer alias) + TextDecoder DataView#17e-fu wants to merge 2 commits into
e-fu wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes two interoperability gaps preventing Go (GOOS=js GOARCH=wasm) modules from running under QuickBEAM: (1) WebAssembly.Memory.buffer now live-aliases WAMR linear memory so host writes are visible to guest code, and (2) TextDecoder.decode() now accepts DataView inputs as a valid BufferSource.
Changes:
- Expose WAMR linear memory as a live
ArrayBuffervia a new__qb_wasm_memory_bufferhost function, and routeWasmMemory.bufferto it. - Add
wamr_bridge_memory_data()to return the native base pointer + size for linear memory aliasing. - Extend
TextDecoder.decode()to supportDataViewinputs by readingbuffer/byteOffset/byteLength.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| priv/ts/webassembly.ts | Switch WasmMemory.buffer from copy-based reads to a live ArrayBuffer alias via __qb_wasm_memory_buffer. |
| priv/c_src/wamr_bridge.h | Declare wamr_bridge_memory_data() to expose WAMR memory base pointer and size for aliasing. |
| priv/c_src/wamr_bridge.c | Implement wamr_bridge_memory_data() using wasm_runtime_addr_app_to_native(inst, 0). |
| lib/quickbeam/wasm_js.zig | Register and implement __qb_wasm_memory_buffer to return an external ArrayBuffer over WAMR linear memory. |
| lib/quickbeam/text_encoding.zig | Add DataView support to TextDecoder.decode() by extracting view window from its backing buffer. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+300
to
+318
| const buffer_val = qjs.JS_GetPropertyStr(ctx, input, "buffer"); | ||
| defer qjs.JS_FreeValue(ctx, buffer_val); | ||
| const offset_val = qjs.JS_GetPropertyStr(ctx, input, "byteOffset"); | ||
| defer qjs.JS_FreeValue(ctx, offset_val); | ||
| const length_val = qjs.JS_GetPropertyStr(ctx, input, "byteLength"); | ||
| defer qjs.JS_FreeValue(ctx, length_val); | ||
|
|
||
| var byte_offset_i64: i64 = 0; | ||
| var byte_len_i64: i64 = 0; | ||
| if (qjs.JS_ToInt64(ctx, &byte_offset_i64, offset_val) != 0 or byte_offset_i64 < 0 or | ||
| qjs.JS_ToInt64(ctx, &byte_len_i64, length_val) != 0 or byte_len_i64 < 0) | ||
| { | ||
| return qjs.JS_ThrowTypeError(ctx, "argument must be a BufferSource"); | ||
| } | ||
|
|
||
| var ab_size: usize = 0; | ||
| const buf_ptr = qjs.JS_GetArrayBuffer(ctx, &ab_size, buffer_val) orelse | ||
| return qjs.JS_NewString(ctx, ""); | ||
| data = @as([*]const u8, @ptrCast(buf_ptr + @as(usize, @intCast(byte_offset_i64))))[0..@intCast(byte_len_i64)]; |
Comment on lines
+296
to
+300
| } else if (qjs.JS_IsDataView(input)) { | ||
| // A DataView is a BufferSource but not a TypedArray, so | ||
| // JS_GetTypedArrayBuffer rejects it; read its view window directly. | ||
| // (Go's wasm_exec.js loadString passes a DataView here.) | ||
| const buffer_val = qjs.JS_GetPropertyStr(ctx, input, "buffer"); |
Comment on lines
+499
to
+503
| if (base == null or size == 0) return throw_error(ctx, "memory not available"); | ||
|
|
||
| // Live alias: DataView/TypedArray writes on this buffer land directly in | ||
| // WAMR linear memory, and reads observe live guest state. No copy. | ||
| return qjs.JS_NewArrayBuffer(ctx, base, size, &wasm_buffer_noop_free, null, false); |
Comment on lines
+184
to
+188
| // Live alias of WAMR linear memory: host writes via DataView/TypedArray | ||
| // propagate into the guest, and reads observe live guest state. A grow | ||
| // may move the backing store, so callers must re-read `.buffer` afterward | ||
| // (matching browser detach-on-grow semantics). | ||
| return qbWasmCall(() => __qb_wasm_memory_buffer(handle), 'memory buffer failed') as ArrayBuffer |
Contributor
Author
|
Needs more work |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two independent correctness fixes that together unblock running Go (
GOOS=js GOARCH=wasm) modules under QuickBEAM.1.
WebAssembly.Memory.buffernow live-aliases WAMR linear memoryThe
buffergetter previously returned a fresh copy of linear memory(
__qb_wasm_read_memory(...).slice().buffer). Host→guest writes throughnew DataView(mem.buffer)/new Uint8Array(mem.buffer)therefore landed in adetached copy and never reached the guest — breaking every host-import callback
that writes its return value into memory (e.g. Go's
syscall/jsshims →fatal error: nanotime returning zero).Now
mem.bufferreturns anArrayBufferthat aliases WAMR's linear memorydirectly (
JS_NewArrayBufferoverwasm_runtime_addr_app_to_native(inst, 0),no-op free since WAMR owns the storage). Writes propagate; reads observe live
state — matching browser semantics.
Isolated repro (73-byte module; host writes
123456atmem[16]via aDataView, guest reads it back):0123456✅Layers:
wamr_bridge_memory_data()(base pointer) →__qb_wasm_memory_bufferC-function (
wasm_js.zig) →buffergetter (webassembly.ts).2.
TextDecoder.decode()accepts aDataViewdecode()rejected anyDataViewwith "argument must be a BufferSource"because it routed non-
ArrayBufferinputs throughJS_GetTypedArrayBuffer,which doesn't accept a
DataView(a separate quickjs class). Per WHATWG,decode(input)takes anyBufferSource, includingDataView. Go'swasm_exec.jsloadStringpasses aDataView.Added a
JS_IsDataViewbranch that reads the view'sbuffer/byteOffset/byteLengthdirectly.Verification
Built (Linux) and exercised against the 73-byte memory repro and a 12 MB Go
signer module: the memory fix clears
nanotime returning zero, and theDataView fix lets the Go module boot fully through
syscall/jsstringmarshalling.