-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[Clang][CodeGen] Do not set inbounds flag for struct GEP with null base pointers #130734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
@llvm/pr-subscribers-clang-codegen Author: Yingwei Zheng (dtcxzyw) ChangesIn the LLVM middle-end we want to fold 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. Full diff: https://github.com/llvm/llvm-project/pull/130734.diff 3 Files Affected:
diff --git a/clang/lib/CodeGen/CGBuilder.h b/clang/lib/CodeGen/CGBuilder.h
index b8036cf6e6a30..11e8818b33397 100644
--- a/clang/lib/CodeGen/CGBuilder.h
+++ b/clang/lib/CodeGen/CGBuilder.h
@@ -223,11 +223,16 @@ class CGBuilderTy : public CGBuilderBaseTy {
const llvm::StructLayout *Layout = DL.getStructLayout(ElTy);
auto Offset = CharUnits::fromQuantity(Layout->getElementOffset(Index));
- return Address(CreateStructGEP(Addr.getElementType(), Addr.getBasePointer(),
- Index, Name),
- ElTy->getElementType(Index),
- Addr.getAlignment().alignmentAtOffset(Offset),
- Addr.isKnownNonNull());
+ // Specially, we don't add inbounds flags if the base pointer is null.
+ // This is a workaround for old-style offsetof macros.
+ llvm::GEPNoWrapFlags NWFlags = llvm::GEPNoWrapFlags::noUnsignedWrap();
+ if (!isa<llvm::ConstantPointerNull>(Addr.getBasePointer()))
+ NWFlags |= llvm::GEPNoWrapFlags::inBounds();
+ return Address(
+ CreateConstGEP2_32(Addr.getElementType(), Addr.getBasePointer(), 0,
+ Index, Name, NWFlags),
+ ElTy->getElementType(Index),
+ Addr.getAlignment().alignmentAtOffset(Offset), Addr.isKnownNonNull());
}
/// Given
diff --git a/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c b/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c
index 68c0ee3a3a885..a7cfd77766712 100644
--- a/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c
+++ b/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c
@@ -17,7 +17,7 @@ struct S {
// CHECK-LABEL: @get_offset_of_y_naively(
// CHECK-NEXT: entry:
-// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
+// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
//
uintptr_t get_offset_of_y_naively(void) {
return ((uintptr_t)(&(((struct S *)0)->y)));
diff --git a/clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp b/clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp
index 34d4f4c9e34eb..f00a2c486574c 100644
--- a/clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp
+++ b/clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp
@@ -10,7 +10,7 @@ struct S {
// CHECK-LABEL: @_Z23get_offset_of_y_naivelyv(
// CHECK-NEXT: entry:
-// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
+// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
//
uintptr_t get_offset_of_y_naively() {
return ((uintptr_t)(&(((S *)nullptr)->y)));
|
@llvm/pr-subscribers-clang Author: Yingwei Zheng (dtcxzyw) ChangesIn the LLVM middle-end we want to fold 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. Full diff: https://github.com/llvm/llvm-project/pull/130734.diff 3 Files Affected:
diff --git a/clang/lib/CodeGen/CGBuilder.h b/clang/lib/CodeGen/CGBuilder.h
index b8036cf6e6a30..11e8818b33397 100644
--- a/clang/lib/CodeGen/CGBuilder.h
+++ b/clang/lib/CodeGen/CGBuilder.h
@@ -223,11 +223,16 @@ class CGBuilderTy : public CGBuilderBaseTy {
const llvm::StructLayout *Layout = DL.getStructLayout(ElTy);
auto Offset = CharUnits::fromQuantity(Layout->getElementOffset(Index));
- return Address(CreateStructGEP(Addr.getElementType(), Addr.getBasePointer(),
- Index, Name),
- ElTy->getElementType(Index),
- Addr.getAlignment().alignmentAtOffset(Offset),
- Addr.isKnownNonNull());
+ // Specially, we don't add inbounds flags if the base pointer is null.
+ // This is a workaround for old-style offsetof macros.
+ llvm::GEPNoWrapFlags NWFlags = llvm::GEPNoWrapFlags::noUnsignedWrap();
+ if (!isa<llvm::ConstantPointerNull>(Addr.getBasePointer()))
+ NWFlags |= llvm::GEPNoWrapFlags::inBounds();
+ return Address(
+ CreateConstGEP2_32(Addr.getElementType(), Addr.getBasePointer(), 0,
+ Index, Name, NWFlags),
+ ElTy->getElementType(Index),
+ Addr.getAlignment().alignmentAtOffset(Offset), Addr.isKnownNonNull());
}
/// Given
diff --git a/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c b/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c
index 68c0ee3a3a885..a7cfd77766712 100644
--- a/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c
+++ b/clang/test/CodeGen/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.c
@@ -17,7 +17,7 @@ struct S {
// CHECK-LABEL: @get_offset_of_y_naively(
// CHECK-NEXT: entry:
-// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
+// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
//
uintptr_t get_offset_of_y_naively(void) {
return ((uintptr_t)(&(((struct S *)0)->y)));
diff --git a/clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp b/clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp
index 34d4f4c9e34eb..f00a2c486574c 100644
--- a/clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp
+++ b/clang/test/CodeGenCXX/catch-nullptr-and-nonzero-offset-in-offsetof-idiom.cpp
@@ -10,7 +10,7 @@ struct S {
// CHECK-LABEL: @_Z23get_offset_of_y_naivelyv(
// CHECK-NEXT: entry:
-// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr inbounds nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
+// CHECK-NEXT: ret i64 ptrtoint (ptr getelementptr nuw ([[STRUCT_S:%.*]], ptr null, i32 0, i32 1) to i64)
//
uintptr_t get_offset_of_y_naively() {
return ((uintptr_t)(&(((S *)nullptr)->y)));
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes sense, but please wait for a second opinion...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I somehow thought we had already done something about this, but I can't find any reference to it.
Anyway, if we're going to support this as an extension, I don't want to bury it in CGBuilder.h. We should decide exactly when we want it to trigger, and make it trigger for those language constructs. Not any construct that happens to go through CreateStructGEP, and not any pointer that happens to get folded to ConstantPointerNull. (Note on some targets, some null pointers are not ConstantPointerNull.)
Agree. We hit other cases where the optimization will break the stage2 build. See also #130742 (comment)
We only perform this optimization in |
I'd still prefer to get this right for all address-spaces, so we don't need to revisit later. But really, I'm more concerned about the "happens to get folded" part: there isn't a stable set of values that get constant-folded, at the LLVM IR level, it's just whether IRBuilder happens to do today. The only way to get a stable answer is to query the AST (Expr::isNullPointerConstant). |
Sentinel pointer value support for non-0 address spaces is still work in progress: #83109
I don't expect it to work on more complex patterns like
|
Sure, that particular pattern probably doesn't need to work, but at the very least, you're depending on certain casts getting constant-folded. And it's not clear what set of casts will in fact be consistently folded. Even if all the constructs you care about get folded right now, it's not clear that will remain true in the future. And with CIR coming along, they also need a consistent rule to follow.
Again, the check should not be in CreateStructGEP; this check needs to happen at a higher level. And at that level, you should have an Expr*, not just an Address. Do you care at all about patterns like |
I tried to use llvm-project/clang/lib/CodeGen/CGExpr.cpp Lines 4774 to 4781 in 59fd287
The base expr is:
Yes. This pattern is used by openssl. |
Just ran into some related code: see 3d0a540 . Maybe using IgnoreParenCasts() like that code does is appropriate. (I don't really like using IgnoreParenCasts() because there's such a big variety of casts, but it's probably okay in this context.)
Note this has two MemberExprs, so if you want to handle this, you'll have to look through the MemberExpr when you're generating the second GEP, I think. |
@efriedma-quic Any more comments? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs release note.
The big thing I'm not sure about is whether we want a corresponding warning. We're detecting a pattern we know has undefined behavior, so we should maybe warn? CC @AaronBallman @erichkeane
4177691
to
670ab42
Compare
This pattern is undefined behavior, but we're wanting to define the behavior in this one specific case. My preference is to be honest about that: add documentation to |
// 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) | ||
// | ||
uintptr_t get_offset_of_y_naively_nested() { | ||
return ((uintptr_t)(&(((T *)nullptr)->s.y))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to have tests for other null pointer constants like 0
, but there's a question of how deep down the rabbit hole we want the extension to go. I think it makes sense to allow it only for null pointer constants, not just any constant expression which converts to a null pointer. But maybe that's too restrictive?
Suggested tests:
nullptr_t{} // Should probably be accepted because this is a valid null pointer constant
constexpr void *null_ptr = nullptr; // Should probably be rejected, null_ptr is not a null pointer constant
0 // Should be accepted because this is a valid null pointer constant
(void *)0 // Should only be accepted in C because it's a valid null pointer constant there, but not in C++
and you should also ensure the C tests cover these cases as well.
f9c7cf9
to
ad576ca
Compare
… type (#134221) If `CreateConstInBoundsGEP2_32` returns a constant null/gep, the cast to GetElementPtrInst will fail. This patch uses two static helpers `GEPOperator::accumulateConstantOffset/GetElementPtrInst::getIndexedType` to infer offset and result type instead of depending on the GEP result. This patch is extracted from #130734.
… and result type (#134221) If `CreateConstInBoundsGEP2_32` returns a constant null/gep, the cast to GetElementPtrInst will fail. This patch uses two static helpers `GEPOperator::accumulateConstantOffset/GetElementPtrInst::getIndexedType` to infer offset and result type instead of depending on the GEP result. This patch is extracted from llvm/llvm-project#130734.
…uct GEPs (#134269) This patch turns off inbounds/nuw flags for member accesses when `-fwrapv-pointer` is set. Closes llvm/llvm-project#132449. It is required by llvm/llvm-project#130734.
ad576ca
to
0f6ff60
Compare
…lvm#134269) This patch turns off inbounds/nuw flags for member accesses when `-fwrapv-pointer` is set. Closes llvm#132449. It is required by llvm#130734.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
…dress` when the base pointer is null (#130952) See also #130734 for the original motivation. This pattern (`container_of`) is also widely used by real-world programs. Examples: https://github.com/llvm/llvm-project/blob/1d89d7d5d76e391b035f50343e2a4890506c6f2b/llvm/include/llvm/IR/SymbolTableListTraits.h#L77-L87 https://github.com/nodejs/node/blob/a2a53cb728ec5f776ac16879ce0f480a8e838320/src/util-inl.h#L134-L137 https://github.com/search?q=*%29nullptr-%3E*&type=code
…taPointerAddress` when the base pointer is null (#130952) See also llvm/llvm-project#130734 for the original motivation. This pattern (`container_of`) is also widely used by real-world programs. Examples: https://github.com/llvm/llvm-project/blob/1d89d7d5d76e391b035f50343e2a4890506c6f2b/llvm/include/llvm/IR/SymbolTableListTraits.h#L77-L87 https://github.com/nodejs/node/blob/a2a53cb728ec5f776ac16879ce0f480a8e838320/src/util-inl.h#L134-L137 https://github.com/search?q=*%29nullptr-%3E*&type=code
…lvm#134269) This patch turns off inbounds/nuw flags for member accesses when `-fwrapv-pointer` is set. Closes llvm#132449. It is required by llvm#130734.
…se pointers (llvm#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.
…dress` when the base pointer is null (llvm#130952) See also llvm#130734 for the original motivation. This pattern (`container_of`) is also widely used by real-world programs. Examples: https://github.com/llvm/llvm-project/blob/1d89d7d5d76e391b035f50343e2a4890506c6f2b/llvm/include/llvm/IR/SymbolTableListTraits.h#L77-L87 https://github.com/nodejs/node/blob/a2a53cb728ec5f776ac16879ce0f480a8e838320/src/util-inl.h#L134-L137 https://github.com/search?q=*%29nullptr-%3E*&type=code
Proof: https://alive2.llvm.org/ce/z/5ZkPx- See also #130734 for the motivation.
…ull` (#130742) Proof: https://alive2.llvm.org/ce/z/5ZkPx- See also llvm/llvm-project#130734 for the motivation.
…130742) Proof: https://alive2.llvm.org/ce/z/5ZkPx- See also llvm#130734 for the motivation.
…130742) Proof: https://alive2.llvm.org/ce/z/5ZkPx- See also llvm#130734 for the motivation.
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.