diff --git a/llvm/include/llvm/IR/Assumptions.h b/llvm/include/llvm/IR/Assumptions.h --- a/llvm/include/llvm/IR/Assumptions.h +++ b/llvm/include/llvm/IR/Assumptions.h @@ -15,6 +15,7 @@ #ifndef LLVM_IR_ASSUMPTIONS_H #define LLVM_IR_ASSUMPTIONS_H +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" @@ -44,11 +45,25 @@ }; /// Return true if \p F has the assumption \p AssumptionStr attached. -bool hasAssumption(Function &F, const KnownAssumptionString &AssumptionStr); +bool hasAssumption(const Function &F, + const KnownAssumptionString &AssumptionStr); /// Return true if \p CB or the callee has the assumption \p AssumptionStr /// attached. -bool hasAssumption(CallBase &CB, const KnownAssumptionString &AssumptionStr); +bool hasAssumption(const CallBase &CB, + const KnownAssumptionString &AssumptionStr); + +/// Return the set of all assumptions for the function \p F. +DenseSet getAssumptions(const Function &F); + +/// Return the set of all assumptions for the call \p CB. +DenseSet getAssumptions(const CallBase &CB); + +/// Appends the set of assumptions \p Assumptions to \F. +bool addAssumptions(Function &F, const DenseSet &Assumptions); + +/// Appends the set of assumptions \p Assumptions to \CB. +bool addAssumptions(CallBase &CB, const DenseSet &Assumptions); } // namespace llvm diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h --- a/llvm/include/llvm/Transforms/IPO/Attributor.h +++ b/llvm/include/llvm/Transforms/IPO/Attributor.h @@ -101,6 +101,7 @@ #include "llvm/ADT/GraphTraits.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SetOperations.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/Triple.h" #include "llvm/ADT/iterator.h" @@ -2511,6 +2512,141 @@ return *this; } }; + +/// Simple state for a set. +/// +/// This represents a state containing a set of values. The interface supports +/// modelling sets that contain all possible elements. The state's internal +/// value is modified using union or intersection operations. +template struct SetState : public AbstractState { + /// A wrapper around a set that has semantics for handling unions and + /// intersections with a "universal" set that contains all elements. + struct SetContents { + /// Creates a universal set with no concrete elements or an empty set. + SetContents(bool Universal) : Universal(Universal) {} + + /// Creates a non-universal set with concrete values. + SetContents(const DenseSet &Assumptions) + : Universal(false), Set(Assumptions) {} + + SetContents(bool Universal, const DenseSet &Assumptions) + : Universal(Universal), Set(Assumptions) {} + + const DenseSet &getSet() const { return Set; } + + bool isUniversal() const { return Universal; } + + bool empty() const { return Set.empty() && !Universal; } + + /// Finds A := A ^ B where A or B could be the "Universal" set which + /// contains every possible attribute. Returns true if changes were made. + bool getIntersection(const SetContents &RHS) { + bool IsUniversal = Universal; + unsigned Size = Set.size(); + + // A := A ^ U = A + if (RHS.isUniversal()) + return false; + + // A := U ^ B = B + if (Universal) + Set = RHS.getSet(); + else + set_intersect(Set, RHS.getSet()); + + Universal &= RHS.isUniversal(); + return IsUniversal != Universal || Size != Set.size(); + } + + /// Finds A := A u B where A or B could be the "Universal" set which + /// contains every possible attribute. returns true if changes were made. + bool getUnion(const SetContents &RHS) { + bool IsUniversal = Universal; + unsigned Size = Set.size(); + + // A := A u U = U = U u B + if (!RHS.isUniversal() && !Universal) + set_union(Set, RHS.getSet()); + + Universal |= RHS.isUniversal(); + return IsUniversal != Universal || Size != Set.size(); + } + + private: + /// Indicates if this set is "universal", containing every possible element. + bool Universal; + + /// The set of currently active assumptions. + DenseSet Set; + }; + + SetState() : Known(false), Assumed(true), IsAtFixedpoint(false) {} + + /// Initializes the known state with an initial set and initializes the + /// assumed state as universal. + SetState(const DenseSet &Known) + : Known(Known), Assumed(true), IsAtFixedpoint(false) {} + + /// See AbstractState::isValidState() + bool isValidState() const override { + return !Assumed.empty(); + } + + /// See AbstractState::isAtFixpoint() + bool isAtFixpoint() const override { return IsAtFixedpoint; } + + /// See AbstractState::indicateOptimisticFixpoint(...) + ChangeStatus indicateOptimisticFixpoint() override { + IsAtFixedpoint = true; + Known = Assumed; + return ChangeStatus::UNCHANGED; + } + + /// See AbstractState::indicatePessimisticFixpoint(...) + ChangeStatus indicatePessimisticFixpoint() override { + IsAtFixedpoint = true; + Assumed = Known; + return ChangeStatus::CHANGED; + } + + /// Return the known state encoding. + const SetContents &getKnown() const { return Known; } + + /// Return the assumed state encoding. + const SetContents &getAssumed() const { return Assumed; } + + /// Returns if the set state contains the element. + bool setContains(const BaseTy &Elem) const { + return Assumed.isUniversal() || Assumed.getSet().contains(Elem); + } + + /// Performs the set intersection between this set and \p RHS. Returns true if + /// changes were made. + bool getIntersection(const SetContents &RHS) { + unsigned SizeBefore = Assumed.getSet().size(); + + // Get intersection and make sure that the known set is still a proper + // subset of the assumed set. A := K u (A ^ R). + Assumed.getIntersection(RHS); + Assumed.getUnion(Known); + + return SizeBefore != Assumed.getSet().size(); + } + + /// Performs the set union between this set and \p RHS. Returns true if + /// changes were made. + bool getUnion(const SetContents &RHS) { return Assumed.getUnion(RHS); } + +private: + /// The set of values known for this state. + SetContents Known; + + /// The set of assumed values for this state. + SetContents Assumed; + + bool IsAtFixedpoint; +}; + /// Helper struct necessary as the modular build fails if the virtual method /// IRAttribute::manifest is defined in the Attributor.cpp. struct IRAttributeManifest { @@ -4609,6 +4745,40 @@ static const char ID; }; +/// An abstract attribute for getting assumption information. +struct AAAssumptionInfo + : public StateWrapper, AbstractAttribute, + DenseSet> { + using Base = + StateWrapper, AbstractAttribute, DenseSet>; + + AAAssumptionInfo(const IRPosition &IRP, Attributor &A, + const DenseSet &Known) + : Base(IRP, Known) {} + + /// Returns true if the assumption set contains the assumption \p Assumption. + virtual bool hasAssumption(const StringRef Assumption) const = 0; + + /// Create an abstract attribute view for the position \p IRP. + static AAAssumptionInfo &createForPosition(const IRPosition &IRP, + Attributor &A); + + /// See AbstractAttribute::getName() + const std::string getName() const override { return "AAAssumptionInfo"; } + + /// See AbstractAttribute::getIdAddr() + const char *getIdAddr() const override { return &ID; } + + /// This function should return true if the type of the \p AA is + /// AAAssumptionInfo + static bool classof(const AbstractAttribute *AA) { + return (AA->getIdAddr() == &ID); + } + + /// Unique ID (due to the unique address) + static const char ID; +}; + raw_ostream &operator<<(raw_ostream &, const AAPointerInfo::Access &); /// Run options, used by the pass manager. diff --git a/llvm/lib/IR/Assumptions.cpp b/llvm/lib/IR/Assumptions.cpp --- a/llvm/lib/IR/Assumptions.cpp +++ b/llvm/lib/IR/Assumptions.cpp @@ -6,9 +6,14 @@ // //===----------------------------------------------------------------------===// // +// This file implements helper functions for accessing assumption infomration +// inside of the "llvm.assume" metadata. +// //===----------------------------------------------------------------------===// #include "llvm/IR/Assumptions.h" +#include "llvm/ADT/SetOperations.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/IR/Attributes.h" #include "llvm/IR/Function.h" #include "llvm/IR/InstrTypes.h" @@ -27,15 +32,48 @@ return llvm::is_contained(Strings, AssumptionStr); } + +DenseSet getAssumptions(const Attribute &A) { + if (!A.isValid()) + return DenseSet(); + assert(A.isStringAttribute() && "Expected a string attribute!"); + + DenseSet Assumptions; + SmallVector Strings; + A.getValueAsString().split(Strings, ","); + + for (StringRef Str : Strings) + Assumptions.insert(Str); + return Assumptions; +} + +template +bool addAssumptionsImpl(AttrSite &Site, + const DenseSet &Assumptions) { + if (Assumptions.empty()) + return false; + + DenseSet CurAssumptions = getAssumptions(Site); + + if (!set_union(CurAssumptions, Assumptions)) + return false; + + LLVMContext &Ctx = Site.getContext(); + Site.addFnAttr(llvm::Attribute::get( + Ctx, llvm::AssumptionAttrKey, + llvm::join(CurAssumptions.begin(), CurAssumptions.end(), ","))); + + return true; +} } // namespace -bool llvm::hasAssumption(Function &F, +bool llvm::hasAssumption(const Function &F, const KnownAssumptionString &AssumptionStr) { const Attribute &A = F.getFnAttribute(AssumptionAttrKey); return ::hasAssumption(A, AssumptionStr); } -bool llvm::hasAssumption(CallBase &CB, +bool llvm::hasAssumption(const CallBase &CB, const KnownAssumptionString &AssumptionStr) { if (Function *F = CB.getCalledFunction()) if (hasAssumption(*F, AssumptionStr)) @@ -45,6 +83,25 @@ return ::hasAssumption(A, AssumptionStr); } +DenseSet llvm::getAssumptions(const Function &F) { + const Attribute &A = F.getFnAttribute(AssumptionAttrKey); + return ::getAssumptions(A); +} + +DenseSet llvm::getAssumptions(const CallBase &CB) { + const Attribute &A = CB.getFnAttr(AssumptionAttrKey); + return ::getAssumptions(A); +} + +bool llvm::addAssumptions(Function &F, const DenseSet &Assumptions) { + return ::addAssumptionsImpl(F, Assumptions); +} + +bool llvm::addAssumptions(CallBase &CB, + const DenseSet &Assumptions) { + return ::addAssumptionsImpl(CB, Assumptions); +} + StringSet<> llvm::KnownAssumptionStrings({ "omp_no_openmp", // OpenMP 5.1 "omp_no_openmp_routines", // OpenMP 5.1 diff --git a/llvm/lib/Transforms/IPO/Attributor.cpp b/llvm/lib/Transforms/IPO/Attributor.cpp --- a/llvm/lib/Transforms/IPO/Attributor.cpp +++ b/llvm/lib/Transforms/IPO/Attributor.cpp @@ -2502,6 +2502,9 @@ // Every function can be "readnone/argmemonly/inaccessiblememonly/...". getOrCreateAAFor(FPos); + // Every function can track active assumptions. + getOrCreateAAFor(FPos); + // Every function might be applicable for Heap-To-Stack conversion. if (EnableHeapToStack) getOrCreateAAFor(FPos); @@ -2587,6 +2590,7 @@ auto CallSitePred = [&](Instruction &I) -> bool { auto &CB = cast(I); IRPosition CBRetPos = IRPosition::callsite_returned(CB); + IRPosition CBFnPos = IRPosition::callsite_function(CB); // Call sites might be dead if they do not have side effects and no live // users. The return value might be dead if there are no live users. @@ -2598,6 +2602,9 @@ if (!Callee) return true; + // Every call site can track active assumptions. + getOrCreateAAFor(CBFnPos); + // Skip declarations except if annotations on their call sites were // explicitly requested. if (!AnnotateDeclarationCallSites && Callee->isDeclaration() && diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp --- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp +++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp @@ -15,6 +15,7 @@ #include "llvm/ADT/APInt.h" #include "llvm/ADT/SCCIterator.h" +#include "llvm/ADT/SetOperations.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/Statistic.h" #include "llvm/Analysis/AliasAnalysis.h" @@ -28,6 +29,7 @@ #include "llvm/Analysis/ScalarEvolution.h" #include "llvm/Analysis/TargetTransformInfo.h" #include "llvm/Analysis/ValueTracking.h" +#include "llvm/IR/Assumptions.h" #include "llvm/IR/Constants.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/Instruction.h" @@ -146,6 +148,7 @@ PIPE_OPERATOR(AACallEdges) PIPE_OPERATOR(AAFunctionReachability) PIPE_OPERATOR(AAPointerInfo) +PIPE_OPERATOR(AAAssumptionInfo) #undef PIPE_OPERATOR @@ -9626,6 +9629,7 @@ } void trackStatistics() const override {} + private: bool canReachUnknownCallee() const override { return WholeFunction.CanReachUnknownCallee; @@ -9639,6 +9643,144 @@ DenseMap CBQueries; }; +/// ---------------------- Assumption Propagation ------------------------------ +struct AAAssumptionInfoImpl : public AAAssumptionInfo { + AAAssumptionInfoImpl(const IRPosition &IRP, Attributor &A, + const DenseSet &Known) + : AAAssumptionInfo(IRP, A, Known) {} + + bool hasAssumption(const StringRef Assumption) const override { + return isValidState() && setContains(Assumption); + } + + /// See AbstractAttribute::getAsStr() + const std::string getAsStr() const override { + const SetContents &Known = getKnown(); + const SetContents &Assumed = getAssumed(); + + const std::string KnownStr = + llvm::join(Known.getSet().begin(), Known.getSet().end(), ","); + const std::string AssumedStr = + (Assumed.isUniversal()) + ? "Universal" + : llvm::join(Assumed.getSet().begin(), Assumed.getSet().end(), ","); + + return "Known [" + KnownStr + "]," + " Assumed [" + AssumedStr + "]"; + } +}; + +/// Propagates assumption information from parent functions to all of their +/// successors. An assumption can be propagated if the containing function +/// dominates the called function. +/// +/// We start with a "known" set of assumptions already valid for the associated +/// function and an "assumed" set that initially contains all possible +/// assumptions. The assumed set is inter-procedurally updated by narrowing its +/// contents as concrete values are known. The concrete values are seeded by the +/// first nodes that are either entries into the call graph, or contains no +/// assumptions. Each node is updated as the intersection of the assumed state +/// with all of its predecessors. +struct AAAssumptionInfoFunction final : AAAssumptionInfoImpl { + AAAssumptionInfoFunction(const IRPosition &IRP, Attributor &A) + : AAAssumptionInfoImpl(IRP, A, + getAssumptions(*IRP.getAssociatedFunction())) {} + + /// See AbstractAttribute::manifest(...). + ChangeStatus manifest(Attributor &A) override { + const auto &Assumptions = getKnown(); + + // Don't manifest a universal set if it somehow made it here. + if (Assumptions.isUniversal()) + return ChangeStatus::UNCHANGED; + + Function *AssociatedFunction = getAssociatedFunction(); + + bool Changed = addAssumptions(*AssociatedFunction, Assumptions.getSet()); + + return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; + } + + /// See AbstractAttribute::updateImpl(...). + ChangeStatus updateImpl(Attributor &A) override { + bool Changed = false; + + auto CallSitePred = [&](AbstractCallSite ACS) { + const auto &AssumptionAA = A.getAAFor( + *this, IRPosition::callsite_function(*ACS.getInstruction()), + DepClassTy::REQUIRED); + // Get the set of assumptions shared by all of this function's callers. + Changed |= getIntersection(AssumptionAA.getAssumed()); + return !getAssumed().empty() || !getKnown().empty(); + }; + + bool AllCallSitesKnown; + // Get the intersection of all assumptions held by this node's predecessors. + // If we don't know all the call sites then this is either an entry into the + // call graph or an empty node. This node is known to only contain its own + // assumptions and can be propagated to its successors. + if (!A.checkForAllCallSites(CallSitePred, *this, true, AllCallSitesKnown)) + return indicatePessimisticFixpoint(); + + return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; + } + + void trackStatistics() const override {} +}; + +/// Assumption Info defined for call sites. +struct AAAssumptionInfoCallSite final : AAAssumptionInfoImpl { + + AAAssumptionInfoCallSite(const IRPosition &IRP, Attributor &A) + : AAAssumptionInfoImpl(IRP, A, getInitialAssumptions(IRP)) {} + + /// See AbstractAttribute::initialize(...). + void initialize(Attributor &A) override { + const IRPosition &FnPos = IRPosition::function(*getAnchorScope()); + A.getAAFor(*this, FnPos, DepClassTy::REQUIRED); + } + + /// See AbstractAttribute::manifest(...). + ChangeStatus manifest(Attributor &A) override { + const IRPosition &FnPos = IRPosition::function(*getAnchorScope()); + auto *AssumptionAA = + A.lookupAAFor(FnPos, this, DepClassTy::REQUIRED); + + // Don't manifest a universal set if it somehow made it here. + if (getKnown().isUniversal()) + return ChangeStatus::UNCHANGED; + + CallBase &AssociatedCall = cast(getAssociatedValue()); + bool Changed = addAssumptions(AssociatedCall, getAssumed().getSet()); + + return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; + } + + /// See AbstractAttribute::updateImpl(...). + ChangeStatus updateImpl(Attributor &A) override { + const IRPosition &FnPos = IRPosition::function(*getAnchorScope()); + auto &AssumptionAA = + A.getAAFor(*this, FnPos, DepClassTy::REQUIRED); + bool Changed = getIntersection(AssumptionAA.getAssumed()); + return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; + } + + /// See AbstractAttribute::trackStatistics() + void trackStatistics() const override {} + +private: + /// Helper to initialized the known set as all the assumptions this call and + /// the callee contain. + DenseSet getInitialAssumptions(const IRPosition &IRP) { + const CallBase &CB = cast(IRP.getAssociatedValue()); + auto Assumptions = getAssumptions(CB); + if (Function *F = IRP.getAssociatedFunction()) + set_union(Assumptions, getAssumptions(*F)); + if (Function *F = IRP.getAssociatedFunction()) + set_union(Assumptions, getAssumptions(*F)); + return Assumptions; + } +}; + } // namespace AACallGraphNode *AACallEdgeIterator::operator*() const { @@ -9674,6 +9816,7 @@ const char AACallEdges::ID = 0; const char AAFunctionReachability::ID = 0; const char AAPointerInfo::ID = 0; +const char AAAssumptionInfo::ID = 0; // Macro magic to create the static generator function for attributes that // follow the naming scheme. @@ -9776,6 +9919,7 @@ CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAReturnedValues) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAMemoryLocation) CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AACallEdges) +CREATE_FUNCTION_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAAssumptionInfo) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANonNull) CREATE_VALUE_ABSTRACT_ATTRIBUTE_FOR_POSITION(AANoAlias) diff --git a/llvm/test/Transforms/Attributor/assumes_info.ll b/llvm/test/Transforms/Attributor/assumes_info.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Attributor/assumes_info.ll @@ -0,0 +1,95 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes --check-globals +; RUN: opt -attributor -enable-new-pm=0 -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=3 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_NPM,NOT_CGSCC_OPM,NOT_TUNIT_NPM,IS__TUNIT____,IS________OPM,IS__TUNIT_OPM +; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=3 -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_CGSCC_OPM,NOT_CGSCC_NPM,NOT_TUNIT_OPM,IS__TUNIT____,IS________NPM,IS__TUNIT_NPM +; RUN: opt -attributor-cgscc -enable-new-pm=0 -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_TUNIT_NPM,NOT_TUNIT_OPM,NOT_CGSCC_NPM,IS__CGSCC____,IS________OPM,IS__CGSCC_OPM +; RUN: opt -aa-pipeline=basic-aa -passes=attributor-cgscc -attributor-manifest-internal -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=CHECK,NOT_TUNIT_NPM,NOT_TUNIT_OPM,NOT_CGSCC_OPM,IS__CGSCC____,IS________NPM,IS__CGSCC_NPM + +define dso_local void @entry(i1 %cond) #0 { +; CHECK-LABEL: define {{[^@]+}}@entry +; CHECK-SAME: (i1 [[COND:%.*]]) #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @foo(i1 [[COND]]) #[[ATTR1:[0-9]+]] +; CHECK-NEXT: call void @bar() #[[ATTR2:[0-9]+]] +; CHECK-NEXT: call void @qux() #[[ATTR1]] +; CHECK-NEXT: ret void +; +entry: + call void @foo(i1 %cond) + call void @bar() + call void @qux() #1 + ret void +} + +define internal void @foo(i1 %cond) #1 { +; CHECK-LABEL: define {{[^@]+}}@foo +; CHECK-SAME: (i1 [[COND:%.*]]) #[[ATTR1]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @baz(i1 [[COND]]) #[[ATTR1]] +; CHECK-NEXT: ret void +; +entry: + call void @baz(i1 %cond) + ret void +} + +define internal void @bar() #2 { +; CHECK-LABEL: define {{[^@]+}}@bar +; CHECK-SAME: () #[[ATTR2]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @baz(i1 noundef false) #[[ATTR2]] +; CHECK-NEXT: ret void +; +entry: + call void @baz(i1 0) + ret void +} + +define internal void @baz(i1 %Cond) { +; CHECK-LABEL: define {{[^@]+}}@baz +; CHECK-SAME: (i1 [[COND:%.*]]) #[[ATTR1]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: [[TOBOOL:%.*]] = icmp ne i1 [[COND]], false +; CHECK-NEXT: br i1 [[TOBOOL]], label [[IF_THEN:%.*]], label [[IF_END:%.*]] +; CHECK: if.then: +; CHECK-NEXT: call void @baz(i1 noundef false) #[[ATTR1]] +; CHECK-NEXT: br label [[IF_END]] +; CHECK: if.end: +; CHECK-NEXT: call void @qux() #[[ATTR1]] +; CHECK-NEXT: ret void +; +entry: + %tobool = icmp ne i1 %Cond, 0 + br i1 %tobool, label %if.then, label %if.end + +if.then: + call void @baz(i1 0) + br label %if.end + +if.end: + call void @qux() + ret void +} + +define internal void @qux() { +; CHECK-LABEL: define {{[^@]+}}@qux +; CHECK-SAME: () #[[ATTR1]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @call() #[[ATTR2]] +; CHECK-NEXT: ret void +; +entry: + call void @call() + ret void +} + +declare void @call() #3 + +attributes #0 = { "llvm.assume"="A" } +attributes #1 = { "llvm.assume"="B" } +attributes #2 = { "llvm.assume"="B,C" } +attributes #3 = { "llvm.assume"="B,C,A" } +;. +; CHECK: attributes #[[ATTR0]] = { "llvm.assume"="A" } +; CHECK: attributes #[[ATTR1]] = { "llvm.assume"="B,A" } +; CHECK: attributes #[[ATTR2]] = { "llvm.assume"="B,C,A" } +;. diff --git a/llvm/test/Transforms/Attributor/depgraph.ll b/llvm/test/Transforms/Attributor/depgraph.ll --- a/llvm/test/Transforms/Attributor/depgraph.ll +++ b/llvm/test/Transforms/Attributor/depgraph.ll @@ -123,6 +123,8 @@ ; GRAPH-NEXT: updates [AAMemoryLocation] for CtxI ' %6 = call i32* @checkAndAdvance(i32* %5)' at position {cs: [@-1]} with state memory:argument ; GRAPH-NEXT: updates [AAMemoryLocation] for CtxI ' %6 = call i32* @checkAndAdvance(i32* %5)' at position {cs: [@-1]} with state memory:argument ; GRAPH-EMPTY: +; GRAPH-NEXT: [AAAssumptionInfo] for CtxI ' %2 = load i32, i32* %0, align 4' at position {fn:checkAndAdvance [checkAndAdvance@-1]} with state Known [], Assumed [] +; GRAPH-EMPTY: ; GRAPH-NEXT: [AAHeapToStack] for CtxI ' %2 = load i32, i32* %0, align 4' at position {fn:checkAndAdvance [checkAndAdvance@-1]} with state [H2S] Mallocs Good/Bad: 0/0 ; GRAPH-EMPTY: ; GRAPH-NEXT: [AAValueSimplify] for CtxI ' %2 = load i32, i32* %0, align 4' at position {fn_ret:checkAndAdvance [checkAndAdvance@-1]} with state not-simple @@ -181,6 +183,8 @@ ; GRAPH-NEXT: updates [AAIsDead] for CtxI ' %6 = call i32* @checkAndAdvance(i32* %5)' at position {cs_ret: [@-1]} with state assumed-live ; GRAPH-NEXT: updates [AAMemoryBehavior] for CtxI ' %2 = load i32, i32* %0, align 4' at position {fn:checkAndAdvance [checkAndAdvance@-1]} with state readonly ; GRAPH-EMPTY: +; GRAPH-NEXT: [AAAssumptionInfo] for CtxI ' %6 = call i32* @checkAndAdvance(i32* %5)' at position {cs: [@-1]} with state Known [], Assumed [] +; GRAPH-EMPTY: ; GRAPH-NEXT: [AAValueSimplify] for CtxI ' %6 = call i32* @checkAndAdvance(i32* %5)' at position {cs_ret: [@-1]} with state not-simple ; GRAPH-EMPTY: ; GRAPH-NEXT: [AAIsDead] for CtxI ' %6 = call i32* @checkAndAdvance(i32* %5)' at position {cs_arg: [@0]} with state assumed-live