authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2024-10-31 09:18:46+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-05-12 17:07:49+02:00
loge95e7651ecd988d4955a46c7adfea29c0996c62f
tree86ff483ac9358ad6d40db92d5eb504025eaacbfb
parent5c2e300f427efd76f52aefdaa8ad7fb85bb1d2d5
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

llvm: Set null_pointer_is_valid attribute when accessing allowzero pointers.

This informs optimization passes that they shouldn't assume that a load from a null pointer invokes undefined behavior. Closes #15816.

1 files changed, 84 insertions(+), 7 deletions(-)

src/codegen/llvm.zig+84-7
......@@ -1419,8 +1419,6 @@ pub const Object = struct {
14191419 }
14201420 }
14211421
1422 function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
1423
14241422 const file, const subprogram = if (!wip.strip) debug_info: {
14251423 const file = try o.getDebugFile(file_scope);
14261424
......@@ -1517,6 +1515,17 @@ pub const Object = struct {
15171515 else => |e| return e,
15181516 };
15191517
1518 // If we saw any loads or stores involving `allowzero` pointers, we need to mark the whole
1519 // function as considering null pointers valid so that LLVM's optimizers don't remove these
1520 // operations on the assumption that they're undefined behavior.
1521 if (fg.allowzero_access) {
1522 try attributes.addFnAttr(.null_pointer_is_valid, &o.builder);
1523 } else {
1524 _ = try attributes.removeFnAttr(.null_pointer_is_valid);
1525 }
1526
1527 function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
1528
15201529 if (fg.fuzz) |*f| {
15211530 {
15221531 const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .i8);
......@@ -4667,6 +4676,15 @@ pub const FuncGen = struct {
46674676
46684677 disable_intrinsics: bool,
46694678
4679 /// Have we seen loads or stores involving `allowzero` pointers?
4680 allowzero_access: bool = false,
4681
4682 pub fn maybeMarkAllowZeroAccess(self: *FuncGen, info: InternPool.Key.PtrType) void {
4683 // LLVM already considers null pointers to be valid in non-generic address spaces, so avoid
4684 // pessimizing optimization for functions with accesses to such pointers.
4685 if (info.flags.address_space == .generic and info.flags.is_allowzero) self.allowzero_access = true;
4686 }
4687
46704688 const Fuzz = struct {
46714689 counters_variable: Builder.Variable.Index,
46724690 pcs: std.ArrayListUnmanaged(Builder.Constant),
......@@ -6206,6 +6224,9 @@ pub const FuncGen = struct {
62066224 const body: []const Air.Inst.Index = @ptrCast(self.air.extra[extra.end..][0..extra.data.body_len]);
62076225 const err_union_ty = self.typeOf(extra.data.ptr).childType(zcu);
62086226 const is_unused = self.liveness.isUnused(inst);
6227
6228 self.maybeMarkAllowZeroAccess(self.typeOf(extra.data.ptr).ptrInfo(zcu));
6229
62096230 return lowerTry(self, err_union_ptr, body, err_union_ty, true, true, is_unused, err_cold);
62106231 }
62116232
......@@ -6751,10 +6772,14 @@ pub const FuncGen = struct {
67516772 if (self.canElideLoad(body_tail))
67526773 return ptr;
67536774
6775 self.maybeMarkAllowZeroAccess(slice_ty.ptrInfo(zcu));
6776
67546777 const elem_alignment = elem_ty.abiAlignment(zcu).toLlvm();
67556778 return self.loadByRef(ptr, elem_ty, elem_alignment, .normal);
67566779 }
67576780
6781 self.maybeMarkAllowZeroAccess(slice_ty.ptrInfo(zcu));
6782
67586783 return self.load(ptr, slice_ty);
67596784 }
67606785
......@@ -6824,10 +6849,15 @@ pub const FuncGen = struct {
68246849 &.{rhs}, "");
68256850 if (isByRef(elem_ty, zcu)) {
68266851 if (self.canElideLoad(body_tail)) return ptr;
6852
6853 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
6854
68276855 const elem_alignment = elem_ty.abiAlignment(zcu).toLlvm();
68286856 return self.loadByRef(ptr, elem_ty, elem_alignment, .normal);
68296857 }
68306858
6859 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
6860
68316861 return self.load(ptr, ptr_ty);
68326862 }
68336863
......@@ -7235,6 +7265,8 @@ pub const FuncGen = struct {
72357265 }),
72367266 }
72377267
7268 self.maybeMarkAllowZeroAccess(output_ty.ptrInfo(zcu));
7269
72387270 // Pass any non-return outputs indirectly, if the constraint accepts a memory location
72397271 is_indirect.* = constraintAllowsMemory(constraint);
72407272 if (is_indirect.*) {
......@@ -7341,10 +7373,11 @@ pub const FuncGen = struct {
73417373
73427374 // In the case of indirect inputs, LLVM requires the callsite to have
73437375 // an elementtype(<ty>) attribute.
7344 llvm_param_attrs[llvm_param_i] = if (constraint[0] == '*')
7345 try o.lowerPtrElemTy(if (is_by_ref) arg_ty else arg_ty.childType(zcu))
7346 else
7347 .none;
7376 llvm_param_attrs[llvm_param_i] = if (constraint[0] == '*') blk: {
7377 if (!is_by_ref) self.maybeMarkAllowZeroAccess(arg_ty.ptrInfo(zcu));
7378
7379 break :blk try o.lowerPtrElemTy(if (is_by_ref) arg_ty else arg_ty.childType(zcu));
7380 } else .none;
73487381
73497382 llvm_param_i += 1;
73507383 total_i += 1;
......@@ -7530,7 +7563,6 @@ pub const FuncGen = struct {
75307563 if (output != .none) {
75317564 const output_ptr = try self.resolveInst(output);
75327565 const output_ptr_ty = self.typeOf(output);
7533
75347566 const alignment = output_ptr_ty.ptrAlignment(zcu).toLlvm();
75357567 _ = try self.wip.store(.normal, output_value, output_ptr, alignment);
75367568 } else {
......@@ -7557,6 +7589,9 @@ pub const FuncGen = struct {
75577589 const optional_ty = if (operand_is_ptr) operand_ty.childType(zcu) else operand_ty;
75587590 const optional_llvm_ty = try o.lowerType(optional_ty);
75597591 const payload_ty = optional_ty.optionalChild(zcu);
7592
7593 if (operand_is_ptr) self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
7594
75607595 if (optional_ty.optionalReprIsPayload(zcu)) {
75617596 const loaded = if (operand_is_ptr)
75627597 try self.wip.load(.normal, optional_llvm_ty, operand, .default, "")
......@@ -7613,6 +7648,8 @@ pub const FuncGen = struct {
76137648 return val.toValue();
76147649 }
76157650
7651 if (operand_is_ptr) self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
7652
76167653 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
76177654 const loaded = if (operand_is_ptr)
76187655 try self.wip.load(.normal, try o.lowerType(err_union_ty), operand, .default, "")
......@@ -7664,6 +7701,8 @@ pub const FuncGen = struct {
76647701 const payload_ty = optional_ty.optionalChild(zcu);
76657702 const non_null_bit = try o.builder.intValue(.i8, 1);
76667703 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
7704 self.maybeMarkAllowZeroAccess(self.typeOf(ty_op.operand).ptrInfo(zcu));
7705
76677706 // We have a pointer to a i8. We need to set it to 1 and then return the same pointer.
76687707 _ = try self.wip.store(.normal, non_null_bit, operand, .default);
76697708 return operand;
......@@ -7677,6 +7716,9 @@ pub const FuncGen = struct {
76777716 // First set the non-null bit.
76787717 const optional_llvm_ty = try o.lowerType(optional_ty);
76797718 const non_null_ptr = try self.wip.gepStruct(optional_llvm_ty, operand, 1, "");
7719
7720 self.maybeMarkAllowZeroAccess(self.typeOf(ty_op.operand).ptrInfo(zcu));
7721
76807722 // TODO set alignment on this store
76817723 _ = try self.wip.store(.normal, non_null_bit, non_null_ptr, .default);
76827724
......@@ -7767,12 +7809,17 @@ pub const FuncGen = struct {
77677809 const payload_ty = err_union_ty.errorUnionPayload(zcu);
77687810 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
77697811 if (!operand_is_ptr) return operand;
7812
7813 self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
7814
77707815 return self.wip.load(.normal, error_type, operand, .default, "");
77717816 }
77727817
77737818 const offset = try errUnionErrorOffset(payload_ty, pt);
77747819
77757820 if (operand_is_ptr or isByRef(err_union_ty, zcu)) {
7821 if (operand_is_ptr) self.maybeMarkAllowZeroAccess(operand_ty.ptrInfo(zcu));
7822
77767823 const err_union_llvm_ty = try o.lowerType(err_union_ty);
77777824 const err_field_ptr = try self.wip.gepStruct(err_union_llvm_ty, operand, offset, "");
77787825 return self.wip.load(.normal, error_type, err_field_ptr, .default, "");
......@@ -7792,11 +7839,15 @@ pub const FuncGen = struct {
77927839 const payload_ty = err_union_ty.errorUnionPayload(zcu);
77937840 const non_error_val = try o.builder.intValue(try o.errorIntType(), 0);
77947841 if (!payload_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
7842 self.maybeMarkAllowZeroAccess(self.typeOf(ty_op.operand).ptrInfo(zcu));
7843
77957844 _ = try self.wip.store(.normal, non_error_val, operand, .default);
77967845 return operand;
77977846 }
77987847 const err_union_llvm_ty = try o.lowerType(err_union_ty);
77997848 {
7849 self.maybeMarkAllowZeroAccess(self.typeOf(ty_op.operand).ptrInfo(zcu));
7850
78007851 const err_int_ty = try pt.errorIntType();
78017852 const error_alignment = err_int_ty.abiAlignment(zcu).toLlvm();
78027853 const error_offset = try errUnionErrorOffset(payload_ty, pt);
......@@ -8017,6 +8068,8 @@ pub const FuncGen = struct {
80178068 const index = try self.resolveInst(extra.lhs);
80188069 const operand = try self.resolveInst(extra.rhs);
80198070
8071 self.maybeMarkAllowZeroAccess(vector_ptr_ty.ptrInfo(zcu));
8072
80208073 const access_kind: Builder.MemoryAccessKind =
80218074 if (vector_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
80228075 const elem_llvm_ty = try o.lowerType(vector_ptr_ty.childType(zcu));
......@@ -9482,6 +9535,8 @@ pub const FuncGen = struct {
94829535 return .none;
94839536 }
94849537
9538 self.maybeMarkAllowZeroAccess(ptr_info);
9539
94859540 const len = try o.builder.intValue(try o.lowerType(Type.usize), operand_ty.abiSize(zcu));
94869541 _ = try self.wip.callMemSet(
94879542 dest_ptr,
......@@ -9497,6 +9552,8 @@ pub const FuncGen = struct {
94979552 return .none;
94989553 }
94999554
9555 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9556
95009557 const src_operand = try self.resolveInst(bin_op.rhs);
95019558 try self.store(dest_ptr, ptr_ty, src_operand, .none);
95029559 return .none;
......@@ -9539,6 +9596,9 @@ pub const FuncGen = struct {
95399596 if (!canElideLoad(fg, body_tail)) break :elide;
95409597 return ptr;
95419598 }
9599
9600 fg.maybeMarkAllowZeroAccess(ptr_info);
9601
95429602 return fg.load(ptr, ptr_ty);
95439603 }
95449604
......@@ -9598,6 +9658,8 @@ pub const FuncGen = struct {
95989658 new_value = try self.wip.conv(signedness, new_value, llvm_abi_ty, "");
95999659 }
96009660
9661 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9662
96019663 const result = try self.wip.cmpxchg(
96029664 kind,
96039665 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal,
......@@ -9649,6 +9711,8 @@ pub const FuncGen = struct {
96499711 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
96509712 const ptr_alignment = ptr_ty.ptrAlignment(zcu).toLlvm();
96519713
9714 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9715
96529716 if (llvm_abi_ty != .none) {
96539717 // operand needs widening and truncating or bitcasting.
96549718 return self.wip.cast(if (is_float) .bitcast else .trunc, try self.wip.atomicrmw(
......@@ -9712,6 +9776,8 @@ pub const FuncGen = struct {
97129776 if (info.flags.is_volatile) .@"volatile" else .normal;
97139777 const elem_llvm_ty = try o.lowerType(elem_ty);
97149778
9779 self.maybeMarkAllowZeroAccess(info);
9780
97159781 if (llvm_abi_ty != .none) {
97169782 // operand needs widening and truncating
97179783 const loaded = try self.wip.loadAtomic(
......@@ -9761,6 +9827,9 @@ pub const FuncGen = struct {
97619827 "",
97629828 );
97639829 }
9830
9831 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9832
97649833 try self.store(ptr, ptr_ty, element, ordering);
97659834 return .none;
97669835 }
......@@ -9778,6 +9847,8 @@ pub const FuncGen = struct {
97789847 const access_kind: Builder.MemoryAccessKind =
97799848 if (ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
97809849
9850 self.maybeMarkAllowZeroAccess(ptr_ty.ptrInfo(zcu));
9851
97819852 if (try self.air.value(bin_op.rhs, pt)) |elem_val| {
97829853 if (elem_val.isUndefDeep(zcu)) {
97839854 // Even if safety is disabled, we still emit a memset to undefined since it conveys
......@@ -9916,6 +9987,9 @@ pub const FuncGen = struct {
99169987 const access_kind: Builder.MemoryAccessKind = if (src_ptr_ty.isVolatilePtr(zcu) or
99179988 dest_ptr_ty.isVolatilePtr(zcu)) .@"volatile" else .normal;
99189989
9990 self.maybeMarkAllowZeroAccess(dest_ptr_ty.ptrInfo(zcu));
9991 self.maybeMarkAllowZeroAccess(src_ptr_ty.ptrInfo(zcu));
9992
99199993 _ = try self.wip.callMemCpy(
99209994 dest_ptr,
99219995 dest_ptr_ty.ptrAlignment(zcu).toLlvm(),
......@@ -9962,6 +10036,9 @@ pub const FuncGen = struct {
996210036 const un_ty = self.typeOf(bin_op.lhs).childType(zcu);
996310037 const layout = un_ty.unionGetLayout(zcu);
996410038 if (layout.tag_size == 0) return .none;
10039
10040 self.maybeMarkAllowZeroAccess(self.typeOf(bin_op.lhs).ptrInfo(zcu));
10041
996510042 const union_ptr = try self.resolveInst(bin_op.lhs);
996610043 const new_tag = try self.resolveInst(bin_op.rhs);
996710044 if (layout.payload_size == 0) {