Symptom
When a buffer+len native-library param receives a Uint8Array view over an existing ArrayBuffer, the lowered data_ptr points at the wrong memory. The length resolves correctly, so the callee happily reads/writes len bytes — of the wrong buffer. No error, no null: silent data corruption.
A Uint8Array that owns its storage (new Uint8Array(64)) works correctly.
Repro
Native side (any perry-ext-*-style wrapper), manifest "params": ["i64", "number", "number", "buffer+len"]:
#[no_mangle]
pub unsafe extern "C" fn js_demo_fill(_h: i64, _off: f64, _size: f64, out: *const u8, out_len: usize) -> f64 {
// fill `out_len` bytes with a known pattern
std::ptr::write_bytes(out as *mut u8, 0xAB, out_len);
out_len as f64
}
TypeScript:
// A) standalone Uint8Array — WORKS
const standalone = new Uint8Array(64);
demoFill(h, 0, 64, standalone);
console.log(standalone[0]); // 0xAB ✓
// B) view over an ArrayBuffer — SILENTLY WRONG
const ab = new ArrayBuffer(64);
const view = new Uint8Array(ab);
demoFill(h, 0, 64, view);
console.log(view[0]); // 0 ✗ (callee reported 64 bytes written)
Observed with the real @perryts/webgpu binding (js_webgpu_buffer_read_mapped_into, js_webgpu_queue_write_buffer):
p8 standalone out: n= 64 bytes: 11 22 33 44 55 (expect 11 22 33 44 55) ✓
p8 view out: n= 64 bytes: 0 0 0 0 0 ✗
Note n=64 in both cases — js_native_abi_check_buffer_byte_len gets the view's length right, so it's specifically js_native_abi_check_buffer_data_ptr that mis-resolves the view (presumably resolving the view object rather than buffer + byteOffset, or failing the registered-buffer lookup and falling back to a bogus pointer).
Why it matters
Views are the normal shape for GPU/binary work — new Uint8Array(f32.buffer, off, len) is how you hand float data to a byte-oriented API, and it's what every WebGPU/three.js-style upload path produces. The failure is silent and far from its cause: we chased "why is my render black" for hours before it reduced to this.
Suggested: js_native_abi_check_buffer_data_ptr should resolve views to storage + byteOffset; if a view genuinely can't be resolved, return null so the callee's null-check fires instead of writing to the wrong place.
Found while bringing up browser-style WebGPU (three.js WebGPURenderer target) on perry. Related: #6486.
Symptom
When a
buffer+lennative-library param receives aUint8Arrayview over an existingArrayBuffer, the lowereddata_ptrpoints at the wrong memory. The length resolves correctly, so the callee happily reads/writeslenbytes — of the wrong buffer. No error, no null: silent data corruption.A
Uint8Arraythat owns its storage (new Uint8Array(64)) works correctly.Repro
Native side (any
perry-ext-*-style wrapper), manifest"params": ["i64", "number", "number", "buffer+len"]:TypeScript:
Observed with the real
@perryts/webgpubinding (js_webgpu_buffer_read_mapped_into,js_webgpu_queue_write_buffer):Note
n=64in both cases —js_native_abi_check_buffer_byte_lengets the view's length right, so it's specificallyjs_native_abi_check_buffer_data_ptrthat mis-resolves the view (presumably resolving the view object rather thanbuffer + byteOffset, or failing the registered-buffer lookup and falling back to a bogus pointer).Why it matters
Views are the normal shape for GPU/binary work —
new Uint8Array(f32.buffer, off, len)is how you hand float data to a byte-oriented API, and it's what every WebGPU/three.js-style upload path produces. The failure is silent and far from its cause: we chased "why is my render black" for hours before it reduced to this.Suggested:
js_native_abi_check_buffer_data_ptrshould resolve views tostorage + byteOffset; if a view genuinely can't be resolved, return null so the callee's null-check fires instead of writing to the wrong place.Found while bringing up browser-style WebGPU (three.js
WebGPURenderertarget) on perry. Related: #6486.