diff --git a/CHANGELOG.md b/CHANGELOG.md index 35160244e..c79647825 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Enable WASM bulk-memory `memory.copy`/`memory.fill` opcodes (`WASM_ENABLE_BULK_MEMORY_OPT`), so modules from standard toolchains (Go `GOOS=js GOARCH=wasm`, TinyGo, Rust `wasm-bindgen`) that emit them no longer fail to instantiate with `unsupported opcode fc 0a` - Update `oxc` to 0.15.1 ## 0.10.15 diff --git a/lib/quickbeam/native.ex b/lib/quickbeam/native.ex index 89330da73..13b97597d 100644 --- a/lib/quickbeam/native.ex +++ b/lib/quickbeam/native.ex @@ -30,6 +30,7 @@ defmodule QuickBEAM.Native do "-DWASM_ENABLE_LIBC_WASI=0", "-DWASM_ENABLE_MULTI_MODULE=0", "-DWASM_ENABLE_BULK_MEMORY=1", + "-DWASM_ENABLE_BULK_MEMORY_OPT=1", "-DWASM_ENABLE_REF_TYPES=1", "-DWASM_ENABLE_SIMD=0", "-DWASM_ENABLE_TAIL_CALL=1", diff --git a/lib/quickbeam/wamr.zig b/lib/quickbeam/wamr.zig index 8cba5427c..2d0be9b79 100644 --- a/lib/quickbeam/wamr.zig +++ b/lib/quickbeam/wamr.zig @@ -6,6 +6,7 @@ pub const wamr = @cImport({ @cDefine("WASM_ENABLE_LIBC_WASI", "0"); @cDefine("WASM_ENABLE_MULTI_MODULE", "0"); @cDefine("WASM_ENABLE_BULK_MEMORY", "1"); + @cDefine("WASM_ENABLE_BULK_MEMORY_OPT", "1"); @cDefine("WASM_ENABLE_REF_TYPES", "1"); @cDefine("WASM_ENABLE_SIMD", "0"); @cDefine("WASM_ENABLE_TAIL_CALL", "1"); diff --git a/test/wasm_test.exs b/test/wasm_test.exs index b0f783f8f..8d34a9844 100644 --- a/test/wasm_test.exs +++ b/test/wasm_test.exs @@ -476,6 +476,152 @@ defmodule QuickBEAM.WASMTest do @custom_section_wasm <<0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x08, 0x04, 0x6D, 0x65, 0x74, 0x61, 0x61, 0x62, 0x63>> + # Bulk-memory regression fixture. Exercises every opcode gated by the WAMR + # config flags WASM_ENABLE_BULK_MEMORY / _OPT (priv/c_src/wamr/config.h): + # memory.fill (fc 0b), memory.copy (fc 0a) <- _OPT (fc 0a is the blocker) + # memory.init (fc 08), data.drop (fc 09) <- BULK_MEMORY + # Toolchains like Go's GOOS=js GOARCH=wasm, TinyGo, and Rust wasm-bindgen emit + # these unconditionally; with the gates off WAMR rejects them at compile time + # with "unsupported opcode fc 0a". + # + # WAT (assembled with wat2wasm, byte-exact-verified against a reference runtime): + # (module + # (memory (export "mem") 1) + # (data $seg "\de\ad\be\ef") + # (func (export "run") (result i32) + # (memory.fill (i32.const 0) (i32.const 0xAB) (i32.const 16)) + # (memory.copy (i32.const 100) (i32.const 0) (i32.const 16)) + # (memory.init $seg (i32.const 200) (i32.const 0) (i32.const 4)) + # (data.drop $seg) + # (i32.add + # (i32.mul (i32.load8_u (i32.const 100)) (i32.const 256)) + # (i32.load8_u (i32.const 200))))) + # run() = load8_u(100)*256 + load8_u(200) = 0xAB*256 + 0xDE = 171*256 + 222 = 43998. + @bulk_memory_wasm << + # Magic + version + 0x00, + 0x61, + 0x73, + 0x6D, + 0x01, + 0x00, + 0x00, + 0x00, + # Type section: 1 type, () -> i32 + 0x01, + 0x05, + 0x01, + 0x60, + 0x00, + 0x01, + 0x7F, + # Function section: 1 function, type 0 + 0x03, + 0x02, + 0x01, + 0x00, + # Memory section: 1 memory, min=1 + 0x05, + 0x03, + 0x01, + 0x00, + 0x01, + # Export section: "mem" (memory 0), "run" (func 0) + 0x07, + 0x0D, + 0x02, + 0x03, + 0x6D, + 0x65, + 0x6D, + 0x02, + 0x00, + 0x03, + 0x72, + 0x75, + 0x6E, + 0x00, + 0x00, + # DataCount section: 1 data segment + 0x0C, + 0x01, + 0x01, + # Code section: 1 function body (54 bytes) + 0x0A, + 0x38, + 0x01, + 0x36, + 0x00, + # memory.fill(dst=0, val=0xAB, len=16) + 0x41, + 0x00, + 0x41, + 0xAB, + 0x01, + 0x41, + 0x10, + 0xFC, + 0x0B, + 0x00, + # memory.copy(dst=100, src=0, len=16) + 0x41, + 0xE4, + 0x00, + 0x41, + 0x00, + 0x41, + 0x10, + 0xFC, + 0x0A, + 0x00, + 0x00, + # memory.init(dst=200, offset=0, len=4) from segment 0 + 0x41, + 0xC8, + 0x01, + 0x41, + 0x00, + 0x41, + 0x04, + 0xFC, + 0x08, + 0x00, + 0x00, + # data.drop 0 + 0xFC, + 0x09, + 0x00, + # load8_u(100) * 256 + load8_u(200) => 0xAB*256 + 0xDE = 43998 + 0x41, + 0xE4, + 0x00, + 0x2D, + 0x00, + 0x00, + 0x41, + 0x80, + 0x02, + 0x6C, + 0x41, + 0xC8, + 0x01, + 0x2D, + 0x00, + 0x00, + 0x6A, + 0x0B, + # Data section: 1 passive segment, "DE AD BE EF" + 0x0B, + 0x07, + 0x01, + 0x01, + 0x04, + 0xDE, + 0xAD, + 0xBE, + 0xEF + >> + describe "disasm/1" do test "parses a minimal add module" do assert {:ok, %Module{} = mod} = WASM.disasm(@add_wasm) @@ -654,6 +800,17 @@ defmodule QuickBEAM.WASMTest do end end + describe "bulk memory opcodes (WASM_ENABLE_BULK_MEMORY_OPT)" do + test "runs memory.fill/copy/init + data.drop (fc 08–0b)" do + # run() = load8_u(100)*256 + load8_u(200) after fill→copy→init→data.drop. + # Without WASM_ENABLE_BULK_MEMORY_OPT this module fails to compile with + # "unsupported opcode fc 0a". + {:ok, pid} = WASM.start(module: @bulk_memory_wasm) + assert {:ok, 43_998} = WASM.call(pid, "run", []) + WASM.stop(pid) + end + end + describe "supervision" do test "child_spec works" do spec = QuickBEAM.WASM.child_spec(name: :test_wasm, module: @add_wasm) @@ -923,6 +1080,15 @@ defmodule QuickBEAM.WASMTest do assert result == ["abc"] end + test "WebAssembly.instantiate compiles bulk-memory opcodes (memory.copy, fc 0a)", %{rt: rt} do + assert {:ok, 43_998} = + QuickBEAM.eval(rt, """ + const bytes = new Uint8Array([#{Enum.join(:binary.bin_to_list(@bulk_memory_wasm), ", ")}]); + const {instance} = await WebAssembly.instantiate(bytes); + instance.exports.run(); + """) + end + test "WebAssembly.compileStreaming", %{rt: rt} do {:ok, result} = QuickBEAM.eval(rt, """