| ... | ... | @@ -919,7 +919,7 @@ fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!vo |
| 919 | 919 | if (self.ret_ptr != .none) { |
| 920 | 920 | const operand = try self.resolveInst(un_op); |
| 921 | 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 | 923 | const len = try o.builder.intValue(try o.lowerType(.usize), ret_ty.abiSize(zcu)); |
| 924 | 924 | _ = try self.wip.callMemSet( |
| 925 | 925 | self.ret_ptr, |
| ... | ... | @@ -973,7 +973,7 @@ fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) Allocator.Error!vo |
| 973 | 973 | const val_is_undef = if (un_op.toInterned()) |i| Value.fromInterned(i).isUndef(zcu) else false; |
| 974 | 974 | const alignment = ret_ty.abiAlignment(zcu).toLlvm(); |
| 975 | 975 | |
| 976 | | if (val_is_undef and safety) { |
| 976 | if (val_is_undef and safety and !self.needMemsetWorkaround(ret_ty.abiSize(zcu))) { |
| 977 | 977 | const llvm_ret_ty = operand.typeOfWip(&self.wip); |
| 978 | 978 | const rp = try self.buildAlloca(llvm_ret_ty, alignment); |
| 979 | 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 | 4697 | const operand_ty = ptr_ty.childType(zcu); |
| 4698 | 4698 | |
| 4699 | 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 | 4701 | const owner_mod = self.ownerModule(); |
| 4702 | 4702 | |
| 4703 | 4703 | // 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 | 5079 | |
| 5080 | 5080 | self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu)); |
| 5081 | 5081 | |
| 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 | 5089 | const elem_val: Value = .fromInterned(elem_ip_index); |
| 5084 | 5090 | if (elem_val.isUndef(zcu)) { |
| 5085 | 5091 | // 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 | 5128 | ); |
| 5123 | 5129 | return .none; |
| 5124 | 5130 | } |
| 5125 | | } |
| 5131 | }; |
| 5126 | 5132 | |
| 5127 | 5133 | const value = try self.resolveInst(bin_op.rhs); |
| 5128 | 5134 | const elem_abi_size = elem_ty.abiSize(zcu); |
| 5129 | 5135 | |
| 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 | 5137 | // In this case we can take advantage of LLVM's intrinsic. |
| 5132 | 5138 | const fill_byte = try self.bitCast(value, elem_ty, Type.u8); |
| 5133 | 5139 | const len = try self.sliceOrArrayLenInBytes(dest_slice, ptr_ty); |
| ... | ... | @@ -7460,6 +7466,32 @@ fn llvmAllocaAddressSpace(target: *const std.Target) Builder.AddrSpace { |
| 7460 | 7466 | }; |
| 7461 | 7467 | } |
| 7462 | 7468 | |
| 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 |
| 7478 | fn 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 | |
| 7463 | 7495 | const mips_clobber_overrides = std.StaticStringMap(enum { |
| 7464 | 7496 | @"$msair", |
| 7465 | 7497 | @"$msacsr", |