/// Return true if \p AA (or its context instruction) is assumed dead.
///
/// If \p LivenessAA is not provided it is queried.
- bool isAssumedDead(const AbstractAttribute &AA, const AAIsDead *LivenessAA);
+ bool isAssumedDead(const AbstractAttribute &AA, const AAIsDead *LivenessAA,
+ bool CheckBBLivenessOnly = false,
+ DepClassTy DepClass = DepClassTy::OPTIONAL);
+
+ /// Return true if \p I is assumed dead.
+ ///
+ /// If \p LivenessAA is not provided it is queried.
+ bool isAssumedDead(const Instruction &I, const AbstractAttribute *QueryingAA,
+ const AAIsDead *LivenessAA,
+ bool CheckBBLivenessOnly = false,
+ DepClassTy DepClass = DepClassTy::OPTIONAL);
+
+ /// Return true if \p U is assumed dead.
+ ///
+ /// If \p FnLivenessAA is not provided it is queried.
+ bool isAssumedDead(const Use &U, const AbstractAttribute *QueryingAA,
+ const AAIsDead *FnLivenessAA,
+ bool CheckBBLivenessOnly = false,
+ DepClassTy DepClass = DepClassTy::OPTIONAL);
+
+ /// Return true if \p IRP is assumed dead.
+ ///
+ /// If \p FnLivenessAA is not provided it is queried.
+ bool isAssumedDead(const IRPosition &IRP, const AbstractAttribute *QueryingAA,
+ const AAIsDead *FnLivenessAA,
+ bool CheckBBLivenessOnly = false,
+ DepClassTy DepClass = DepClassTy::OPTIONAL);
/// Check \p Pred on all (transitive) uses of \p V.
///
/// This method will evaluate \p Pred on all (transitive) uses of the
/// associated value and return true if \p Pred holds every time.
bool checkForAllUses(const function_ref<bool(const Use &, bool &)> &Pred,
- const AbstractAttribute &QueryingAA, const Value &V);
+ const AbstractAttribute &QueryingAA, const Value &V,
+ DepClassTy LivenessDepClass = DepClassTy::OPTIONAL);
/// Helper struct used in the communication between an abstract attribute (AA)
/// that wants to change the signature of a function and the Attributor which
/// present in \p Opcode and return true if \p Pred holds on all of them.
bool checkForAllInstructions(const function_ref<bool(Instruction &)> &Pred,
const AbstractAttribute &QueryingAA,
- const ArrayRef<unsigned> &Opcodes);
+ const ArrayRef<unsigned> &Opcodes,
+ bool CheckBBLivenessOnly = false);
/// Check \p Pred on all call-like instructions (=CallBased derived).
///
public IRPosition {
AAIsDead(const IRPosition &IRP) : IRPosition(IRP) {}
+protected:
+ /// The query functions are protected such that other attributes need to go
+ /// through the Attributor interfaces: `Attributor::isAssumedDead(...)`
+
/// Returns true if the underlying value is assumed dead.
virtual bool isAssumedDead() const = 0;
return false;
}
+public:
/// Return an IR position, see struct IRPosition.
const IRPosition &getIRPosition() const override { return *this; }
/// Unique ID (due to the unique address)
static const char ID;
+
+ friend struct Attributor;
};
/// State for dereferenceable attribute
"Expected liveness in the presence of instructions!");
for (unsigned u = 0, e = PHI->getNumIncomingValues(); u < e; u++) {
const BasicBlock *IncomingBB = PHI->getIncomingBlock(u);
- if (LivenessAA->isAssumedDead(IncomingBB->getTerminator())) {
+ if (A.isAssumedDead(*IncomingBB->getTerminator(), &QueryingAA,
+ LivenessAA,
+ /* CheckBBLivenessOnly */ true)) {
AnyDead = true;
continue;
}
A.checkForAllInstructions(InspectMemAccessInstForUB, *this,
{Instruction::Load, Instruction::Store,
Instruction::AtomicCmpXchg,
- Instruction::AtomicRMW});
- A.checkForAllInstructions(InspectBrInstForUB, *this, {Instruction::Br});
+ Instruction::AtomicRMW},
+ /* CheckBBLivenessOnly */ true);
+ A.checkForAllInstructions(InspectBrInstForUB, *this, {Instruction::Br},
+ /* CheckBBLivenessOnly */ true);
if (NoUBPrevSize != AssumedNoUBInsts.size() ||
UBPrevSize != KnownUBInsts.size())
return ChangeStatus::CHANGED;
}
/// Check if all uses are assumed dead.
- bool areAllUsesAssumedDead(Attributor &A) {
- auto UsePred = [&](const Use &U, bool &Follow) {
- Instruction *UserI = cast<Instruction>(U.getUser());
- if (CallSite CS = CallSite(UserI)) {
- if (!CS.isArgOperand(&U))
- return false;
- const IRPosition &CSArgPos =
- IRPosition::callsite_argument(CS, CS.getArgumentNo(&U));
- const auto &CSArgIsDead = A.getAAFor<AAIsDead>(*this, CSArgPos);
- return CSArgIsDead.isAssumedDead();
- }
- if (ReturnInst *RI = dyn_cast<ReturnInst>(UserI)) {
- const IRPosition &RetPos = IRPosition::returned(*RI->getFunction());
- const auto &RetIsDeadAA = A.getAAFor<AAIsDead>(*this, RetPos);
- return RetIsDeadAA.isAssumedDead();
- }
- Follow = true;
- return wouldInstructionBeTriviallyDead(UserI);
- };
-
- return A.checkForAllUses(UsePred, *this, getAssociatedValue());
+ bool areAllUsesAssumedDead(Attributor &A, Value &V) {
+ auto UsePred = [&](const Use &U, bool &Follow) { return false; };
+ // Explicitly set the dependence class to required because we want a long
+ // chain of N dependent instructions to be considered live as soon as one is
+ // without going through N update cycles. This is not required for
+ // correctness.
+ return A.checkForAllUses(UsePred, *this, V, DepClassTy::REQUIRED);
}
/// Determine if \p I is assumed to be side-effect free.
if (!isAssumedSideEffectFree(A, I))
return indicatePessimisticFixpoint();
- if (!areAllUsesAssumedDead(A))
+ if (!areAllUsesAssumedDead(A, getAssociatedValue()))
return indicatePessimisticFixpoint();
return ChangeStatus::UNCHANGED;
}
return AAIsDeadFloating::isAssumedDead() && IsAssumedSideEffectFree;
}
- /// Return true if all users are assumed dead.
- bool hasOnlyAssumedDeadUses() const { return getAssumed(); }
-
/// See AbstractAttribute::initialize(...).
void initialize(Attributor &A) override {
if (isa<UndefValue>(getAssociatedValue())) {
Changed = ChangeStatus::CHANGED;
}
- if (!areAllUsesAssumedDead(A))
+ if (!areAllUsesAssumedDead(A, getAssociatedValue()))
return indicatePessimisticFixpoint();
return Changed;
}
/// See AbstractAttribute::updateImpl(...).
ChangeStatus updateImpl(Attributor &A) override {
- bool AllKnownDead = true;
auto PredForCallSite = [&](AbstractCallSite ACS) {
- if (ACS.isCallbackCall())
+ if (ACS.isCallbackCall() || !ACS.getInstruction())
return false;
- const IRPosition &CSRetPos =
- IRPosition::callsite_returned(ACS.getCallSite());
- const auto &RetIsDeadAA = A.getAAFor<AAIsDead>(*this, CSRetPos);
- AllKnownDead &= RetIsDeadAA.isKnownDead();
- return static_cast<const AAIsDeadCallSiteReturned &>(RetIsDeadAA)
- .hasOnlyAssumedDeadUses();
+ return areAllUsesAssumedDead(A, *ACS.getInstruction());
};
bool AllCallSitesKnown;
AllCallSitesKnown))
return indicatePessimisticFixpoint();
- if (AllCallSitesKnown && AllKnownDead)
- indicateOptimisticFixpoint();
-
return ChangeStatus::UNCHANGED;
}
/// See CaptureTracker::shouldExplore(...).
bool shouldExplore(const Use *U) override {
- // Check liveness, if it is used to stop exploring we need a dependence.
- if (IsDeadAA.isAssumedDead(cast<Instruction>(U->getUser()))) {
- A.recordDependence(IsDeadAA, NoCaptureAA, DepClassTy::OPTIONAL);
- return false;
- }
- return true;
+ // Check liveness.
+ return !A.isAssumedDead(*U, &NoCaptureAA, &IsDeadAA);
}
/// Update the state according to \p CapturedInMem, \p CapturedInInt, and
const Use *U = Uses[i];
Instruction *UserI = cast<Instruction>(U->getUser());
LLVM_DEBUG(dbgs() << "[AAMemoryBehavior] Use: " << **U << " in " << *UserI
- << " [Dead: " << (LivenessAA.isAssumedDead(UserI))
+ << " [Dead: " << (A.isAssumedDead(*U, this, &LivenessAA))
<< "]\n");
- if (LivenessAA.isAssumedDead(UserI)) {
- A.recordDependence(LivenessAA, *this, DepClassTy::OPTIONAL);
+ if (A.isAssumedDead(*U, this, &LivenessAA))
continue;
- }
// Check if the users of UserI should also be visited.
if (followUsersOfUseIn(A, U, UserI))
/// ----------------------------------------------------------------------------
bool Attributor::isAssumedDead(const AbstractAttribute &AA,
- const AAIsDead *LivenessAA) {
- const Instruction *CtxI = AA.getIRPosition().getCtxI();
- if (!CtxI || !Functions.count(const_cast<Function *>(CtxI->getFunction())))
+ const AAIsDead *FnLivenessAA,
+ bool CheckBBLivenessOnly, DepClassTy DepClass) {
+ const IRPosition &IRP = AA.getIRPosition();
+ if (!Functions.count(IRP.getAnchorScope()))
return false;
+ return isAssumedDead(IRP, &AA, FnLivenessAA, CheckBBLivenessOnly, DepClass);
+}
+
+bool Attributor::isAssumedDead(const Use &U,
+ const AbstractAttribute *QueryingAA,
+ const AAIsDead *FnLivenessAA,
+ bool CheckBBLivenessOnly, DepClassTy DepClass) {
+ Instruction *UserI = dyn_cast<Instruction>(U.getUser());
+ if (!UserI)
+ return isAssumedDead(IRPosition::value(*U.get()), QueryingAA, FnLivenessAA,
+ CheckBBLivenessOnly, DepClass);
+
+ if (CallSite CS = CallSite(UserI)) {
+ // For call site argument uses we can check if the argument is
+ // unused/dead.
+ if (CS.isArgOperand(&U)) {
+ const IRPosition &CSArgPos =
+ IRPosition::callsite_argument(CS, CS.getArgumentNo(&U));
+ return isAssumedDead(CSArgPos, QueryingAA, FnLivenessAA,
+ CheckBBLivenessOnly, DepClass);
+ }
+ } else if (ReturnInst *RI = dyn_cast<ReturnInst>(UserI)) {
+ const IRPosition &RetPos = IRPosition::returned(*RI->getFunction());
+ return isAssumedDead(RetPos, QueryingAA, FnLivenessAA, CheckBBLivenessOnly,
+ DepClass);
+ } else if (PHINode *PHI = dyn_cast<PHINode>(UserI)) {
+ BasicBlock *IncomingBB = PHI->getIncomingBlock(U);
+ return isAssumedDead(*IncomingBB->getTerminator(), QueryingAA, FnLivenessAA,
+ CheckBBLivenessOnly, DepClass);
+ }
+
+ return isAssumedDead(IRPosition::value(*UserI), QueryingAA, FnLivenessAA,
+ CheckBBLivenessOnly, DepClass);
+}
- // TODO: Find a good way to utilize fine and coarse grained liveness
- // information.
- if (!LivenessAA)
- LivenessAA =
- &getAAFor<AAIsDead>(AA, IRPosition::function(*CtxI->getFunction()),
- /* TrackDependence */ false);
+bool Attributor::isAssumedDead(const Instruction &I,
+ const AbstractAttribute *QueryingAA,
+ const AAIsDead *FnLivenessAA,
+ bool CheckBBLivenessOnly, DepClassTy DepClass) {
+ if (!FnLivenessAA)
+ FnLivenessAA = lookupAAFor<AAIsDead>(IRPosition::function(*I.getFunction()),
+ QueryingAA,
+ /* TrackDependence */ false);
+
+ // If we have a context instruction and a liveness AA we use it.
+ if (FnLivenessAA &&
+ FnLivenessAA->getIRPosition().getAnchorScope() == I.getFunction() &&
+ FnLivenessAA->isAssumedDead(&I)) {
+ if (QueryingAA)
+ recordDependence(*FnLivenessAA, *QueryingAA, DepClass);
+ return true;
+ }
+ if (CheckBBLivenessOnly)
+ return false;
+
+ const AAIsDead &IsDeadAA = getOrCreateAAFor<AAIsDead>(
+ IRPosition::value(I), QueryingAA, /* TrackDependence */ false);
// Don't check liveness for AAIsDead.
- if (&AA == LivenessAA)
+ if (QueryingAA == &IsDeadAA)
return false;
- if (!LivenessAA->isAssumedDead(CtxI))
+ if (IsDeadAA.isAssumedDead()) {
+ if (QueryingAA)
+ recordDependence(IsDeadAA, *QueryingAA, DepClass);
+ return true;
+ }
+
+ return false;
+}
+
+bool Attributor::isAssumedDead(const IRPosition &IRP,
+ const AbstractAttribute *QueryingAA,
+ const AAIsDead *FnLivenessAA,
+ bool CheckBBLivenessOnly, DepClassTy DepClass) {
+ Instruction *CtxI = IRP.getCtxI();
+ if (CtxI &&
+ isAssumedDead(*CtxI, QueryingAA, FnLivenessAA,
+ /* CheckBBLivenessOnly */ true,
+ CheckBBLivenessOnly ? DepClass : DepClassTy::OPTIONAL))
+ return true;
+
+ if (CheckBBLivenessOnly)
return false;
- // We actually used liveness information so we have to record a dependence.
- recordDependence(*LivenessAA, AA, DepClassTy::OPTIONAL);
+ // If we haven't succeeded we query the specific liveness info for the IRP.
+ const AAIsDead *IsDeadAA;
+ if (IRP.getPositionKind() == IRPosition::IRP_CALL_SITE)
+ IsDeadAA = &getOrCreateAAFor<AAIsDead>(
+ IRPosition::callsite_returned(cast<CallBase>(IRP.getAssociatedValue())),
+ QueryingAA, /* TrackDependence */ false);
+ else
+ IsDeadAA = &getOrCreateAAFor<AAIsDead>(IRP, QueryingAA,
+ /* TrackDependence */ false);
+ // Don't check liveness for AAIsDead.
+ if (QueryingAA == IsDeadAA)
+ return false;
- return true;
+ if (IsDeadAA->isAssumedDead()) {
+ if (QueryingAA)
+ recordDependence(*IsDeadAA, *QueryingAA, DepClass);
+ return true;
+ }
+
+ return false;
}
bool Attributor::checkForAllUses(
const function_ref<bool(const Use &, bool &)> &Pred,
- const AbstractAttribute &QueryingAA, const Value &V) {
+ const AbstractAttribute &QueryingAA, const Value &V,
+ DepClassTy LivenessDepClass) {
// Check the trivial case first as it catches void values.
if (V.use_empty())
LLVM_DEBUG(dbgs() << "[Attributor] Got " << Worklist.size()
<< " initial uses to check\n");
- bool AnyDead = false;
const Function *ScopeFn = IRP.getAnchorScope();
const auto *LivenessAA =
ScopeFn ? &getAAFor<AAIsDead>(QueryingAA, IRPosition::function(*ScopeFn),
const Use *U = Worklist.pop_back_val();
if (!Visited.insert(U).second)
continue;
- LLVM_DEBUG(dbgs() << "[Attributor] Check use: " << **U << " [" << LivenessAA
- << "]\n");
- if (LivenessAA) {
- if (Instruction *UserI = dyn_cast<Instruction>(U->getUser())) {
- if (LivenessAA->isAssumedDead(UserI)) {
- LLVM_DEBUG(dbgs() << "[Attributor] Dead user: " << *UserI << ": "
- << *LivenessAA << "\n");
- AnyDead = true;
- continue;
- }
- if (PHINode *PHI = dyn_cast<PHINode>(UserI)) {
- BasicBlock *IncomingBB = PHI->getIncomingBlock(*U);
- if (LivenessAA->isAssumedDead(IncomingBB->getTerminator())) {
- LLVM_DEBUG(dbgs() << "[Attributor] Dead user: " << *UserI << ": "
- << *LivenessAA << "\n");
- AnyDead = true;
- continue;
- }
- }
- }
+ LLVM_DEBUG(dbgs() << "[Attributor] Check use: " << **U << " in "
+ << *U->getUser() << "\n");
+ if (isAssumedDead(*U, &QueryingAA, LivenessAA,
+ /* CheckBBLivenessOnly */ false, LivenessDepClass)) {
+ LLVM_DEBUG(dbgs() << "[Attributor] Dead use, skip!\n");
+ continue;
}
bool Follow = false;
Worklist.push_back(&UU);
}
- if (AnyDead)
- recordDependence(*LivenessAA, QueryingAA, DepClassTy::OPTIONAL);
-
return true;
}
AllCallSitesKnown = RequireAllCallSites;
for (const Use &U : Fn.uses()) {
+ LLVM_DEBUG(dbgs() << "[Attributor] Check use: " << *U << " in "
+ << *U.getUser() << "\n");
+ if (isAssumedDead(U, QueryingAA, nullptr, /* CheckBBLivenessOnly */ true)) {
+ LLVM_DEBUG(dbgs() << "[Attributor] Dead use, skip!\n");
+ continue;
+ }
+
AbstractCallSite ACS(&U);
if (!ACS) {
LLVM_DEBUG(dbgs() << "[Attributor] Function " << Fn.getName()
return false;
}
- Instruction *I = ACS.getInstruction();
- Function *Caller = I->getFunction();
-
- const auto *LivenessAA =
- lookupAAFor<AAIsDead>(IRPosition::function(*Caller), QueryingAA,
- /* TrackDependence */ false);
-
- // Skip dead calls.
- if (LivenessAA && LivenessAA->isAssumedDead(I)) {
- // We actually used liveness information so we have to record a
- // dependence.
- if (QueryingAA)
- recordDependence(*LivenessAA, *QueryingAA, DepClassTy::OPTIONAL);
- AllCallSitesKnown = false;
- continue;
- }
-
const Use *EffectiveUse =
ACS.isCallbackCall() ? &ACS.getCalleeUseForCallback() : &U;
if (!ACS.isCallee(EffectiveUse)) {
});
}
-static bool
-checkForAllInstructionsImpl(InformationCache::OpcodeInstMapTy &OpcodeInstMap,
- const function_ref<bool(Instruction &)> &Pred,
- const AAIsDead *LivenessAA, bool &AnyDead,
- const ArrayRef<unsigned> &Opcodes) {
+static bool checkForAllInstructionsImpl(
+ Attributor *A, InformationCache::OpcodeInstMapTy &OpcodeInstMap,
+ const function_ref<bool(Instruction &)> &Pred,
+ const AbstractAttribute *QueryingAA, const AAIsDead *LivenessAA,
+ const ArrayRef<unsigned> &Opcodes, bool CheckBBLivenessOnly = false) {
for (unsigned Opcode : Opcodes) {
for (Instruction *I : OpcodeInstMap[Opcode]) {
// Skip dead instructions.
- if (LivenessAA && LivenessAA->isAssumedDead(I)) {
- AnyDead = true;
+ if (A && A->isAssumedDead(IRPosition::value(*I), QueryingAA, LivenessAA,
+ CheckBBLivenessOnly))
continue;
- }
if (!Pred(*I))
return false;
bool Attributor::checkForAllInstructions(
const llvm::function_ref<bool(Instruction &)> &Pred,
- const AbstractAttribute &QueryingAA, const ArrayRef<unsigned> &Opcodes) {
+ const AbstractAttribute &QueryingAA, const ArrayRef<unsigned> &Opcodes,
+ bool CheckBBLivenessOnly) {
const IRPosition &IRP = QueryingAA.getIRPosition();
// Since we need to provide instructions we have to have an exact definition.
const IRPosition &QueryIRP = IRPosition::function(*AssociatedFunction);
const auto &LivenessAA =
getAAFor<AAIsDead>(QueryingAA, QueryIRP, /* TrackDependence */ false);
- bool AnyDead = false;
auto &OpcodeInstMap =
InfoCache.getOpcodeInstMapForFunction(*AssociatedFunction);
- if (!checkForAllInstructionsImpl(OpcodeInstMap, Pred, &LivenessAA, AnyDead,
- Opcodes))
+ if (!checkForAllInstructionsImpl(this, OpcodeInstMap, Pred, &QueryingAA,
+ &LivenessAA, Opcodes, CheckBBLivenessOnly))
return false;
- // If we actually used liveness information so we have to record a dependence.
- if (AnyDead)
- recordDependence(LivenessAA, QueryingAA, DepClassTy::OPTIONAL);
-
return true;
}
const IRPosition &QueryIRP = IRPosition::function(*AssociatedFunction);
const auto &LivenessAA =
getAAFor<AAIsDead>(QueryingAA, QueryIRP, /* TrackDependence */ false);
- bool AnyDead = false;
for (Instruction *I :
InfoCache.getReadOrWriteInstsForFunction(*AssociatedFunction)) {
// Skip dead instructions.
- if (LivenessAA.isAssumedDead(I)) {
- AnyDead = true;
+ if (isAssumedDead(IRPosition::value(*I), &QueryingAA, &LivenessAA))
continue;
- }
if (!Pred(*I))
return false;
}
- // If we actually used liveness information so we have to record a dependence.
- if (AnyDead)
- recordDependence(LivenessAA, QueryingAA, DepClassTy::OPTIONAL);
-
return true;
}
// Update all abstract attribute in the work list and record the ones that
// changed.
for (AbstractAttribute *AA : Worklist)
- if (!AA->getState().isAtFixpoint() && !isAssumedDead(*AA, nullptr)) {
+ if (!AA->getState().isAtFixpoint() &&
+ !isAssumedDead(*AA, nullptr, /* CheckBBLivenessOnly */ true)) {
QueriedNonFixAA = false;
if (AA->update(*this) == ChangeStatus::CHANGED) {
ChangedAAs.push_back(AA);
continue;
// Skip dead code.
- if (isAssumedDead(*AA, nullptr))
+ if (isAssumedDead(*AA, nullptr, /* CheckBBLivenessOnly */ true))
continue;
// Manifest the state and record if we changed the IR.
ChangeStatus LocalChange = AA->manifest(*this);
// Forbid must-tail calls for now.
// TODO:
- bool AnyDead;
auto &OpcodeInstMap = InfoCache.getOpcodeInstMapForFunction(*Fn);
- if (!checkForAllInstructionsImpl(OpcodeInstMap, InstPred, nullptr, AnyDead,
- {Instruction::Call})) {
+ if (!checkForAllInstructionsImpl(nullptr, OpcodeInstMap, InstPred, nullptr,
+ nullptr, {Instruction::Call})) {
LLVM_DEBUG(dbgs() << "[Attributor] Cannot rewrite due to instructions\n");
return false;
}
};
auto &OpcodeInstMap = InfoCache.getOpcodeInstMapForFunction(F);
- bool Success, AnyDead = false;
+ bool Success;
Success = checkForAllInstructionsImpl(
- OpcodeInstMap, CallSitePred, nullptr, AnyDead,
+ nullptr, OpcodeInstMap, CallSitePred, nullptr, nullptr,
{(unsigned)Instruction::Invoke, (unsigned)Instruction::CallBr,
(unsigned)Instruction::Call});
(void)Success;
- assert(Success && !AnyDead && "Expected the check call to be successful!");
+ assert(Success && "Expected the check call to be successful!");
auto LoadStorePred = [&](Instruction &I) -> bool {
if (isa<LoadInst>(I))
return true;
};
Success = checkForAllInstructionsImpl(
- OpcodeInstMap, LoadStorePred, nullptr, AnyDead,
+ nullptr, OpcodeInstMap, LoadStorePred, nullptr, nullptr,
{(unsigned)Instruction::Load, (unsigned)Instruction::Store});
(void)Success;
- assert(Success && !AnyDead && "Expected the check call to be successful!");
+ assert(Success && "Expected the check call to be successful!");
}
/// Helpers to ease debugging through output streams and print calls.
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 -S < %s | FileCheck %s
+; RUN: opt -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=6 -S < %s | FileCheck %s
define internal i32 @deref(i32* %x) nounwind {
; CHECK-LABEL: define {{[^@]+}}@deref
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=3 -S < %s | FileCheck %s
+; RUN: opt -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 -S < %s | FileCheck %s
; PR2498
; This test tries to convince CHECK about promoting the load from %A + 2,
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -disable-output -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=3 < %s
+; RUN: opt -disable-output -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s
%T = type { i32, i32, i32, i32 }
@G = constant %T { i32 0, i32 0, i32 17, i32 25 }
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=6 < %s | FileCheck %s
+; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=7 < %s | FileCheck %s
target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128"
define internal i32 @test(i32* %X, i32* %Y) {
; CHECK-NEXT: store i32 1, i32* [[TMP1]], align 8
; CHECK-NEXT: [[TMP4:%.*]] = getelementptr [[STRUCT_SS]], %struct.ss* [[S]], i32 0, i32 1
; CHECK-NEXT: store i64 2, i64* [[TMP4]], align 4
-; CHECK-NEXT: [[S_CAST:%.*]] = bitcast %struct.ss* [[S]] to i32*
-; CHECK-NEXT: [[TMP0:%.*]] = load i32, i32* [[S_CAST]], align 1
-; CHECK-NEXT: [[S_0_1:%.*]] = getelementptr [[STRUCT_SS]], %struct.ss* [[S]], i32 0, i32 1
-; CHECK-NEXT: [[TMP1:%.*]] = load i64, i64* [[S_0_1]], align 1
-; CHECK-NEXT: call void @f(i32 [[TMP0]], i64 [[TMP1]])
; CHECK-NEXT: [[S_CAST1:%.*]] = bitcast %struct.ss* [[S]] to i32*
-; CHECK-NEXT: [[TMP2:%.*]] = load i32, i32* [[S_CAST1]], align 1
+; CHECK-NEXT: [[TMP0:%.*]] = load i32, i32* [[S_CAST1]], align 1
; CHECK-NEXT: [[S_0_12:%.*]] = getelementptr [[STRUCT_SS]], %struct.ss* [[S]], i32 0, i32 1
-; CHECK-NEXT: [[TMP3:%.*]] = load i64, i64* [[S_0_12]], align 1
+; CHECK-NEXT: [[TMP1:%.*]] = load i64, i64* [[S_0_12]], align 1
+; CHECK-NEXT: call void @f(i32 [[TMP0]], i64 [[TMP1]])
+; CHECK-NEXT: [[S_CAST:%.*]] = bitcast %struct.ss* [[S]] to i32*
+; CHECK-NEXT: [[TMP2:%.*]] = load i32, i32* [[S_CAST]], align 1
+; CHECK-NEXT: [[S_0_1:%.*]] = getelementptr [[STRUCT_SS]], %struct.ss* [[S]], i32 0, i32 1
+; CHECK-NEXT: [[TMP3:%.*]] = load i64, i64* [[S_0_1]], align 1
; CHECK-NEXT: call void @g(i32 [[TMP2]], i64 [[TMP3]])
; CHECK-NEXT: ret i32 0
;
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=3 < %s | FileCheck %s
+; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s | FileCheck %s
@G1 = constant i32 0
@G2 = constant i32* @G1
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=3 < %s | FileCheck %s
+; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s
; Don't promote around control flow.
define internal i32 @callee(i1 %C, i32* %P) {
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=9 < %s | FileCheck %s
+; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=7 < %s | FileCheck %s
target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128"
define void @caller(i32** %Y, %struct.pair* %P) {
; CHECK-LABEL: define {{[^@]+}}@caller
-; CHECK-SAME: (i32** nocapture readonly [[Y:%.*]], %struct.pair* nocapture nofree readonly [[P:%.*]])
+; CHECK-SAME: (i32** nocapture readonly [[Y:%.*]], %struct.pair* nocapture nofree readnone [[P:%.*]])
; CHECK-NEXT: call void @test(i32** nocapture readonly align 8 [[Y]]), !dbg !4
; CHECK-NEXT: call void @test_byval(), !dbg !5
; CHECK-NEXT: ret void
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s --check-prefixes=ATTRIBUTOR
-; RUN: opt -S -passes='globalopt,attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s --check-prefixes=GLOBALOPT_ATTRIBUTOR
+; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=6 < %s | FileCheck %s --check-prefixes=ATTRIBUTOR
+; RUN: opt -S -passes='globalopt,attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=6 < %s | FileCheck %s --check-prefixes=GLOBALOPT_ATTRIBUTOR
target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128"
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -basicaa -attributor -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=3 < %s | FileCheck %s --check-prefixes=CHECK,OLDPM,OLDPM_MODULE
+; RUN: opt -S -basicaa -attributor -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=4 < %s | FileCheck %s --check-prefixes=CHECK,OLDPM,OLDPM_MODULE
; RUN: opt -S -basicaa -attributor-cgscc -attributor-disable=false < %s | FileCheck %s --check-prefixes=CHECK,OLDPM,OLDPM_CGSCC
-; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=3 < %s | FileCheck %s --check-prefixes=CHECK,NEWPM,NEWPM_MODULE
+; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=4 < %s | FileCheck %s --check-prefixes=CHECK,NEWPM,NEWPM_MODULE
; RUN: opt -S -passes='attributor-cgscc' -aa-pipeline='basic-aa' -attributor-disable=false < %s | FileCheck %s --check-prefixes=CHECK,NEWPM,NEWPM_CGSCC
; OLDPM_MODULE-NOT: @dead
ret i32 1
}
+; FIXME: This function should not be writeonly because only stack memory is written. Once we realize that @caller can be deleted.
+
define internal i32 @caller(i32* %B) {
; OLDPM_MODULE-LABEL: define {{[^@]+}}@caller()
; OLDPM_MODULE-NEXT: [[A:%.*]] = alloca i32
; OLDPM_CGSCC-NEXT: [[C:%.*]] = call i32 @test(i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[A]])
; OLDPM_CGSCC-NEXT: ret i32 0
;
-; NEWPM_MODULE-LABEL: define {{[^@]+}}@caller()
-; NEWPM_MODULE-NEXT: [[A:%.*]] = alloca i32
-; NEWPM_MODULE-NEXT: store i32 1, i32* [[A]], align 4
-; NEWPM_MODULE-NEXT: [[C:%.*]] = call i32 @test(i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[A]])
-; NEWPM_MODULE-NEXT: ret i32 undef
-;
-; NEWPM_CGSCC-LABEL: define {{[^@]+}}@caller()
-; NEWPM_CGSCC-NEXT: [[A:%.*]] = alloca i32
-; NEWPM_CGSCC-NEXT: store i32 1, i32* [[A]], align 4
-; NEWPM_CGSCC-NEXT: [[C:%.*]] = call i32 @test(i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[A]])
-; NEWPM_CGSCC-NEXT: ret i32 0
+; NEWPM-LABEL: define {{[^@]+}}@caller()
+; NEWPM-NEXT: [[A:%.*]] = alloca i32
+; NEWPM-NEXT: store i32 1, i32* [[A]], align 4
+; NEWPM-NEXT: [[C:%.*]] = call i32 @test(i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[A]])
+; NEWPM-NEXT: ret i32 undef
;
%A = alloca i32
store i32 1, i32* %A
--- /dev/null
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
+; RUN: opt -S -basicaa -attributor -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=4 < %s | FileCheck %s --check-prefixes=CHECK,OLDPM,OLDPM_MODULE
+; RUN: opt -S -basicaa -attributor-cgscc -attributor-disable=false < %s | FileCheck %s --check-prefixes=CHECK,OLDPM,OLDPM_CGSCC
+; RUN: opt -S -passes='attributor' -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=4 < %s | FileCheck %s --check-prefixes=CHECK,NEWPM,NEWPM_MODULE
+; RUN: opt -S -passes='attributor-cgscc' -aa-pipeline='basic-aa' -attributor-disable=false < %s | FileCheck %s --check-prefixes=CHECK,NEWPM,NEWPM_CGSCC
+
+; OLDPM_MODULE-NOT: @dead
+; NEWPM_MODULE-NOT: @dead
+; OLDPM_CGSCC-NOT: @dead
+; NEWPM_CGSCC-NOT: @dead
+
+define internal void @dead() {
+ call i32 @test(i32* null, i32* null)
+ ret void
+}
+
+define internal i32 @test(i32* %X, i32* %Y) {
+; OLDPM_MODULE-LABEL: define {{[^@]+}}@test
+; OLDPM_MODULE-SAME: (i32* noalias nocapture nofree writeonly align 4 [[X:%.*]])
+; OLDPM_MODULE-NEXT: br i1 true, label [[LIVE:%.*]], label [[DEAD:%.*]]
+; OLDPM_MODULE: live:
+; OLDPM_MODULE-NEXT: store i32 0, i32* [[X]], align 4
+; OLDPM_MODULE-NEXT: ret i32 undef
+; OLDPM_MODULE: dead:
+; OLDPM_MODULE-NEXT: unreachable
+;
+; OLDPM_CGSCC-LABEL: define {{[^@]+}}@test
+; OLDPM_CGSCC-SAME: (i32* nocapture nofree writeonly [[X:%.*]])
+; OLDPM_CGSCC-NEXT: br i1 true, label [[LIVE:%.*]], label [[DEAD:%.*]]
+; OLDPM_CGSCC: live:
+; OLDPM_CGSCC-NEXT: store i32 0, i32* [[X]]
+; OLDPM_CGSCC-NEXT: ret i32 undef
+; OLDPM_CGSCC: dead:
+; OLDPM_CGSCC-NEXT: unreachable
+;
+; NEWPM_MODULE-LABEL: define {{[^@]+}}@test
+; NEWPM_MODULE-SAME: (i32* noalias nocapture nofree writeonly align 4 [[X:%.*]])
+; NEWPM_MODULE-NEXT: br i1 true, label [[LIVE:%.*]], label [[DEAD:%.*]]
+; NEWPM_MODULE: live:
+; NEWPM_MODULE-NEXT: store i32 0, i32* [[X]], align 4
+; NEWPM_MODULE-NEXT: ret i32 undef
+; NEWPM_MODULE: dead:
+; NEWPM_MODULE-NEXT: unreachable
+;
+; NEWPM_CGSCC-LABEL: define {{[^@]+}}@test
+; NEWPM_CGSCC-SAME: (i32* nocapture nofree writeonly [[X:%.*]])
+; NEWPM_CGSCC-NEXT: br i1 true, label [[LIVE:%.*]], label [[DEAD:%.*]]
+; NEWPM_CGSCC: live:
+; NEWPM_CGSCC-NEXT: store i32 0, i32* [[X]]
+; NEWPM_CGSCC-NEXT: ret i32 undef
+; NEWPM_CGSCC: dead:
+; NEWPM_CGSCC-NEXT: unreachable
+;
+ br i1 true, label %live, label %dead
+live:
+ store i32 0, i32* %X
+ ret i32 0
+dead:
+ call i32 @caller(i32* null)
+ call void @dead()
+ ret i32 1
+}
+
+define internal i32 @caller(i32* %B) {
+; OLDPM_MODULE-LABEL: define {{[^@]+}}@caller
+; OLDPM_MODULE-SAME: (i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[B:%.*]])
+; OLDPM_MODULE-NEXT: [[A:%.*]] = alloca i32
+; OLDPM_MODULE-NEXT: store i32 1, i32* [[A]], align 4
+; OLDPM_MODULE-NEXT: [[C:%.*]] = call i32 @test(i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[B]])
+; OLDPM_MODULE-NEXT: ret i32 undef
+;
+; OLDPM_CGSCC-LABEL: define {{[^@]+}}@caller
+; OLDPM_CGSCC-SAME: (i32* nocapture nofree writeonly [[B:%.*]])
+; OLDPM_CGSCC-NEXT: [[A:%.*]] = alloca i32
+; OLDPM_CGSCC-NEXT: store i32 1, i32* [[A]], align 4
+; OLDPM_CGSCC-NEXT: [[C:%.*]] = call i32 @test(i32* nocapture nofree writeonly [[B]])
+; OLDPM_CGSCC-NEXT: ret i32 0
+;
+; NEWPM_MODULE-LABEL: define {{[^@]+}}@caller
+; NEWPM_MODULE-SAME: (i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[B:%.*]])
+; NEWPM_MODULE-NEXT: [[A:%.*]] = alloca i32
+; NEWPM_MODULE-NEXT: store i32 1, i32* [[A]], align 4
+; NEWPM_MODULE-NEXT: [[C:%.*]] = call i32 @test(i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[B]])
+; NEWPM_MODULE-NEXT: ret i32 undef
+;
+; NEWPM_CGSCC-LABEL: define {{[^@]+}}@caller
+; NEWPM_CGSCC-SAME: (i32* nocapture nofree writeonly [[B:%.*]])
+; NEWPM_CGSCC-NEXT: [[A:%.*]] = alloca i32
+; NEWPM_CGSCC-NEXT: store i32 1, i32* [[A]], align 4
+; NEWPM_CGSCC-NEXT: [[C:%.*]] = call i32 @test(i32* nocapture nofree writeonly [[B]])
+; NEWPM_CGSCC-NEXT: ret i32 undef
+;
+ %A = alloca i32
+ store i32 1, i32* %A
+ %C = call i32 @test(i32* %B, i32* %A)
+ ret i32 %C
+}
+
+define i32 @callercaller() {
+; OLDPM_MODULE-LABEL: define {{[^@]+}}@callercaller()
+; OLDPM_MODULE-NEXT: [[B:%.*]] = alloca i32
+; OLDPM_MODULE-NEXT: store i32 2, i32* [[B]], align 4
+; OLDPM_MODULE-NEXT: [[X:%.*]] = call i32 @caller(i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[B]])
+; OLDPM_MODULE-NEXT: ret i32 0
+;
+; OLDPM_CGSCC-LABEL: define {{[^@]+}}@callercaller()
+; OLDPM_CGSCC-NEXT: [[B:%.*]] = alloca i32
+; OLDPM_CGSCC-NEXT: store i32 2, i32* [[B]], align 4
+; OLDPM_CGSCC-NEXT: [[X:%.*]] = call i32 @caller(i32* noalias nofree nonnull writeonly align 4 dereferenceable(4) [[B]])
+; OLDPM_CGSCC-NEXT: ret i32 0
+;
+; NEWPM_MODULE-LABEL: define {{[^@]+}}@callercaller()
+; NEWPM_MODULE-NEXT: [[B:%.*]] = alloca i32
+; NEWPM_MODULE-NEXT: store i32 2, i32* [[B]], align 4
+; NEWPM_MODULE-NEXT: [[X:%.*]] = call i32 @caller(i32* noalias nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[B]])
+; NEWPM_MODULE-NEXT: ret i32 0
+;
+; NEWPM_CGSCC-LABEL: define {{[^@]+}}@callercaller()
+; NEWPM_CGSCC-NEXT: [[B:%.*]] = alloca i32
+; NEWPM_CGSCC-NEXT: store i32 2, i32* [[B]], align 4
+; NEWPM_CGSCC-NEXT: [[X:%.*]] = call i32 @caller(i32* noalias nofree nonnull writeonly align 4 dereferenceable(4) [[B]])
+; NEWPM_CGSCC-NEXT: ret i32 0
+;
+ %B = alloca i32
+ store i32 2, i32* %B
+ %X = call i32 @caller(i32* %B)
+ ret i32 %X
+}
+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=3 < %s | FileCheck %s
+; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s | FileCheck %s
; PR36543
; Don't promote arguments of musttail callee
define i32 @caller2(%T* %g) {
; CHECK-LABEL: define {{[^@]+}}@caller2
-; CHECK-SAME: (%T* nocapture nofree readonly [[G:%.*]])
+; CHECK-SAME: (%T* nocapture nofree readnone [[G:%.*]])
; CHECK-NEXT: ret i32 0
;
%v = call i32 @test2(%T* %g, i32 0)
; CHECK-LABEL: define {{[^@]+}}@fn
; CHECK-SAME: (i32* nocapture nofree nonnull readonly align 4 dereferenceable(4) [[P1:%.*]])
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[TMP0:%.*]] = load i64, i64* undef, align 8, !tbaa !0
-; CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP0]] to i32
-; CHECK-NEXT: [[TMP1:%.*]] = load i32, i32* @g, align 4, !tbaa !4
-; CHECK-NEXT: [[CONV1:%.*]] = trunc i32 [[TMP1]] to i8
-; CHECK-NEXT: store i8 [[CONV1]], i8* @d, align 1, !tbaa !6
+; CHECK-NEXT: [[TMP0:%.*]] = load i32, i32* @g, align 4, !tbaa !0
+; CHECK-NEXT: [[CONV1:%.*]] = trunc i32 [[TMP0]] to i8
+; CHECK-NEXT: store i8 [[CONV1]], i8* @d, align 1, !tbaa !4
; CHECK-NEXT: ret void
;
entry:
define i32 @main() {
; CHECK-LABEL: define {{[^@]+}}@main()
; CHECK-NEXT: entry:
-; CHECK-NEXT: [[TMP0:%.*]] = load i32**, i32*** @e, align 8, !tbaa !7
-; CHECK-NEXT: store i32* @g, i32** [[TMP0]], align 8, !tbaa !7
-; CHECK-NEXT: [[TMP1:%.*]] = load i32*, i32** @a, align 8, !tbaa !7
-; CHECK-NEXT: store i32 1, i32* [[TMP1]], align 4, !tbaa !4
+; CHECK-NEXT: [[TMP0:%.*]] = load i32**, i32*** @e, align 8, !tbaa !5
+; CHECK-NEXT: store i32* @g, i32** [[TMP0]], align 8, !tbaa !5
+; CHECK-NEXT: [[TMP1:%.*]] = load i32*, i32** @a, align 8, !tbaa !5
+; CHECK-NEXT: store i32 1, i32* [[TMP1]], align 4, !tbaa !0
; CHECK-NEXT: call fastcc void @fn(i32* nofree nonnull readonly align 4 dereferenceable(4) @g)
; CHECK-NEXT: ret i32 0
;
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s
+; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=5 < %s | FileCheck %s
; Don't constant-propagate byval pointers, since they are not pointers!
; PR5038
%struct.MYstr = type { i8, i32 }
; CHECK-LABEL: define {{[^@]+}}@baz
; CHECK-SAME: (<8 x i32> [[ARG:%.*]]) local_unnamed_addr
; CHECK-NEXT: bb:
-; CHECK-NEXT: [[TMP1:%.*]] = extractvalue [[STRUCT_ZOT:%.*]] undef, 0, 0
; CHECK-NEXT: ret void
;
bb:
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=2 < %s | FileCheck %s
+; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=1 < %s | FileCheck %s
; CHECK-NOT: %X
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
-; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=9 < %s | FileCheck %s
+; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-max-iterations=8 < %s | FileCheck %s
;; This function returns its second argument on all return statements
define internal i32* @incdec(i1 %C, i32* %V) {
}
define internal %0 @bar(i1 %Q) {
+; CHECK-LABEL: define {{[^@]+}}@bar
+; CHECK-SAME: (i1 [[Q:%.*]])
+; CHECK-NEXT: [[A:%.*]] = insertvalue [[TMP0:%.*]] undef, i32 21, 0
+; CHECK-NEXT: br i1 [[Q]], label [[T:%.*]], label [[F:%.*]]
+; CHECK: T:
+; CHECK-NEXT: [[B:%.*]] = insertvalue [[TMP0]] %A, i32 22, 1
+; CHECK-NEXT: ret [[TMP0]] %B
+; CHECK: F:
+; CHECK-NEXT: [[C:%.*]] = insertvalue [[TMP0]] %A, i32 23, 1
+; CHECK-NEXT: ret [[TMP0]] %C
+;
%A = insertvalue %0 undef, i32 21, 0
br i1 %Q, label %T, label %F
; CHECK-LABEL: define {{[^@]+}}@caller
; CHECK-SAME: (i1 [[Q:%.*]])
; CHECK-NEXT: [[X:%.*]] = call [[TMP0:%.*]] @foo(i1 [[Q]])
+; CHECK-NEXT: ret [[TMP0]] %X
+;
+ %X = call %0 @foo(i1 %Q)
+ %A = extractvalue %0 %X, 0
+ %B = extractvalue %0 %X, 1
+ %Y = call %0 @bar(i1 %Q)
+ %C = extractvalue %0 %Y, 0
+ %D = extractvalue %0 %Y, 1
+ %M = add i32 %A, %C
+ %N = add i32 %B, %D
+ ret %0 %X
+}
+
+; Similar to @caller but the result of both calls are actually used.
+define i32 @caller2(i1 %Q) {
+; CHECK-LABEL: define {{[^@]+}}@caller2
+; CHECK-SAME: (i1 [[Q:%.*]])
+; CHECK-NEXT: [[X:%.*]] = call [[TMP0:%.*]] @foo(i1 [[Q]])
; CHECK-NEXT: [[A:%.*]] = extractvalue [[TMP0]] %X, 0
; CHECK-NEXT: [[B:%.*]] = extractvalue [[TMP0]] %X, 1
-; CHECK-NEXT: [[C:%.*]] = extractvalue [[TMP0]] undef, 0
-; CHECK-NEXT: [[D:%.*]] = extractvalue [[TMP0]] undef, 1
+; CHECK-NEXT: [[Y:%.*]] = call [[TMP0]] @bar(i1 [[Q]])
+; CHECK-NEXT: [[C:%.*]] = extractvalue [[TMP0]] %Y, 0
+; CHECK-NEXT: [[D:%.*]] = extractvalue [[TMP0]] %Y, 1
; CHECK-NEXT: [[M:%.*]] = add i32 [[A]], [[C]]
; CHECK-NEXT: [[N:%.*]] = add i32 [[B]], [[D]]
-; CHECK-NEXT: ret [[TMP0]] %X
+; CHECK-NEXT: [[R:%.*]] = add i32 [[N]], [[M]]
+; CHECK-NEXT: ret i32 [[R]]
;
%X = call %0 @foo(i1 %Q)
%A = extractvalue %0 %X, 0
%M = add i32 %A, %C
;; Check that the second return values didn't get propagated
%N = add i32 %B, %D
- ret %0 %X
+ %R = add i32 %N, %M
+ ret i32 %R
}
; Multiple arguments may be dereferenceable.
define void @ordering(i8* %ptr1, i32* %ptr2) {
-; ATTRIBUTOR-LABEL: @ordering(i8* nocapture nofree nonnull readonly dereferenceable(3) %ptr1, i32* nocapture nofree nonnull readonly dereferenceable(8) %ptr2)
+; ATTRIBUTOR-LABEL: @ordering(i8* nocapture nofree nonnull readnone dereferenceable(3) %ptr1, i32* nocapture nofree nonnull readnone dereferenceable(8) %ptr2)
%a20 = getelementptr i32, i32* %ptr2, i64 0
%a12 = getelementptr i8, i8* %ptr1, i64 2
%t12 = load i8, i8* %a12
; Not in entry block.
define void @not_entry_but_guaranteed_to_execute(i8* %ptr) {
-; ATTRIBUTOR-LABEL: @not_entry_but_guaranteed_to_execute(i8* nocapture nofree nonnull readonly dereferenceable(3) %ptr)
+; ATTRIBUTOR-LABEL: @not_entry_but_guaranteed_to_execute(i8* nocapture nofree nonnull readnone dereferenceable(3) %ptr)
entry:
br label %exit
exit:
; Not in entry block and not guaranteed to execute.
define void @not_entry_not_guaranteed_to_execute(i8* %ptr, i1 %cond) {
-; ATTRIBUTOR-LABEL: @not_entry_not_guaranteed_to_execute(i8* nocapture nofree readonly %ptr, i1 %cond)
+; ATTRIBUTOR-LABEL: @not_entry_not_guaranteed_to_execute(i8* nocapture nofree readnone %ptr, i1 %cond)
entry:
br i1 %cond, label %loads, label %exit
loads:
; The last load may not execute, so derefenceable bytes only covers the 1st two loads.
define void @partial_in_entry(i16* %ptr, i1 %cond) {
-; ATTRIBUTOR-LABEL: @partial_in_entry(i16* nocapture nofree nonnull readonly dereferenceable(4) %ptr, i1 %cond)
+; ATTRIBUTOR-LABEL: @partial_in_entry(i16* nocapture nofree nonnull readnone dereferenceable(4) %ptr, i1 %cond)
entry:
%arrayidx0 = getelementptr i16, i16* %ptr, i64 0
%arrayidx1 = getelementptr i16, i16* %ptr, i64 1
; TODO: We should allow inference for atomic (but not volatile) ops.
define void @atomic_is_alright(i16* %ptr) {
-; ATTRIBUTOR-LABEL: @atomic_is_alright(i16* nocapture nofree nonnull readonly align 2 dereferenceable(6) %ptr)
+; ATTRIBUTOR-LABEL: @atomic_is_alright(i16* nocapture nofree nonnull readnone align 2 dereferenceable(6) %ptr)
%arrayidx0 = getelementptr i16, i16* %ptr, i64 0
%arrayidx1 = getelementptr i16, i16* %ptr, i64 1
%arrayidx2 = getelementptr i16, i16* %ptr, i64 2
declare void @may_not_return()
define void @not_guaranteed_to_transfer_execution(i16* %ptr) {
-; ATTRIBUTOR-LABEL: @not_guaranteed_to_transfer_execution(i16* nocapture nonnull readonly dereferenceable(2) %ptr)
+; ATTRIBUTOR-LABEL: @not_guaranteed_to_transfer_execution(i16* nocapture nofree nonnull readnone dereferenceable(2) %ptr)
%arrayidx0 = getelementptr i16, i16* %ptr, i64 0
%arrayidx1 = getelementptr i16, i16* %ptr, i64 1
%arrayidx2 = getelementptr i16, i16* %ptr, i64 2
; We must have consecutive accesses.
define void @variable_gep_index(i8* %unused, i8* %ptr, i64 %variable_index) {
-; ATTRIBUTOR-LABEL: @variable_gep_index(i8* nocapture nofree readnone %unused, i8* nocapture nofree nonnull readonly dereferenceable(1) %ptr, i64 %variable_index)
+; ATTRIBUTOR-LABEL: @variable_gep_index(i8* nocapture nofree readnone %unused, i8* nocapture nofree nonnull readnone dereferenceable(1) %ptr, i64 %variable_index)
%arrayidx1 = getelementptr i8, i8* %ptr, i64 %variable_index
%arrayidx2 = getelementptr i8, i8* %ptr, i64 2
%t0 = load i8, i8* %ptr
define void @multi_index_gep(<4 x i8>* %ptr) {
; FIXME: %ptr should be dereferenceable(4)
-; ATTRIBUTOR-LABEL: @multi_index_gep(<4 x i8>* nocapture nofree nonnull readonly dereferenceable(1) %ptr)
+; ATTRIBUTOR-LABEL: @multi_index_gep(<4 x i8>* nocapture nofree nonnull readnone dereferenceable(1) %ptr)
%arrayidx00 = getelementptr <4 x i8>, <4 x i8>* %ptr, i64 0, i64 0
%t0 = load i8, i8* %arrayidx00
ret void
; Could round weird bitwidths down?
define void @not_byte_multiple(i9* %ptr) {
-; ATTRIBUTOR-LABEL: @not_byte_multiple(i9* nocapture nofree nonnull readonly dereferenceable(2) %ptr)
+; ATTRIBUTOR-LABEL: @not_byte_multiple(i9* nocapture nofree nonnull readnone dereferenceable(2) %ptr)
%arrayidx0 = getelementptr i9, i9* %ptr, i64 0
%t0 = load i9, i9* %arrayidx0
ret void
; Missing direct access from the pointer.
define void @no_pointer_deref(i16* %ptr) {
-; ATTRIBUTOR-LABEL: @no_pointer_deref(i16* nocapture nofree readonly %ptr)
+; ATTRIBUTOR-LABEL: @no_pointer_deref(i16* nocapture nofree readnone %ptr)
%arrayidx1 = getelementptr i16, i16* %ptr, i64 1
%arrayidx2 = getelementptr i16, i16* %ptr, i64 2
%t1 = load i16, i16* %arrayidx1
; Out-of-order is ok, but missing access concludes dereferenceable range.
define void @non_consecutive(i32* %ptr) {
-; ATTRIBUTOR-LABEL: @non_consecutive(i32* nocapture nofree nonnull readonly dereferenceable(8) %ptr)
+; ATTRIBUTOR-LABEL: @non_consecutive(i32* nocapture nofree nonnull readnone dereferenceable(8) %ptr)
%arrayidx1 = getelementptr i32, i32* %ptr, i64 1
%arrayidx0 = getelementptr i32, i32* %ptr, i64 0
%arrayidx3 = getelementptr i32, i32* %ptr, i64 3
; Improve on existing dereferenceable attribute.
define void @more_bytes(i32* dereferenceable(8) %ptr) {
-; ATTRIBUTOR-LABEL: @more_bytes(i32* nocapture nofree nonnull readonly dereferenceable(16) %ptr)
+; ATTRIBUTOR-LABEL: @more_bytes(i32* nocapture nofree nonnull readnone dereferenceable(16) %ptr)
%arrayidx3 = getelementptr i32, i32* %ptr, i64 3
%arrayidx1 = getelementptr i32, i32* %ptr, i64 1
%arrayidx0 = getelementptr i32, i32* %ptr, i64 0
; Improve on existing dereferenceable_or_null attribute.
define void @more_bytes_and_not_null(i32* dereferenceable_or_null(8) %ptr) {
-; ATTRIBUTOR-LABEL: @more_bytes_and_not_null(i32* nocapture nofree nonnull readonly dereferenceable(16) %ptr)
+; ATTRIBUTOR-LABEL: @more_bytes_and_not_null(i32* nocapture nofree nonnull readnone dereferenceable(16) %ptr)
%arrayidx3 = getelementptr i32, i32* %ptr, i64 3
%arrayidx1 = getelementptr i32, i32* %ptr, i64 1
%arrayidx0 = getelementptr i32, i32* %ptr, i64 0
; But don't pessimize existing dereferenceable attribute.
define void @better_bytes(i32* dereferenceable(100) %ptr) {
-; ATTRIBUTOR-LABEL: @better_bytes(i32* nocapture nofree nonnull readonly dereferenceable(100) %ptr)
+; ATTRIBUTOR-LABEL: @better_bytes(i32* nocapture nofree nonnull readnone dereferenceable(100) %ptr)
%arrayidx3 = getelementptr i32, i32* %ptr, i64 3
%arrayidx1 = getelementptr i32, i32* %ptr, i64 1
%arrayidx0 = getelementptr i32, i32* %ptr, i64 0
}
define void @bitcast(i32* %arg) {
-; ATTRIBUTOR-LABEL: @bitcast(i32* nocapture nofree nonnull readonly dereferenceable(8) %arg)
+; ATTRIBUTOR-LABEL: @bitcast(i32* nocapture nofree nonnull readnone dereferenceable(8) %arg)
%ptr = bitcast i32* %arg to float*
%arrayidx0 = getelementptr float, float* %ptr, i64 0
%arrayidx1 = getelementptr float, float* %ptr, i64 1
}
define void @bitcast_different_sizes(double* %arg1, i8* %arg2) {
-; ATTRIBUTOR-LABEL: @bitcast_different_sizes(double* nocapture nofree nonnull readonly dereferenceable(12) %arg1, i8* nocapture nofree nonnull readonly dereferenceable(16) %arg2)
+; ATTRIBUTOR-LABEL: @bitcast_different_sizes(double* nocapture nofree nonnull readnone dereferenceable(12) %arg1, i8* nocapture nofree nonnull readnone dereferenceable(16) %arg2)
%ptr1 = bitcast double* %arg1 to float*
%a10 = getelementptr float, float* %ptr1, i64 0
%a11 = getelementptr float, float* %ptr1, i64 1
}
define void @negative_offset(i32* %arg) {
-; ATTRIBUTOR-LABEL: @negative_offset(i32* nocapture nofree nonnull readonly dereferenceable(4) %arg)
+; ATTRIBUTOR-LABEL: @negative_offset(i32* nocapture nofree nonnull readnone dereferenceable(4) %arg)
%ptr = bitcast i32* %arg to float*
%arrayidx0 = getelementptr float, float* %ptr, i64 0
%arrayidx1 = getelementptr float, float* %ptr, i64 -1
}
define void @load_store(i32* %arg) {
-; ATTRIBUTOR-LABEL: @load_store(i32* nocapture nofree nonnull dereferenceable(8) %arg)
+; ATTRIBUTOR-LABEL: @load_store(i32* nocapture nofree nonnull writeonly dereferenceable(8) %arg)
%ptr = bitcast i32* %arg to float*
%arrayidx0 = getelementptr float, float* %ptr, i64 0
%arrayidx1 = getelementptr float, float* %ptr, i64 1
-; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=5 < %s | FileCheck %s
+; RUN: opt -S -passes=attributor -aa-pipeline='basic-aa' -attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=6 < %s | FileCheck %s
define dso_local i32 @visible(i32* noalias %A, i32* noalias %B) #0 {
entry:
; TEST 1: Only first block is live.
; CHECK: Function Attrs: nofree noreturn nosync nounwind
-; MODULE-NEXT: define i32 @first_block_no_return(i32 %a, i32* nocapture nofree nonnull readonly %ptr1, i32* nocapture nofree readnone %ptr2)
-; CGSCC-NEXT: define i32 @first_block_no_return(i32 %a, i32* nocapture nofree nonnull readonly align 4 dereferenceable(4) %ptr1, i32* nocapture nofree readnone %ptr2)
+; MODULE-NEXT: define i32 @first_block_no_return(i32 %a, i32* nocapture nofree nonnull readnone %ptr1, i32* nocapture nofree readnone %ptr2)
+; CGSCC-NEXT: define i32 @first_block_no_return(i32 %a, i32* nocapture nofree nonnull readnone align 4 dereferenceable(4) %ptr1, i32* nocapture nofree readnone %ptr2)
define i32 @first_block_no_return(i32 %a, i32* nonnull %ptr1, i32* %ptr2) #0 {
entry:
call i32 @internal_load(i32* %ptr1)
--- /dev/null
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
+; RUN: opt -attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s
+; RUN: opt -attributor-cgscc --attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s
+; RUN: opt -passes=attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s
+; RUN: opt -passes='attributor-cgscc' --attributor-disable=false -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s
+
+; Make sure we need a single iteration to determine the chains are dead/alive.
+
+declare i32 @source() nounwind readonly
+
+define i32 @chain_dead(i32 %arg) {
+; CHECK-LABEL: define {{[^@]+}}@chain_dead
+; CHECK-SAME: (i32 [[ARG:%.*]])
+; CHECK-NEXT: ret i32 0
+;
+ %init = call i32 @source()
+ %v0 = add i32 %arg, %init
+ %v1 = add i32 %init, %v0
+ %v2 = add i32 %v0, %v1
+ %v3 = add i32 %v1, %v2
+ %v4 = add i32 %v2, %v3
+ %v5 = add i32 %v3, %v4
+ %v6 = add i32 %v4, %v5
+ %v7 = add i32 %v5, %v6
+ %v8 = add i32 %v6, %v7
+ %v9 = add i32 %v7, %v8
+ ret i32 0
+}
+
+define i32 @chain_alive(i32 %arg) {
+; CHECK-LABEL: define {{[^@]+}}@chain_alive
+; CHECK-SAME: (i32 [[ARG:%.*]])
+; CHECK-NEXT: [[INIT:%.*]] = call i32 @source()
+; CHECK-NEXT: [[V0:%.*]] = add i32 [[ARG]], [[INIT]]
+; CHECK-NEXT: [[V1:%.*]] = add i32 [[INIT]], [[V0]]
+; CHECK-NEXT: [[V2:%.*]] = add i32 [[V0]], [[V1]]
+; CHECK-NEXT: [[V3:%.*]] = add i32 [[V1]], [[V2]]
+; CHECK-NEXT: [[V4:%.*]] = add i32 [[V2]], [[V3]]
+; CHECK-NEXT: [[V5:%.*]] = add i32 [[V3]], [[V4]]
+; CHECK-NEXT: [[V6:%.*]] = add i32 [[V4]], [[V5]]
+; CHECK-NEXT: [[V7:%.*]] = add i32 [[V5]], [[V6]]
+; CHECK-NEXT: [[V8:%.*]] = add i32 [[V6]], [[V7]]
+; CHECK-NEXT: [[V9:%.*]] = add i32 [[V7]], [[V8]]
+; CHECK-NEXT: ret i32 [[V9]]
+;
+ %init = call i32 @source()
+ %v0 = add i32 %arg, %init
+ %v1 = add i32 %init, %v0
+ %v2 = add i32 %v0, %v1
+ %v3 = add i32 %v1, %v2
+ %v4 = add i32 %v2, %v3
+ %v5 = add i32 %v3, %v4
+ %v6 = add i32 %v4, %v5
+ %v7 = add i32 %v5, %v6
+ %v8 = add i32 %v6, %v7
+ %v9 = add i32 %v7, %v8
+ ret i32 %v9
+}
; CHECK-SAME: (void (i8*)* [[FP:%.*]])
; CHECK-NEXT: entry:
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
-; CHECK-NEXT: [[TMP:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @foo(i32* nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[A]])
; CHECK-NEXT: call void [[FP]](i8* bitcast (void (i32*)* @foo to i8*))
; CHECK-NEXT: call void @callback1(void (i32*)* nonnull @foo)
; DECL_CS-SAME: (void (i8*)* [[FP:%.*]])
; DECL_CS-NEXT: entry:
; DECL_CS-NEXT: [[A:%.*]] = alloca i32, align 4
-; DECL_CS-NEXT: [[TMP:%.*]] = bitcast i32* [[A]] to i8*
; DECL_CS-NEXT: call void @foo(i32* nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[A]])
; DECL_CS-NEXT: call void [[FP]](i8* bitcast (void (i32*)* @foo to i8*))
; DECL_CS-NEXT: call void @callback1(void (i32*)* nonnull @foo)
; CHECK-SAME: (void (i8*)* [[FP:%.*]])
; CHECK-NEXT: entry:
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
-; CHECK-NEXT: [[TMP:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @foo(i32* nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[A]])
; CHECK-NEXT: call void @callback1(void (i32*)* nonnull @foo)
; CHECK-NEXT: call void @callback2(void (i8*)* bitcast (void (i32*)* @foo to void (i8*)*))
; DECL_CS-SAME: (void (i8*)* [[FP:%.*]])
; DECL_CS-NEXT: entry:
; DECL_CS-NEXT: [[A:%.*]] = alloca i32, align 4
-; DECL_CS-NEXT: [[TMP:%.*]] = bitcast i32* [[A]] to i8*
; DECL_CS-NEXT: call void @foo(i32* nocapture nofree nonnull writeonly align 4 dereferenceable(4) [[A]])
; DECL_CS-NEXT: call void @callback1(void (i32*)* nonnull @foo)
; DECL_CS-NEXT: call void @callback2(void (i8*)* nonnull bitcast (void (i32*)* @foo to void (i8*)*))
; ALL-LABEL: define {{[^@]+}}@foo
; ALL-SAME: (i32* nocapture nofree nonnull writeonly dereferenceable(4) [[A:%.*]])
; ALL-NEXT: entry:
-; ALL-NEXT: store i32 0, i32* %a
+; ALL-NEXT: store i32 0, i32* [[A]]
; ALL-NEXT: ret void
;
entry:
ret void
}
-declare void @external(i8*) readonly nounwind
-; ATTRIBUTOR: define void @nc4(i8* nocapture readonly %p)
+; The following test is tricky because improvements to AAIsDead can cause the call to be removed.
+; FIXME: readonly and nocapture missing on the pointer.
+declare void @external(i8* readonly) nounwind argmemonly
+; ATTRIBUTOR: define void @nc4(i8* %p)
define void @nc4(i8* %p) {
call void @external(i8* %p)
ret void
declare float @llvm.floor.f32(float)
; FIXME: missing nofree
-; ATTRIBUTOR: Function Attrs: noinline nosync nounwind readnone uwtable
+; ATTRIBUTOR: Function Attrs: nofree noinline nosync nounwind readnone uwtable willreturn
; ATTRIBUTOR-NEXT: define void @call_floor(float %a)
define void @call_floor(float %a) #0 {
ret void
}
+; FIXME: missing nofree
+; ATTRIBUTOR: Function Attrs: noinline nosync nounwind readnone uwtable
+; ATTRIBUTOR-NEXT: define float @call_floor2(float %a)
+define float @call_floor2(float %a) #0 {
+ %c = tail call float @llvm.floor.f32(float %a)
+ ret float %c
+}
+
; TEST 11 (positive case)
; Check propagation.
-; RUN: opt -functionattrs -attributor -attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=2 -S < %s | FileCheck %s
+; RUN: opt -functionattrs -attributor -attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=1 -S < %s | FileCheck %s
;
; Test cases specifically designed for the "no-return" function attribute.
; We use FIXME's to indicate problems and missing attributes.
; TEST 19 - positive, readnone & non-convergent intrinsic.
-; ATTRIBUTOR: Function Attrs: nosync nounwind
+; ATTRIBUTOR: Function Attrs: nofree nosync nounwind readnone willreturn
; ATTRIBUTOR-NEXT: define i32 @cos_test(float %x)
define i32 @cos_test(float %x) {
call float @llvm.cos(float %x)
ret i32 4
}
+
+; ATTRIBUTOR: Function Attrs: nosync nounwind
+; ATTRIBUTOR-NEXT: define float @cos_test2(float %x)
+define float @cos_test2(float %x) {
+ %c = call float @llvm.cos(float %x)
+ ret float %c
+}
-; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --scrub-attributes
; RUN: opt -attributor -attributor-disable=false -S < %s | FileCheck %s --check-prefixes=CHECK,OLD_PM,MODULE_OLD_PM
; RUN: opt -passes=attributor -attributor-disable=false -S < %s | FileCheck %s --check-prefixes=CHECK,NEW_PM,MODULE_NEW_PM
; RUN: opt -attributor-cgscc -attributor-disable=false -S < %s | FileCheck %s --check-prefixes=CHECK,CGSCC_OLD_PM
define i32 @test2_check(i32* %p) {
; OLD_PM-LABEL: define {{[^@]+}}@test2_check
-; OLD_PM-SAME: (i32* nocapture nofree readonly align 4 [[P:%.*]])
+; OLD_PM-SAME: (i32* nocapture nofree readnone align 4 [[P:%.*]])
; OLD_PM-NEXT: entry:
; OLD_PM-NEXT: br label [[IF_THEN:%.*]]
; OLD_PM: if.then:
; OLD_PM-NEXT: ret i32 2
;
; NEW_PM-LABEL: define {{[^@]+}}@test2_check
-; NEW_PM-SAME: (i32* nocapture nofree readonly align 4 [[P:%.*]])
+; NEW_PM-SAME: (i32* nocapture nofree readnone align 4 [[P:%.*]])
; NEW_PM-NEXT: entry:
; NEW_PM-NEXT: br label [[IF_THEN:%.*]]
; NEW_PM: if.then:
; return *a ? a : rt0(a);
; }
;
-; BOTH: Function Attrs: nofree noinline norecurse noreturn nosync nounwind readonly uwtable
-; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt0(i32* nocapture nofree nonnull readonly align 4 dereferenceable(4) %a)
+; BOTH: Function Attrs: nofree noinline norecurse noreturn nosync nounwind readnone uwtable willreturn
+; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt0(i32* nocapture nofree nonnull readnone align 4 dereferenceable(4) %a)
define i32* @rt0(i32* %a) #0 {
entry:
%v = load i32, i32* %a, align 4
; return *a ? undef : rt1(a);
; }
;
-; BOTH: Function Attrs: nofree noinline norecurse noreturn nosync nounwind readonly uwtable
-; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt1(i32* nocapture nofree nonnull readonly align 4 dereferenceable(4) %a)
+; BOTH: Function Attrs: nofree noinline norecurse noreturn nosync nounwind readnone uwtable willreturn
+; BOTH-NEXT: define noalias nonnull align 536870912 dereferenceable(4294967295) i32* @rt1(i32* nocapture nofree nonnull readnone align 4 dereferenceable(4) %a)
define i32* @rt1(i32* %a) #0 {
entry:
%v = load i32, i32* %a, align 4
define void @load_null_pointer_is_defined() "null-pointer-is-valid"="true" {
; ATTRIBUTOR-LABEL: @load_null_pointer_is_defined(
-; ATTRIBUTOR-NEXT: [[A:%.*]] = load i32, i32* null
; ATTRIBUTOR-NEXT: ret void
;
%a = load i32, i32* null
; FIXME: null is propagated but the instruction
; is not changed to unreachable.
-define void @load_null_propagated() {
+define i32 @load_null_propagated() {
; ATTRIBUTOR-LABEL: @load_null_propagated(
; ATTRIBUTOR-NEXT: [[A:%.*]] = load i32, i32* null
-; ATTRIBUTOR-NEXT: ret void
+; ATTRIBUTOR-NEXT: ret i32 [[A]]
;
%ptr = call i32* @ret_null()
%a = load i32, i32* %ptr
- ret void
+ ret i32 %a
}
; -- Store tests --
; TODO: Add max-iteration check
; Disable update test checks and enable it where required.
-; UTC_ARGS: --turn off
+; UTC_ARGS: --disable
; ModuleID = 'value-simplify.ll'
source_filename = "value-simplify.ll"
}
define i1 @ipccp2() {
-; CHECK-LABEL: define {{[^@]+}}@ipccp2() #1
+; CHECK-LABEL: define {{[^@]+}}@ipccp2()
; CHECK-NEXT: ret i1 true
;
%r = call i1 @ipccp2i(i1 true)
}
define i1 @ipccp2b() {
-; CHECK-LABEL: define {{[^@]+}}@ipccp2b() #1
+; CHECK-LABEL: define {{[^@]+}}@ipccp2b()
; CHECK-NEXT: ret i1 true
;
%r = call i1 @ipccp2ib(i1 true)
ret i32 %r
}
-; UTC_ARGS: --turn on
+; UTC_ARGS: --enable
; Do not touch complicated arguments (for now)
%struct.X = type { i8* }
ret void
}
-; UTC_ARGS: --turn off
+; UTC_ARGS: --disable
-; RUN: opt -passes=attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=7 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_MODULE
+; RUN: opt -passes=attributor --attributor-disable=false -attributor-max-iterations-verify -attributor-annotate-decl-cs -attributor-max-iterations=6 -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_MODULE
; RUN: opt -passes=attributor-cgscc --attributor-disable=false -attributor-annotate-decl-cs -S < %s | FileCheck %s --check-prefixes=ATTRIBUTOR,ATTRIBUTOR_CGSCC
; TEST 6 (positive case)
; Call intrinsic function
-; FIXME: missing willreturn
-; ATTRIBUTOR: Function Attrs: nounwind readnone speculatable
+; ATTRIBUTOR: Function Attrs: nounwind readnone speculatable willreturn
; ATTRIBUTOR-NEXT: declare float @llvm.floor.f32(float)
declare float @llvm.floor.f32(float)
-; FIXME: missing willreturn
-; ATTRIBUTOR: Function Attrs: noinline nosync nounwind readnone uwtable
-; ATTRIBUTOR-NEXT: define void @call_floor(float %a)
+; ATTRIBUTOR_MODULE: Function Attrs: nofree noinline nosync nounwind readnone uwtable willreturn
+; ATTRIBUTOR_CGSCC: Function Attrs: nofree noinline norecurse nosync nounwind readnone uwtable willreturn
+; ATTRIBUTOR-NEXT: define void @call_floor(float %a)
define void @call_floor(float %a) #0 {
tail call float @llvm.floor.f32(float %a)
ret void
}
+; ATTRIBUTOR: Function Attrs: noinline nosync nounwind readnone uwtable willreturn
+; ATTRIBUTOR-NEXT: define float @call_floor2(float %a)
+define float @call_floor2(float %a) #0 {
+ %c = tail call float @llvm.floor.f32(float %a)
+ ret float %c
+}
+
; TEST 7 (negative case)
; Call function declaration without willreturn