Skip to content

Commit 9b7a1bb

Browse files
committed
Fix WebAssembly CI failures
1 parent b704a7f commit 9b7a1bb

File tree

9 files changed

+61
-35
lines changed

9 files changed

+61
-35
lines changed

lib/quickbeam/wasm.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ defmodule QuickBEAM.WASM do
5151
{:ok, pid} = QuickBEAM.WASM.start(module: wasm_bytes)
5252
{:ok, 42} = QuickBEAM.WASM.call(pid, "add", [40, 2])
5353
"""
54+
alias QuickBEAM.WASM.Server
55+
5456
@spec start(keyword()) :: GenServer.on_start()
5557
def start(opts) do
56-
QuickBEAM.WASM.Server.start_link(opts)
58+
Server.start_link(opts)
5759
end
5860

5961
@doc """
@@ -64,7 +66,7 @@ defmodule QuickBEAM.WASM do
6466
"""
6567
@spec start_link(keyword()) :: GenServer.on_start()
6668
def start_link(opts) do
67-
QuickBEAM.WASM.Server.start_link(opts)
69+
Server.start_link(opts)
6870
end
6971

7072
@doc """

lib/quickbeam/wasm/import_rewriter.ex

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -81,30 +81,9 @@ defmodule QuickBEAM.WASM.ImportRewriter do
8181
defp validate_import_value(%{"kind" => "memory", "min" => min, "max" => max}, provided) do
8282
bytes = Map.get(provided, "bytes", <<>>)
8383

84-
cond do
85-
not is_binary(bytes) ->
86-
{:error, "memory import bytes must be a binary"}
87-
88-
rem(byte_size(bytes), 65_536) != 0 ->
89-
{:error, "memory import size must be page-aligned"}
90-
91-
true ->
92-
actual_min = div(byte_size(bytes), 65_536)
93-
actual_max = Map.get(provided, "max")
94-
95-
cond do
96-
actual_min < min ->
97-
{:error, "memory import minimum too small"}
98-
99-
max != nil and actual_min > max ->
100-
{:error, "memory import current size exceeds declared maximum"}
101-
102-
max != nil and (is_nil(actual_max) or actual_max > max) ->
103-
{:error, "memory import maximum too large"}
104-
105-
true ->
106-
:ok
107-
end
84+
case validate_memory_bytes(bytes) do
85+
:ok -> validate_memory_limits(min, max, bytes, Map.get(provided, "max"))
86+
error -> error
10887
end
10988
end
11089

@@ -118,6 +97,32 @@ defmodule QuickBEAM.WASM.ImportRewriter do
11897
defp validate_import_value(%{"kind" => "global"}, _provided),
11998
do: {:error, "global import type mismatch"}
12099

100+
defp validate_memory_bytes(bytes) when not is_binary(bytes),
101+
do: {:error, "memory import bytes must be a binary"}
102+
103+
defp validate_memory_bytes(bytes) when rem(byte_size(bytes), 65_536) != 0,
104+
do: {:error, "memory import size must be page-aligned"}
105+
106+
defp validate_memory_bytes(_bytes), do: :ok
107+
108+
defp validate_memory_limits(min, max, bytes, actual_max) do
109+
actual_min = div(byte_size(bytes), 65_536)
110+
111+
cond do
112+
actual_min < min ->
113+
{:error, "memory import minimum too small"}
114+
115+
max != nil and actual_min > max ->
116+
{:error, "memory import current size exceeds declared maximum"}
117+
118+
max != nil and (is_nil(actual_max) or actual_max > max) ->
119+
{:error, "memory import maximum too large"}
120+
121+
true ->
122+
:ok
123+
end
124+
end
125+
121126
defp validate_global_value("i32", value) when is_integer(value), do: :ok
122127
defp validate_global_value("i64", value) when is_integer(value) or is_binary(value), do: :ok
123128
defp validate_global_value("f32", value) when is_number(value), do: :ok

lib/quickbeam/wasm/parser.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule QuickBEAM.WASM.Parser do
22
@moduledoc false
33

4-
alias QuickBEAM.WASM.{Module, Function}
4+
alias QuickBEAM.WASM.{Function, Module}
55

66
@wasm_magic <<0x00, 0x61, 0x73, 0x6D>>
77
@wasm_version_1 <<0x01, 0x00, 0x00, 0x00>>

lib/quickbeam/wasm_api.ex

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -242,10 +242,7 @@ defmodule QuickBEAM.WasmAPI do
242242
defp initialize_imported_memories(_inst_ref, []), do: :ok
243243

244244
defp initialize_imported_memories(inst_ref, [bytes]) do
245-
case QuickBEAM.Native.wasm_write_memory(inst_ref, 0, bytes) do
246-
:ok -> :ok
247-
{:error, msg} -> {:error, msg}
248-
end
245+
QuickBEAM.Native.wasm_write_memory(inst_ref, 0, bytes)
249246
end
250247

251248
defp initialize_imported_memories(_inst_ref, _many),

lib/quickbeam/wasm_js.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ fn instruction_limit(ctx: *qjs.JSContext, state: *ContextState) c_int {
237237
}
238238

239239
fn js_value_to_wasm(ctx: *qjs.JSContext, value: qjs.JSValue, kind: wamr.wasm_valkind_t) !wamr.wasm_val_t {
240+
// SAFETY: `result` is fully assigned in the kind-specific branch before it is returned.
240241
var result: wamr.wasm_val_t = undefined;
241242
result.kind = kind;
242243
result._paddings = [_]u8{0} ** 7;
@@ -490,6 +491,7 @@ fn wasm_read_global_impl(
490491
defer qjs.JS_FreeCString(ctx, name_ptr);
491492

492493
var err_buf: [256]u8 = undefined;
494+
// SAFETY: WAMR initializes `value` on successful global reads before it is used.
493495
var value: wamr.wasm_val_t = undefined;
494496
if (!wamr.wamr_bridge_read_global(entry.managed.inst, name_ptr, &value, &err_buf, err_buf.len)) {
495497
return throw_error(ctx, std.mem.sliceTo(&err_buf, 0));
@@ -515,6 +517,7 @@ fn wasm_write_global_impl(
515517
defer qjs.JS_FreeCString(ctx, name_ptr);
516518

517519
var err_buf: [256]u8 = undefined;
520+
// SAFETY: WAMR initializes `current` on successful global reads before it is inspected.
518521
var current: wamr.wasm_val_t = undefined;
519522
if (!wamr.wamr_bridge_read_global(entry.managed.inst, name_ptr, &current, &err_buf, err_buf.len)) {
520523
return throw_error(ctx, std.mem.sliceTo(&err_buf, 0));

lib/quickbeam/wasm_nif.zig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const HostImportSpec = wasm_host_imports.ImportSpec;
2828

2929
fn get_map_binary(env: *e.ErlNifEnv, map: e.ErlNifTerm, key: [:0]const u8) ![]const u8 {
3030
const key_term = beam.make_into_atom(key, .{ .env = env });
31+
// SAFETY: `enif_get_map_value` initializes `value_term` before it is read.
3132
var value_term: e.ErlNifTerm = undefined;
3233
if (e.enif_get_map_value(env, map, key_term.v, &value_term) == 0) return error.BadArg;
3334
return beam.get([]const u8, .{ .v = value_term }, .{ .env = env });
@@ -43,7 +44,9 @@ fn parse_host_imports(env: *e.ErlNifEnv, imports: beam.term) ![]HostImportSpec {
4344
var list = imports.v;
4445
var index: usize = 0;
4546
while (index < result.len) : (index += 1) {
47+
// SAFETY: `enif_get_list_cell` initializes `head` and `tail` on success before use.
4648
var head: e.ErlNifTerm = undefined;
49+
// SAFETY: `enif_get_list_cell` initializes `head` and `tail` on success before use.
4750
var tail: e.ErlNifTerm = undefined;
4851
if (e.enif_get_list_cell(env, list, &head, &tail) == 0) return error.BadArg;
4952

@@ -67,6 +70,7 @@ fn next_host_call_id() u64 {
6770
}
6871

6972
fn extract_error_message(env: *e.ErlNifEnv, term: e.ErlNifTerm, fallback: []const u8) []const u8 {
73+
// SAFETY: `enif_inspect_binary` initializes `bin` on success before it is read.
7074
var bin: e.ErlNifBinary = undefined;
7175
if (e.enif_inspect_binary(env, term, &bin) != 0 and bin.size > 0) {
7276
return bin.data[0..bin.size];
@@ -256,6 +260,7 @@ fn parse_f64_term(env: *e.ErlNifEnv, term: beam.term) !f64 {
256260
}
257261

258262
fn term_to_wasm_val(env: *e.ErlNifEnv, term: beam.term, kind: wamr.wasm_valkind_t) !wamr.wasm_val_t {
263+
// SAFETY: `value` is fully populated in the kind-specific branch before it is returned.
259264
var value: wamr.wasm_val_t = undefined;
260265
value.kind = kind;
261266
value._paddings = [_]u8{0} ** 7;
@@ -509,6 +514,7 @@ pub fn wasm_memory_grow(inst_res: WasmInstanceResource, delta: u32) beam.term {
509514

510515
pub fn wasm_read_memory(inst_res: WasmInstanceResource, offset: u32, length: u32) beam.term {
511516
const env = beam.context.env orelse return make_error("no env");
517+
// SAFETY: `enif_alloc_binary` initializes `bin` on success before it is passed on.
512518
var bin: e.ErlNifBinary = undefined;
513519
if (e.enif_alloc_binary(length, &bin) == 0) return make_error("out of memory");
514520

@@ -538,6 +544,7 @@ pub fn wasm_read_global(inst_res: WasmInstanceResource, name: []const u8) beam.t
538544
defer std.heap.c_allocator.free(name_z);
539545

540546
var err_buf: [256]u8 = undefined;
547+
// SAFETY: WAMR initializes `value` on successful global reads before it is used.
541548
var value: wamr.wasm_val_t = undefined;
542549

543550
if (!wamr.wamr_bridge_read_global(inst, name_z.ptr, &value, &err_buf, err_buf.len)) {
@@ -557,6 +564,7 @@ pub fn wasm_write_global(inst_res: WasmInstanceResource, name: []const u8, value
557564
defer std.heap.c_allocator.free(name_z);
558565

559566
var err_buf: [256]u8 = undefined;
567+
// SAFETY: WAMR initializes `current` on successful global reads before it is inspected.
560568
var current: wamr.wasm_val_t = undefined;
561569

562570
if (!wamr.wamr_bridge_read_global(inst, name_z.ptr, &current, &err_buf, err_buf.len)) {

lib/quickbeam/worker.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,7 @@ pub const WorkerState = struct {
589589
}
590590
self.atoms.deinit(self.ctx);
591591
wasm_js.destroy_context(self.ctx);
592+
qjs.JS_RunGC(self.rt);
592593
qjs.JS_FreeContext(self.ctx);
593594
self.ctx = qjs.JS_NewContext(self.rt) orelse {
594595
result.ok = false;
@@ -1200,6 +1201,7 @@ pub fn quickbeam_wasm_host_invoke_js_impl(runtime_data: ?*anyopaque, callback_na
12001201
if (result.env) |result_env| {
12011202
defer beam.free_env(result_env);
12021203
if (result.term) |term| {
1204+
// SAFETY: `enif_inspect_binary` initializes `bin` on success before it is read.
12031205
var bin: e.ErlNifBinary = undefined;
12041206
if (e.enif_inspect_binary(result_env, term, &bin) != 0 and bin.size > 0) {
12051207
copy_error_buf(err_buf, err_buf_size, bin.data[0..bin.size]);

test/core/memory_test.exs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,16 @@ defmodule QuickBEAM.Core.MemoryTest do
3535
test "memory stabilizes across reset cycles", %{} do
3636
{:ok, rt} = QuickBEAM.start()
3737

38-
# First cycle establishes the pool size
38+
# First reset can under-report because some runtime structures are only
39+
# allocated on the first evaluation after a fresh context is installed.
40+
# Establish the baseline after one full warm reset cycle.
41+
QuickBEAM.eval(rt, """
42+
globalThis.data = [];
43+
for (let i = 0; i < 5000; i++) data.push({x: i});
44+
""")
45+
46+
QuickBEAM.reset(rt)
47+
3948
QuickBEAM.eval(rt, """
4049
globalThis.data = [];
4150
for (let i = 0; i < 5000; i++) data.push({x: i});

test/wasm_test.exs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule QuickBEAM.WASMTest do
22
use ExUnit.Case, async: false
33

44
alias QuickBEAM.WASM
5-
alias QuickBEAM.WASM.{Module, Function}
5+
alias QuickBEAM.WASM.{Function, Module}
66

77
# Minimal "add" module in WAT:
88
# (module
@@ -643,13 +643,13 @@ defmodule QuickBEAM.WASMTest do
643643
test "memory_size returns bytes" do
644644
{:ok, pid} = WASM.start(module: @memory_func_wasm)
645645
{:ok, size} = WASM.memory_size(pid)
646-
assert size == 65536
646+
assert size == 65_536
647647
WASM.stop(pid)
648648
end
649649

650650
test "read out of bounds returns error" do
651651
{:ok, pid} = WASM.start(module: @memory_func_wasm)
652-
{:error, _} = WASM.read_memory(pid, 65530, 100)
652+
{:error, _} = WASM.read_memory(pid, 65_530, 100)
653653
WASM.stop(pid)
654654
end
655655
end

0 commit comments

Comments
 (0)