Skip to content

Commit 0970e2f

Browse files
committed
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.
1 parent d54ebf4 commit 0970e2f

File tree

4 files changed

+64
-20
lines changed

4 files changed

+64
-20
lines changed

src/codegen/llvm.zig

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8514,8 +8514,7 @@ 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+
const intrinsic_len0_traps = o.target.isWasm() and
85198518
ptr_ty.isSlice(mod) and
85208519
std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory);
85218520

@@ -8529,7 +8528,7 @@ pub const FuncGen = struct {
85298528
else
85308529
u8_llvm_ty.getUndef();
85318530
const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
8532-
if (needs_wasm_safety_check) {
8531+
if (intrinsic_len0_traps) {
85338532
try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
85348533
} else {
85358534
_ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
@@ -8552,7 +8551,7 @@ pub const FuncGen = struct {
85528551
});
85538552
const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
85548553

8555-
if (needs_wasm_safety_check) {
8554+
if (intrinsic_len0_traps) {
85568555
try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
85578556
} else {
85588557
_ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
@@ -8569,7 +8568,7 @@ pub const FuncGen = struct {
85698568
const fill_byte = try self.bitCast(value, elem_ty, Type.u8);
85708569
const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
85718570

8572-
if (needs_wasm_safety_check) {
8571+
if (intrinsic_len0_traps) {
85738572
try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
85748573
} else {
85758574
_ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
@@ -8654,13 +8653,10 @@ pub const FuncGen = struct {
86548653
) !void {
86558654
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);
8656+
const cond = try self.cmp(len, llvm_usize_ty.constInt(0, .False), Type.usize, .neq);
86588657
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);
8658+
_ = self.builder.buildCondBr(cond, then_block, parent_block);
86618659
self.builder.positionBuilderAtEnd(then_block);
8662-
_ = self.builder.buildBr(parent_block);
8663-
self.builder.positionBuilderAtEnd(else_block);
86648660
_ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
86658661
_ = self.builder.buildBr(parent_block);
86668662
self.llvm_func.appendExistingBasicBlock(parent_block);
@@ -8682,24 +8678,18 @@ 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.
86878683
if (o.target.isWasm() and
86888684
std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory) and
8689-
(src_ptr_ty.isSlice(mod) or dest_ptr_ty.isSlice(mod)))
8685+
dest_ptr_ty.isSlice(mod))
86908686
{
86918687
const parent_block = self.context.createBasicBlock("Block");
8692-
86938688
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);
8689+
const cond = try self.cmp(len, llvm_usize_ty.constInt(0, .False), Type.usize, .neq);
86958690
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-
8691+
_ = self.builder.buildCondBr(cond, then_block, parent_block);
86998692
self.builder.positionBuilderAtEnd(then_block);
8700-
_ = self.builder.buildBr(parent_block);
8701-
8702-
self.builder.positionBuilderAtEnd(else_block);
87038693
_ = self.builder.buildMemCpy(
87048694
dest_ptr,
87058695
dest_ptr_ty.ptrAlignment(mod),

test/standalone.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

235239
const std = @import("std");
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const std = @import("std");
2+
3+
pub 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+
13+
fn 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+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const std = @import("std");
2+
3+
test {
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+
15+
fn 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+
20+
fn 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+
}

0 commit comments

Comments
 (0)