mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibJS: Only cache TypedArray data pointers for owned buffers
WebAssembly.Memory-backed ArrayBuffers wrap external ByteBuffer storage. When that memory grows, ByteBuffer::try_resize() may realloc the backing storage while old fixed-length buffer objects remain reachable from JS. TypedArrayBase cached m_data for all fixed-length buffers, and the asm interpreter fast path dereferenced that cached pointer directly. For wasm memory views this could leave a stale pointer behind across grow(). Restrict cached typed-array data pointers to fixed-length ArrayBuffers that own stable ByteBuffer storage. External/unowned buffers, including WebAssembly.Memory buffers, now keep m_data == nullptr and fall back to code that re-derives buffer().data() on each access. Add regressions for both the original shared-memory grow case and the second-grow stale-view case.
This commit is contained in:
Notes:
github-actions[bot]
2026-04-25 04:12:12 +00:00
Author: https://github.com/Yayoi-cs Commit: https://github.com/LadybirdBrowser/ladybird/commit/0b9636fadfc Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/9086
@@ -261,17 +261,6 @@ void ArrayBuffer::detach_buffer()
|
||||
m_data_block.byte_buffer = Empty {};
|
||||
}
|
||||
|
||||
void ArrayBuffer::refresh_cached_typed_array_view_data_pointers()
|
||||
{
|
||||
if (m_data_block.byte_buffer.has<Empty>())
|
||||
return;
|
||||
auto* new_base = buffer().data();
|
||||
for (auto& view : m_cached_views) {
|
||||
if (view.viewed_array_buffer() == this)
|
||||
view.set_cached_data_ptr(new_base + view.byte_offset());
|
||||
}
|
||||
}
|
||||
|
||||
void ArrayBuffer::register_cached_typed_array_view(TypedArrayBase& view)
|
||||
{
|
||||
m_cached_views.set(view);
|
||||
|
||||
@@ -99,7 +99,6 @@ public:
|
||||
|
||||
void detach_buffer();
|
||||
void register_cached_typed_array_view(TypedArrayBase&);
|
||||
void refresh_cached_typed_array_view_data_pointers();
|
||||
|
||||
// 25.1.3.4 IsDetachedBuffer ( arrayBuffer ), https://tc39.es/ecma262/#sec-isdetachedbuffer
|
||||
bool is_detached() const
|
||||
@@ -122,6 +121,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool can_cache_typed_array_view_data_pointer() const
|
||||
{
|
||||
return !is_detached() && is_fixed_length() && m_data_block.byte_buffer.has<ByteBuffer>();
|
||||
}
|
||||
|
||||
// 25.2.2.2 IsSharedArrayBuffer ( obj ), https://tc39.es/ecma262/#sec-issharedarraybuffer
|
||||
bool is_shared_array_buffer() const
|
||||
{
|
||||
|
||||
@@ -43,8 +43,8 @@ public:
|
||||
ArrayBuffer* viewed_array_buffer() const { return m_viewed_array_buffer; }
|
||||
|
||||
// Cached raw pointer: viewed_array_buffer->buffer().data() + byte_offset.
|
||||
// nullptr means "not cached, use slow path". This avoids chasing through
|
||||
// ArrayBuffer -> DataBlock -> Variant -> ByteBuffer -> inline/outline on every access.
|
||||
// nullptr means "not cached, use slow path". This is only safe for
|
||||
// fixed-length ArrayBuffers that own stable backing storage.
|
||||
u8* cached_data_ptr() const { return m_data; }
|
||||
void set_cached_data_ptr(u8* ptr) { m_data = ptr; }
|
||||
|
||||
@@ -91,7 +91,7 @@ protected:
|
||||
|
||||
void update_cached_data_ptr()
|
||||
{
|
||||
if (!m_viewed_array_buffer || m_viewed_array_buffer->is_detached() || !m_viewed_array_buffer->is_fixed_length()) {
|
||||
if (!m_viewed_array_buffer || !m_viewed_array_buffer->can_cache_typed_array_view_data_pointer()) {
|
||||
m_data = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -216,8 +216,6 @@ void Memory::refresh_the_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::Memor
|
||||
if (!buffer->is_shared_array_buffer()) {
|
||||
// 1. Perform ! DetachArrayBuffer(buffer, "WebAssembly.Memory").
|
||||
MUST(JS::detach_array_buffer(vm, *buffer, JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string)));
|
||||
} else {
|
||||
buffer->refresh_cached_typed_array_view_data_pointers();
|
||||
}
|
||||
|
||||
// 2. Let newBuffer be the result of creating a fixed length memory buffer from memaddr.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
newView[0x0]: 0xde
|
||||
newView[0x1]: 0xad
|
||||
newView[0x2]: 0xbe
|
||||
newView[0x3]: 0xef
|
||||
oldView[0x4]: 0xab
|
||||
oldView[0x5]: 0xad
|
||||
oldView[0x6]: 0x1d
|
||||
oldView[0x7]: 0xea
|
||||
newView[0x8]: 0xca
|
||||
newView[0x9]: 0xfe
|
||||
newView[0xa]: 0xba
|
||||
newView[0xb]: 0xbe
|
||||
firstView[0x0]: 0x11
|
||||
firstView[0x1]: 0x22
|
||||
firstView[0x2]: 0x33
|
||||
firstView[0x3]: 0x44
|
||||
thirdView[0x4]: 0x55
|
||||
thirdView[0x5]: 0x66
|
||||
thirdView[0x6]: 0x77
|
||||
thirdView[0x7]: 0x88
|
||||
firstView[0x8]: 0x99
|
||||
firstView[0x9]: 0xaa
|
||||
firstView[0xa]: 0xbb
|
||||
firstView[0xb]: 0xcc
|
||||
|
||||
@@ -4,40 +4,43 @@
|
||||
test(() => {
|
||||
// regression test for the stale-view case on shared WebAssembly.Memory.
|
||||
// A typed array created from the old memory.buffer before grow() should still read and write the same underlying memory after the grow.
|
||||
const mem = new WebAssembly.Memory({ initial: 1, maximum: 2, shared: true });
|
||||
const oldView = new Uint8Array(mem.buffer);
|
||||
oldView[0x0] = 0xde;
|
||||
oldView[0x1] = 0xad;
|
||||
oldView[0x2] = 0xbe;
|
||||
oldView[0x3] = 0xef;
|
||||
const mem = new WebAssembly.Memory({ initial: 1, maximum: 1000, shared: true });
|
||||
const firstView = new Uint8Array(mem.buffer);
|
||||
|
||||
mem.grow(1);
|
||||
const secondView = new Uint8Array(mem.buffer);
|
||||
|
||||
const newView = new Uint8Array(mem.buffer);
|
||||
// verify array created from the new memory.buffer after grow() reads the same underlying memory before the grow.
|
||||
println(`newView[0x0]: 0x${newView[0x0].toString(16)}`);
|
||||
println(`newView[0x1]: 0x${newView[0x1].toString(16)}`);
|
||||
println(`newView[0x2]: 0x${newView[0x2].toString(16)}`);
|
||||
println(`newView[0x3]: 0x${newView[0x3].toString(16)}`);
|
||||
mem.grow(100);
|
||||
const thirdView = new Uint8Array(mem.buffer);
|
||||
|
||||
// verify array created from the new memory.buffer before grow() reads the same underlying memory after the grow.
|
||||
newView[0x4] = 0xab;
|
||||
newView[0x5] = 0xad;
|
||||
newView[0x6] = 0x1d;
|
||||
newView[0x7] = 0xea;
|
||||
println(`oldView[0x4]: 0x${oldView[0x4].toString(16)}`);
|
||||
println(`oldView[0x5]: 0x${oldView[0x5].toString(16)}`);
|
||||
println(`oldView[0x6]: 0x${oldView[0x6].toString(16)}`);
|
||||
println(`oldView[0x7]: 0x${oldView[0x7].toString(16)}`);
|
||||
// Writes through the newest view must be visible through the oldest view
|
||||
thirdView[0x0] = 0x11;
|
||||
thirdView[0x1] = 0x22;
|
||||
thirdView[0x2] = 0x33;
|
||||
thirdView[0x3] = 0x44;
|
||||
println(`firstView[0x0]: 0x${firstView[0x0].toString(16)}`);
|
||||
println(`firstView[0x1]: 0x${firstView[0x1].toString(16)}`);
|
||||
println(`firstView[0x2]: 0x${firstView[0x2].toString(16)}`);
|
||||
println(`firstView[0x3]: 0x${firstView[0x3].toString(16)}`);
|
||||
|
||||
// verify array created from the new memory.buffer before grow() writes the same underlying memory after the grow.
|
||||
oldView[0x8] = 0xca;
|
||||
oldView[0x9] = 0xfe;
|
||||
oldView[0xa] = 0xba;
|
||||
oldView[0xb] = 0xbe;
|
||||
println(`newView[0x8]: 0x${newView[0x8].toString(16)}`);
|
||||
println(`newView[0x9]: 0x${newView[0x9].toString(16)}`);
|
||||
println(`newView[0xa]: 0x${newView[0xa].toString(16)}`);
|
||||
println(`newView[0xb]: 0x${newView[0xb].toString(16)}`);
|
||||
// Writes through the oldest view must be visible through the newest view.
|
||||
firstView[0x4] = 0x55;
|
||||
firstView[0x5] = 0x66;
|
||||
firstView[0x6] = 0x77;
|
||||
firstView[0x7] = 0x88;
|
||||
println(`thirdView[0x4]: 0x${thirdView[0x4].toString(16)}`);
|
||||
println(`thirdView[0x5]: 0x${thirdView[0x5].toString(16)}`);
|
||||
println(`thirdView[0x6]: 0x${thirdView[0x6].toString(16)}`);
|
||||
println(`thirdView[0x7]: 0x${thirdView[0x7].toString(16)}`);
|
||||
|
||||
// And via the intermediate view.
|
||||
secondView[0x8] = 0x99;
|
||||
secondView[0x9] = 0xaa;
|
||||
secondView[0xa] = 0xbb;
|
||||
secondView[0xb] = 0xcc;
|
||||
println(`firstView[0x8]: 0x${firstView[0x8].toString(16)}`);
|
||||
println(`firstView[0x9]: 0x${firstView[0x9].toString(16)}`);
|
||||
println(`firstView[0xa]: 0x${firstView[0xa].toString(16)}`);
|
||||
println(`firstView[0xb]: 0x${firstView[0xb].toString(16)}`);
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user