authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-04-16 09:30:50+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-04-16 17:00:04+02:00
log84c2d809ec90aa3d5934adac33551f82656625ee
treef9e0d5ffdb8439ca5a94b0bf8437c8092a32946f
parentcfa0ada9e191237340a8293d89b92c8e59fc01c3

llvm: workaround crash on large inline memset

See https://codeberg.org/ziglang/zig/issues/31701 for details. I am not re-enabling the disabled compiler-rt module tests here because they are also affected by https://codeberg.org/ziglang/zig/issues/31702.

1 files changed, 38 insertions(+), 6 deletions(-)

src/codegen/llvm/FuncGen.zig+38-6
...@@ -919,7 +919,7 @@ fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!vo...@@ -919,7 +919,7 @@ fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!vo
919 if (self.ret_ptr != .none) {919 if (self.ret_ptr != .none) {
920 const operand = try self.resolveInst(un_op);920 const operand = try self.resolveInst(un_op);
921 const val_is_undef = if (un_op.toInterned()) |i| Value.fromInterned(i).isUndef(zcu) else false;921 const val_is_undef = if (un_op.toInterned()) |i| Value.fromInterned(i).isUndef(zcu) else false;
922 if (val_is_undef and safety) {922 if (val_is_undef and safety and !self.needMemsetWorkaround(ret_ty.abiSize(zcu))) {
923 const len = try o.builder.intValue(try o.lowerType(.usize), ret_ty.abiSize(zcu));923 const len = try o.builder.intValue(try o.lowerType(.usize), ret_ty.abiSize(zcu));
924 _ = try self.wip.callMemSet(924 _ = try self.wip.callMemSet(
925 self.ret_ptr,925 self.ret_ptr,
...@@ -973,7 +973,7 @@ fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!vo...@@ -973,7 +973,7 @@ fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!vo
973 const val_is_undef = if (un_op.toInterned()) |i| Value.fromInterned(i).isUndef(zcu) else false;973 const val_is_undef = if (un_op.toInterned()) |i| Value.fromInterned(i).isUndef(zcu) else false;
974 const alignment = ret_ty.abiAlignment(zcu).toLlvm();974 const alignment = ret_ty.abiAlignment(zcu).toLlvm();
975975
976 if (val_is_undef and safety) {976 if (val_is_undef and safety and !self.needMemsetWorkaround(ret_ty.abiSize(zcu))) {
977 const llvm_ret_ty = operand.typeOfWip(&self.wip);977 const llvm_ret_ty = operand.typeOfWip(&self.wip);
978 const rp = try self.buildAlloca(llvm_ret_ty, alignment);978 const rp = try self.buildAlloca(llvm_ret_ty, alignment);
979 const len = try o.builder.intValue(try o.lowerType(.usize), ret_ty.abiSize(zcu));979 const len = try o.builder.intValue(try o.lowerType(.usize), ret_ty.abiSize(zcu));
...@@ -4697,7 +4697,7 @@ fn airStore(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!...@@ -4697,7 +4697,7 @@ fn airStore(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!
4697 const operand_ty = ptr_ty.childType(zcu);4697 const operand_ty = ptr_ty.childType(zcu);
46984698
4699 const val_is_undef = if (bin_op.rhs.toInterned()) |i| Value.fromInterned(i).isUndef(zcu) else false;4699 const val_is_undef = if (bin_op.rhs.toInterned()) |i| Value.fromInterned(i).isUndef(zcu) else false;
4700 if (val_is_undef) {4700 if (val_is_undef and !self.needMemsetWorkaround(operand_ty.abiSize(zcu))) {
4701 const owner_mod = self.ownerModule();4701 const owner_mod = self.ownerModule();
47024702
4703 // Even if safety is disabled, we still emit a memset to undefined since it conveys4703 // Even if safety is disabled, we still emit a memset to undefined since it conveys
...@@ -5079,7 +5079,13 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error...@@ -5079,7 +5079,13 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error
50795079
5080 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));5080 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
50815081
5082 if (bin_op.rhs.toInterned()) |elem_ip_index| {5082 const allow_byte_memset = !self.needMemsetWorkaround(switch (ptr_ty.ptrSize(zcu)) {
5083 .one => ptr_ty.childType(zcu).abiSize(zcu),
5084 .slice => null,
5085 .many, .c => unreachable,
5086 });
5087
5088 if (allow_byte_memset) if (bin_op.rhs.toInterned()) |elem_ip_index| {
5083 const elem_val: Value = .fromInterned(elem_ip_index);5089 const elem_val: Value = .fromInterned(elem_ip_index);
5084 if (elem_val.isUndef(zcu)) {5090 if (elem_val.isUndef(zcu)) {
5085 // Even if safety is disabled, we still emit a memset to undefined since it conveys5091 // Even if safety is disabled, we still emit a memset to undefined since it conveys
...@@ -5122,12 +5128,12 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error...@@ -5122,12 +5128,12 @@ fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error
5122 );5128 );
5123 return .none;5129 return .none;
5124 }5130 }
5125 }5131 };
51265132
5127 const value = try self.resolveInst(bin_op.rhs);5133 const value = try self.resolveInst(bin_op.rhs);
5128 const elem_abi_size = elem_ty.abiSize(zcu);5134 const elem_abi_size = elem_ty.abiSize(zcu);
51295135
5130 if (elem_abi_size == 1 and elem_ty.bitSize(zcu) == 8) {5136 if (allow_byte_memset and elem_abi_size == 1 and elem_ty.bitSize(zcu) == 8) {
5131 // In this case we can take advantage of LLVM's intrinsic.5137 // In this case we can take advantage of LLVM's intrinsic.
5132 const fill_byte = try self.bitCast(value, elem_ty, Type.u8);5138 const fill_byte = try self.bitCast(value, elem_ty, Type.u8);
5133 const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);5139 const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
...@@ -7460,6 +7466,32 @@ fn llvmAllocaAddressSpace(target: *const std.Target) Builder.AddrSpace {...@@ -7460,6 +7466,32 @@ fn llvmAllocaAddressSpace(target: *const std.Target) Builder.AddrSpace {
7460 };7466 };
7461}7467}
74627468
7469/// Due to an LLVM bug, calls to `@llvm.memset.inline.*` with large constant length arguments cause
7470/// LLVM to crash. As a mitigation, this function returns `true` if we should avoid emitting a
7471/// memset call of the given length.
7472///
7473/// Most of our call sites are just setting memory to `undefined`, so can simply skip the memset
7474/// call if we return `true`.
7475///
7476/// Upstream issue: https://github.com/llvm/llvm-project/issues/189161
7477/// Zig issue: https://codeberg.org/ziglang/zig/issues/31701
7478fn needMemsetWorkaround(fg: *const FuncGen, maybe_len: ?u64) bool {
7479 if (!fg.disable_intrinsics) {
7480 // The bug is limited to `@llvm.memset.inline.*`: normal memset calls are fine.
7481 return false;
7482 }
7483 const len = maybe_len orelse {
7484 // We don't think the length is constant, but a trivial optimization on LLVM's side could
7485 // turn it into one and potentially trigger the bug. Therefore, always apply the workaround
7486 // if the length is not a known constant.
7487 return true;
7488 };
7489 // Empirically, the crash first happens at 1048561 bytes, which is 1 MiB less 15 bytes. To be
7490 // safe (just in case the limit is target-specific or something like that), let's just set the
7491 // cap at half of that, i.e. 512 KiB.
7492 return len > 1024 * 512;
7493}
7494
7463const mips_clobber_overrides = std.StaticStringMap(enum {7495const mips_clobber_overrides = std.StaticStringMap(enum {
7464 @"$msair",7496 @"$msair",
7465 @"$msacsr",7497 @"$msacsr",