authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-03-10 08:00:17+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-04-04 06:08:10+02:00
log0d6f3aa6c14824996e5f0b5e4383644f4205882d
tree634ae25899d8830c5ddb5e9b42f78835427f8c37
parent33103071a7b835fec1cbb0551a44ce5c8483fde2
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

llvm: Remove workaround for zero-length memset/memcpy on wasm.

Closes #16360.

1 files changed, 24 insertions(+), 118 deletions(-)

src/codegen/llvm.zig+24-118
...@@ -9766,14 +9766,6 @@ pub const FuncGen = struct {...@@ -9766,14 +9766,6 @@ pub const FuncGen = struct {
9766 const access_kind: Builder.MemoryAccessKind =9766 const access_kind: Builder.MemoryAccessKind =
9767 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;9767 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
97689768
9769 // Any WebAssembly runtime will trap when the destination pointer is out-of-bounds, regardless
9770 // of the length. This means we need to emit a check where we skip the memset when the length
9771 // is 0 as we allow for undefined pointers in 0-sized slices.
9772 // This logic can be removed once https://github.com/ziglang/zig/issues/16360 is done.
9773 const intrinsic_len0_traps = o.target.cpu.arch.isWasm() and
9774 ptr_ty.isSlice(zcu) and
9775 std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory);
9776
9777 if (try self.air.value(bin_op.rhs, pt)) |elem_val| {9769 if (try self.air.value(bin_op.rhs, pt)) |elem_val| {
9778 if (elem_val.isUndefDeep(zcu)) {9770 if (elem_val.isUndefDeep(zcu)) {
9779 // Even if safety is disabled, we still emit a memset to undefined since it conveys9771 // Even if safety is disabled, we still emit a memset to undefined since it conveys
...@@ -9784,24 +9776,14 @@ pub const FuncGen = struct {...@@ -9784,24 +9776,14 @@ pub const FuncGen = struct {
9784 else9776 else
9785 try o.builder.undefValue(.i8);9777 try o.builder.undefValue(.i8);
9786 const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);9778 const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
9787 if (intrinsic_len0_traps) {9779 _ = try self.wip.callMemSet(
9788 try self.safeWasmMemset(9780 dest_ptr,
9789 dest_ptr,9781 dest_ptr_align,
9790 fill_byte,9782 fill_byte,
9791 len,9783 len,
9792 dest_ptr_align,9784 access_kind,
9793 access_kind,9785 self.disable_intrinsics,
9794 );9786 );
9795 } else {
9796 _ = try self.wip.callMemSet(
9797 dest_ptr,
9798 dest_ptr_align,
9799 fill_byte,
9800 len,
9801 access_kind,
9802 self.disable_intrinsics,
9803 );
9804 }
9805 const owner_mod = self.ng.ownerModule();9787 const owner_mod = self.ng.ownerModule();
9806 if (safety and owner_mod.valgrind) {9788 if (safety and owner_mod.valgrind) {
9807 try self.valgrindMarkUndef(dest_ptr, len);9789 try self.valgrindMarkUndef(dest_ptr, len);
...@@ -9816,24 +9798,14 @@ pub const FuncGen = struct {...@@ -9816,24 +9798,14 @@ pub const FuncGen = struct {
9816 if (try elem_val.hasRepeatedByteRepr(pt)) |byte_val| {9798 if (try elem_val.hasRepeatedByteRepr(pt)) |byte_val| {
9817 const fill_byte = try o.builder.intValue(.i8, byte_val);9799 const fill_byte = try o.builder.intValue(.i8, byte_val);
9818 const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);9800 const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
9819 if (intrinsic_len0_traps) {9801 _ = try self.wip.callMemSet(
9820 try self.safeWasmMemset(9802 dest_ptr,
9821 dest_ptr,9803 dest_ptr_align,
9822 fill_byte,9804 fill_byte,
9823 len,9805 len,
9824 dest_ptr_align,9806 access_kind,
9825 access_kind,9807 self.disable_intrinsics,
9826 );9808 );
9827 } else {
9828 _ = try self.wip.callMemSet(
9829 dest_ptr,
9830 dest_ptr_align,
9831 fill_byte,
9832 len,
9833 access_kind,
9834 self.disable_intrinsics,
9835 );
9836 }
9837 return .none;9809 return .none;
9838 }9810 }
9839 }9811 }
...@@ -9846,24 +9818,14 @@ pub const FuncGen = struct {...@@ -9846,24 +9818,14 @@ pub const FuncGen = struct {
9846 const fill_byte = try self.bitCast(value, elem_ty, Type.u8);9818 const fill_byte = try self.bitCast(value, elem_ty, Type.u8);
9847 const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);9819 const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
98489820
9849 if (intrinsic_len0_traps) {9821 _ = try self.wip.callMemSet(
9850 try self.safeWasmMemset(9822 dest_ptr,
9851 dest_ptr,9823 dest_ptr_align,
9852 fill_byte,9824 fill_byte,
9853 len,9825 len,
9854 dest_ptr_align,9826 access_kind,
9855 access_kind,9827 self.disable_intrinsics,
9856 );9828 );
9857 } else {
9858 _ = try self.wip.callMemSet(
9859 dest_ptr,
9860 dest_ptr_align,
9861 fill_byte,
9862 len,
9863 access_kind,
9864 self.disable_intrinsics,
9865 );
9866 }
9867 return .none;9829 return .none;
9868 }9830 }
98699831
...@@ -9927,33 +9889,6 @@ pub const FuncGen = struct {...@@ -9927,33 +9889,6 @@ pub const FuncGen = struct {
9927 return .none;9889 return .none;
9928 }9890 }
99299891
9930 fn safeWasmMemset(
9931 self: *FuncGen,
9932 dest_ptr: Builder.Value,
9933 fill_byte: Builder.Value,
9934 len: Builder.Value,
9935 dest_ptr_align: Builder.Alignment,
9936 access_kind: Builder.MemoryAccessKind,
9937 ) !void {
9938 const o = self.ng.object;
9939 const usize_zero = try o.builder.intValue(try o.lowerType(Type.usize), 0);
9940 const cond = try self.cmp(.normal, .neq, Type.usize, len, usize_zero);
9941 const memset_block = try self.wip.block(1, "MemsetTrapSkip");
9942 const end_block = try self.wip.block(2, "MemsetTrapEnd");
9943 _ = try self.wip.brCond(cond, memset_block, end_block, .none);
9944 self.wip.cursor = .{ .block = memset_block };
9945 _ = try self.wip.callMemSet(
9946 dest_ptr,
9947 dest_ptr_align,
9948 fill_byte,
9949 len,
9950 access_kind,
9951 self.disable_intrinsics,
9952 );
9953 _ = try self.wip.br(end_block);
9954 self.wip.cursor = .{ .block = end_block };
9955 }
9956
9957 fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {9892 fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
9958 const o = self.ng.object;9893 const o = self.ng.object;
9959 const pt = o.pt;9894 const pt = o.pt;
...@@ -9969,35 +9904,6 @@ pub const FuncGen = struct {...@@ -9969,35 +9904,6 @@ pub const FuncGen = struct {
9969 const access_kind: Builder.MemoryAccessKind = if (src_ptr_ty.isVolatilePtr(zcu) or9904 const access_kind: Builder.MemoryAccessKind = if (src_ptr_ty.isVolatilePtr(zcu) or
9970 dest_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;9905 dest_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
99719906
9972 // When bulk-memory is enabled, this will be lowered to WebAssembly's memory.copy instruction.
9973 // This instruction will trap on an invalid address, regardless of the length.
9974 // For this reason we must add a check for 0-sized slices as its pointer field can be undefined.
9975 // We only have to do this for slices as arrays will have a valid pointer.
9976 // This logic can be removed once https://github.com/ziglang/zig/issues/16360 is done.
9977 if (o.target.cpu.arch.isWasm() and
9978 std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory) and
9979 dest_ptr_ty.isSlice(zcu))
9980 {
9981 const usize_zero = try o.builder.intValue(try o.lowerType(Type.usize), 0);
9982 const cond = try self.cmp(.normal, .neq, Type.usize, len, usize_zero);
9983 const memcpy_block = try self.wip.block(1, "MemcpyTrapSkip");
9984 const end_block = try self.wip.block(2, "MemcpyTrapEnd");
9985 _ = try self.wip.brCond(cond, memcpy_block, end_block, .none);
9986 self.wip.cursor = .{ .block = memcpy_block };
9987 _ = try self.wip.callMemCpy(
9988 dest_ptr,
9989 dest_ptr_ty.ptrAlignment(zcu).toLlvm(),
9990 src_ptr,
9991 src_ptr_ty.ptrAlignment(zcu).toLlvm(),
9992 len,
9993 access_kind,
9994 self.disable_intrinsics,
9995 );
9996 _ = try self.wip.br(end_block);
9997 self.wip.cursor = .{ .block = end_block };
9998 return .none;
9999 }
10000
10001 _ = try self.wip.callMemCpy(9907 _ = try self.wip.callMemCpy(
10002 dest_ptr,9908 dest_ptr,
10003 dest_ptr_ty.ptrAlignment(zcu).toLlvm(),9909 dest_ptr_ty.ptrAlignment(zcu).toLlvm(),