Skip to content
Merged
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
24 changes: 14 additions & 10 deletions src/memory.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub const WaitQueue = struct {
waiters: std.ArrayList(Waiter) = .empty,

const Waiter = struct {
addr: u32,
addr: u64,
cond: std.Thread.Condition = .{},
};

Expand Down Expand Up @@ -170,9 +170,11 @@ pub const Memory = struct {
}

/// Read a typed value at offset + address (little-endian).
pub fn read(self: *const Memory, comptime T: type, offset: u32, address: u32) !T {
const effective = @as(u33, offset) + @as(u33, address);
if (effective + @sizeOf(T) > self.data.items.len) return error.OutOfBoundsMemoryAccess;
/// For memory64, offset and address may be full u64; overflow → OOB.
pub fn read(self: *const Memory, comptime T: type, offset: u64, address: u64) !T {
const effective, const overflow = @addWithOverflow(offset, address);
const len = self.data.items.len;
if (overflow != 0 or len < @sizeOf(T) or effective > len - @sizeOf(T)) return error.OutOfBoundsMemoryAccess;

const ptr: *const [@sizeOf(T)]u8 = @ptrCast(&self.data.items[effective]);
return switch (T) {
Expand All @@ -185,9 +187,11 @@ pub const Memory = struct {
}

/// Write a typed value at offset + address (little-endian).
pub fn write(self: *Memory, comptime T: type, offset: u32, address: u32, value: T) !void {
const effective = @as(u33, offset) + @as(u33, address);
if (effective + @sizeOf(T) > self.data.items.len) return error.OutOfBoundsMemoryAccess;
/// For memory64, offset and address may be full u64; overflow → OOB.
pub fn write(self: *Memory, comptime T: type, offset: u64, address: u64, value: T) !void {
const effective, const overflow = @addWithOverflow(offset, address);
const len = self.data.items.len;
if (overflow != 0 or len < @sizeOf(T) or effective > len - @sizeOf(T)) return error.OutOfBoundsMemoryAccess;

const ptr: *[@sizeOf(T)]u8 = @ptrCast(&self.data.items[effective]);
switch (T) {
Expand All @@ -209,7 +213,7 @@ pub const Memory = struct {

/// memory.atomic.wait32: block until notified or timeout.
/// Returns 0 (ok/woken), 1 (not-equal), 2 (timed-out).
pub fn atomicWait32(self: *Memory, addr: u32, expected: i32, timeout_ns: i64) !i32 {
pub fn atomicWait32(self: *Memory, addr: u64, expected: i32, timeout_ns: i64) !i32 {
if (!self.is_shared_memory) return error.Trap;
const loaded = try self.read(i32, 0, addr);
if (loaded != expected) return 1; // not-equal
Expand Down Expand Up @@ -246,7 +250,7 @@ pub const Memory = struct {

/// memory.atomic.wait64: block until notified or timeout.
/// Returns 0 (ok/woken), 1 (not-equal), 2 (timed-out).
pub fn atomicWait64(self: *Memory, addr: u32, expected: i64, timeout_ns: i64) !i32 {
pub fn atomicWait64(self: *Memory, addr: u64, expected: i64, timeout_ns: i64) !i32 {
if (!self.is_shared_memory) return error.Trap;
const loaded = try self.read(i64, 0, addr);
if (loaded != expected) return 1; // not-equal
Expand Down Expand Up @@ -279,7 +283,7 @@ pub const Memory = struct {

/// memory.atomic.notify: wake up to `count` waiters at `addr`.
/// Returns the number of waiters woken.
pub fn atomicNotify(self: *Memory, addr: u32, count: u32) !i32 {
pub fn atomicNotify(self: *Memory, addr: u64, count: u32) !i32 {
// Notify is valid on non-shared memory (returns 0 per spec).
_ = try self.read(u32, 0, addr); // bounds check
if (count == 0) return 0; // wake 0 threads
Expand Down
8 changes: 4 additions & 4 deletions src/module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -884,7 +884,7 @@ pub const Module = struct {
0x28...0x3E => { // memory load/store
const align_flags = try r.readU32();
if (align_flags & 0x40 != 0) _ = try r.readU32(); // memidx (multi-memory)
_ = try r.readU32(); // offset
_ = try r.readU64(); // offset (u64 for memory64)
},
0x3F, 0x40 => _ = try r.readU32(), // memory.size/grow (memidx)
0xD0 => _ = try r.readI33(), // ref.null (heap type, S33 LEB128)
Expand All @@ -910,12 +910,12 @@ pub const Module = struct {
if (sub <= 11 or sub == 92 or sub == 93) {
// v128.load/store variants (0-11), load32/64_zero (92-93) — memarg
const simd_align = try r.readU32();
_ = try r.readU32(); // offset
_ = try r.readU64(); // offset (u64 for memory64)
if (simd_align & 0x40 != 0) _ = try r.readU32(); // memidx
} else if (sub >= 84 and sub <= 91) {
// v128.load*_lane (84-87), v128.store*_lane (88-91) — memarg + lane_index
const lane_align = try r.readU32();
_ = try r.readU32(); // offset
_ = try r.readU64(); // offset (u64 for memory64)
if (lane_align & 0x40 != 0) _ = try r.readU32(); // memidx
_ = try r.readByte();
} else if (sub == 12) {
Expand Down Expand Up @@ -965,7 +965,7 @@ pub const Module = struct {
} else {
// All other atomic ops have memarg (align + offset)
_ = try r.readU32(); // align
_ = try r.readU32(); // offset
_ = try r.readU64(); // offset (u64 for memory64)
}
},
else => {}, // opcodes with no immediates
Expand Down
13 changes: 10 additions & 3 deletions src/predecode.zig
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,14 @@ pub fn predecode(alloc: Allocator, bytecode: []const u8) PredecodeError!?*IrFunc
@intCast(reader.readU32() catch return error.InvalidWasm)
else
0;
const offset = reader.readU32() catch return error.InvalidWasm;
// memory64: offset is u64 LEB128 (safe to read u64 always — validation already passed)
const offset64 = reader.readU64() catch return error.InvalidWasm;
// Bail to bytecode interpreter if offset > u32_max (very rare, memory64 only)
const offset = std.math.cast(u32, offset64) orelse {
code.deinit(alloc);
pool64.deinit(alloc);
return null;
};
try code.append(alloc, .{ .opcode = @intCast(byte), .extra = memidx, .operand = offset });
},

Expand Down Expand Up @@ -481,7 +488,7 @@ fn predecodeSimd(alloc: Allocator, code: *std.ArrayList(PreInstr), pool64: *std.
@intCast(reader.readU32() catch return false)
else
0;
const offset = reader.readU32() catch return false;
const offset = std.math.cast(u32, reader.readU64() catch return false) orelse return false;
try code.append(alloc, .{ .opcode = ir_op, .extra = memidx, .operand = offset });
},
// v128.const (0x0C): 16 raw bytes → store as 2 pool64 entries
Expand Down Expand Up @@ -516,7 +523,7 @@ fn predecodeSimd(alloc: Allocator, code: *std.ArrayList(PreInstr), pool64: *std.
@intCast(reader.readU32() catch return false)
else
0;
const offset = reader.readU32() catch return false;
const offset = std.math.cast(u32, reader.readU64() catch return false) orelse return false;
const lane = reader.readByte() catch return false;
// Pack lane into extra high byte, memidx into extra low byte
try code.append(alloc, .{ .opcode = ir_op, .extra = (@as(u16, lane) << 8) | memidx, .operand = offset });
Expand Down
54 changes: 33 additions & 21 deletions src/validate.zig
Original file line number Diff line number Diff line change
Expand Up @@ -972,7 +972,11 @@ const Validator = struct {
const align_val = align_byte & 0x3F;
const has_memidx = (align_byte & 0x40) != 0;
const memidx: u32 = if (has_memidx) try reader.readU32() else 0;
_ = try reader.readU32(); // offset
// memory64: offset is u64 LEB128
if (self.memAddrType(memidx) == .i64)
_ = try reader.readU64()
else
_ = try reader.readU32();

if (naturalAlignment(op)) |nat| {
if (align_val > nat) return error.InvalidAlignment;
Expand All @@ -990,7 +994,11 @@ const Validator = struct {
const align_val = align_byte & 0x3F;
const has_memidx = (align_byte & 0x40) != 0;
const memidx: u32 = if (has_memidx) try reader.readU32() else 0;
_ = try reader.readU32(); // offset
// memory64: offset is u64 LEB128
if (self.memAddrType(memidx) == .i64)
_ = try reader.readU64()
else
_ = try reader.readU32();

if (naturalAlignment(op)) |nat| {
if (align_val > nat) return error.InvalidAlignment;
Expand Down Expand Up @@ -1194,7 +1202,11 @@ const Validator = struct {
const align_val = align_byte & 0x3F;
const has_memidx = (align_byte & 0x40) != 0;
const memidx: u32 = if (has_memidx) try reader.readU32() else 0;
_ = try reader.readU32(); // offset
// memory64: offset is u64 LEB128
if (self.memAddrType(memidx) == .i64)
_ = try reader.readU64()
else
_ = try reader.readU32();

// Check memory index
const total_mems = self.module.num_imported_memories + self.module.memories.items.len;
Expand Down Expand Up @@ -1244,21 +1256,21 @@ const Validator = struct {
const align_byte = try reader.readU32();
const align_val = align_byte & 0x3F;
const has_memidx = (align_byte & 0x40) != 0;
if (has_memidx) _ = try reader.readU32();
_ = try reader.readU32(); // offset
const memidx: u32 = if (has_memidx) try reader.readU32() else 0;
if (self.memAddrType(memidx) == .i64) _ = try reader.readU64() else _ = try reader.readU32();
if (align_val > 4) return error.InvalidAlignment; // natural = 16 = 2^4
try self.popI32(); // address
_ = try self.popExpecting(self.memAddrType(memidx)); // address
try self.pushVal(.v128);
},
// v128.load8x8_s/u, load16x4_s/u, load32x2_s/u (1-6) — memarg, natural align 8 (2^3)
1, 2, 3, 4, 5, 6 => {
const align_byte = try reader.readU32();
const align_val = align_byte & 0x3F;
const has_memidx = (align_byte & 0x40) != 0;
if (has_memidx) _ = try reader.readU32();
_ = try reader.readU32();
const memidx: u32 = if (has_memidx) try reader.readU32() else 0;
if (self.memAddrType(memidx) == .i64) _ = try reader.readU64() else _ = try reader.readU32();
if (align_val > 3) return error.InvalidAlignment;
try self.popI32();
_ = try self.popExpecting(self.memAddrType(memidx));
try self.pushVal(.v128);
},
// v128.load8_splat(7), load16_splat(8), load32_splat(9), load64_splat(10) — memarg
Expand All @@ -1271,11 +1283,11 @@ const Validator = struct {
const align_byte = try reader.readU32();
const align_val = align_byte & 0x3F;
const has_memidx = (align_byte & 0x40) != 0;
if (has_memidx) _ = try reader.readU32();
_ = try reader.readU32();
const memidx: u32 = if (has_memidx) try reader.readU32() else 0;
if (self.memAddrType(memidx) == .i64) _ = try reader.readU64() else _ = try reader.readU32();
if (align_val > 4) return error.InvalidAlignment;
try self.popV128(); // value
try self.popI32(); // address
_ = try self.popExpecting(self.memAddrType(memidx)); // address
},
// v128.const (12) — 16 bytes immediate
12 => { _ = try reader.readBytes(16); try self.pushVal(.v128); },
Expand Down Expand Up @@ -1419,38 +1431,38 @@ const Validator = struct {
const align_byte = try reader.readU32();
const align_val = align_byte & 0x3F;
const has_memidx = (align_byte & 0x40) != 0;
if (has_memidx) _ = try reader.readU32();
_ = try reader.readU32(); // offset
const memidx: u32 = if (has_memidx) try reader.readU32() else 0;
if (self.memAddrType(memidx) == .i64) _ = try reader.readU64() else _ = try reader.readU32();
if (align_val > natural_align) return error.InvalidAlignment;
try self.popI32(); // address
_ = try self.popExpecting(self.memAddrType(memidx)); // address
try self.pushVal(.v128);
}

fn validateSimdLoadLane(self: *Validator, reader: *Reader, natural_align: u5, max_lanes: u8) !void {
const align_byte = try reader.readU32();
const align_val = align_byte & 0x3F;
const has_memidx = (align_byte & 0x40) != 0;
if (has_memidx) _ = try reader.readU32();
_ = try reader.readU32(); // offset
const memidx: u32 = if (has_memidx) try reader.readU32() else 0;
if (self.memAddrType(memidx) == .i64) _ = try reader.readU64() else _ = try reader.readU32();
const lane = try reader.readByte();
if (align_val > natural_align) return error.InvalidAlignment;
if (lane >= max_lanes) return error.InvalidLaneIndex;
try self.popV128(); // existing vector
try self.popI32(); // address
_ = try self.popExpecting(self.memAddrType(memidx)); // address
try self.pushVal(.v128);
}

fn validateSimdStoreLane(self: *Validator, reader: *Reader, natural_align: u5, max_lanes: u8) !void {
const align_byte = try reader.readU32();
const align_val = align_byte & 0x3F;
const has_memidx = (align_byte & 0x40) != 0;
if (has_memidx) _ = try reader.readU32();
_ = try reader.readU32(); // offset
const memidx: u32 = if (has_memidx) try reader.readU32() else 0;
if (self.memAddrType(memidx) == .i64) _ = try reader.readU64() else _ = try reader.readU32();
const lane = try reader.readByte();
if (align_val > natural_align) return error.InvalidAlignment;
if (lane >= max_lanes) return error.InvalidLaneIndex;
try self.popV128(); // vector value
try self.popI32(); // address
_ = try self.popExpecting(self.memAddrType(memidx)); // address
}

fn validateLane(self: *Validator, reader: *Reader, max_lanes: u8) !void {
Expand Down
Loading
Loading