@@ -8511,6 +8511,14 @@ pub const FuncGen = struct {
8511
8511
const dest_ptr = self.sliceOrArrayPtr(dest_slice, ptr_ty);
8512
8512
const is_volatile = ptr_ty.isVolatilePtr(mod);
8513
8513
8514
+ // Any WebAssembly runtime will trap when the destination pointer is out-of-bounds, regardless
8515
+ // of the length. This means we need to emit a check where we skip the memset when the length
8516
+ // is 0 as we allow for undefined pointers in 0-sized slices.
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
8519
+ ptr_ty.isSlice(mod) and
8520
+ std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory);
8521
+
8514
8522
if (try self.air.value(bin_op.rhs, mod)) |elem_val| {
8515
8523
if (elem_val.isUndefDeep(mod)) {
8516
8524
// Even if safety is disabled, we still emit a memset to undefined since it conveys
@@ -8521,7 +8529,11 @@ pub const FuncGen = struct {
8521
8529
else
8522
8530
u8_llvm_ty.getUndef();
8523
8531
const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
8524
- _ = self .builder .buildMemSet (dest_ptr , fill_byte , len , dest_ptr_align , is_volatile );
8532
+ if (intrinsic_len0_traps) {
8533
+ try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8534
+ } else {
8535
+ _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8536
+ }
8525
8537
8526
8538
if (safety and mod.comp.bin_file.options.valgrind) {
8527
8539
self.valgrindMarkUndef(dest_ptr, len);
@@ -8539,7 +8551,12 @@ pub const FuncGen = struct {
8539
8551
.val = byte_val,
8540
8552
});
8541
8553
const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
8542
- _ = self .builder .buildMemSet (dest_ptr , fill_byte , len , dest_ptr_align , is_volatile );
8554
+
8555
+ if (intrinsic_len0_traps) {
8556
+ try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8557
+ } else {
8558
+ _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8559
+ }
8543
8560
return null;
8544
8561
}
8545
8562
}
@@ -8551,7 +8568,12 @@ pub const FuncGen = struct {
8551
8568
// In this case we can take advantage of LLVM's intrinsic.
8552
8569
const fill_byte = try self.bitCast(value, elem_ty, Type.u8);
8553
8570
const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
8554
- _ = self .builder .buildMemSet (dest_ptr , fill_byte , len , dest_ptr_align , is_volatile );
8571
+
8572
+ if (intrinsic_len0_traps) {
8573
+ try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8574
+ } else {
8575
+ _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8576
+ }
8555
8577
return null;
8556
8578
}
8557
8579
@@ -8622,6 +8644,25 @@ pub const FuncGen = struct {
8622
8644
return null;
8623
8645
}
8624
8646
8647
+ fn safeWasmMemset(
8648
+ self: *FuncGen,
8649
+ dest_ptr: *llvm.Value,
8650
+ fill_byte: *llvm.Value,
8651
+ len: *llvm.Value,
8652
+ dest_ptr_align: u32,
8653
+ is_volatile: bool,
8654
+ ) !void {
8655
+ const llvm_usize_ty = self.context.intType(self.dg.object.target.ptrBitWidth());
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);
8661
+ _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
8662
+ _ = self.builder.buildBr(end_block);
8663
+ self.builder.positionBuilderAtEnd(end_block);
8664
+ }
8665
+
8625
8666
fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
8626
8667
const o = self.dg.object;
8627
8668
const mod = o.module;
@@ -8634,6 +8675,35 @@ pub const FuncGen = struct {
8634
8675
const len = self.sliceOrArrayLenInBytes(dest_slice, dest_ptr_ty);
8635
8676
const dest_ptr = self.sliceOrArrayPtr(dest_slice, dest_ptr_ty);
8636
8677
const is_volatile = src_ptr_ty.isVolatilePtr(mod) or dest_ptr_ty.isVolatilePtr(mod);
8678
+
8679
+ // When bulk-memory is enabled, this will be lowered to WebAssembly's memory.copy instruction.
8680
+ // This instruction will trap on an invalid address, regardless of the length.
8681
+ // For this reason we must add a check for 0-sized slices as its pointer field can be undefined.
8682
+ // 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.
8684
+ if (o.target.isWasm() and
8685
+ std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory) and
8686
+ dest_ptr_ty.isSlice(mod))
8687
+ {
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);
8694
+ _ = self.builder.buildMemCpy(
8695
+ dest_ptr,
8696
+ dest_ptr_ty.ptrAlignment(mod),
8697
+ src_ptr,
8698
+ src_ptr_ty.ptrAlignment(mod),
8699
+ len,
8700
+ is_volatile,
8701
+ );
8702
+ _ = self.builder.buildBr(end_block);
8703
+ self.builder.positionBuilderAtEnd(end_block);
8704
+ return null;
8705
+ }
8706
+
8637
8707
_ = self.builder.buildMemCpy(
8638
8708
dest_ptr,
8639
8709
dest_ptr_ty.ptrAlignment(mod),
0 commit comments