Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Fixed

- **`load_module/3` now propagates top-level module evaluation errors** — runtime exceptions thrown while evaluating module code are returned as `{:error, %QuickBEAM.JSError{}}` instead of incorrectly succeeding with `:ok`.
- **Propagate runtime errors from pending job execution** — `load_module/3`, `eval/3`, `call/3`, and `load_bytecode/2` now detect and return errors thrown during QuickJS job draining instead of silently swallowing them.

## 0.8.1

Expand Down
103 changes: 103 additions & 0 deletions docs/wasm-js-api-roadmap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# WebAssembly JS API roadmap

## Goal

Bring QuickBEAM's `WebAssembly` polyfill closer to the WebAssembly JavaScript Interface standard.

## Standards checked

- https://webassembly.github.io/spec/js-api/
- https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface
- https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface/instantiate_static
- https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface/Module
- https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface/Memory
- https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface/Table
- https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/JavaScript_interface/Global

## Current status

### Implemented

- `WebAssembly.compile(bytes)`
- `WebAssembly.instantiate(bytes | module)`
- `WebAssembly.validate(bytes)`
- `new WebAssembly.Module(bytes)`
- `new WebAssembly.Instance(module)`
- `WebAssembly.Module.exports(module)`
- `WebAssembly.Module.imports(module)`
- numeric wasm calls for `i32`, `i64`, `f32`, `f64`
- `i64` results mapped to JS `BigInt`
- exported numeric globals
- exported memory exposure
- `WebAssembly.Module.customSections()`
- `WebAssembly.compileStreaming()`
- `WebAssembly.instantiateStreaming()`
- `importObject` validation for memory/global imports
- snapshot-style memory/global imports for instantiation
- exported imported memory reuses the original `WebAssembly.Memory` wrapper

### Not yet standard-complete

- function imports
- runtime-backed `Memory.buffer` semantics
- runtime-backed tables
- full global import/export parity
- table imports
- live shared imported memory/global semantics
- compile options (`builtins`, `importedStringConstants`)
- `Tag`, `Exception`, `JSTag`
- exact object caching / identity semantics from the spec
- exact error semantics for every edge case

## Implementation phases

### Phase 1 — Instantiation and linking

1. implement function imports
2. implement memory imports
3. implement table imports
4. implement global imports
5. validate `importObject` shape and types
6. return `LinkError` and `TypeError` in the right places

### Phase 2 — Memory

1. make exported memory runtime-backed
2. make imported memory visible to wasm
3. improve `buffer` semantics
4. improve `grow()` semantics

### Phase 3 — Table

1. make exported tables runtime-backed
2. make imported tables runtime-backed
3. implement shared JS/wasm mutation visibility

### Phase 4 — Global

1. imported globals
2. exported globals with shared state
3. mutability checks
4. `i64` globals as `BigInt`

### Phase 5 — Namespace completeness

1. `compileStreaming()`
2. `instantiateStreaming()`
3. `Module.customSections()`

### Phase 6 — JS API 2.0 / newer features

1. compile options
2. `WebAssembly.JSTag`
3. `WebAssembly.Tag`
4. `WebAssembly.Exception`

## Recommended order

1. imports
2. real memory semantics
3. real tables
4. real globals
5. streaming and custom sections
6. newer API additions
3 changes: 2 additions & 1 deletion lib/quickbeam/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ defmodule QuickBEAM.Application do
id: :quickbeam_pg,
start: {:pg, :start_link, [QuickBEAM.BroadcastChannel]}
},
QuickBEAM.LockManager
QuickBEAM.LockManager,
QuickBEAM.WasmAPI
]

QuickBEAM.Storage.init()
Expand Down
7 changes: 6 additions & 1 deletion lib/quickbeam/js_to_beam.zig
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ fn convert_recursive(ctx: *qjs.JSContext, val: qjs.JSValue, state: *ConvertState
const ptr = qjs.JS_ToCString(ctx, val);
if (ptr != null) {
defer qjs.JS_FreeCString(ctx, ptr);
return beam.make(std.mem.span(ptr), state.opts).v;
const value = std.mem.span(ptr);
if (std.fmt.parseInt(i64, value, 10)) |parsed| {
return beam.make(parsed, state.opts).v;
} else |_| {
return beam.make(value, state.opts).v;
}
}
return beam.make_into_atom("nil", state.opts).v;
}
Expand Down
111 changes: 107 additions & 4 deletions lib/quickbeam/native.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,93 @@ defmodule QuickBEAM.Native do
{:priv, String.replace_prefix(path, "priv/", ""), @lexbor_cflags}
end)

@wamr_cflags [
"-std=c11",
"-D_GNU_SOURCE",
"-DWASM_ENABLE_INTERP=1",
"-DWASM_ENABLE_AOT=0",
"-DWASM_ENABLE_FAST_INTERP=0",
"-DWASM_ENABLE_LIBC_BUILTIN=0",
"-DWASM_ENABLE_LIBC_WASI=0",
"-DWASM_ENABLE_MULTI_MODULE=0",
"-DWASM_ENABLE_BULK_MEMORY=1",
"-DWASM_ENABLE_REF_TYPES=1",
"-DWASM_ENABLE_SIMD=0",
"-DWASM_ENABLE_TAIL_CALL=1",
"-DWASM_ENABLE_MEMORY64=0",
"-DWASM_ENABLE_GC=0",
"-DWASM_ENABLE_THREAD_MGR=0",
"-DWASM_ENABLE_SHARED_MEMORY=0",
"-DWASM_ENABLE_EXCE_HANDLING=0",
"-DWASM_ENABLE_MINI_LOADER=0",
"-DWASM_ENABLE_WAMR_COMPILER=0",
"-DWASM_ENABLE_JIT=0",
"-DWASM_ENABLE_FAST_JIT=0",
"-DWASM_ENABLE_DEBUG_INTERP=0",
"-DWASM_ENABLE_DUMP_CALL_STACK=0",
"-DWASM_ENABLE_PERF_PROFILING=0",
"-DWASM_ENABLE_LOAD_CUSTOM_SECTION=0",
"-DWASM_ENABLE_CUSTOM_NAME_SECTION=1",
"-DWASM_ENABLE_GLOBAL_HEAP_POOL=0",
"-DWASM_ENABLE_SPEC_TEST=0",
"-DWASM_ENABLE_LABELS_AS_VALUES=1",
"-DWASM_ENABLE_WASM_CACHE=0",
"-DWASM_ENABLE_STRINGREF=0",
"-DWASM_MEM_ALLOC_WITH_SYSTEM_ALLOCATOR=1",
"-DWASM_RUNTIME_API_EXTERN=",
"-DBH_MALLOC=wasm_runtime_malloc",
"-DBH_FREE=wasm_runtime_free",

"-I#{@c_src_dir}",
"-I#{@c_src_dir}/wamr/include",
"-I#{@c_src_dir}/wamr/interpreter",
"-I#{@c_src_dir}/wamr/common",
"-I#{@c_src_dir}/wamr/shared/utils",
"-I#{@c_src_dir}/wamr/shared/platform/include",
"-I#{@c_src_dir}/wamr/shared/mem-alloc",
"-I#{@c_src_dir}/wamr/shared/platform/#{if(:os.type() == {:unix, :darwin}, do: "darwin", else: "linux")}"
]

@wamr_src (Path.wildcard("priv/c_src/wamr/interpreter/wasm_loader.c") ++
Path.wildcard("priv/c_src/wamr/interpreter/wasm_interp_classic.c") ++
Path.wildcard("priv/c_src/wamr/interpreter/wasm_runtime.c") ++
Path.wildcard("priv/c_src/wamr/common/wasm_runtime_common.c") ++
Path.wildcard("priv/c_src/wamr/common/wasm_exec_env.c") ++
Path.wildcard("priv/c_src/wamr/common/wasm_memory.c") ++
Path.wildcard("priv/c_src/wamr/common/wasm_native.c") ++
Path.wildcard("priv/c_src/wamr/common/wasm_application.c") ++
Path.wildcard("priv/c_src/wamr/common/wasm_loader_common.c") ++
Path.wildcard("priv/c_src/wamr/common/wasm_blocking_op.c") ++
Path.wildcard("priv/c_src/wamr/common/wasm_c_api.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/bh_assert.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/bh_common.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/bh_hashmap.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/bh_leb128.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/bh_list.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/bh_log.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/bh_queue.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/bh_vector.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/bh_bitmap.c") ++
Path.wildcard("priv/c_src/wamr/shared/utils/runtime_timer.c") ++
Path.wildcard("priv/c_src/wamr/shared/mem-alloc/mem_alloc.c") ++
Path.wildcard("priv/c_src/wamr/shared/mem-alloc/ems/*.c") ++
Path.wildcard("priv/c_src/wamr/shared/platform/common/posix/posix_malloc.c") ++
Path.wildcard("priv/c_src/wamr/shared/platform/common/posix/posix_memmap.c") ++
Path.wildcard("priv/c_src/wamr/shared/platform/common/posix/posix_thread.c") ++
Path.wildcard("priv/c_src/wamr/shared/platform/common/posix/posix_time.c") ++
Path.wildcard("priv/c_src/wamr/shared/platform/common/posix/posix_blocking_op.c") ++
[if(:os.type() == {:unix, :darwin},
do: "priv/c_src/wamr/shared/platform/darwin/platform_init.c",
else: "priv/c_src/wamr/shared/platform/linux/platform_init.c"
)] ++
["priv/c_src/wamr/common/arch/invokeNative_general.c"] ++
["priv/c_src/wamr/shared/platform/common/memory/mremap.c"] ++
["priv/c_src/wamr_bridge.c"])
|> Enum.sort()
|> Enum.map(fn path ->
{:priv, String.replace_prefix(path, "priv/", ""), @wamr_cflags}
end)

@quickjs_cflags if System.get_env("QUICKBEAM_UBSAN") == "1",
do: [
"-std=c11",
Expand All @@ -39,7 +126,13 @@ defmodule QuickBEAM.Native do
c: [
include_dirs: [
{:priv, "c_src"},
{:priv, "c_src/lexbor/ports/posix"}
{:priv, "c_src/lexbor/ports/posix"},
{:priv, "c_src/wamr/include"},
{:priv, "c_src/wamr/interpreter"},
{:priv, "c_src/wamr/common"},
{:priv, "c_src/wamr/shared/utils"},
{:priv, "c_src/wamr/shared/platform/include"},
{:priv, "c_src/wamr/shared/mem-alloc"}
],
src:
[
Expand All @@ -48,9 +141,9 @@ defmodule QuickBEAM.Native do
{:priv, "c_src/libunicode.c", @quickjs_cflags},
{:priv, "c_src/dtoa.c", @quickjs_cflags},
{:priv, "c_src/lexbor_bridge.c", @lexbor_cflags}
] ++ @lexbor_src
] ++ @lexbor_src ++ @wamr_src
],
resources: [:RuntimeResource, :PoolResource],
resources: [:RuntimeResource, :PoolResource, :WasmModuleResource, :WasmInstanceResource],
nifs: [
eval: 3,
compile: 2,
Expand Down Expand Up @@ -95,6 +188,16 @@ defmodule QuickBEAM.Native do
pool_dom_text: 3,
pool_dom_html: 2,
disasm_bytecode: 1,
load_addon: 3
load_addon: 3,
wasm_compile: 1,
wasm_start: 3,
wasm_stop: 1,
wasm_call: 3,
wasm_memory_size: 1,
wasm_memory_grow: 2,
wasm_read_memory: 3,
wasm_write_memory: 3,
wasm_read_global: 2,
wasm_write_global: 3
]
end
13 changes: 13 additions & 0 deletions lib/quickbeam/quickbeam.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@ const worker = @import("worker.zig");
const ct = @import("context_types.zig");
const context_worker = @import("context_worker.zig");
pub const napi = @import("napi.zig");
pub const wasm_nif = @import("wasm_nif.zig");
pub const WasmModuleResource = wasm_nif.WasmModuleResource;
pub const WasmInstanceResource = wasm_nif.WasmInstanceResource;
pub const wasm_compile = wasm_nif.wasm_compile;
pub const wasm_start = wasm_nif.wasm_start;
pub const wasm_stop = wasm_nif.wasm_stop;
pub const wasm_call = wasm_nif.wasm_call;
pub const wasm_memory_size = wasm_nif.wasm_memory_size;
pub const wasm_memory_grow = wasm_nif.wasm_memory_grow;
pub const wasm_read_memory = wasm_nif.wasm_read_memory;
pub const wasm_write_memory = wasm_nif.wasm_write_memory;
pub const wasm_read_global = wasm_nif.wasm_read_global;
pub const wasm_write_global = wasm_nif.wasm_write_global;

const std = types.std;
const beam = @import("beam");
Expand Down
15 changes: 14 additions & 1 deletion lib/quickbeam/runtime.ex
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,20 @@ defmodule QuickBEAM.Runtime do
"__storage_key" => &QuickBEAM.Storage.key/1,
"__storage_length" => &QuickBEAM.Storage.length/1,
"__eventsource_open" => {:with_caller, &QuickBEAM.EventSource.open/2},
"__eventsource_close" => &QuickBEAM.EventSource.close/1
"__eventsource_close" => &QuickBEAM.EventSource.close/1,
"__wasm_compile" => &QuickBEAM.WasmAPI.compile/1,
"__wasm_validate" => &QuickBEAM.WasmAPI.validate/1,
"__wasm_start" => &QuickBEAM.WasmAPI.start/1,
"__wasm_call" => &QuickBEAM.WasmAPI.call/1,
"__wasm_module_exports" => &QuickBEAM.WasmAPI.module_exports/1,
"__wasm_module_imports" => &QuickBEAM.WasmAPI.module_imports/1,
"__wasm_module_custom_sections" => &QuickBEAM.WasmAPI.module_custom_sections/1,
"__wasm_memory_size" => &QuickBEAM.WasmAPI.memory_size/1,
"__wasm_memory_grow" => &QuickBEAM.WasmAPI.memory_grow/1,
"__wasm_read_memory" => &QuickBEAM.WasmAPI.read_memory/1,
"__wasm_write_memory" => &QuickBEAM.WasmAPI.write_memory/1,
"__wasm_read_global" => &QuickBEAM.WasmAPI.read_global/1,
"__wasm_write_global" => &QuickBEAM.WasmAPI.write_global/1
}

@beam_handlers %{
Expand Down
Loading
Loading