Feature request: expose a way to back an ArrayBuffer/SharedArrayBuffer with embedder-owned memory
Summary
When embedding Boa, there is currently no public API to create an ArrayBuffer (or SharedArrayBuffer) whose bytes are owned by the
embedder rather than allocated by Boa. Every construction path (JsArrayBuffer::from_byte_block, JsArrayBuffer::new,
JsSharedArrayBuffer::new) allocates a fresh, Boa-owned backing store and copies into it.
This makes it impossible to share a byte region between JavaScript and native host code with zero copies — the JS side and the native side each hold their own allocation, so writes on one side are not visible on the other.
Motivation / use case
I'm embedding Boa as the engine of a small JS runtime. A common need is to hand JavaScript a live view over memory that lives outside the engine. For example
a WebAssembly linear memory, an mmap'd file, or a GPU-mapped buffer. The
canonical shape in other engines is V8's
ArrayBuffer::New(data_ptr, byte_length, ...) / backing-store API.
Concretely: to run napi-rs WebAssembly addons (@napi-rs/wasm-runtime + @emnapi/core), the JS runtime must expose WebAssembly.Memory.prototype.buffer as an ArrayBuffer whose bytes are the wasm linear memory. emnapi does
new Uint8Array(wasmMemory.buffer).set(bytes, ptr) to pass data into wasm, and relies on those writes being visible to the running module. With a Boa-owned, copied buffer they are not, so the addon silently fails to receive its inputs.
What already exists and almost works
Boa is actually very close:
JsArrayBuffer::data_mut() hands out a stable &mut [u8] over the internal AlignedVec. I verified the base pointer is stable across re-borrows, and that a raw write through that pointer is observable from the JS side and vice-versa so the aliasing model itself works.
SharedArrayBuffer already stores its bytes in Arc<Inner> with an AlignedBox<[AtomicU8]> backing and an internal as_ptr().
The only missing piece is a public way to either:
- construct an
ArrayBuffer/SharedArrayBuffer from an embedder-supplied backing (a pointer + length, or a type implementing a
small BackingStore trait), the embedder guaranteeing the region outlives the buffer; or
- failing that, make
SharedArrayBuffer::as_ptr() / bytes() (currently pub(crate)) public, so an embedder can at least read/write the existing backing in place and mirror it though (1) is what actually enables true zero-copy sharing.
Proposed API sketch (illustrative, not prescriptive)
// On the JS wrapper:
impl JsArrayBuffer {
/// Create an ArrayBuffer that aliases `len` bytes at `ptr`.
///
/// # Safety
/// The caller must keep the region valid and unmoved for the buffer's
/// lifetime, and must not alias it mutably from Rust while JS may run.
pub unsafe fn from_external_ptr(
ptr: *mut u8,
len: usize,
context: &mut Context,
) -> JsResult<Self>;
}
An Arc-based BackingStore handle (à la V8) would be even nicer for lifetime management, but a documented unsafe pointer constructor would already unblock the use case.
Environment
- boa_engine from
git+https://github.com/boa-dev/boa.git?branch=main
at commit 5718cc8.
Notes
Happy to prototype a PR if the maintainers agree on the shape. The main design questions are lifetime/ownership (raw pointer + unsafe vs. an Arc backing handle) and whether detach/resize should be disallowed for externally-backed buffers.
Feature request: expose a way to back an
ArrayBuffer/SharedArrayBufferwith embedder-owned memorySummary
When embedding Boa, there is currently no public API to create an
ArrayBuffer(orSharedArrayBuffer) whose bytes are owned by theembedder rather than allocated by Boa. Every construction path (
JsArrayBuffer::from_byte_block,JsArrayBuffer::new,JsSharedArrayBuffer::new) allocates a fresh, Boa-owned backing store and copies into it.This makes it impossible to share a byte region between JavaScript and native host code with zero copies — the JS side and the native side each hold their own allocation, so writes on one side are not visible on the other.
Motivation / use case
I'm embedding Boa as the engine of a small JS runtime. A common need is to hand JavaScript a live view over memory that lives outside the engine. For example
a WebAssembly linear memory, an mmap'd file, or a GPU-mapped buffer. The
canonical shape in other engines is V8's
ArrayBuffer::New(data_ptr, byte_length, ...)/ backing-store API.Concretely: to run napi-rs WebAssembly addons (
@napi-rs/wasm-runtime+@emnapi/core), the JS runtime must exposeWebAssembly.Memory.prototype.bufferas anArrayBufferwhose bytes are the wasm linear memory. emnapi doesnew Uint8Array(wasmMemory.buffer).set(bytes, ptr)to pass data into wasm, and relies on those writes being visible to the running module. With a Boa-owned, copied buffer they are not, so the addon silently fails to receive its inputs.What already exists and almost works
Boa is actually very close:
JsArrayBuffer::data_mut()hands out a stable&mut [u8]over the internalAlignedVec. I verified the base pointer is stable across re-borrows, and that a raw write through that pointer is observable from the JS side and vice-versa so the aliasing model itself works.SharedArrayBufferalready stores its bytes inArc<Inner>with anAlignedBox<[AtomicU8]>backing and an internalas_ptr().The only missing piece is a public way to either:
ArrayBuffer/SharedArrayBufferfrom an embedder-supplied backing (a pointer + length, or a type implementing asmall
BackingStoretrait), the embedder guaranteeing the region outlives the buffer; orSharedArrayBuffer::as_ptr()/bytes()(currentlypub(crate)) public, so an embedder can at least read/write the existing backing in place and mirror it though (1) is what actually enables true zero-copy sharing.Proposed API sketch (illustrative, not prescriptive)
An
Arc-basedBackingStorehandle (à la V8) would be even nicer for lifetime management, but a documentedunsafepointer constructor would already unblock the use case.Environment
git+https://github.com/boa-dev/boa.git?branch=mainat commit
5718cc8.Notes
Happy to prototype a PR if the maintainers agree on the shape. The main design questions are lifetime/ownership (raw pointer +
unsafevs. anArcbacking handle) and whether detach/resize should be disallowed for externally-backed buffers.