Skip to content

Commit

Permalink
[Attributor] Use knowledge retained in llvm.assume (operand bundles)
Browse files Browse the repository at this point in the history
This patch integrates operand bundle llvm.assumes [0] with the
Attributor. Most IRAttributes will now look at uses of the associated
value and if there are llvm.assume operand bundle uses with the right
tag we will check if they are in the must-be-executed-context (around
the context instruction). Droppable users, which is currently only
llvm::assume, are handled special in some places now as well.

[0] http://lists.llvm.org/pipermail/llvm-dev/2019-December/137632.html

Reviewed By: uenoku

Differential Revision: https://reviews.llvm.org/D74888
  • Loading branch information
jdoerfert committed Mar 24, 2020
1 parent 7339fca commit 5699d08
Show file tree
Hide file tree
Showing 8 changed files with 292 additions and 54 deletions.
10 changes: 9 additions & 1 deletion llvm/include/llvm/IR/KnowledgeRetention.h
Expand Up @@ -22,6 +22,7 @@
#include "llvm/ADT/DenseMap.h"

namespace llvm {
class IntrinsicInst;

/// Build a call to llvm.assume to preserve informations that can be derived
/// from the given instruction.
Expand Down Expand Up @@ -84,7 +85,14 @@ struct MinMax {
unsigned Max;
};

using RetainedKnowledgeMap = DenseMap<RetainedKnowledgeKey, MinMax>;
/// A mapping from intrinsics (=`llvm.assume` calls) to a value range
/// (=knowledge) that is encoded in them. How the value range is interpreted
/// depends on the RetainedKnowledgeKey that was used to get this out of the
/// RetainedKnowledgeMap.
using Assume2KnowledgeMap = DenseMap<IntrinsicInst *, MinMax>;

using RetainedKnowledgeMap =
DenseMap<RetainedKnowledgeKey, Assume2KnowledgeMap>;

/// Insert into the map all the informations contained in the operand bundles of
/// the llvm.assume. This should be used instead of hasAttributeInAssume when
Expand Down
23 changes: 20 additions & 3 deletions llvm/include/llvm/Transforms/IPO/Attributor.h
Expand Up @@ -110,11 +110,13 @@
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/IR/CallSite.h"
#include "llvm/IR/ConstantRange.h"
#include "llvm/IR/KnowledgeRetention.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Transforms/Utils/CallGraphUpdater.h"

namespace llvm {

struct Attributor;
struct AbstractAttribute;
struct InformationCache;
struct AAIsDead;
Expand Down Expand Up @@ -391,7 +393,8 @@ struct IRPosition {
/// e.g., the function position if this is an
/// argument position, should be ignored.
bool hasAttr(ArrayRef<Attribute::AttrKind> AKs,
bool IgnoreSubsumingPositions = false) const;
bool IgnoreSubsumingPositions = false,
Attributor *A = nullptr) const;

/// Return the attributes of any kind in \p AKs existing in the IR at a
/// position that will affect this one. While each position can only have a
Expand All @@ -403,7 +406,8 @@ struct IRPosition {
/// argument position, should be ignored.
void getAttrs(ArrayRef<Attribute::AttrKind> AKs,
SmallVectorImpl<Attribute> &Attrs,
bool IgnoreSubsumingPositions = false) const;
bool IgnoreSubsumingPositions = false,
Attributor *A = nullptr) const;

/// Remove the attribute of kind \p AKs existing in the IR at this position.
void removeAttrs(ArrayRef<Attribute::AttrKind> AKs) const {
Expand Down Expand Up @@ -463,6 +467,12 @@ struct IRPosition {
bool getAttrsFromIRAttr(Attribute::AttrKind AK,
SmallVectorImpl<Attribute> &Attrs) const;

/// Return the attributes of kind \p AK existing in the IR as operand bundles
/// of an llvm.assume.
bool getAttrsFromAssumes(Attribute::AttrKind AK,
SmallVectorImpl<Attribute> &Attrs,
Attributor &A) const;

protected:
/// The value this position is anchored at.
Value *AnchorVal;
Expand Down Expand Up @@ -607,6 +617,9 @@ struct InformationCache {
/// Return datalayout used in the module.
const DataLayout &getDL() { return DL; }

/// Return the map conaining all the knowledge we have from `llvm.assume`s.
const RetainedKnowledgeMap &getKnowledgeMap() const { return KnowledgeMap; }

private:
/// A map type from functions to opcode to instruction maps.
using FuncInstOpcodeMapTy = DenseMap<const Function *, OpcodeInstMapTy>;
Expand All @@ -627,6 +640,9 @@ struct InformationCache {
/// MustBeExecutedContextExplorer
MustBeExecutedContextExplorer Explorer;

/// A map with knowledge retained in `llvm.assume` instructions.
RetainedKnowledgeMap KnowledgeMap;

/// Getters for analysis.
AnalysisGetter &AG;

Expand Down Expand Up @@ -1710,7 +1726,8 @@ struct IRAttribute : public IRPosition, public Base {
/// See AbstractAttribute::initialize(...).
virtual void initialize(Attributor &A) override {
const IRPosition &IRP = this->getIRPosition();
if (isa<UndefValue>(IRP.getAssociatedValue()) || hasAttr(getAttrKind())) {
if (isa<UndefValue>(IRP.getAssociatedValue()) ||
hasAttr(getAttrKind(), /* IgnoreSubsumingPositions */ false, &A)) {
this->getState().indicateOptimisticFixpoint();
return;
}
Expand Down
12 changes: 7 additions & 5 deletions llvm/lib/IR/KnowledgeRetention.cpp
Expand Up @@ -206,6 +206,8 @@ static Value *getValueFromBundleOpInfo(IntrinsicInst &Assume,
bool llvm::hasAttributeInAssume(CallInst &AssumeCI, Value *IsOn,
StringRef AttrName, uint64_t *ArgVal,
AssumeQuery AQR) {
assert(isa<IntrinsicInst>(AssumeCI) &&
"this function is intended to be used on llvm.assume");
IntrinsicInst &Assume = cast<IntrinsicInst>(AssumeCI);
assert(Assume.getIntrinsicID() == Intrinsic::assume &&
"this function is intended to be used on llvm.assume");
Expand Down Expand Up @@ -253,19 +255,19 @@ void llvm::fillMapFromAssume(CallInst &AssumeCI, RetainedKnowledgeMap &Result) {
if (Key.first == nullptr && Key.second == Attribute::None)
continue;
if (!BundleHasArguement(Bundles, BOIE_Argument)) {
Result[Key] = {0, 0};
Result[Key][&Assume] = {0, 0};
continue;
}
unsigned Val = cast<ConstantInt>(
getValueFromBundleOpInfo(Assume, Bundles, BOIE_Argument))
->getZExtValue();
auto Lookup = Result.find(Key);
if (Lookup == Result.end()) {
Result[Key] = {Val, Val};
if (Lookup == Result.end() || !Lookup->second.count(&Assume)) {
Result[Key][&Assume] = {Val, Val};
continue;
}
Lookup->second.Min = std::min(Val, Lookup->second.Min);
Lookup->second.Max = std::max(Val, Lookup->second.Max);
Lookup->second[&Assume].Min = std::min(Val, Lookup->second[&Assume].Min);
Lookup->second[&Assume].Max = std::max(Val, Lookup->second[&Assume].Max);
}
}

Expand Down
63 changes: 56 additions & 7 deletions llvm/lib/Transforms/IPO/Attributor.cpp
Expand Up @@ -677,7 +677,7 @@ SubsumingPositionIterator::SubsumingPositionIterator(const IRPosition &IRP) {
}

bool IRPosition::hasAttr(ArrayRef<Attribute::AttrKind> AKs,
bool IgnoreSubsumingPositions) const {
bool IgnoreSubsumingPositions, Attributor *A) const {
SmallVector<Attribute, 4> Attrs;
for (const IRPosition &EquivIRP : SubsumingPositionIterator(*this)) {
for (Attribute::AttrKind AK : AKs)
Expand All @@ -689,12 +689,16 @@ bool IRPosition::hasAttr(ArrayRef<Attribute::AttrKind> AKs,
if (IgnoreSubsumingPositions)
break;
}
if (A)
for (Attribute::AttrKind AK : AKs)
if (getAttrsFromAssumes(AK, Attrs, *A))
return true;
return false;
}

void IRPosition::getAttrs(ArrayRef<Attribute::AttrKind> AKs,
SmallVectorImpl<Attribute> &Attrs,
bool IgnoreSubsumingPositions) const {
bool IgnoreSubsumingPositions, Attributor *A) const {
for (const IRPosition &EquivIRP : SubsumingPositionIterator(*this)) {
for (Attribute::AttrKind AK : AKs)
EquivIRP.getAttrsFromIRAttr(AK, Attrs);
Expand All @@ -704,6 +708,9 @@ void IRPosition::getAttrs(ArrayRef<Attribute::AttrKind> AKs,
if (IgnoreSubsumingPositions)
break;
}
if (A)
for (Attribute::AttrKind AK : AKs)
getAttrsFromAssumes(AK, Attrs, *A);
}

bool IRPosition::getAttrsFromIRAttr(Attribute::AttrKind AK,
Expand All @@ -723,6 +730,31 @@ bool IRPosition::getAttrsFromIRAttr(Attribute::AttrKind AK,
return HasAttr;
}

bool IRPosition::getAttrsFromAssumes(Attribute::AttrKind AK,
SmallVectorImpl<Attribute> &Attrs,
Attributor &A) const {
assert(getPositionKind() != IRP_INVALID && "Did expect a valid position!");
Value &AssociatedValue = getAssociatedValue();

const Assume2KnowledgeMap &A2K =
A.getInfoCache().getKnowledgeMap().lookup({&AssociatedValue, AK});

// Check if we found any potential assume use, if not we don't need to create
// explorer iterators.
if (A2K.empty())
return false;

LLVMContext &Ctx = AssociatedValue.getContext();
unsigned AttrsSize = Attrs.size();
MustBeExecutedContextExplorer &Explorer =
A.getInfoCache().getMustBeExecutedContextExplorer();
auto EIt = Explorer.begin(getCtxI()), EEnd = Explorer.end(getCtxI());
for (auto &It : A2K)
if (Explorer.findInContextOf(It.first, EIt, EEnd))
Attrs.push_back(Attribute::get(Ctx, AK, It.second.Max));
return AttrsSize != Attrs.size();
}

void IRPosition::verify() {
switch (KindOrArgNo) {
default:
Expand Down Expand Up @@ -2057,7 +2089,8 @@ struct AANonNullImpl : AANonNull {
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
if (!NullIsDefined &&
hasAttr({Attribute::NonNull, Attribute::Dereferenceable}))
hasAttr({Attribute::NonNull, Attribute::Dereferenceable},
/* IgnoreSubsumingPositions */ false, &A))
indicateOptimisticFixpoint();
else if (isa<ConstantPointerNull>(getAssociatedValue()))
indicatePessimisticFixpoint();
Expand Down Expand Up @@ -3643,7 +3676,7 @@ struct AADereferenceableImpl : AADereferenceable {
void initialize(Attributor &A) override {
SmallVector<Attribute, 4> Attrs;
getAttrs({Attribute::Dereferenceable, Attribute::DereferenceableOrNull},
Attrs);
Attrs, /* IgnoreSubsumingPositions */ false, &A);
for (const Attribute &Attr : Attrs)
takeKnownDerefBytesMaximum(Attr.getValueAsInt());

Expand Down Expand Up @@ -4394,8 +4427,9 @@ struct AACaptureUseTracker final : public CaptureTracker {

/// See CaptureTracker::shouldExplore(...).
bool shouldExplore(const Use *U) override {
// Check liveness.
return !A.isAssumedDead(*U, &NoCaptureAA, &IsDeadAA);
// Check liveness and ignore droppable users.
return !U->getUser()->isDroppable() &&
!A.isAssumedDead(*U, &NoCaptureAA, &IsDeadAA);
}

/// Update the state according to \p CapturedInMem, \p CapturedInInt, and
Expand Down Expand Up @@ -6157,6 +6191,10 @@ ChangeStatus AAMemoryBehaviorFloating::updateImpl(Attributor &A) {
if (A.isAssumedDead(*U, this, &LivenessAA))
continue;

// Droppable users, e.g., llvm::assume does not actually perform any action.
if (UserI->isDroppable())
continue;

// Check if the users of UserI should also be visited.
if (followUsersOfUseIn(A, U, UserI))
for (const Use &UserIUse : UserI->uses())
Expand Down Expand Up @@ -7414,6 +7452,10 @@ bool Attributor::checkForAllUses(function_ref<bool(const Use &, bool &)> Pred,
LLVM_DEBUG(dbgs() << "[Attributor] Dead use, skip!\n");
continue;
}
if (U->getUser()->isDroppable()) {
LLVM_DEBUG(dbgs() << "[Attributor] Droppable user, skip!\n");
continue;
}

bool Follow = false;
if (!Pred(*U, Follow))
Expand Down Expand Up @@ -8308,11 +8350,18 @@ void Attributor::initializeInformationCache(Function &F) {
"New call site/base instruction type needs to be known in the "
"Attributor.");
break;
case Instruction::Call:
// Calls are interesting but for `llvm.assume` calls we also fill the
// KnowledgeMap as we find them.
if (IntrinsicInst *Assume = dyn_cast<IntrinsicInst>(&I)) {
if (Assume->getIntrinsicID() == Intrinsic::assume)
fillMapFromAssume(*Assume, InfoCache.KnowledgeMap);
}
LLVM_FALLTHROUGH;
case Instruction::Load:
// The alignment of a pointer is interesting for loads.
case Instruction::Store:
// The alignment of a pointer is interesting for stores.
case Instruction::Call:
case Instruction::CallBr:
case Instruction::Invoke:
case Instruction::CleanupRet:
Expand Down
62 changes: 62 additions & 0 deletions llvm/test/Transforms/Attributor/dereferenceable-1.ll
Expand Up @@ -455,5 +455,67 @@ if.end8: ; preds = %if.then5, %if.else6
ret void
}

declare void @unknown()
define void @nonnull_assume_pos(i8* %arg1, i8* %arg2, i8* %arg3, i8* %arg4) {
; ATTRIBUTOR-LABEL: define {{[^@]+}}@nonnull_assume_pos
; ATTRIBUTOR-SAME: (i8* nocapture nofree nonnull readnone dereferenceable(101) [[ARG1:%.*]], i8* nocapture nofree readnone dereferenceable_or_null(31) [[ARG2:%.*]], i8* nocapture nofree nonnull readnone [[ARG3:%.*]], i8* nocapture nofree readnone dereferenceable_or_null(42) [[ARG4:%.*]])
; ATTRIBUTOR-NEXT: call void @llvm.assume(i1 true) #6 [ "nonnull"(i8* undef), "dereferenceable"(i8* undef, i64 1), "dereferenceable"(i8* undef, i64 2), "dereferenceable"(i8* undef, i64 101), "dereferenceable_or_null"(i8* undef, i64 31), "dereferenceable_or_null"(i8* undef, i64 42) ]
; ATTRIBUTOR-NEXT: call void @unknown()
; ATTRIBUTOR-NEXT: ret void
;
call void @llvm.assume(i1 true) [ "nonnull"(i8* %arg3), "dereferenceable"(i8* %arg1, i64 1), "dereferenceable"(i8* %arg1, i64 2), "dereferenceable"(i8* %arg1, i64 101), "dereferenceable_or_null"(i8* %arg2, i64 31), "dereferenceable_or_null"(i8* %arg4, i64 42)]
call void @unknown()
ret void
}
define void @nonnull_assume_neg(i8* %arg1, i8* %arg2, i8* %arg3) {
; ATTRIBUTOR-LABEL: define {{[^@]+}}@nonnull_assume_neg
; ATTRIBUTOR-SAME: (i8* nocapture nofree readnone [[ARG1:%.*]], i8* nocapture nofree readnone [[ARG2:%.*]], i8* nocapture nofree readnone [[ARG3:%.*]])
; ATTRIBUTOR-NEXT: call void @unknown()
; ATTRIBUTOR-NEXT: call void @llvm.assume(i1 true) [ "dereferenceable"(i8* undef, i64 101), "dereferenceable"(i8* undef, i64 -2), "dereferenceable_or_null"(i8* undef, i64 31) ]
; ATTRIBUTOR-NEXT: ret void
;
call void @unknown()
call void @llvm.assume(i1 true) ["dereferenceable"(i8* %arg1, i64 101), "dereferenceable"(i8* %arg2, i64 -2), "dereferenceable_or_null"(i8* %arg3, i64 31)]
ret void
}
define void @nonnull_assume_call(i8* %arg1, i8* %arg2, i8* %arg3, i8* %arg4) {
; ATTRIBUTOR-LABEL: define {{[^@]+}}@nonnull_assume_call
; ATTRIBUTOR-SAME: (i8* [[ARG1:%.*]], i8* [[ARG2:%.*]], i8* [[ARG3:%.*]], i8* [[ARG4:%.*]])
; ATTRIBUTOR-NEXT: call void @unknown()
; ATTRIBUTOR-NEXT: [[P:%.*]] = call nonnull dereferenceable(101) i32* @unkown_ptr()
; ATTRIBUTOR-NEXT: call void @unknown_use32(i32* nonnull dereferenceable(101) [[P]])
; ATTRIBUTOR-NEXT: call void @unknown_use8(i8* nonnull dereferenceable(42) [[ARG4]])
; ATTRIBUTOR-NEXT: call void @unknown_use8(i8* nonnull [[ARG3]])
; ATTRIBUTOR-NEXT: call void @unknown_use8(i8* nonnull dereferenceable(31) [[ARG2]])
; ATTRIBUTOR-NEXT: call void @unknown_use8(i8* nonnull dereferenceable(2) [[ARG1]])
; ATTRIBUTOR-NEXT: call void @llvm.assume(i1 true) [ "nonnull"(i8* [[ARG3]]), "dereferenceable"(i8* [[ARG1]], i64 1), "dereferenceable"(i8* [[ARG1]], i64 2), "dereferenceable"(i32* [[P]], i64 101), "dereferenceable_or_null"(i8* [[ARG2]], i64 31), "dereferenceable_or_null"(i8* [[ARG4]], i64 42) ]
; ATTRIBUTOR-NEXT: call void @unknown_use8(i8* nonnull dereferenceable(2) [[ARG1]])
; ATTRIBUTOR-NEXT: call void @unknown_use8(i8* nonnull dereferenceable(31) [[ARG2]])
; ATTRIBUTOR-NEXT: call void @unknown_use8(i8* nonnull [[ARG3]])
; ATTRIBUTOR-NEXT: call void @unknown_use8(i8* nonnull dereferenceable(42) [[ARG4]])
; ATTRIBUTOR-NEXT: call void @unknown_use32(i32* nonnull dereferenceable(101) [[P]])
; ATTRIBUTOR-NEXT: call void @unknown()
; ATTRIBUTOR-NEXT: ret void
;
call void @unknown()
%p = call i32* @unkown_ptr()
call void @unknown_use32(i32* %p)
call void @unknown_use8(i8* %arg4)
call void @unknown_use8(i8* %arg3)
call void @unknown_use8(i8* %arg2)
call void @unknown_use8(i8* %arg1)
call void @llvm.assume(i1 true) [ "nonnull"(i8* %arg3), "dereferenceable"(i8* %arg1, i64 1), "dereferenceable"(i8* %arg1, i64 2), "dereferenceable"(i32* %p, i64 101), "dereferenceable_or_null"(i8* %arg2, i64 31), "dereferenceable_or_null"(i8* %arg4, i64 42)]
call void @unknown_use8(i8* %arg1)
call void @unknown_use8(i8* %arg2)
call void @unknown_use8(i8* %arg3)
call void @unknown_use8(i8* %arg4)
call void @unknown_use32(i32* %p)
call void @unknown()
ret void
}
declare void @unknown_use8(i8*) willreturn nounwind
declare void @unknown_use32(i32*) willreturn nounwind
declare void @llvm.assume(i1)

!0 = !{i64 10, i64 100}

0 comments on commit 5699d08

Please sign in to comment.