-
Notifications
You must be signed in to change notification settings - Fork 13.5k
[CaptureTracking][NFC] Clarify usage expectations in PointerMayBeCaptured comments #132744
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
Fixes issue llvm#132739. This fixes a missed capture detection scenario where only the base pointer is captured. Previously, PointerMayBeCaptured only performed forward def-use analysis on the given pointer, which worked for base pointers (Arguments/Allocas/Calls) but failed to detect captures of derived pointers when their base pointers were captured. The fix: For the input pointer, first trace back to its base pointer using `getUnderlyingObject`, then perform capture analysis on the base. This ensures proper handling of derived pointers with bases captured. Performance considerations: - Most existing callers already pass base pointers (the common case), so the added backtracing has negligible overhead - The few callers (ThreadSanitizer.cpp/SanitizerBinaryMetadata.cpp) passing arbitrary pointers are sanitizer-related components where compilation time is less critical compared to runtime overhead This addresses false negatives in capture detection while maintaining reasonable compilation efficiency.
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@llvm/pr-subscribers-llvm-analysis Author: None (Camsyn) ChangesFixes issue #132739. This fixes a missed capture detection scenario where only the base pointer is captured, as follows: int arr[2] = {0, 0};
foo(arr); // arr is captured
arr[1] = 1; // arr + 1 is mis-analyzed as non-capture Previously, PointerMayBeCaptured only performed forward def-use analysis on the given pointer, which worked for base pointers (Arguments/Allocas/Calls) but failed to detect captures of derived pointers when their base pointers were captured. // Forward Tracing
return analyzeCaptures(Ptr); The fix: For the input pointer, first trace back to its base pointer using // Backward Tracing
const Value *Obj = getUnderlyingObject(Ptr);
// Forward Tracing
return analyzeCaptures(Obj); Performance considerations:
This addresses false negatives in capture detection while maintaining reasonable compilation efficiency. Full diff: https://github.com/llvm/llvm-project/pull/132744.diff 2 Files Affected:
diff --git a/llvm/lib/Analysis/CaptureTracking.cpp b/llvm/lib/Analysis/CaptureTracking.cpp
index aa53a27537567..0d598cd245464 100644
--- a/llvm/lib/Analysis/CaptureTracking.cpp
+++ b/llvm/lib/Analysis/CaptureTracking.cpp
@@ -424,6 +424,10 @@ void llvm::PointerMayBeCaptured(const Value *V, CaptureTracker *Tracker,
if (MaxUsesToExplore == 0)
MaxUsesToExplore = DefaultMaxUsesToExplore;
+ // When analyzing a derived pointer, we need to analyze its underlying
+ // object to determine whether it is captured.
+ // E.g., `ptr + 1` is captured if `ptr` is captured.
+ V = getUnderlyingObjectAggressive(V);
SmallVector<const Use *, 20> Worklist;
Worklist.reserve(getDefaultMaxUsesToExploreForCaptureTracking());
SmallSet<const Use *, 20> Visited;
diff --git a/llvm/unittests/Analysis/CaptureTrackingTest.cpp b/llvm/unittests/Analysis/CaptureTrackingTest.cpp
index ea3f21efc014c..f35fc0a151470 100644
--- a/llvm/unittests/Analysis/CaptureTrackingTest.cpp
+++ b/llvm/unittests/Analysis/CaptureTrackingTest.cpp
@@ -132,3 +132,31 @@ TEST(CaptureTracking, MultipleUsesInSameInstruction) {
EXPECT_EQ(ICmp, CT.Captures[6]->getUser());
EXPECT_EQ(1u, CT.Captures[6]->getOperandNo());
}
+
+TEST(CaptureTracking, DerivedPointerIfBasePointerCaptured) {
+ StringRef Assembly = R"(
+ declare void @bar(ptr)
+
+ define void @test() {
+ %stkobj = alloca [2 x i32]
+ %derived = getelementptr inbounds [2 x i32], ptr %stkobj, i64 0, i64 1
+ store i32 1, ptr %derived
+ call void @bar(ptr %stkobj)
+ ret void
+ }
+ )";
+
+ LLVMContext Context;
+ SMDiagnostic Error;
+ auto M = parseAssemblyString(Assembly, Error, Context);
+ ASSERT_TRUE(M) << "Bad assembly?";
+
+ Function *F = M->getFunction("test");
+ BasicBlock *BB = &F->getEntryBlock();
+ Instruction *StackObj = &*BB->begin();
+ Instruction *DerviedPtr = StackObj->getNextNode();
+
+ // The base object and its derived pointer are both captured.
+ EXPECT_TRUE(PointerMayBeCaptured(StackObj, true));
+ EXPECT_TRUE(PointerMayBeCaptured(DerviedPtr, true));
+}
|
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.
Mind sharing the compile-time impact for this change?
Thanks @dtcxzyw for his performance testing. In the llvm-opt-benchmark he built, this modification brings ~0.01% compilation overhead. I also tried llvm-test-suite with release mode clang locally, and the output also shows that the patch has a negligible impact on compile time, as follows:
|
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.
We definitely should not be doing this in CaptureTracking -- passing a sensible value is the responsibility of the caller. In most contexts, performing capture checks only makes sense on "identified function-local objects".
If so, should we consider stating this in the comments of The name of this function is very misleading, in fact, ThreadSanitizer.cpp and SanitizerBinaryMetadata .cpp have passed an arbitrary pointer, i.e., operand pointer of load/store, to it. Finally, thanks for your feedback 😀. |
✅ With the latest revision this PR passed the C/C++ code formatter. |
Sure, expanding the comment on the function is a good idea. |
…iewer" This reverts commit cd64fa3.
This reverts commit 87c0937.
…ured comments The comments for PointerMayBeCaptured and related APIs have been updated to explicitly state that these functions assume the input is an function-local object. It is the caller's responsibility to ensure this precondition is met. This clarification aligns with recent discussions and feedback from code reviews, aiming to prevent misuse and potential analysis errors.
Since I didn’t see the comment update in the latest commit yet, I’ve gone ahead and included it in this PR — hope that’s okay. |
@@ -41,13 +41,17 @@ namespace llvm { | |||
/// MaxUsesToExplore specifies how many uses the analysis should explore for | |||
/// one value before giving up due too "too many uses". If MaxUsesToExplore | |||
/// is zero, a default value is assumed. | |||
/// This assumes the pointer is to a function-local object. The caller is | |||
/// responsible for ensuring this. |
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.
This depends on the use case. In most cases you'd want to pass a function-local object, but e.g. to infer captures() attribute you'd just work with arbitrary function arguments.
I think I'd add something like this instead:
This function only considers captures of the passed value via its use-def chain, without considering captures of values it may be based on, or implicit captures such as for external globals.
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.
This depends on the use case. In most cases you'd want to pass a function-local object, but e.g. to infer captures() attribute you'd just work with arbitrary function arguments.
I think I'd add something like this instead:
This function only considers captures of the passed value via its use-def chain, without considering captures of values it may be based on, or implicit captures such as for external globals.
Good point, and thank you for the improved phrasing! Your suggested comment is indeed more appropriate. Just a minor correction: it should be 'def-use chain' rather than 'use-def chain' because our CaptureAnalysis works by forward tracking using getUser
.
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
Can you please update the PR description to match the new contents of the PR? |
Very honored
Done |
@Camsyn Congratulations on having your first Pull Request (PR) merged into the LLVM Project! Your changes will be combined with recent changes from other authors, then tested by our build bots. If there is a problem with a build, you may receive a report in an email or a comment on this PR. Please check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues. How to do this, and the rest of the post-merge process, is covered in detail here. If your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of LLVM development. You can fix your changes and open a new PR to merge them again. If you don't get any reports, no action is required from you. Your changes are working as expected, well done! |
…ured comments (llvm#132744) Fixes issue llvm#132739. CaptureAnalysis only considers captures through the def-use chain of the provided pointer, explicitly excluding captures of underlying values or implicit captures like those involving external globals. The previous comment for `PointerMayBeCaptured` did not clearly state this limitation, leading to its incorrect usage in files such as ThreadSanitizer.cpp and SanitizerMetadata.cpp. This PR addresses this by refining the comments for the relevant APIs within `PointerMayBeCaptured` to explicitly document this behavior.
Fixes issue #132739.
CaptureAnalysis only considers captures through the def-use chain of the provided pointer, explicitly excluding captures of underlying values or implicit captures like those involving external globals.
The previous comment for
PointerMayBeCaptured
did not clearly state this limitation, leading to its incorrect usage in files such as ThreadSanitizer.cpp and SanitizerMetadata.cpp.This PR addresses this by refining the comments for the relevant APIs within
PointerMayBeCaptured
to explicitly document this behavior.