Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 113 additions & 2 deletions llvm/lib/Transforms/Utils/InlineFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/ModRef.h"
#include "llvm/Transforms/Utils/AssumeBundleBuilder.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/Local.h"
Expand Down Expand Up @@ -1364,6 +1365,104 @@ static bool MayContainThrowingOrExitingCallAfterCB(CallBase *Begin,
++BeginIt, End->getIterator(), InlinerAttributeWindow + 1);
}

template <typename RangeT> static bool ContainsSideEffects(RangeT Range) {
// Any instruction that may clear local scratch space CB stored
// into.
return any_of(Range, [](Instruction &I) { return I.mayHaveSideEffects(); });
}

template <typename RangeT> static bool ContainsScratchSpace(RangeT Range) {
return any_of(Range, [](Instruction &I) {
// Any instruction that may create local scratch space CB can store
// into.
return I.mayHaveSideEffects() || isa<AllocaInst>(&I);
});
}

template <typename NextFn, typename CheckFn>
static bool CheckPathFromBBRecurse(DenseMap<BasicBlock *, bool> &CachedRes,
bool First, BasicBlock *BB, NextFn Next,
CheckFn Check) {
if (!First) {
// Initialize to true (okay to propagate) `nocapture`. This means that loops
// will be okay.
auto [Iter, Inserted] = CachedRes.try_emplace(BB, true);
// If we already have a result, return it.
if (!Inserted)
return Iter->second;

if (!Check(BB->instructionsWithoutDebug())) {
Iter->second = false;
return false;
}
}
auto NextBBs = Next(BB);
// Check all Succs/Preds
for (BasicBlock *NextBB : NextBBs) {
if (!CheckPathFromBBRecurse(CachedRes, /*First=*/false, NextBB, Next,
Check)) {
CachedRes[BB] = false;
return false;
}
}

return true;
}

// Assuming we have:
// define @foo(ptr nocapture %p) {
// entry:
// ...
// bar (ptr %p)
// ...
// }
//
// Determine if we can propagate `nocapture` to the `%p` at the
// `bar`.
static bool
CanPropagateNoCaptureAtCB(DenseMap<BasicBlock *, bool> &PureFromBB,
DenseMap<BasicBlock *, bool> &NoLocalStateToBB,
BasicBlock *BB, CallBase *CB) {
// If CB returns and its used by anything other than `ret`, assume it may be
// capturing.
// Potential TODO: We could allow many operations.
if (!CB->getType()->isVoidTy())
for (auto Use : CB->users())
if (!isa<ReturnInst>(Use))
return false;

// Can't capture via return, so if no side-effects we are set.
if (!CB->mayHaveSideEffects())
return true;

auto It = CB->getIterator();
++It;

// Check that CB instruction with side-effects on all paths from
// `entry` that go through the CB and there are no `alloca`
// instructions. This accomplishes two things. 1) It ensures that
// after CB, there is no way a store/other could "clean up" any
// captures from CB. 2) There is no local state (i.e `alloca` or a
// local `malloc`) that could CB could have stored in params in.
if (ContainsSideEffects(make_range(It, BB->end())) ||
ContainsScratchSpace(make_range(BB->begin(), CB->getIterator())))
return false;

if (!CheckPathFromBBRecurse(
PureFromBB, /*First=*/true, BB,
[](BasicBlock *CheckedBB) { return successors(CheckedBB); },
[](const auto &Region) { return !ContainsSideEffects(Region); }))
return false;

if (!CheckPathFromBBRecurse(
PureFromBB, /*First=*/true, BB,
[](BasicBlock *CheckedBB) { return predecessors(CheckedBB); },
[](const auto &Region) { return !ContainsScratchSpace(Region); }))
return false;

return true;
}

// Add attributes from CB params and Fn attributes that can always be propagated
// to the corresponding argument / inner callbases.
static void AddParamAndFnBasicAttributes(const CallBase &CB,
Expand All @@ -1376,6 +1475,9 @@ static void AddParamAndFnBasicAttributes(const CallBase &CB,
SmallVector<AttrBuilder> ValidObjParamAttrs, ValidExactParamAttrs;
bool HasAttrToPropagate = false;

DenseMap<BasicBlock *, bool> PureFromBB{};
DenseMap<BasicBlock *, bool> NoLocalStateToBB{};

// Attributes we can only propagate if the exact parameter is forwarded.
// We can propagate both poison generating and UB generating attributes
// without any extra checks. The only attribute that is tricky to propagate
Expand All @@ -1394,6 +1496,8 @@ static void AddParamAndFnBasicAttributes(const CallBase &CB,
ValidObjParamAttrs.back().addAttribute(Attribute::ReadNone);
if (CB.paramHasAttr(I, Attribute::ReadOnly))
ValidObjParamAttrs.back().addAttribute(Attribute::ReadOnly);
if (CB.doesNotCapture(I))
ValidObjParamAttrs.back().addCapturesAttr(CaptureInfo::none());

for (Attribute::AttrKind AK : ExactAttrsToPropagate) {
Attribute Attr = CB.getParamAttr(I, AK);
Expand Down Expand Up @@ -1478,9 +1582,16 @@ static void AddParamAndFnBasicAttributes(const CallBase &CB,
continue;
}

// If so, propagate its access attributes.
AL = AL.addParamAttributes(Context, I, ValidObjParamAttrs[ArgNo]);
AttributeSet AS = AttributeSet::get(Context, ValidObjParamAttrs[ArgNo]);
// Check if we can propagate `captures(none)`.
if (capturesNothing(AS.getCaptureInfo()) &&
(NewInnerCB->doesNotCapture(I) ||
!CanPropagateNoCaptureAtCB(PureFromBB, NoLocalStateToBB, &BB,
cast<CallBase>(&Ins))))
AS = AS.removeAttribute(Context, Attribute::Captures);

// If so, propagate its access attributes.
AL = AL.addParamAttributes(Context, I, AttrBuilder{Context, AS});
// We can have conflicting attributes from the inner callsite and
// to-be-inlined callsite. In that case, choose the most
// restrictive.
Expand Down
Loading