diff options
author | Nikita Popov <npopov@redhat.com> | 2025-02-13 09:36:35 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-13 09:36:35 +0100 |
commit | ee655ca27aad466bcc54f6eba03f7e564940ad5a (patch) | |
tree | 931f52c4f0979e5abf40717b90a121f979cf9b36 /llvm/lib/Transforms/IPO/FunctionAttrs.cpp | |
parent | 3bf425764e1837e909263a7e61a13e2308dc7d5f (diff) | |
download | llvm-ee655ca27aad466bcc54f6eba03f7e564940ad5a.zip llvm-ee655ca27aad466bcc54f6eba03f7e564940ad5a.tar.gz llvm-ee655ca27aad466bcc54f6eba03f7e564940ad5a.tar.bz2 |
[CaptureTracking][FunctionAttrs] Add support for CaptureInfo (#125880)
This extends CaptureTracking to support inferring non-trivial
CaptureInfos. The focus of this patch is to only support FunctionAttrs,
other users of CaptureTracking will be updated in followups.
The key API changes here are:
* DetermineUseCaptureKind() now returns a UseCaptureInfo where the UseCC
component specifies what is captured at that Use and the ResultCC
component specifies what may be captured via the return value of the
User. Usually only one or the other will be used (corresponding to
previous MAY_CAPTURE or PASSTHROUGH results), but both may be set for
call captures.
* The CaptureTracking::captures() extension point is passed this
UseCaptureInfo as well and then can decide what to do with it by
returning an Action, which is one of: Stop: stop traversal.
ContinueIgnoringReturn: continue traversal but don't follow the
instruction return value. Continue: continue traversal and follow the
instruction return value if it has additional CaptureComponents.
For now, this patch retains the (unsound) special logic for comparison
of null with a dereferenceable pointer. I'd like to switch key code to
take advantage of address/address_is_null before dropping it.
This PR mainly intends to introduce necessary API changes and basic
inference support, there are various possible improvements marked with
TODOs.
Diffstat (limited to 'llvm/lib/Transforms/IPO/FunctionAttrs.cpp')
-rw-r--r-- | llvm/lib/Transforms/IPO/FunctionAttrs.cpp | 149 |
1 files changed, 103 insertions, 46 deletions
diff --git a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp index cf56f67..4445d56 100644 --- a/llvm/lib/Transforms/IPO/FunctionAttrs.cpp +++ b/llvm/lib/Transforms/IPO/FunctionAttrs.cpp @@ -71,7 +71,9 @@ using namespace llvm; #define DEBUG_TYPE "function-attrs" STATISTIC(NumMemoryAttr, "Number of functions with improved memory attribute"); -STATISTIC(NumNoCapture, "Number of arguments marked nocapture"); +STATISTIC(NumCapturesNone, "Number of arguments marked captures(none)"); +STATISTIC(NumCapturesPartial, "Number of arguments marked with captures " + "attribute other than captures(none)"); STATISTIC(NumReturned, "Number of arguments marked returned"); STATISTIC(NumReadNoneArg, "Number of arguments marked readnone"); STATISTIC(NumReadOnlyArg, "Number of arguments marked readonly"); @@ -108,6 +110,13 @@ static cl::opt<bool> DisableThinLTOPropagation( "disable-thinlto-funcattrs", cl::init(true), cl::Hidden, cl::desc("Don't propagate function-attrs in thinLTO")); +static void addCapturesStat(CaptureInfo CI) { + if (capturesNothing(CI)) + ++NumCapturesNone; + else + ++NumCapturesPartial; +} + namespace { using SCCNodeSet = SmallSetVector<Function *, 8>; @@ -494,6 +503,9 @@ namespace { /// SCC of the arguments. struct ArgumentGraphNode { Argument *Definition; + /// CaptureComponents for this argument, excluding captures via Uses. + /// We don't distinguish between other/return captures here. + CaptureComponents CC = CaptureComponents::None; SmallVector<ArgumentGraphNode *, 4> Uses; }; @@ -535,18 +547,36 @@ public: struct ArgumentUsesTracker : public CaptureTracker { ArgumentUsesTracker(const SCCNodeSet &SCCNodes) : SCCNodes(SCCNodes) {} - void tooManyUses() override { Captured = true; } + void tooManyUses() override { CI = CaptureInfo::all(); } + + Action captured(const Use *U, UseCaptureInfo UseCI) override { + if (updateCaptureInfo(U, UseCI.UseCC)) { + // Don't bother continuing if we already capture everything. + if (capturesAll(CI.getOtherComponents())) + return Stop; + return Continue; + } + + // For SCC argument tracking, we're not going to analyze other/ret + // components separately, so don't follow the return value. + return ContinueIgnoringReturn; + } - bool captured(const Use *U) override { + bool updateCaptureInfo(const Use *U, CaptureComponents CC) { CallBase *CB = dyn_cast<CallBase>(U->getUser()); if (!CB) { - Captured = true; + if (isa<ReturnInst>(U->getUser())) + CI |= CaptureInfo::retOnly(CC); + else + // Conservatively assume that the captured value might make its way + // into the return value as well. This could be made more precise. + CI |= CaptureInfo(CC); return true; } Function *F = CB->getCalledFunction(); if (!F || !F->hasExactDefinition() || !SCCNodes.count(F)) { - Captured = true; + CI |= CaptureInfo(CC); return true; } @@ -560,22 +590,24 @@ struct ArgumentUsesTracker : public CaptureTracker { // use. In this case it does not matter if the callee is within our SCC // or not -- we've been captured in some unknown way, and we have to be // conservative. - Captured = true; + CI |= CaptureInfo(CC); return true; } if (UseIndex >= F->arg_size()) { assert(F->isVarArg() && "More params than args in non-varargs call"); - Captured = true; + CI |= CaptureInfo(CC); return true; } + // TODO(captures): Could improve precision by remembering maximum + // capture components for the argument. Uses.push_back(&*std::next(F->arg_begin(), UseIndex)); return false; } - // True only if certainly captured (used outside our SCC). - bool Captured = false; + // Does not include potential captures via Uses in the SCC. + CaptureInfo CI = CaptureInfo::none(); // Uses within our SCC. SmallVector<Argument *, 4> Uses; @@ -1190,6 +1222,15 @@ static void addArgumentAttrs(const SCCNodeSet &SCCNodes, bool SkipInitializes) { ArgumentGraph AG; + auto DetermineAccessAttrsForSingleton = [](Argument *A) { + SmallPtrSet<Argument *, 8> Self; + Self.insert(A); + Attribute::AttrKind R = determinePointerAccessAttrs(A, Self); + if (R != Attribute::None) + return addAccessAttr(A, R); + return false; + }; + // Check each function in turn, determining which pointer arguments are not // captured. for (Function *F : SCCNodes) { @@ -1210,7 +1251,7 @@ static void addArgumentAttrs(const SCCNodeSet &SCCNodes, if (A.getType()->isPointerTy() && !A.hasNoCaptureAttr()) { A.addAttr(Attribute::getWithCaptureInfo(A.getContext(), CaptureInfo::none())); - ++NumNoCapture; + ++NumCapturesNone; Changed.insert(F); } } @@ -1221,21 +1262,23 @@ static void addArgumentAttrs(const SCCNodeSet &SCCNodes, if (!A.getType()->isPointerTy()) continue; bool HasNonLocalUses = false; - if (!A.hasNoCaptureAttr()) { + CaptureInfo OrigCI = A.getAttributes().getCaptureInfo(); + if (!capturesNothing(OrigCI)) { ArgumentUsesTracker Tracker(SCCNodes); PointerMayBeCaptured(&A, &Tracker); - if (!Tracker.Captured) { + CaptureInfo NewCI = Tracker.CI & OrigCI; + if (NewCI != OrigCI) { if (Tracker.Uses.empty()) { - // If it's trivially not captured, mark it nocapture now. - A.addAttr(Attribute::getWithCaptureInfo(A.getContext(), - CaptureInfo::none())); - ++NumNoCapture; + // If the information is complete, add the attribute now. + A.addAttr(Attribute::getWithCaptureInfo(A.getContext(), NewCI)); + addCapturesStat(NewCI); Changed.insert(F); } else { // If it's not trivially captured and not trivially not captured, // then it must be calling into another function in our SCC. Save // its particulars for Argument-SCC analysis later. ArgumentGraphNode *Node = AG[&A]; + Node->CC = CaptureComponents(NewCI); for (Argument *Use : Tracker.Uses) { Node->Uses.push_back(AG[Use]); if (Use != &A) @@ -1250,12 +1293,8 @@ static void addArgumentAttrs(const SCCNodeSet &SCCNodes, // an SCC? Note that we don't allow any calls at all here, or else our // result will be dependent on the iteration order through the // functions in the SCC. - SmallPtrSet<Argument *, 8> Self; - Self.insert(&A); - Attribute::AttrKind R = determinePointerAccessAttrs(&A, Self); - if (R != Attribute::None) - if (addAccessAttr(&A, R)) - Changed.insert(F); + if (DetermineAccessAttrsForSingleton(&A)) + Changed.insert(F); } if (!SkipInitializes && !A.onlyReadsMemory()) { if (inferInitializes(A, *F)) @@ -1281,17 +1320,17 @@ static void addArgumentAttrs(const SCCNodeSet &SCCNodes, if (ArgumentSCC[0]->Uses.size() == 1 && ArgumentSCC[0]->Uses[0] == ArgumentSCC[0]) { Argument *A = ArgumentSCC[0]->Definition; - A->addAttr(Attribute::getWithCaptureInfo(A->getContext(), - CaptureInfo::none())); - ++NumNoCapture; - Changed.insert(A->getParent()); - - // Infer the access attributes given the new nocapture one - SmallPtrSet<Argument *, 8> Self; - Self.insert(&*A); - Attribute::AttrKind R = determinePointerAccessAttrs(&*A, Self); - if (R != Attribute::None) - addAccessAttr(A, R); + CaptureInfo OrigCI = A->getAttributes().getCaptureInfo(); + CaptureInfo NewCI = CaptureInfo(ArgumentSCC[0]->CC) & OrigCI; + if (NewCI != OrigCI) { + A->addAttr(Attribute::getWithCaptureInfo(A->getContext(), NewCI)); + addCapturesStat(NewCI); + Changed.insert(A->getParent()); + } + + // Infer the access attributes given the new captures one + if (DetermineAccessAttrsForSingleton(A)) + Changed.insert(A->getParent()); } continue; } @@ -1303,27 +1342,45 @@ static void addArgumentAttrs(const SCCNodeSet &SCCNodes, ArgumentSCCNodes.insert(I->Definition); } - bool SCCCaptured = false; + // At the SCC level, only track merged CaptureComponents. We're not + // currently prepared to handle propagation of return-only captures across + // the SCC. + CaptureComponents CC = CaptureComponents::None; for (ArgumentGraphNode *N : ArgumentSCC) { for (ArgumentGraphNode *Use : N->Uses) { Argument *A = Use->Definition; - if (A->hasNoCaptureAttr() || ArgumentSCCNodes.count(A)) - continue; - SCCCaptured = true; + if (ArgumentSCCNodes.count(A)) + CC |= Use->CC; + else + CC |= CaptureComponents(A->getAttributes().getCaptureInfo()); break; } - if (SCCCaptured) + if (capturesAll(CC)) break; } - if (SCCCaptured) - continue; - for (ArgumentGraphNode *N : ArgumentSCC) { - Argument *A = N->Definition; - A->addAttr( - Attribute::getWithCaptureInfo(A->getContext(), CaptureInfo::none())); - ++NumNoCapture; - Changed.insert(A->getParent()); + if (!capturesAll(CC)) { + for (ArgumentGraphNode *N : ArgumentSCC) { + Argument *A = N->Definition; + CaptureInfo OrigCI = A->getAttributes().getCaptureInfo(); + CaptureInfo NewCI = CaptureInfo(N->CC | CC) & OrigCI; + if (NewCI != OrigCI) { + A->addAttr(Attribute::getWithCaptureInfo(A->getContext(), NewCI)); + addCapturesStat(NewCI); + Changed.insert(A->getParent()); + } + } + } + + // TODO(captures): Ignore address-only captures. + if (capturesAnything(CC)) { + // As the pointer may be captured, determine the pointer attributes + // looking at each argument invidivually. + for (ArgumentGraphNode *N : ArgumentSCC) { + if (DetermineAccessAttrsForSingleton(N->Definition)) + Changed.insert(N->Definition->getParent()); + } + continue; } // We also want to compute readonly/readnone/writeonly. With a small number |