authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-07-06 19:31:08+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2023-07-10 20:05:13+02:00
log37e2a04da8688a168ed0ad81bf3ead5d9a3b8474
tree87004058faacfb26e3063cf703b344b410a4d4c7
parentd54ebf4356eaeeab4d256d0da4f81678226f81a6
signaturelock-open Commit is signed but in an unrecognized format.

add stand alone test to verify bulk-memory features

This adds a standalone test case to ensure the runtime does not trap when performing a memory.copy or memory.fill instruction while the destination or source address is out-of-bounds and the length is 0.

4 files changed, 77 insertions(+), 33 deletions(-)

src/codegen/llvm.zig+23-33
......@@ -8514,8 +8514,8 @@ pub const FuncGen = struct {
85148514 // Any WebAssembly runtime will trap when the destination pointer is out-of-bounds, regardless
85158515 // of the length. This means we need to emit a check where we skip the memset when the length
85168516 // is 0 as we allow for undefined pointers in 0-sized slices.
8517 const needs_wasm_safety_check = safety and
8518 o.target.isWasm() and
8517 // This logic can be removed once https://github.com/ziglang/zig/issues/16360 is done.
8518 const intrinsic_len0_traps = o.target.isWasm() and
85198519 ptr_ty.isSlice(mod) and
85208520 std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory);
85218521
......@@ -8529,7 +8529,7 @@ pub const FuncGen = struct {
85298529 else
85308530 u8_llvm_ty.getUndef();
85318531 const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
8532 if (needs_wasm_safety_check) {
8532 if (intrinsic_len0_traps) {
85338533 try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
85348534 } else {
85358535 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
......@@ -8552,7 +8552,7 @@ pub const FuncGen = struct {
85528552 });
85538553 const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
85548554
8555 if (needs_wasm_safety_check) {
8555 if (intrinsic_len0_traps) {
85568556 try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
85578557 } else {
85588558 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
......@@ -8569,7 +8569,7 @@ pub const FuncGen = struct {
85698569 const fill_byte = try self.bitCast(value, elem_ty, Type.u8);
85708570 const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
85718571
8572 if (needs_wasm_safety_check) {
8572 if (intrinsic_len0_traps) {
85738573 try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
85748574 } else {
85758575 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
......@@ -8652,19 +8652,15 @@ pub const FuncGen = struct {
86528652 dest_ptr_align: u32,
86538653 is_volatile: bool,
86548654 ) !void {
8655 const parent_block = self.context.createBasicBlock("Block");
86568655 const llvm_usize_ty = self.context.intType(self.dg.object.target.ptrBitWidth());
8657 const cond = try self.cmp(len, llvm_usize_ty.constInt(0, .False), Type.usize, .eq);
8658 const then_block = self.context.appendBasicBlock(self.llvm_func, "Then");
8659 const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");
8660 _ = self.builder.buildCondBr(cond, then_block, else_block);
8661 self.builder.positionBuilderAtEnd(then_block);
8662 _ = self.builder.buildBr(parent_block);
8663 self.builder.positionBuilderAtEnd(else_block);
8656 const cond = try self.cmp(len, llvm_usize_ty.constInt(0, .False), Type.usize, .neq);
8657 const memset_block = self.context.appendBasicBlock(self.llvm_func, "MemsetTrapSkip");
8658 const end_block = self.context.appendBasicBlock(self.llvm_func, "MemsetTrapEnd");
8659 _ = self.builder.buildCondBr(cond, memset_block, end_block);
8660 self.builder.positionBuilderAtEnd(memset_block);
86648661 _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8665 _ = self.builder.buildBr(parent_block);
8666 self.llvm_func.appendExistingBasicBlock(parent_block);
8667 self.builder.positionBuilderAtEnd(parent_block);
8662 _ = self.builder.buildBr(end_block);
8663 self.builder.positionBuilderAtEnd(end_block);
86688664 }
86698665
86708666 fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
......@@ -8682,24 +8678,19 @@ pub const FuncGen = struct {
86828678
86838679 // When bulk-memory is enabled, this will be lowered to WebAssembly's memory.copy instruction.
86848680 // This instruction will trap on an invalid address, regardless of the length.
8685 // For this reason we must add a safety-check for 0-sized slices as its pointer field can be undefined.
8681 // For this reason we must add a check for 0-sized slices as its pointer field can be undefined.
86868682 // We only have to do this for slices as arrays will have a valid pointer.
8683 // This logic can be removed once https://github.com/ziglang/zig/issues/16360 is done.
86878684 if (o.target.isWasm() and
86888685 std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory) and
8689 (src_ptr_ty.isSlice(mod) or dest_ptr_ty.isSlice(mod)))
8686 dest_ptr_ty.isSlice(mod))
86908687 {
8691 const parent_block = self.context.createBasicBlock("Block");
8692
8693 const llvm_usize_ty = self.context.intType(o.target.ptrBitWidth());
8694 const cond = try self.cmp(len, llvm_usize_ty.constInt(0, .False), Type.usize, .eq);
8695 const then_block = self.context.appendBasicBlock(self.llvm_func, "Then");
8696 const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");
8697 _ = self.builder.buildCondBr(cond, then_block, else_block);
8698
8699 self.builder.positionBuilderAtEnd(then_block);
8700 _ = self.builder.buildBr(parent_block);
8701
8702 self.builder.positionBuilderAtEnd(else_block);
8688 const llvm_usize_ty = self.context.intType(self.dg.object.target.ptrBitWidth());
8689 const cond = try self.cmp(len, llvm_usize_ty.constInt(0, .False), Type.usize, .neq);
8690 const memcpy_block = self.context.appendBasicBlock(self.llvm_func, "MemcpyTrapSkip");
8691 const end_block = self.context.appendBasicBlock(self.llvm_func, "MemcpyTrapEnd");
8692 _ = self.builder.buildCondBr(cond, memcpy_block, end_block);
8693 self.builder.positionBuilderAtEnd(memcpy_block);
87038694 _ = self.builder.buildMemCpy(
87048695 dest_ptr,
87058696 dest_ptr_ty.ptrAlignment(mod),
......@@ -8708,9 +8699,8 @@ pub const FuncGen = struct {
87088699 len,
87098700 is_volatile,
87108701 );
8711 _ = self.builder.buildBr(parent_block);
8712 self.llvm_func.appendExistingBasicBlock(parent_block);
8713 self.builder.positionBuilderAtEnd(parent_block);
8702 _ = self.builder.buildBr(end_block);
8703 self.builder.positionBuilderAtEnd(end_block);
87148704 return null;
87158705 }
87168706
test/standalone.zig+4
......@@ -230,6 +230,10 @@ pub const build_cases = [_]BuildCase{
230230 .build_root = "test/standalone/cmakedefine",
231231 .import = @import("standalone/cmakedefine/build.zig"),
232232 },
233 .{
234 .build_root = "test/standalone/zerolength_check",
235 .import = @import("standalone/zerolength_check/build.zig"),
236 },
233237};
234238
235239const std = @import("std");
test/standalone/zerolength_check/build.zig created+27
......@@ -0,0 +1,27 @@
1const std = @import("std");
2
3pub fn build(b: *std.Build) void {
4 const test_step = b.step("test", "Test it");
5 b.default_step = test_step;
6
7 add(b, test_step, .Debug);
8 add(b, test_step, .ReleaseFast);
9 add(b, test_step, .ReleaseSmall);
10 add(b, test_step, .ReleaseSafe);
11}
12
13fn add(b: *std.Build, test_step: *std.Build.Step, optimize: std.builtin.OptimizeMode) void {
14 const unit_tests = b.addTest(.{
15 .root_source_file = .{ .path = "src/main.zig" },
16 .target = .{
17 .os_tag = .wasi,
18 .cpu_arch = .wasm32,
19 .cpu_features_add = std.Target.wasm.featureSet(&.{.bulk_memory}),
20 },
21 .optimize = optimize,
22 });
23
24 const run_unit_tests = b.addRunArtifact(unit_tests);
25 run_unit_tests.skip_foreign_checks = true;
26 test_step.dependOn(&run_unit_tests.step);
27}
test/standalone/zerolength_check/src/main.zig created+23
......@@ -0,0 +1,23 @@
1const std = @import("std");
2
3test {
4 var dest = foo();
5 var source = foo();
6
7 @memcpy(dest, source);
8 @memset(dest, 4);
9 @memset(dest, undefined);
10
11 var dest2 = foo2();
12 @memset(dest2, 0);
13}
14
15fn foo() []u8 {
16 const ptr = comptime std.mem.alignBackward(usize, std.math.maxInt(usize), 1);
17 return @as([*]align(1) u8, @ptrFromInt(ptr))[0..0];
18}
19
20fn foo2() []u64 {
21 const ptr = comptime std.mem.alignBackward(usize, std.math.maxInt(usize), 1);
22 return @as([*]align(1) u64, @ptrFromInt(ptr))[0..0];
23}