diff --git a/Libraries/LibWeb/Bindings/MainThreadVM.cpp b/Libraries/LibWeb/Bindings/MainThreadVM.cpp index af61553117c..788d9b7aa2e 100644 --- a/Libraries/LibWeb/Bindings/MainThreadVM.cpp +++ b/Libraries/LibWeb/Bindings/MainThreadVM.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include namespace Web::Bindings { @@ -690,6 +691,14 @@ void initialize_main_thread_vm(AgentType type) s_main_thread_vm->host_unrecognized_date_string = [](StringView date) { dbgln("Unable to parse date string: \"{}\"", date); }; + + s_main_thread_vm->host_resize_array_buffer = [default_host_resize_array_buffer = move(s_main_thread_vm->host_resize_array_buffer)](JS::ArrayBuffer& buffer, size_t new_byte_length) -> JS::ThrowCompletionOr { + auto wasm_handled = TRY(WebAssembly::Detail::host_resize_array_buffer(*s_main_thread_vm, buffer, new_byte_length)); + if (wasm_handled == JS::HandledByHost::Handled) + return JS::HandledByHost::Handled; + + return default_host_resize_array_buffer(buffer, new_byte_length); + }; } JS::VM& main_thread_vm() diff --git a/Libraries/LibWeb/WebAssembly/Memory.cpp b/Libraries/LibWeb/WebAssembly/Memory.cpp index e2dc04cd486..022d879e8bc 100644 --- a/Libraries/LibWeb/WebAssembly/Memory.cpp +++ b/Libraries/LibWeb/WebAssembly/Memory.cpp @@ -89,7 +89,7 @@ void Memory::visit_edges(Visitor& visitor) } // https://webassembly.github.io/spec/js-api/#dom-memory-grow -WebIDL::ExceptionOr Memory::grow(u32 delta) +JS::ThrowCompletionOr Memory::grow(u32 delta) { auto& vm = this->vm(); @@ -106,6 +106,91 @@ WebIDL::ExceptionOr Memory::grow(u32 delta) return previous_size; } +// https://webassembly.github.io/threads/js-api/index.html#dom-memory-tofixedlengthbuffer +WebIDL::ExceptionOr> Memory::to_fixed_length_buffer() +{ + auto& vm = this->vm(); + + // 1. Let buffer be this.[[BufferObject]]. + // 2. Let memaddr be this.[[Memory]]. + // 3. If IsSharedArrayBuffer(buffer) is false, + if (m_shared == Shared::No) { + // 1. If IsFixedLengthArrayBuffer(buffer) is true, return buffer. + if (m_buffer->is_fixed_length()) + return GC::Ref(*m_buffer); + + // 2. Otherwise, + // 1. Let fixedBuffer be the result of creating a fixed length memory buffer from memaddr. + auto fixed_buffer = create_a_fixed_length_memory_buffer(vm, realm(), m_address, m_shared); + + // 2. Perform ! DetachArrayBuffer(buffer, "WebAssembly.Memory"). + MUST(JS::detach_array_buffer(vm, *m_buffer, JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string))); + + // 3. Set this.[[BufferObject]] to fixedBuffer. + m_buffer = fixed_buffer; + + // 4. Return fixedBuffer. + return fixed_buffer; + } + + // 4. Otherwise, + // 1. Let map be the surrounding agent's associated Memory object cache. + auto& cache = Detail::get_cache(realm()); + + // 2. Assert: map[memaddr] exists. + // 3. Let newMemory be map[memaddr]. + auto new_memory = cache.get_memory_instance(m_address); + VERIFY(new_memory.has_value()); + + // 4. Let newBufferObject be newMemory.[[BufferObject]]. + auto new_buffer_object = new_memory.value()->m_buffer; + + // 5. Set this.[[BufferObject]] to newBufferObject. + m_buffer = new_buffer_object; + + // 6. Return newBufferObject. + return GC::Ref(*new_buffer_object); +} + +// https://webassembly.github.io/spec/js-api/#dom-memory-toresizablebuffer +WebIDL::ExceptionOr> Memory::to_resizable_buffer() +{ + auto& vm = this->vm(); + + // 1. Let buffer be this.[[BufferObject]]. + // 2. If IsFixedLengthArrayBuffer(buffer) is false, return buffer. + if (!m_buffer->is_fixed_length()) + return GC::Ref(*m_buffer); + + // 3. Let memaddr be this.[[Memory]]. + // 4. Let store be the surrounding agent’s associated store. + auto& store = Detail::get_cache(realm()).abstract_machine().store(); + + // 5. Let memtype be mem_type(store, memaddr). + auto mem_type = store.get(m_address)->type(); + + // 6. If memtype has a max, + // 1. Let maxsize be the max value in memtype. + // 7. Otherwise, + // 1. Let maxsize be 65536 × 65536. + size_t max_size = mem_type.limits().max().value_or(65536) * Wasm::Constants::page_size; + + // 8. Let resizableBuffer be the result of creating a resizable memory buffer from memaddr and maxsize. + auto resizable_buffer = TRY(create_a_resizable_memory_buffer(vm, realm(), m_address, m_shared, max_size)); + + // https://webassembly.github.io/threads/js-api/index.html#dom-memory-toresizablebuffer + // 5. If IsSharedArrayBuffer(buffer) is false, + // 9. Perform ! DetachArrayBuffer(buffer, "WebAssembly.Memory"). + if (!m_buffer->is_shared_array_buffer()) + MUST(JS::detach_array_buffer(vm, *m_buffer, JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string))); + + // 10. Set this.[[BufferObject]] to resizableBuffer. + m_buffer = resizable_buffer; + + // 11. Return resizeableBuffer. + return resizable_buffer; +} + // https://webassembly.github.io/spec/js-api/#refresh-the-memory-buffer void Memory::refresh_the_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address) { @@ -130,15 +215,45 @@ void Memory::refresh_the_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::Memor // 2. Let newBuffer be the result of creating a fixed length memory buffer from memaddr. // 3. Set memory.[[BufferObject]] to newBuffer. - } + buffer = create_a_fixed_length_memory_buffer(vm, realm, address, memory.value()->m_shared); + } else { + // 1. Let block be a Data Block which is identified with the underlying memory of memaddr. + auto& bytes = cache.abstract_machine().store().get(address)->data(); - buffer = create_a_fixed_length_memory_buffer(vm, realm, address, memory.value()->m_shared); + // 2. Set buffer.[[ArrayBufferData]] to block. + // 3. Set buffer.[[ArrayBufferByteLength]] to the length of block. + buffer->set_data_block({ JS::DataBlock::UnownedFixedLengthByteBuffer(&bytes) }); + } } -// https://webassembly.github.io/spec/js-api/#dom-memory-buffer +// https://webassembly.github.io/threads/js-api/#dom-memory-buffer WebIDL::ExceptionOr> Memory::buffer() const { - return GC::Ref(*m_buffer); + // 1. Let memaddr be this.[[Memory]]. + // 2. Let block be a Data Block which is identified with the underlying memory of memaddr. + // 3. If block is a Shared Data Block, + if (m_shared == Shared::Yes) { + // 1. Let map be the surrounding agent's associated Memory object cache. + // 2. Assert: map[memaddr] exists. + // 3. Let newMemory be map[memaddr]. + auto& cache = Detail::get_cache(realm()); + auto new_memory = cache.get_memory_instance(m_address); + VERIFY(new_memory.has_value()); + + // 4. Let newBufferObject be newMemory.[[BufferObject]]. + auto new_buffer_object = new_memory.value()->m_buffer; + + // 5. Set this.[[BufferObject]] to newBufferObject. + m_buffer = new_buffer_object; + + // 6. Return newBufferObject. + return GC::Ref(*new_buffer_object); + } + // 4. Otherwise, + else { + // 1. Return this.[[BufferObject]]. + return GC::Ref(*m_buffer); + } } // https://webassembly.github.io/spec/js-api/#create-a-fixed-length-memory-buffer @@ -173,4 +288,62 @@ GC::Ref Memory::create_a_fixed_length_memory_buffer(JS::VM& vm, return GC::Ref(*array_buffer); } +// https://webassembly.github.io/spec/js-api/#create-a-resizable-memory-buffer +JS::ThrowCompletionOr> Memory::create_a_resizable_memory_buffer(JS::VM& vm, JS::Realm& realm, Wasm::MemoryAddress address, Shared shared, size_t max_size) +{ + auto& context = Detail::get_cache(realm); + auto* memory = context.abstract_machine().store().get(address); + VERIFY(memory); + + // 3. If maxsize > (65536 × 65536), + if (max_size > (65536 * Wasm::Constants::page_size)) { + // 1. Throw a RangeError exception. + return vm.throw_completion("Maximum memory length exceeds 65536 * 65536 bytes"sv); + } + + // https://webassembly.github.io/threads/js-api/index.html#create-a-resizable-memory-buffer + // 5. If share is shared, + if (shared == Shared::Yes) { + // 1. Let block be a Shared Data Block which is identified with the underlying memory of memaddr. + // 2. Let buffer be a new SharedArrayBuffer with the internal slots [[ArrayBufferData]], [[ArrayBufferByteLength]], and [[ArrayBufferMaxByteLength]]. + // 3. Set buffer.[[ArrayBufferData]] to block. + auto buffer = JS::ArrayBuffer::create(realm, &memory->data()); + + // AD-HOC: The threads proposal uses the memory type's minimum for both shared and + // non-shared memories, but the upstream spec uses the memory instance's current + // size. We assume the upstream spec is correct for both cases. + // 4. Set buffer.[[ArrayBufferByteLength]] to min. + VERIFY(buffer->byte_length() == memory->size()); + + // 5. Set buffer.[[ArrayBufferMaxByteLength]] to maxsize. + buffer->set_max_byte_length(max_size); + + // 6. Perform ! SetIntegrityLevel(buffer, "frozen"). + MUST(buffer->set_integrity_level(IntegrityLevel::Frozen)); + + // 7. Return buffer. + return buffer; + } + // 6. Otherwise, + else { + // 1. Let block be a Data Block which is identified with the underlying memory of memaddr. + // 4. Let buffer be a new ArrayBuffer with the internal slots [[ArrayBufferData]], [[ArrayBufferByteLength]], [[ArrayBufferMaxByteLength]], and [[ArrayBufferDetachKey]]. + // 5. Set buffer.[[ArrayBufferData]] to block. + auto buffer = JS::ArrayBuffer::create(realm, &memory->data()); + + // 2. Let length be the length of block. + // 6. Set buffer.[[ArrayBufferByteLength]] to length. + VERIFY(buffer->byte_length() == memory->size()); + + // 7. Set buffer.[[ArrayBufferMaxByteLength]] to maxsize. + buffer->set_max_byte_length(max_size); + + // 8. Set buffer.[[ArrayBufferDetachKey]] to "WebAssembly.Memory". + buffer->set_detach_key(JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string)); + + // 9. Return buffer. + return buffer; + } +} + } diff --git a/Libraries/LibWeb/WebAssembly/Memory.h b/Libraries/LibWeb/WebAssembly/Memory.h index dd85e307e43..9f6c62b271e 100644 --- a/Libraries/LibWeb/WebAssembly/Memory.h +++ b/Libraries/LibWeb/WebAssembly/Memory.h @@ -35,10 +35,14 @@ class Memory : public Bindings::PlatformObject { public: static WebIDL::ExceptionOr> construct_impl(JS::Realm&, MemoryDescriptor& descriptor); - WebIDL::ExceptionOr grow(u32 delta); + JS::ThrowCompletionOr grow(u32 delta); + + WebIDL::ExceptionOr> to_fixed_length_buffer(); + WebIDL::ExceptionOr> to_resizable_buffer(); WebIDL::ExceptionOr> buffer() const; Wasm::MemoryAddress address() const { return m_address; } + GC::Ptr buffer_object() const { return m_buffer; } private: Memory(JS::Realm&, Wasm::MemoryAddress, Shared shared); @@ -48,6 +52,7 @@ private: static void refresh_the_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress); static GC::Ref create_a_fixed_length_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress, Shared shared); + static JS::ThrowCompletionOr> create_a_resizable_memory_buffer(JS::VM&, JS::Realm&, Wasm::MemoryAddress, Shared shared, size_t max_size); Wasm::MemoryAddress m_address; Shared m_shared { Shared::No }; diff --git a/Libraries/LibWeb/WebAssembly/Memory.idl b/Libraries/LibWeb/WebAssembly/Memory.idl index 9b67bf2ffcb..223b68d0606 100644 --- a/Libraries/LibWeb/WebAssembly/Memory.idl +++ b/Libraries/LibWeb/WebAssembly/Memory.idl @@ -12,5 +12,7 @@ interface Memory { unsigned long grow([EnforceRange] unsigned long delta); + ArrayBuffer toFixedLengthBuffer(); + ArrayBuffer toResizableBuffer(); readonly attribute ArrayBuffer buffer; }; diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp index 4f3b99b2bab..16cd48248a5 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.cpp +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.cpp @@ -438,6 +438,56 @@ JS::ThrowCompletionOr> compile_a_webass return compiled_module; } +JS::ThrowCompletionOr host_resize_array_buffer(JS::VM& vm, JS::ArrayBuffer& buffer, size_t new_length) +{ + // 1. If buffer.[[ArrayBufferDetachKey]] is "WebAssembly.Memory", + auto detach_key = buffer.detach_key(); + if (detach_key.is_string() && detach_key.as_string() == JS::PrimitiveString::create(vm, "WebAssembly.Memory"_string)) { + // 1. Let map be the surrounding agent's associated Memory object cache. + auto const& map = get_cache(*vm.current_realm()).memory_instances(); + + // 3. For each memaddr → mem in map, + bool seen = false; + for (auto [address, memory] : map) { + auto buffer_object = memory->buffer_object(); + // 1. If SameValue(mem.[[BufferObject]], buffer) is true, + if (buffer_object.ptr() == &buffer) { + // 2. Assert: buffer is the [[BufferObject]] of exactly one value in map. + VERIFY(!seen); + seen = true; + + // 1. Assert: buffer.[[ArrayBufferByteLength]] modulo 65536 is 0. + VERIFY(buffer.byte_length() % Wasm::Constants::page_size == 0); + + // 2. Let lengthDelta be newLength - buffer.[[ArrayBufferByteLength]]. + auto length_delta = new_length - buffer.byte_length(); + + // 3. If lengthDelta < 0 or lengthDelta modulo 65536 is not 0, + if (new_length < buffer.byte_length() || length_delta % Wasm::Constants::page_size != 0) { + // 1. Throw a RangeError exception. + return vm.throw_completion("WebAssembly.Memory buffers must be resized by a multiple of the page size"sv); + } + + // 4. Let delta be lengthDelta ÷ 65536. + auto delta = length_delta / Wasm::Constants::page_size; + + // 5. Grow the memory buffer associated with memaddr by delta. + // FIXME: "Grow the memory buffer" is a separate algorithm from the Memory#grow() method. + TRY(memory->grow(delta)); + } + } + + // 2. Assert: buffer is the [[BufferObject]] of exactly one value in map. + VERIFY(seen); + + // 4. Return handled. + return JS::HandledByHost::Handled; + } + + // 2. Otherwise, return unhandled. + return JS::HandledByHost::Unhandled; +} + GC_DEFINE_ALLOCATOR(ExportedWasmFunction); GC::Ref ExportedWasmFunction::create(JS::Realm& realm, Utf16FlyString name, Function(JS::VM&)> behavior, Wasm::FunctionAddress exported_address) diff --git a/Libraries/LibWeb/WebAssembly/WebAssembly.h b/Libraries/LibWeb/WebAssembly/WebAssembly.h index 7c693394dd0..6f89ef52769 100644 --- a/Libraries/LibWeb/WebAssembly/WebAssembly.h +++ b/Libraries/LibWeb/WebAssembly/WebAssembly.h @@ -107,6 +107,7 @@ JS::ThrowCompletionOr to_webassembly_value(JS::VM&, JS::Value value Wasm::Value default_webassembly_value(JS::VM&, Wasm::ValueType type); JS::Value to_js_value(JS::VM&, Wasm::Value& wasm_value, Wasm::ValueType type); JS::ThrowCompletionOr host_ensure_can_compile_wasm_bytes(JS::VM&); +JS::ThrowCompletionOr host_resize_array_buffer(JS::VM&, JS::ArrayBuffer&, size_t); extern HashMap, WebAssemblyCache> s_caches;