Skip to content

Commit 1711996

Browse files
authored
[Clang][CodeGen] Do not set inbounds flag for struct GEP with null base pointers (#130734)
In the LLVM middle-end we want to fold `gep inbounds null, idx -> null`: https://alive2.llvm.org/ce/z/5ZkPx- This pattern is common in real-world programs (dtcxzyw/llvm-opt-benchmark#55 (comment)). Generally, it exists in some (actually) unreachable blocks, which is introduced by JumpThreading. However, some old-style offsetof macros are still widely used in real-world C/C++ code (e.g., hwloc/slurm/luajit). To avoid breaking existing code and inconvenience to downstream users, this patch removes the inbounds flag from the struct gep if the base pointer is null.
1 parent d14acb7 commit 1711996

File tree

5 files changed

+110
-13
lines changed

5 files changed

+110
-13
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@ Potentially Breaking Changes
4242
C/C++ Language Potentially Breaking Changes
4343
-------------------------------------------
4444

45+
- New LLVM optimizations have been implemented that optimize pointer arithmetic on
46+
null pointers more aggressively. As part of this, clang has implemented a special
47+
case for old-style offsetof idioms like ``((int)(&(((struct S *)0)->field)))``, to
48+
ensure they are not caught by these optimizations. It is also possible to use
49+
``-fwrapv-pointer`` or ``-fno-delete-null-pointer-checks`` to make pointer arithmetic
50+
on null pointers well-defined. (#GH130734, #GH130742)
51+
4552
C++ Specific Potentially Breaking Changes
4653
-----------------------------------------
4754

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4772,13 +4772,25 @@ EmitExtVectorElementExpr(const ExtVectorElementExpr *E) {
47724772
Base.getBaseInfo(), TBAAAccessInfo());
47734773
}
47744774

4775+
bool CodeGenFunction::isUnderlyingBasePointerConstantNull(const Expr *E) {
4776+
const Expr *UnderlyingBaseExpr = E->IgnoreParens();
4777+
while (auto *BaseMemberExpr = dyn_cast<MemberExpr>(UnderlyingBaseExpr))
4778+
UnderlyingBaseExpr = BaseMemberExpr->getBase()->IgnoreParens();
4779+
return getContext().isSentinelNullExpr(UnderlyingBaseExpr);
4780+
}
4781+
47754782
LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
47764783
if (DeclRefExpr *DRE = tryToConvertMemberExprToDeclRefExpr(*this, E)) {
47774784
EmitIgnoredExpr(E->getBase());
47784785
return EmitDeclRefLValue(DRE);
47794786
}
47804787

47814788
Expr *BaseExpr = E->getBase();
4789+
// Check whether the underlying base pointer is a constant null.
4790+
// If so, we do not set inbounds flag for GEP to avoid breaking some
4791+
// old-style offsetof idioms.
4792+
bool IsInBounds = !getLangOpts().PointerOverflowDefined &&
4793+
!isUnderlyingBasePointerConstantNull(BaseExpr);
47824794
// If this is s.x, emit s as an lvalue. If it is s->x, emit s as a scalar.
47834795
LValue BaseLV;
47844796
if (E->isArrow()) {
@@ -4800,7 +4812,7 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
48004812

48014813
NamedDecl *ND = E->getMemberDecl();
48024814
if (auto *Field = dyn_cast<FieldDecl>(ND)) {
4803-
LValue LV = EmitLValueForField(BaseLV, Field);
4815+
LValue LV = EmitLValueForField(BaseLV, Field, IsInBounds);
48044816
setObjCGCLValueClass(getContext(), E, LV);
48054817
if (getLangOpts().OpenMP) {
48064818
// If the member was explicitly marked as nontemporal, mark it as
@@ -4886,12 +4898,15 @@ unsigned CodeGenFunction::getDebugInfoFIndex(const RecordDecl *Rec,
48864898
/// Get the address of a zero-sized field within a record. The resulting
48874899
/// address doesn't necessarily have the right type.
48884900
static Address emitAddrOfZeroSizeField(CodeGenFunction &CGF, Address Base,
4889-
const FieldDecl *Field) {
4901+
const FieldDecl *Field,
4902+
bool IsInBounds) {
48904903
CharUnits Offset = CGF.getContext().toCharUnitsFromBits(
48914904
CGF.getContext().getFieldOffset(Field));
48924905
if (Offset.isZero())
48934906
return Base;
48944907
Base = Base.withElementType(CGF.Int8Ty);
4908+
if (!IsInBounds)
4909+
return CGF.Builder.CreateConstByteGEP(Base, Offset);
48954910
return CGF.Builder.CreateConstInBoundsByteGEP(Base, Offset);
48964911
}
48974912

@@ -4900,16 +4915,16 @@ static Address emitAddrOfZeroSizeField(CodeGenFunction &CGF, Address Base,
49004915
///
49014916
/// The resulting address doesn't necessarily have the right type.
49024917
static Address emitAddrOfFieldStorage(CodeGenFunction &CGF, Address base,
4903-
const FieldDecl *field) {
4918+
const FieldDecl *field, bool IsInBounds) {
49044919
if (isEmptyFieldForLayout(CGF.getContext(), field))
4905-
return emitAddrOfZeroSizeField(CGF, base, field);
4920+
return emitAddrOfZeroSizeField(CGF, base, field, IsInBounds);
49064921

49074922
const RecordDecl *rec = field->getParent();
49084923

49094924
unsigned idx =
49104925
CGF.CGM.getTypes().getCGRecordLayout(rec).getLLVMFieldNo(field);
49114926

4912-
if (CGF.getLangOpts().PointerOverflowDefined)
4927+
if (!IsInBounds)
49134928
return CGF.Builder.CreateConstGEP2_32(base, 0, idx, field->getName());
49144929

49154930
return CGF.Builder.CreateStructGEP(base, idx, field->getName());
@@ -4947,8 +4962,8 @@ static bool hasAnyVptr(const QualType Type, const ASTContext &Context) {
49474962
return false;
49484963
}
49494964

4950-
LValue CodeGenFunction::EmitLValueForField(LValue base,
4951-
const FieldDecl *field) {
4965+
LValue CodeGenFunction::EmitLValueForField(LValue base, const FieldDecl *field,
4966+
bool IsInBounds) {
49524967
LValueBaseInfo BaseInfo = base.getBaseInfo();
49534968

49544969
if (field->isBitField()) {
@@ -4971,7 +4986,7 @@ LValue CodeGenFunction::EmitLValueForField(LValue base,
49714986
(!getDebugInfo() || !rec->hasAttr<BPFPreserveAccessIndexAttr>())) {
49724987
if (Idx != 0) {
49734988
// For structs, we GEP to the field that the record layout suggests.
4974-
if (getLangOpts().PointerOverflowDefined)
4989+
if (!IsInBounds)
49754990
Addr = Builder.CreateConstGEP2_32(Addr, 0, Idx, field->getName());
49764991
else
49774992
Addr = Builder.CreateStructGEP(Addr, Idx, field->getName());
@@ -5084,7 +5099,7 @@ LValue CodeGenFunction::EmitLValueForField(LValue base,
50845099
if (!IsInPreservedAIRegion &&
50855100
(!getDebugInfo() || !rec->hasAttr<BPFPreserveAccessIndexAttr>()))
50865101
// For structs, we GEP to the field that the record layout suggests.
5087-
addr = emitAddrOfFieldStorage(*this, addr, field);
5102+
addr = emitAddrOfFieldStorage(*this, addr, field, IsInBounds);
50885103
else
50895104
// Remember the original struct field index
50905105
addr = emitPreserveStructAccess(*this, base, addr, field);
@@ -5128,7 +5143,9 @@ CodeGenFunction::EmitLValueForFieldInitialization(LValue Base,
51285143
if (!FieldType->isReferenceType())
51295144
return EmitLValueForField(Base, Field);
51305145

5131-
Address V = emitAddrOfFieldStorage(*this, Base.getAddress(), Field);
5146+
Address V = emitAddrOfFieldStorage(
5147+
*this, Base.getAddress(), Field,
5148+
/*IsInBounds=*/!getLangOpts().PointerOverflowDefined);
51325149

51335150
// Make sure that the address is pointing to the right type.
51345151
llvm::Type *llvmType = ConvertTypeForMem(FieldType);

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4461,7 +4461,8 @@ class CodeGenFunction : public CodeGenTypeCache {
44614461
const ObjCIvarDecl *Ivar);
44624462
llvm::Value *EmitIvarOffsetAsPointerDiff(const ObjCInterfaceDecl *Interface,
44634463
const ObjCIvarDecl *Ivar);
4464-
LValue EmitLValueForField(LValue Base, const FieldDecl *Field);
4464+
LValue EmitLValueForField(LValue Base, const FieldDecl *Field,
4465+
bool IsInBounds = true);
44654466
LValue EmitLValueForLambdaField(const FieldDecl *Field);
44664467
LValue EmitLValueForLambdaField(const FieldDecl *Field,
44674468
llvm::Value *ThisValue);
@@ -4558,6 +4559,8 @@ class CodeGenFunction : public CodeGenTypeCache {
45584559
const CXXRecordDecl *RD);
45594560

45604561
bool isPointerKnownNonNull(const Expr *E);
4562+
/// Check whether the underlying base pointer is a constant null.
4563+
bool isUnderlyingBasePointerConstantNull(const Expr *E);
45614564

45624565
/// Create the discriminator from the storage address and the entity hash.
45634566
llvm::Value *EmitPointerAuthBlendDiscriminator(llvm::Value *StorageAddress,

clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ struct S {
1717

1818
// CHECK-LABEL: @get_offset_of_y_naively(
1919
// CHECK-NEXT: entry:
20-
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
20+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
2121
//
2222
uintptr_t get_offset_of_y_naively(void) {
2323
return ((uintptr_t)(&(((struct S *)0)->y)));

clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,82 @@ struct S {
1010

1111
// CHECK-LABEL: @_Z23get_offset_of_y_naivelyv(
1212
// CHECK-NEXT: entry:
13-
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
13+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
1414
//
1515
uintptr_t get_offset_of_y_naively() {
1616
return ((uintptr_t)(&(((S *)nullptr)->y)));
1717
}
1818

19+
struct Empty {};
20+
21+
struct T {
22+
int a;
23+
S s;
24+
[[no_unique_address]] Empty e1;
25+
int b;
26+
[[no_unique_address]] Empty e2;
27+
};
28+
29+
// CHECK-LABEL: @_Z30get_offset_of_y_naively_nestedv(
30+
// CHECK-NEXT: entry:
31+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr getelementptr ([[STRUCT_T:%.*]], ptr null, i32 0, i32 1), i32 0, i32 1) to i64)
32+
//
33+
uintptr_t get_offset_of_y_naively_nested() {
34+
return ((uintptr_t)(&(((T *)nullptr)->s.y)));
35+
}
36+
37+
// CHECK-LABEL: @_Z42get_offset_of_y_naively_nested_with_parensv(
38+
// CHECK-NEXT: entry:
39+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr getelementptr ([[STRUCT_T:%.*]], ptr null, i32 0, i32 1), i32 0, i32 1) to i64)
40+
//
41+
uintptr_t get_offset_of_y_naively_nested_with_parens() {
42+
return ((uintptr_t)(&((((T *)nullptr)->s).y)));
43+
}
44+
45+
// CHECK-LABEL: @_Z26get_offset_of_zero_storagev(
46+
// CHECK-NEXT: entry:
47+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr (i8, ptr null, i64 16) to i64)
48+
//
49+
uintptr_t get_offset_of_zero_storage() {
50+
return ((uintptr_t)(&(((T *)nullptr)->e2)));
51+
}
52+
53+
namespace std { typedef decltype(__nullptr) nullptr_t; }
54+
// CHECK-LABEL: @_Z29get_offset_of_y_integral_zerov(
55+
// CHECK-NEXT: entry:
56+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
57+
//
58+
uintptr_t get_offset_of_y_integral_zero() {
59+
return ((uintptr_t)(&(((S *)0)->y)));
60+
}
61+
62+
// CHECK-LABEL: @_Z37get_offset_of_y_integral_zero_voidptrv(
63+
// CHECK-NEXT: entry:
64+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
65+
//
66+
uintptr_t get_offset_of_y_integral_zero_voidptr() {
67+
return ((uintptr_t)(&(((S *)(void*)0)->y)));
68+
}
69+
70+
// CHECK-LABEL: @_Z25get_offset_of_y_nullptr_tv(
71+
// CHECK-NEXT: entry:
72+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
73+
//
74+
uintptr_t get_offset_of_y_nullptr_t() {
75+
return ((uintptr_t)(&(((S *)std::nullptr_t{})->y)));
76+
}
77+
78+
// CHECK-LABEL: @_Z32get_offset_of_y_nullptr_constantv(
79+
// CHECK-NEXT: entry:
80+
// CHECK-NEXT: [[NULL:%.*]] = alloca ptr, align 8
81+
// CHECK-NEXT: store ptr null, ptr [[NULL]], align 8
82+
// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
83+
//
84+
uintptr_t get_offset_of_y_nullptr_constant() {
85+
constexpr void *null = nullptr;
86+
return ((uintptr_t)(&(((S *)null)->y)));
87+
}
88+
1989
// CHECK-LABEL: @_Z27get_offset_of_y_via_builtinv(
2090
// CHECK-NEXT: entry:
2191
// CHECK-NEXT: ret i64 4

0 commit comments

Comments
 (0)