Skip to content

fix: Go-wasm host memory writes (live mem.buffer alias) + TextDecoder DataView - #17

Closed
e-fu wants to merge 2 commits into
elixir-volt:masterfrom
ZenHive:upstream-go-wasm-memory
Closed

fix: Go-wasm host memory writes (live mem.buffer alias) + TextDecoder DataView#17
e-fu wants to merge 2 commits into
elixir-volt:masterfrom
ZenHive:upstream-go-wasm-memory

Conversation

@e-fu

@e-fu e-fu commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Two independent correctness fixes that together unblock running Go (GOOS=js GOARCH=wasm) modules under QuickBEAM.

1. WebAssembly.Memory.buffer now live-aliases WAMR linear memory

The buffer getter previously returned a fresh copy of linear memory
(__qb_wasm_read_memory(...).slice().buffer). Host→guest writes through
new DataView(mem.buffer) / new Uint8Array(mem.buffer) therefore landed in a
detached copy and never reached the guest — breaking every host-import callback
that writes its return value into memory (e.g. Go's syscall/js shims →
fatal error: nanotime returning zero).

Now mem.buffer returns an ArrayBuffer that aliases WAMR's linear memory
directly (JS_NewArrayBuffer over wasm_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 123456 at mem[16] via a
DataView, guest reads it back):

  • before: guest reads 0
  • after: guest reads 123456

Layers: wamr_bridge_memory_data() (base pointer) → __qb_wasm_memory_buffer
C-function (wasm_js.zig) → buffer getter (webassembly.ts).

A memory.grow may move the backing store; callers must re-read .buffer
after a grow (browser detach-on-grow). Go's wasm_exec.js already does this.

2. TextDecoder.decode() accepts a DataView

decode() rejected any DataView with "argument must be a BufferSource"
because it routed non-ArrayBuffer inputs through JS_GetTypedArrayBuffer,
which doesn't accept a DataView (a separate quickjs class). Per WHATWG,
decode(input) takes any BufferSource, including DataView. Go's
wasm_exec.js loadString passes a DataView.

Added a JS_IsDataView branch that reads the view's
buffer/byteOffset/byteLength directly.

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 the
DataView fix lets the Go module boot fully through syscall/js string
marshalling.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ArrayBuffer via a new __qb_wasm_memory_buffer host function, and route WasmMemory.buffer to it.
  • Add wamr_bridge_memory_data() to return the native base pointer + size for linear memory aliasing.
  • Extend TextDecoder.decode() to support DataView inputs by reading buffer/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 thread lib/quickbeam/wasm_js.zig
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 thread priv/ts/webassembly.ts
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
@e-fu e-fu closed this Jun 18, 2026
@e-fu

e-fu commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

Needs more work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants