Skip to content

Commit

Permalink
mem64 now actually tests offsets >4GB, fixed a bunch of bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
rdunnington committed May 17, 2024
1 parent e2a22ae commit 2b17276
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 23 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
python-version: '3.11'
cache: pip

- name: Install dependencies
- name: Install python dependencies
working-directory: test/wasi/wasi-testsuite/test-runner
run: python3 -m pip install -r requirements.txt

Expand All @@ -44,6 +44,10 @@ jobs:
run: |
zig build test-wasm -- --log-suite
- name: Run mem64 test
run: |
zig build test-mem64
- name: Run wasi testsuite
run: |
zig build test-wasi
33 changes: 30 additions & 3 deletions src/definition.zig
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ pub const Limits = struct {
// 0x06 n:u64 ⇒ i64, {min n, max ?}, 1 ;; from threads proposal
// 0x07 n:u64 m:u64 ⇒ i64, {min n, max m}, 1 ;; from threads proposal

const k_max_bytes_i32 = k_max_pages_i32 * MemoryDefinition.k_page_size;
const k_max_pages_i32 = std.math.powi(usize, 2, 16) catch unreachable;

// Technically the max bytes should be maxInt(u64), but that is wayyy more memory than PCs have available and
// is just a waste of virtual address space in the implementation. Instead we'll set the upper limit to 128GB.
const k_max_bytes_i64 = (1024 * 1024 * 1024 * 128);
const k_max_pages_i64 = k_max_bytes_i64 / MemoryDefinition.k_page_size;

fn decode(reader: anytype) !Limits {
const limit_type: u8 = try reader.readByte();

Expand Down Expand Up @@ -327,6 +335,18 @@ pub const Limits = struct {
pub fn indexType(self: Limits) ValType {
return if (self.limit_type < 4) .I32 else .I64;
}

pub fn maxPages(self: Limits) usize {
if (self.max) |max| {
return @max(1, max);
}

return self.indexTypeMaxPages();
}

pub fn indexTypeMaxPages(self: Limits) usize {
return if (self.limit_type < 4) k_max_pages_i32 else k_max_pages_i64;
}
};

const BlockType = enum {
Expand Down Expand Up @@ -652,7 +672,6 @@ pub const MemoryDefinition = struct {
limits: Limits,

pub const k_page_size: usize = 64 * 1024;
pub const k_max_pages: usize = std.math.powi(usize, 2, 16) catch unreachable;
};

pub const ElementMode = enum {
Expand Down Expand Up @@ -2942,15 +2961,23 @@ pub const ModuleDefinition = struct {
while (memory_index < num_memories) : (memory_index += 1) {
var limits = try Limits.decode(reader);

if (limits.min > MemoryDefinition.k_max_pages) {
if (limits.min > limits.maxPages()) {
self.log.err(
"Validation error: max memory pages exceeded. Got {} but max is {}",
.{ limits.min, limits.indexTypeMaxPages() },
);
return error.ValidationMemoryMaxPagesExceeded;
}

if (limits.max) |max| {
if (max < limits.min) {
return error.ValidationMemoryInvalidMaxLimit;
}
if (max > MemoryDefinition.k_max_pages) {
if (max > limits.indexTypeMaxPages()) {
self.log.err(
"Validation error: max memory pages exceeded. Got {} but max is {}",
.{ max, limits.indexTypeMaxPages() },
);
return error.ValidationMemoryMaxPagesExceeded;
}
}
Expand Down
12 changes: 6 additions & 6 deletions src/instance.zig
Original file line number Diff line number Diff line change
Expand Up @@ -240,16 +240,16 @@ pub const MemoryInstance = struct {
};

pub const k_page_size: usize = MemoryDefinition.k_page_size;
pub const k_max_pages: usize = MemoryDefinition.k_max_pages;

limits: Limits,
mem: BackingMemory,

pub fn init(limits: Limits, params: ?WasmMemoryExternal) MemoryInstance {
const max_pages = if (limits.max) |max| @max(1, max) else k_max_pages;
const max_pages = limits.maxPages();
const max_bytes: u64 = max_pages * k_page_size;

var mem = if (params == null) BackingMemory{
.Internal = StableArray(u8).init(max_pages * k_page_size),
.Internal = StableArray(u8).init(@intCast(max_bytes)),
} else BackingMemory{ .External = .{
.buffer = &[0]u8{},
.params = params.?,
Expand All @@ -258,7 +258,7 @@ pub const MemoryInstance = struct {
var instance = MemoryInstance{
.limits = Limits{
.min = 0,
.max = @as(u32, @intCast(max_pages)),
.max = max_pages,
.limit_type = limits.limit_type,
},
.mem = mem,
Expand Down Expand Up @@ -287,7 +287,7 @@ pub const MemoryInstance = struct {
}

const total_pages = self.limits.min + num_pages;
const max_pages = if (self.limits.max) |max| max else k_max_pages;
const max_pages = self.limits.maxPages();

if (total_pages > max_pages) {
return false;
Expand All @@ -306,7 +306,7 @@ pub const MemoryInstance = struct {
},
}

self.limits.min = @as(u32, @intCast(total_pages));
self.limits.min = total_pages;

return true;
}
Expand Down
16 changes: 12 additions & 4 deletions src/vm_stack.zig
Original file line number Diff line number Diff line change
Expand Up @@ -262,16 +262,16 @@ const Stack = struct {
stack.num_labels -= 1;
}

fn findLabel(stack: *const Stack, id: u32) *const Label {
fn findLabel(stack: Stack, id: u32) *const Label {
const index: usize = (stack.num_labels - 1) - id;
return &stack.labels[index];
}

fn topLabel(stack: *const Stack) *const Label {
fn topLabel(stack: Stack) *const Label {
return &stack.labels[stack.num_labels - 1];
}

fn frameLabel(stack: *const Stack) *const Label {
fn frameLabel(stack: Stack) *const Label {
var frame: *const CallFrame = stack.topFrame();
var frame_label: *const Label = &stack.labels[frame.start_offset_labels];
return frame_label;
Expand Down Expand Up @@ -359,7 +359,7 @@ const Stack = struct {
return null;
}

fn topFrame(stack: *const Stack) *CallFrame {
fn topFrame(stack: Stack) *CallFrame {
return &stack.frames[stack.num_frames - 1];
}

Expand All @@ -368,6 +368,14 @@ const Stack = struct {
stack.num_labels = 0;
stack.num_frames = 0;
}

fn debugDump(stack: Stack) void {
std.debug.print("===== stack dump =====\n", .{});
for (stack.values[0..stack.num_values]) |val| {
std.debug.print("I32: {}, I64: {}, F32: {}, F64: {}\n", .{ val.I32, val.I64, val.F32, val.F64 });
}
std.debug.print("======================\n", .{});
}
};

// TODO move all definition stuff into definition.zig and vm stuff into vm_stack.zig
Expand Down
49 changes: 46 additions & 3 deletions src/zig-stable-array/stable_array.zig
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,14 @@ pub fn StableArrayAligned(comptime T: type, comptime alignment: u29) type {
self.items.ptr = @alignCast(@ptrCast(addr));
self.items.len = 0;
} else {
const prot: u32 = std.c.PROT.READ | std.c.PROT.WRITE;
const prot: u32 = std.c.PROT.NONE;
const map: u32 = std.c.MAP.PRIVATE | std.c.MAP.ANONYMOUS;
const fd: os.fd_t = -1;
const offset: usize = 0;
var slice = try os.mmap(null, self.max_virtual_alloc_bytes, prot, map, fd, offset);
var slice = os.mmap(null, self.max_virtual_alloc_bytes, prot, map, fd, offset) catch |e| {
std.debug.print("caught initial sizing error {}, total bytes: {}\n", .{ e, self.max_virtual_alloc_bytes });
return e;
};
self.items.ptr = @alignCast(@ptrCast(slice.ptr));
self.items.len = 0;
}
Expand All @@ -279,6 +282,20 @@ pub fn StableArrayAligned(comptime T: type, comptime alignment: u29) type {
if (builtin.os.tag == .windows) {
const w = std.os.windows;
_ = try w.VirtualAlloc(@as(w.PVOID, @ptrCast(self.items.ptr)), new_capacity_bytes, w.MEM_COMMIT, w.PAGE_READWRITE);
} else {
const resize_capacity = new_capacity_bytes - current_capacity_bytes;
const region_begin: [*]u8 = @ptrCast(self.items.ptr);
const remap_region_begin: [*]u8 = region_begin + current_capacity_bytes;

const prot: u32 = std.c.PROT.READ | std.c.PROT.WRITE;
const map: u32 = std.c.MAP.PRIVATE | std.c.MAP.ANONYMOUS | std.c.MAP.FIXED;
const fd: os.fd_t = -1;
const offset: usize = 0;

_ = os.mmap(@alignCast(remap_region_begin), resize_capacity, prot, map, fd, offset) catch |e| {
std.debug.print("caught error {}\n", .{e});
return e;
};
}
}

Expand Down Expand Up @@ -395,6 +412,7 @@ test "shrinkAndFree" {
test "resize" {
const max: usize = 1024 * 1024 * 1;
var a = StableArray(u8).init(max);
defer a.deinit();

var size: usize = 512;
while (size <= max) {
Expand All @@ -405,6 +423,8 @@ test "resize" {

test "out of memory" {
var a = StableArrayAligned(u8, mem.page_size).init(TEST_VIRTUAL_ALLOC_SIZE);
defer a.deinit();

const max_capacity: usize = TEST_VIRTUAL_ALLOC_SIZE / mem.page_size;
try a.appendNTimes(0xFF, max_capacity);
for (a.items) |v| {
Expand All @@ -420,5 +440,28 @@ test "out of memory" {
assert(err == error.OutOfMemory);
};
assert(didCatchError == true);
a.deinit();
}

test "huge max size" {
const KB = 1024;
const MB = KB * 1024;
const GB = MB * 1024;

var a = StableArray(u8).init(GB * 128);
defer a.deinit();

try a.resize(MB * 4);
try a.resize(MB * 8);
try a.resize(MB * 16);
a.items[MB * 16 - 1] = 0xFF;
}

test "growing retains values" {
var a = StableArray(u8).init(TEST_VIRTUAL_ALLOC_SIZE);
defer a.deinit();

try a.resize(mem.page_size);
a.items[0] = 0xFF;
try a.resize(mem.page_size * 2);
assert(a.items[0] == 0xFF);
}
6 changes: 3 additions & 3 deletions test/mem64/memtest.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@

__attribute__((visibility("default"))) int64_t memtest(int32_t val_i32, int64_t val_i64, float val_f32, double val_f64)
{
int64_t start_page = __builtin_wasm_memory_grow(0, PAGES_PER_GB * 2); // memory.grow
int64_t start_page = __builtin_wasm_memory_grow(0, PAGES_PER_GB * 6); // memory.grow
assert(start_page != -1);

char* mem = (char*)(start_page);
char* mem = (char*)(start_page) + GB * 4;
volatile char* mem_stores = mem + MB * 1;
volatile char* mem_loads = mem + MB * 2;

int64_t num_pages = __builtin_wasm_memory_size(0); // memory.size
assert(num_pages >= (PAGES_PER_GB * 2));
assert(num_pages >= (PAGES_PER_GB * 6));

*(int32_t*)(mem_loads + 0) = val_i32; // i32.store
*(int64_t*)(mem_loads + 8) = val_i64; // i64.store
Expand Down
28 changes: 25 additions & 3 deletions test/wasm/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ const TestOpts = struct {
trace_mode: bytebox.DebugTrace.Mode = .None,
force_wasm_regen_only: bool = false,
log_suite: bool = false,
log: bytebox.Logger = bytebox.Logger.empty(),
};

fn makeSpectestImports(allocator: std.mem.Allocator) !bytebox.ModuleImportPackage {
Expand Down Expand Up @@ -872,7 +873,11 @@ fn run(allocator: std.mem.Allocator, suite_path: []const u8, opts: *const TestOp

module.filename = try allocator.dupe(u8, module_filename);

module.def = try bytebox.createModuleDefinition(allocator, .{ .debug_name = std.fs.path.basename(module_filename) });
const module_def_opts = bytebox.ModuleDefinitionOpts{
.debug_name = std.fs.path.basename(module_filename),
.log = opts.log,
};
module.def = try bytebox.createModuleDefinition(allocator, module_def_opts);
(module.def.?).decode(module_data) catch |e| {
var expected_str_or_null: ?[]const u8 = null;
if (decode_expected_error) |unwrapped_expected| {
Expand Down Expand Up @@ -935,7 +940,12 @@ fn run(allocator: std.mem.Allocator, suite_path: []const u8, opts: *const TestOp
}

module.inst = try bytebox.createModuleInstance(opts.vm_type, module.def.?, allocator);
(module.inst.?).instantiate(.{ .imports = imports.items }) catch |e| {

const instantiate_opts = bytebox.ModuleInstantiateOpts{
.imports = imports.items,
.log = opts.log,
};
(module.inst.?).instantiate(instantiate_opts) catch |e| {
if (instantiate_expected_error) |expected_str| {
if (isSameError(e, expected_str)) {
logVerbose("\tSuccess!\n", .{});
Expand Down Expand Up @@ -1081,7 +1091,14 @@ fn run(allocator: std.mem.Allocator, suite_path: []const u8, opts: *const TestOp
print("assert_return: {s}:{s}({any})\n", .{ module.filename, c.action.field, c.action.args.items });
}

print("\tFail on return {}/{}. Expected: {}, Actual: {}\n", .{ i + 1, returns.len, expected_value, r });
const format_str = "\tFail on return {}/{}. Expected: {}, Actual: {}\n";
switch (expected_value.type) {
.I32 => print(format_str, .{ i + 1, returns.len, expected_value.val.I32, r.I32 }),
.I64 => print(format_str, .{ i + 1, returns.len, expected_value.val.I64, r.I64 }),
.F32 => print(format_str, .{ i + 1, returns.len, expected_value.val.F32, r.F32 }),
.F64 => print(format_str, .{ i + 1, returns.len, expected_value.val.F64, r.F64 }),
else => unreachable,
}
action_succeeded = false;
}
} else {
Expand Down Expand Up @@ -1308,6 +1325,9 @@ pub fn main() !void {
\\ --log-suite
\\ Log the name of each suite and aggregate test result.
\\
\\ --module-logging
\\ Enables logging from inside the module when reporting errors.
\\
\\ --verbose
\\ Turn on verbose logging for each step of the test suite run.
\\
Expand Down Expand Up @@ -1347,6 +1367,8 @@ pub fn main() !void {
print("Force-regenerating wasm files and driver .json, skipping test run\n", .{});
} else if (strcmp("--log-suite", arg)) {
opts.log_suite = true;
} else if (strcmp("--module-logging", arg)) {
opts.log = bytebox.Logger.default();
} else if (strcmp("--verbose", arg) or strcmp("-v", arg)) {
g_verbose_logging = true;
print("verbose logging: on\n", .{});
Expand Down

0 comments on commit 2b17276

Please sign in to comment.