Skip to content

Commit

Permalink
remove clang dependency (#51)
Browse files Browse the repository at this point in the history
* Closes #45. Now that we're using zig 0.13.0, it has the incorrect wasm64 codegen fixed and we can use it for the wasm program now.
* Replaced memory64 wasm C test program with a zig version with some better validation, and updated build.zig.
* Fixed the memory.fill and memory.copy instructions not being memory64-aware
  • Loading branch information
rdunnington authored Jun 10, 2024
1 parent 3f8d187 commit 610e142
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 141 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ Bytebox currently builds with [Zig 0.13.x](https://ziglang.org/download) to avoi

To run the tests:
* `wasm-tools` is required to run the wasm testsuite. You can install it via the rust toolchain `cargo install wasm-tools` or directly from the [release page](https://github.com/bytecodealliance/wasm-tools/releases).
* `clang` v15.x+ is required to build the mem64 tests. However, if you don't have a compatible version of `clang` installed, you can pass `--noclang` to `zig build` to avoid the requirement.
* `python3` is required to run the wasi testsuite. You may need to run `python3 -m pip install -r test/wasi/wasi-testsuite/test-runner/requirements.txt` to ensure the wasi test runner has all the necessary dependencies installed.

## Run Tests
Expand Down
75 changes: 35 additions & 40 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const ExeOpts = struct {

pub fn build(b: *Build) void {
const should_emit_asm = b.option(bool, "asm", "Emit asm for the bytebox binaries") orelse false;
const no_clang = b.option(bool, "noclang", "Pass this if clang isn't in the PATH") orelse false;

const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
Expand All @@ -27,9 +26,9 @@ pub fn build(b: *Build) void {
.optimize = optimize,
});

var bench_add_one_step: *CompileStep = buildWasmExe(b, "bench/samples/add-one.zig");
var bench_fibonacci_step: *CompileStep = buildWasmExe(b, "bench/samples/fibonacci.zig");
var bench_mandelbrot_step: *CompileStep = buildWasmExe(b, "bench/samples/mandelbrot.zig");
var bench_add_one_step: *CompileStep = buildWasmExe(b, "bench/samples/add-one.zig", .Wasm32);
var bench_fibonacci_step: *CompileStep = buildWasmExe(b, "bench/samples/fibonacci.zig", .Wasm32);
var bench_mandelbrot_step: *CompileStep = buildWasmExe(b, "bench/samples/mandelbrot.zig", .Wasm32);

const stable_array_import = ModuleImport{ .name = "stable-array", .module = stable_array.module("zig-stable-array") };

Expand Down Expand Up @@ -101,43 +100,23 @@ pub fn build(b: *Build) void {
const wasi_testsuite_step = b.step("test-wasi", "Run wasi testsuite");
wasi_testsuite_step.dependOn(&wasi_testsuite.step);

// mem64 step
var mem64_test_step: ?*Build.Step = null;
if (!no_clang) {
// need to use clang to compile the C test due to https://github.com/ziglang/zig/issues/19942
// eventually we will ziggify this test
// ideally this test would go away, but the existing spec tests don't provide very good coverage
// of the instructions
const compile_memtest = b.addSystemCommand(&.{"clang"});
compile_memtest.addArg("--target=wasm64-freestanding");
compile_memtest.addArg("-mbulk-memory");
compile_memtest.addArg("-nostdlib");
compile_memtest.addArg("-O2");
compile_memtest.addArg("-Wl,--no-entry");
compile_memtest.addArg("-Wl,--export-dynamic");
compile_memtest.addArg("-o");
compile_memtest.addArg("test/mem64/memtest.wasm");
compile_memtest.addFileArg(b.path("test/mem64/memtest.c"));
compile_memtest.has_side_effects = true;

b.getInstallStep().dependOn(&compile_memtest.step);

mem64_test_step = buildExeWithRunStep(b, target, optimize, &imports, .{
.exe_name = "test-mem64",
.root_src = "test/mem64/main.zig",
.step_name = "test-mem64",
.description = "Run the mem64 test",
});
}
// mem64 test
const compile_mem64_test = buildWasmExe(b, "test/mem64/memtest.zig", .Wasm64);
b.getInstallStep().dependOn(&compile_mem64_test.step);

const mem64_test_step: *Build.Step = buildExeWithRunStep(b, target, optimize, &imports, .{
.exe_name = "test-mem64",
.root_src = "test/mem64/main.zig",
.step_name = "test-mem64",
.description = "Run the mem64 test",
});

// All tests
const all_tests_step = b.step("test", "Run unit, wasm, and wasi tests");
all_tests_step.dependOn(unit_test_step);
all_tests_step.dependOn(wasm_testsuite_step);
all_tests_step.dependOn(wasi_testsuite_step);
if (mem64_test_step) |step| {
all_tests_step.dependOn(step);
}
all_tests_step.dependOn(mem64_test_step);
}

fn buildExeWithRunStep(b: *Build, target: Build.ResolvedTarget, optimize: std.builtin.Mode, imports: []const ModuleImport, opts: ExeOpts) *Build.Step {
Expand Down Expand Up @@ -173,17 +152,33 @@ fn buildExeWithRunStep(b: *Build, target: Build.ResolvedTarget, optimize: std.bu
return step;
}

fn buildWasmExe(b: *Build, filepath: []const u8) *CompileStep {
const WasmArch = enum {
Wasm32,
Wasm64,
};

fn buildWasmExe(b: *Build, filepath: []const u8, arch: WasmArch) *CompileStep {
var filename: []const u8 = std.fs.path.basename(filepath);
const filename_no_extension: []const u8 = filename[0 .. filename.len - 4];

const cpu_arch: std.Target.Cpu.Arch = if (arch == .Wasm32) .wasm32 else .wasm64;

var target_query: std.Target.Query = .{
.cpu_arch = cpu_arch,
.os_tag = .freestanding,
};
target_query.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.bulk_memory));
target_query.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.nontrapping_fptoint));
target_query.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.multivalue));
target_query.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.mutable_globals));
target_query.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.reference_types));
target_query.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.sign_ext));
target_query.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.simd128));

var exe = b.addExecutable(.{
.name = filename_no_extension,
.root_source_file = b.path(filepath),
.target = b.resolveTargetQuery(.{
.cpu_arch = .wasm32,
.os_tag = .freestanding,
}),
.target = b.resolveTargetQuery(target_query),
.optimize = .ReleaseSmall,
});
exe.rdynamic = true;
Expand Down
38 changes: 19 additions & 19 deletions src/vm_stack.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3466,28 +3466,28 @@ const InstructionFuncs = struct {
try debugPreamble("Memory_Copy", pc, code, stack);
const memory: *MemoryInstance = &stack.topFrame().module_instance.store.memories.items[0];

const length = stack.popI32();
const source_offset = stack.popI32();
const dest_offset = stack.popI32();
const length_s = stack.popIndexType();
const source_offset_s = stack.popIndexType();
const dest_offset_s = stack.popIndexType();

if (length < 0) {
if (length_s < 0) {
return error.TrapOutOfBoundsMemoryAccess;
}

const buffer = memory.buffer();
if (buffer.len < source_offset + length or source_offset < 0) {
if (buffer.len < source_offset_s + length_s or source_offset_s < 0) {
return error.TrapOutOfBoundsMemoryAccess;
}
if (buffer.len < dest_offset + length or dest_offset < 0) {
if (buffer.len < dest_offset_s + length_s or dest_offset_s < 0) {
return error.TrapOutOfBoundsMemoryAccess;
}

const source_offset_u32 = @as(u32, @intCast(source_offset));
const dest_offset_u32 = @as(u32, @intCast(dest_offset));
const length_u32 = @as(u32, @intCast(length));
const source_offset = @as(u64, @intCast(source_offset_s));
const dest_offset = @as(u64, @intCast(dest_offset_s));
const length = @as(u64, @intCast(length_s));

const source = buffer[source_offset_u32 .. source_offset_u32 + length_u32];
const destination = buffer[dest_offset_u32 .. dest_offset_u32 + length_u32];
const source = buffer[source_offset .. source_offset + length];
const destination = buffer[dest_offset .. dest_offset + length];

if (@intFromPtr(destination.ptr) < @intFromPtr(source.ptr)) {
std.mem.copyForwards(u8, destination, source);
Expand All @@ -3501,25 +3501,25 @@ const InstructionFuncs = struct {
try debugPreamble("Memory_Fill", pc, code, stack);
const memory: *MemoryInstance = &stack.topFrame().module_instance.store.memories.items[0];

const length = stack.popI32();
const length_s: i64 = stack.popIndexType();
const value: u8 = @as(u8, @truncate(@as(u32, @bitCast(stack.popI32()))));
const offset = stack.popI32();
const offset_s: i64 = stack.popIndexType();

if (length < 0) {
if (length_s < 0) {
return error.TrapOutOfBoundsMemoryAccess;
}

const buffer = memory.buffer();
if (buffer.len < offset + length or offset < 0) {
if (buffer.len < offset_s + length_s or offset_s < 0) {
return error.TrapOutOfBoundsMemoryAccess;
}

const offset_u32 = @as(u32, @intCast(offset));
const length_u32 = @as(u32, @intCast(length));

const destination = buffer[offset_u32 .. offset_u32 + length_u32];
const offset = @as(u64, @intCast(offset_s));
const length = @as(u64, @intCast(length_s));

const destination = buffer[offset .. offset + length];
@memset(destination, value);

try @call(.always_tail, InstructionFuncs.lookup(code[pc + 1].opcode), .{ pc + 1, code, stack });
}

Expand Down
2 changes: 1 addition & 1 deletion test/mem64/main.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var allocator: std.mem.Allocator = gpa.allocator();

const wasm_data: []u8 = try std.fs.cwd().readFileAlloc(allocator, "test/mem64/memtest.wasm", 1024 * 128);
const wasm_data: []u8 = try std.fs.cwd().readFileAlloc(allocator, "zig-out/bin/memtest.wasm", 1024 * 128);
defer allocator.free(wasm_data);

const module_def = try bytebox.createModuleDefinition(allocator, .{});
Expand Down
66 changes: 0 additions & 66 deletions test/mem64/memtest.c

This file was deleted.

111 changes: 97 additions & 14 deletions test/mem64/memtest.zig
Original file line number Diff line number Diff line change
@@ -1,25 +1,108 @@
// 0.12.0: zig build-exe memtest.zig -target wasm64-freestanding -fno-entry --export=memtest -O ReleaseSmall
// 0.11.0: zig build-lib memtest.zig -target wasm64-freestanding -dynamic -rdynamic -O ReleaseSmall

const KB = 1024;
const MB = 1024 * KB;
const GB = 1024 * MB;

const PAGE_SIZE = 64 * KB;
const PAGES_PER_GB = GB / PAGE_SIZE;

export fn memtest() i32 {
_ = @wasmMemoryGrow(0, PAGES_PER_GB * 4);

var mem: [*]u8 = @ptrFromInt(4 * GB);

for (0..MB) |i| {
mem[i] = 0xFF;
mem[(4 * GB) - MB + i] = 0xFF;
fn assert(cond: bool) !void {
if (!cond) {
return error.Failed;
}
}

export fn memtest(val_i32: i32, val_i64: i64, val_f32: f32, val_f64: f64) i32 {
testInternal(val_i32, val_i64, val_f32, val_f64) catch {
return 1;
};
return 0;
}

// export fn memtest() void {
// _ = @wasmMemoryGrow(0, PAGES_PER_GB * 8);
// }
fn testInternal(val_i32: i32, val_i64: i64, val_f32: f32, val_f64: f64) !void {
_ = @wasmMemoryGrow(0, PAGES_PER_GB * 4);

const grow_value: isize = @wasmMemoryGrow(0, PAGES_PER_GB * 6); // memory.grow
try assert(grow_value != -1);
const start_page: [*]volatile u8 = @ptrFromInt(@as(usize, @intCast(grow_value)));

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

const num_pages: usize = @wasmMemorySize(0);
try assert(num_pages >= PAGES_PER_GB * 6);

const ptr_load_i32 = @as(*volatile i32, @ptrCast(@alignCast(mem_loads)));
const ptr_load_i64 = @as(*volatile i64, @ptrCast(@alignCast(mem_loads + 8)));
const ptr_load_f32 = @as(*volatile f32, @ptrCast(@alignCast(mem_loads + 16)));
const ptr_load_f64 = @as(*volatile f64, @ptrCast(@alignCast(mem_loads + 24)));

ptr_load_i32.* = val_i32; // i32.store
ptr_load_i64.* = val_i64; // i64.store
ptr_load_f32.* = val_f32; // f32.store
ptr_load_f64.* = val_f64; // f64.store

try assert(ptr_load_i32.* == val_i32);
try assert(ptr_load_i64.* == val_i64);
try assert(ptr_load_f32.* == val_f32);
try assert(ptr_load_f64.* == val_f64);

const ptr_store_i32 = @as(*volatile i32, @ptrCast(@alignCast(mem_stores)));
const ptr_store_i64 = @as(*volatile i64, @ptrCast(@alignCast(mem_stores + 8)));
const ptr_store_f32 = @as(*volatile f32, @ptrCast(@alignCast(mem_stores + 16)));
const ptr_store_f64 = @as(*volatile f64, @ptrCast(@alignCast(mem_stores + 24)));

ptr_store_i32.* = ptr_load_i32.*; // i32.load && i32.store
ptr_store_i64.* = ptr_load_i64.*; // i64.load && i64.store
ptr_store_f32.* = ptr_load_f32.*; // f32.load && f32.store
ptr_store_f64.* = ptr_load_f64.*; // f64.load && f64.store

try assert(ptr_store_i32.* == ptr_load_i32.*);
try assert(ptr_store_i64.* == ptr_load_i64.*);
try assert(ptr_store_f32.* == ptr_load_f32.*);
try assert(ptr_store_f64.* == ptr_load_f64.*);

var load32: i32 = 0;
ptr_load_i32.* = 0x7F;
load32 = @as(*volatile i8, @ptrCast(@alignCast(ptr_load_i32))).*; // i32.load8_s
try assert(load32 == 0x7F);
ptr_load_i32.* = 0xFF;
load32 = @as(*volatile u8, @ptrCast(@alignCast(ptr_load_i32))).*; // i32.load8_u
try assert(load32 == 0xFF);
ptr_load_i32.* = 0x7FFF;
load32 = @as(*volatile i16, @ptrCast(@alignCast(ptr_load_i32))).*; // i32.load16_s
try assert(load32 == 0x7FFF);
ptr_load_i32.* = 0xFFFF;
load32 = @as(*volatile u16, @ptrCast(@alignCast(ptr_load_i32))).*; // i32.load16_s
try assert(load32 == 0xFFFF);

var load64: i64 = 0;
ptr_load_i64.* = 0x7F;
load64 = @as(*volatile i8, @ptrCast(@alignCast(ptr_load_i64))).*; // i64.load8_s
try assert(load64 == 0x7F);
ptr_load_i64.* = 0xFF;
load64 = @as(*volatile u8, @ptrCast(@alignCast(ptr_load_i64))).*; // i64.load8_u
try assert(load64 == 0xFF);
ptr_load_i64.* = 0x7FFF;
load64 = @as(*volatile i16, @ptrCast(@alignCast(ptr_load_i64))).*; // i64.load16_s
try assert(load64 == 0x7FFF);
ptr_load_i64.* = 0xFFFF;
load64 = @as(*volatile u16, @ptrCast(@alignCast(ptr_load_i64))).*; // i64.load16_s
try assert(load64 == 0xFFFF);
ptr_load_i64.* = 0x7FFFFFFF;
load64 = @as(*volatile i32, @ptrCast(@alignCast(ptr_load_i64))).*; // i64.load32_s
try assert(load64 == 0x7FFFFFFF);
ptr_load_i64.* = 0xFFFFFFFF;
load64 = @as(*volatile u32, @ptrCast(@alignCast(ptr_load_i64))).*; // i64.load32_s
try assert(load64 == 0xFFFFFFFF);

const memset_dest = (mem + KB)[0..KB];
const memcpy_dest = (mem + KB * 2)[0..KB];
@memset(memset_dest, 0xFF); // memory.fill
@memcpy(memcpy_dest, memset_dest); // memory.copy

try assert(memset_dest[0] == 0xFF);
try assert(memset_dest[KB - 1] == 0xFF);
try assert(memcpy_dest[0] == 0xFF);
try assert(memcpy_dest[KB - 1] == 0xFF);
}

0 comments on commit 610e142

Please sign in to comment.