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,138 @@ 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 set'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 UniversalSet { + /// Creates a universal set with no concrete elements or an empty set. + UniversalSet(bool Universal) : Universal(Universal) {} + + /// Creates a non-universal set with concrete values. + UniversalSet(const DenseSet &Assumptions) + : Universal(false), 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 UniversalSet &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 UniversalSet &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) {} + + /// Initializes the known state with an initial set and initializes the + /// assumed state as universal. + SetState(const DenseSet &Known, bool Universal) + : Known(Known), Assumed(Universal) {} + + /// See AbstractState::isValidState() + bool isValidState() const override { return !Assumed.isUniversal(); } + + /// 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 UniversalSet &getKnown() const { return Known; } + + /// Return the assumed state encoding. + const UniversalSet &getAssumed() const { return Assumed; } + + /// Returns if the assumed state contains the element. + bool setContains(const base_ty &Elem) const { + return Assumed.getSet().contains(Elem); + } + + /// Indicates if the last operation changed the assumed state. + bool hasChanged() const { return Changed; } + + /// Performs the set intersection between this set and \p RHS. Returns true if + /// changes were made. + bool getIntersection(const UniversalSet &RHS) { + Changed = Assumed.getIntersection(RHS); + return Changed; + } + + /// Performs the set union between this set and \p RHS. Returns true if + /// changes were made. + bool getUnion(const UniversalSet &RHS) { + Changed = Assumed.getUnion(RHS); + return Changed; + } + +private: + /// The set of values known for this state. + UniversalSet Known; + + /// The set of assumed values for this state. + UniversalSet Assumed; + + bool Changed; + + 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 +4742,40 @@ static const char ID; }; +/// An abstract attribute for getting assumption information. +struct AAAssumptionInfo + : public StateWrapper, AbstractAttribute, + DenseSet, bool> { + using Base = StateWrapper, AbstractAttribute, + DenseSet, bool>; + + AAAssumptionInfo(const IRPosition &IRP, Attributor &A, + const DenseSet &Known, bool Universal) + : Base(IRP, Known, Universal) {} + + /// 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 @@ -9,6 +9,8 @@ //===----------------------------------------------------------------------===// #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" @@ -29,15 +31,29 @@ return Assumption == 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; +} } // 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)) @@ -47,6 +63,51 @@ 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) { + if (Assumptions.empty()) + return false; + + DenseSet CurAssumptions = getAssumptions(F); + + if (!set_union(CurAssumptions, Assumptions)) + return false; + + LLVMContext &Ctx = F.getContext(); + F.addFnAttr(llvm::Attribute::get( + Ctx, llvm::AssumptionAttrKey, + llvm::join(CurAssumptions.begin(), CurAssumptions.end(), ","))); + + return true; +} + +bool llvm::addAssumptions(CallBase &CB, + const DenseSet &Assumptions) { + if (Assumptions.empty()) + return false; + + DenseSet CurAssumptions = getAssumptions(CB); + + if (!set_union(CurAssumptions, Assumptions)) + return false; + + LLVMContext &Ctx = CB.getContext(); + CB.addFnAttr(llvm::Attribute::get( + Ctx, llvm::AssumptionAttrKey, + llvm::join(CurAssumptions.begin(), CurAssumptions.end(), ","))); + + return true; +} + 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 @@ -2494,6 +2494,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); @@ -2579,6 +2582,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. @@ -2590,6 +2594,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 @@ -9658,6 +9661,7 @@ } void trackStatistics() const override {} + private: bool canReachUnknownCallee() const override { return WholeFunction.CanReachUnknownCallee; @@ -9671,6 +9675,131 @@ DenseMap CBQueries; }; +/// ---------------------- Assumption Propagation ------------------------------ +struct AAAssumptionInfoImpl : public AAAssumptionInfo { + AAAssumptionInfoImpl(const IRPosition &IRP, Attributor &A, + const DenseSet &Known, bool Universal) + : AAAssumptionInfo(IRP, A, Known, Universal) {} + + bool hasAssumption(const StringRef Assumption) const override { + return isValidState() && setContains(Assumption); + } + + /// See AbstractAttribute::getAsStr() + const std::string getAsStr() const override { + const UniversalSet &Assumptions = getAssumed(); + + if (Assumptions.isUniversal()) + return "[Universal]"; + + std::string AssumptionStr = llvm::join(Assumptions.getSet().begin(), + Assumptions.getSet().end(), ","); + return "[" + AssumptionStr + "]"; + } +}; + +/// Propogates assumption information from a parent function to all of its +/// direct sucessors. An assumption can be prpogated 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 interprocedurally updated by narrowing its +/// type as concrete types are known. The concrete types 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 +/// of its predecessors taken with the union of its known state. +struct AAAssumptionInfoFunction final : AAAssumptionInfoImpl { + AAAssumptionInfoFunction(const IRPosition &IRP, Attributor &A) + : AAAssumptionInfoImpl( + IRP, A, getAssumptions(*IRP.getAssociatedFunction()), true) {} + + /// See AbstractAttribute::manifest(...). + ChangeStatus manifest(Attributor &A) override { + auto &Assumptions = getAssumed(); + + Function *AssociatedFunction = getIRPosition().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 &Assumptions = getAssumed(); + + auto CallSitePred = [&](AbstractCallSite ACS) { + const auto &AssumptionAA = A.getAAFor( + *this, IRPosition::function(*ACS.getInstruction()->getFunction()), + DepClassTy::REQUIRED); + 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 propogated to its successors. + if (!A.checkForAllCallSites(CallSitePred, *this, true, AllCallSitesKnown)) + return indicatePessimisticFixpoint(); + + // Add this function's attributes to the set. + Changed |= getUnion(getKnown()); + + 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, getAssumptions(cast(IRP.getAssociatedValue())), + false) {} + + /// See AbstractAttribute::manifest(...). + ChangeStatus manifest(Attributor &A) override { + Function *F = getAssociatedFunction(); + + auto &Assumptions = getAssumed(); + + const IRPosition &FnPos = IRPosition::function(*F); + auto *AssumptionAA = + A.lookupAAFor(FnPos, this, DepClassTy::REQUIRED); + + assert(AssumptionAA && "Attempting to manifest an uninitialized AA!"); + + // Add in the assumptions derived for the parent function. + getUnion(AssumptionAA->getAssumed()); + + CallBase &AssociatedCall = cast(getAssociatedValue()); + + bool Changed = addAssumptions(AssociatedCall, Assumptions.getSet()); + + return Changed ? ChangeStatus::CHANGED : ChangeStatus::UNCHANGED; + } + + /// See AbstractAttribute::updateImpl(...). + ChangeStatus updateImpl(Attributor &A) override { + const Function *F = getAssociatedFunction(); + const IRPosition &FnPos = IRPosition::function(*F); + auto &AssumptionAA = + A.getAAFor(*this, FnPos, DepClassTy::REQUIRED); + return AssumptionAA.hasChanged() ? ChangeStatus::CHANGED + : ChangeStatus::UNCHANGED; + } + + /// See AbstractAttribute::trackStatistics() + void trackStatistics() const override {} +}; + } // namespace AACallGraphNode *AACallEdgeIterator::operator*() const { @@ -9706,6 +9835,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. @@ -9808,6 +9938,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,109 @@ +; 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=2 -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=2 -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 +declare void @call() + +define dso_local void @entry(i1 %cond) #0 { +; IS__TUNIT____-LABEL: define {{[^@]+}}@entry +; IS__TUNIT____-SAME: (i1 [[COND:%.*]]) #[[ATTR0:[0-9]+]] { +; IS__TUNIT____-NEXT: entry: +; IS__TUNIT____-NEXT: call void @foo(i1 [[COND]]) #[[ATTR1:[0-9]+]] +; IS__TUNIT____-NEXT: call void @bar() #[[ATTR2:[0-9]+]] +; IS__TUNIT____-NEXT: call void @qux() #[[ATTR0]] +; IS__TUNIT____-NEXT: ret void +; +; IS__CGSCC_OPM-LABEL: define {{[^@]+}}@entry +; IS__CGSCC_OPM-SAME: (i1 [[COND:%.*]]) #[[ATTR0:[0-9]+]] { +; IS__CGSCC_OPM-NEXT: entry: +; IS__CGSCC_OPM-NEXT: call void @foo(i1 [[COND]]) +; IS__CGSCC_OPM-NEXT: call void @bar() +; IS__CGSCC_OPM-NEXT: call void @qux() +; IS__CGSCC_OPM-NEXT: ret void +; +; IS__CGSCC_NPM-LABEL: define {{[^@]+}}@entry +; IS__CGSCC_NPM-SAME: (i1 [[COND:%.*]]) #[[ATTR0:[0-9]+]] { +; IS__CGSCC_NPM-NEXT: entry: +; IS__CGSCC_NPM-NEXT: call void @foo(i1 [[COND]]) #[[ATTR0]] +; IS__CGSCC_NPM-NEXT: call void @bar() #[[ATTR0]] +; IS__CGSCC_NPM-NEXT: call void @qux() #[[ATTR0]] +; IS__CGSCC_NPM-NEXT: ret void +; +entry: + call void @foo(i1 %cond) + call void @bar() + call void @qux() + ret void +} + +define internal void @foo(i1 %cond) #1 { +; CHECK-LABEL: define {{[^@]+}}@foo +; CHECK-SAME: (i1 [[COND:%.*]]) #[[ATTR1:[0-9]+]] { +; 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:[0-9]+]] { +; 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: () #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: call void @call() #[[ATTR0]] +; CHECK-NEXT: ret void +; +entry: + call void @call() + ret void +} + +attributes #0 = { "llvm.assume"="A" } +attributes #1 = { "llvm.assume"="B" } +attributes #2 = { "llvm.assume"="B,C" } +;. +; 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 [] +; 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 [] +; 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