diff --git a/llvm/include/llvm/InitializePasses.h b/llvm/include/llvm/InitializePasses.h --- a/llvm/include/llvm/InitializePasses.h +++ b/llvm/include/llvm/InitializePasses.h @@ -90,6 +90,7 @@ void initializeBreakCriticalEdgesPass(PassRegistry&); void initializeBreakFalseDepsPass(PassRegistry&); void initializeCanonicalizeAliasesLegacyPassPass(PassRegistry &); +void initializeCanonicalizeFreezeInLoopsPass(PassRegistry&); void initializeCFGOnlyPrinterLegacyPassPass(PassRegistry&); void initializeCFGOnlyViewerLegacyPassPass(PassRegistry&); void initializeCFGPrinterLegacyPassPass(PassRegistry&); diff --git a/llvm/include/llvm/Transforms/Utils.h b/llvm/include/llvm/Transforms/Utils.h --- a/llvm/include/llvm/Transforms/Utils.h +++ b/llvm/include/llvm/Transforms/Utils.h @@ -134,6 +134,14 @@ // exit blocks. // FunctionPass *createUnifyLoopExitsPass(); + +//===----------------------------------------------------------------------===// +// +// CanonicalizeFreezeInLoops - Canonicalize freeze instructions in loops so they +// don't block SCEV. +// +Pass *createCanonicalizeFreezeInLoopsPass(); + } #endif diff --git a/llvm/include/llvm/Transforms/Utils/CanonicalizeFreezeInLoops.h b/llvm/include/llvm/Transforms/Utils/CanonicalizeFreezeInLoops.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Transforms/Utils/CanonicalizeFreezeInLoops.h @@ -0,0 +1,32 @@ +//==- CanonicalizeFreezeInLoop.h - Canonicalize freezes in a loop-*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file canonicalizes freeze instructions in a loop. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_UTILS_CANONICALIZE_FREEZES_IN_LOOPS_H +#define LLVM_TRANSFORMS_UTILS_CANONICALIZE_FREEZES_IN_LOOPS_H + +#include "llvm/Analysis/LoopInfo.h" +#include "llvm/IR/PassManager.h" +#include "llvm/Transforms/Scalar/LoopPassManager.h" + +namespace llvm { + +/// A pass that canonicalizes freeze instructions in a loop. +class CanonicalizeFreezeInLoopsPass + : public PassInfoMixin { +public: + PreservedAnalyses run(Loop &L, LoopAnalysisManager &AM, + LoopStandardAnalysisResults &AR, LPMUpdater &U); +}; + +} // end namespace llvm + +#endif // LLVM_TRANSFORMS_UTILS_CANONICALIZE_FREEZES_IN_LOOPS_H diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp --- a/llvm/lib/Passes/PassBuilder.cpp +++ b/llvm/lib/Passes/PassBuilder.cpp @@ -178,6 +178,7 @@ #include "llvm/Transforms/Utils/AssumeBundleBuilder.h" #include "llvm/Transforms/Utils/BreakCriticalEdges.h" #include "llvm/Transforms/Utils/CanonicalizeAliases.h" +#include "llvm/Transforms/Utils/CanonicalizeFreezeInLoops.h" #include "llvm/Transforms/Utils/EntryExitInstrumenter.h" #include "llvm/Transforms/Utils/InjectTLIMappings.h" #include "llvm/Transforms/Utils/LCSSA.h" diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -311,6 +311,7 @@ #ifndef LOOP_PASS #define LOOP_PASS(NAME, CREATE_PASS) #endif +LOOP_PASS("canon-freeze", CanonicalizeFreezeInLoopsPass()) LOOP_PASS("invalidate", InvalidateAllAnalysesPass()) LOOP_PASS("licm", LICMPass()) LOOP_PASS("loop-idiom", LoopIdiomRecognizePass()) diff --git a/llvm/lib/Transforms/Utils/CMakeLists.txt b/llvm/lib/Transforms/Utils/CMakeLists.txt --- a/llvm/lib/Transforms/Utils/CMakeLists.txt +++ b/llvm/lib/Transforms/Utils/CMakeLists.txt @@ -10,6 +10,7 @@ CallPromotionUtils.cpp CallGraphUpdater.cpp CanonicalizeAliases.cpp + CanonicalizeFreezeInLoops.cpp CloneFunction.cpp CloneModule.cpp CodeExtractor.cpp diff --git a/llvm/lib/Transforms/Utils/CanonicalizeFreezeInLoops.cpp b/llvm/lib/Transforms/Utils/CanonicalizeFreezeInLoops.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Transforms/Utils/CanonicalizeFreezeInLoops.cpp @@ -0,0 +1,419 @@ +//==- CanonicalizeFreezeInLoops - Canonicalize freezes in a loop-*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This pass canonicalizes freeze instructions in a loop by pushing them out to +// the preheader. +// +// loop: +// i = phi init, i.next +// i.next = add nsw i, 1 +// i.add2 = add nsw i, 2 +// i.add2.fr = freeze i.add2 // push this out of this loop +// br i1 (i.add2.fr <= N), loop, exit +// => +// init.fr = freeze init +// loop: +// i = phi init.fr, i.next +// i.next = add i, 1 // nsw is dropped here +// i.add2 = add i, 2 // nsw is dropped here, too +// br i1 (i.add2 <= N), loop, exit +// +// Removing freezes from these chains help scalar evolution successfully analyze +// expressions. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Utils/CanonicalizeFreezeInLoops.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/Analysis/IVUsers.h" +#include "llvm/Analysis/LoopAnalysisManager.h" +#include "llvm/Analysis/LoopInfo.h" +#include "llvm/Analysis/LoopPass.h" +#include "llvm/Analysis/ValueTracking.h" +#include "llvm/IR/PatternMatch.h" +#include "llvm/InitializePasses.h" +#include "llvm/Pass.h" +#include "llvm/Support/Debug.h" +#include "llvm/Transforms/Utils.h" +#include +#include + +using namespace llvm; +using namespace PatternMatch; + +#define DEBUG_TYPE "canon-freeze" + +namespace { + +class CanonicalizeFreezeInLoops : public LoopPass { +public: + static char ID; + + CanonicalizeFreezeInLoops(); + +private: + bool runOnLoop(Loop *L, LPPassManager &LPM) override; + void getAnalysisUsage(AnalysisUsage &AU) const override; +}; + +class CanonicalizeFreezeInLoopsImpl { +private: + Loop *L; + ScalarEvolution &SE; + DominatorTree &DT; + const unsigned MaxDepth = 6; + + bool canHandleInst(const Instruction *I) { + auto Opc = I->getOpcode(); + // If add/sub/mul, drop nsw/nuw flags. + // TODO: support more instructions + return Opc == Instruction::Add || Opc == Instruction::Sub || + Opc == Instruction::Mul; + } + + // Returns + std::pair getStepInstFrom(PHINode *PHI) { + auto *StepInst = dyn_cast( + PHI->getIncomingValueForBlock(L->getLoopLatch())); + if (!StepInst || + !(StepInst->getOperand(0) == PHI || StepInst->getOperand(1) == PHI) || + !canHandleInst(StepInst)) { + // Don't know how to push freeze into this step instruction. + return {nullptr, 0}; + } + return {StepInst, StepInst->getOperand(0) == PHI}; + } + + void getInstsReachableFromPHI( + PHINode &PHI, DenseMap> &FIs); + + bool CollectUsesToFreezeAndInstsToDropFlags( + const DenseMap> &InstsFromPHIs, + const SmallSet &PHIs, FreezeInst *FI, + SmallVector &UsesToInsertFreeze, + SmallPtrSet &InstsToDropFlags); + + void InsertFreezeAndForgetFromSCEV(Use &U); + +public: + CanonicalizeFreezeInLoopsImpl(Loop *L, ScalarEvolution &SE, DominatorTree &DT) + : L(L), SE(SE), DT(DT) {} + bool run(); +}; + +} // anonymous namespace + +void CanonicalizeFreezeInLoopsImpl::getInstsReachableFromPHI( + PHINode &PHI, + DenseMap> &Insts) { + assert(Insts.count(&PHI) == 0); + Insts[&PHI] = {}; + // The collected reachable instructions should drop their flags like nsw/nuw, + // so only visit instructions that are known how to be dealt with + + // list of (Instructions, depth) + SmallVector, 8> Worklist; + Worklist.emplace_back(&PHI, 0); + + while (!Worklist.empty()) { + auto P = Worklist.pop_back_val(); + Instruction *I = P.first; + unsigned CurDepth = P.second; + + for (auto *U : I->users()) { + Instruction *I2 = dyn_cast(U); + // Ignore I2 if it is not in the loop or it is not known how to be dealt + // with + if (!I2 || !L->contains(I2->getParent())) + continue; + else if (!canHandleInst(I2) && !isa(I2)) + continue; + + SmallPtrSet S = {I}; + auto Result = Insts.insert(std::make_pair(I2, std::move(S))); + if (!Result.second) { + // I2 is reachable from I. + Result.first->second.insert(I); + continue; + } + + if (CurDepth < MaxDepth) + Worklist.emplace_back(I2, CurDepth + 1); + } + } +} + +// Visit all instructions that are on possible paths from PHI to FI, and +// check whether it is possible to push FI out of this loop. +bool CanonicalizeFreezeInLoopsImpl::CollectUsesToFreezeAndInstsToDropFlags( + const DenseMap> &InstsFromPHIs, + const SmallSet &IndPHIs, FreezeInst *FI, + SmallVector &UsesToInsertFreeze, + SmallPtrSet &InstsToDropFlags) { + + auto *PH = L->getLoopPreheader(); + + // Start from FI, traverse in a backward direction + SmallPtrSet Visited; + SmallVector Queue; + Queue.push_back(FI); + bool Stop = false; + + while (!Queue.empty()) { + auto *I = Queue.pop_back_val(); + + auto *PHI = dyn_cast(I); + if (PHI && IndPHIs.count(PHI)) { + // The initial value should be frozen. + bool InitValueInserted = false; + for (unsigned i = 0, e = PHI->getNumIncomingValues(); i != e; ++i) { + if (PHI->getIncomingBlock(i) != PH) + continue; + + auto &InitUse = + PHI->getOperandUse(PHINode::getOperandNumForIncomingValue(i)); + UsesToInsertFreeze.push_back(&InitUse); + InitValueInserted = true; + break; + } + assert(InitValueInserted); + + // Flags of a step instruction should be dropped + auto StepPair = getStepInstFrom(PHI); + BinaryOperator *StepInst = StepPair.first; + InstsToDropFlags.insert(StepInst); + + // Step value should be frozen + UsesToInsertFreeze.push_back(&StepInst->getOperandUse(StepPair.second)); + + continue; + } + + if (I != FI) { + assert(canHandleInst(I)); + InstsToDropFlags.insert(I); + } + + // Try to push freeze into I's operands. + for (auto &U : I->operands()) { + auto *UI = dyn_cast(U.get()); + if (!UI) { + // It should be a constant. Unconditionally put it into + // UsesToInsertFreeze. + UsesToInsertFreeze.push_back(&U); + continue; + } + + bool OnThePathsFromPHIs = InstsFromPHIs.count(UI); + if (L->contains(UI->getParent()) && !OnThePathsFromPHIs) { + // The operand is not on any path from PHI to FI, but still in the + // loop. This causes insertion of new freeze in L. + LLVM_DEBUG(dbgs() << "canonfr: inst " << *I << "\n" + << "\t" << *UI << " is in the loop!"); + Stop = true; + break; + } + + if (OnThePathsFromPHIs) { + // Push further + if (!Visited.count(UI)) { + Visited.insert(UI); + Queue.push_back(UI); + } + } else { + assert(!L->contains(UI->getParent())); + UsesToInsertFreeze.push_back(&U); + } + } + if (Stop) + break; + } + + return !Stop; +} + +// Given U = (value, user), replace value with freeze(value), and let +// SCEV forget user. The inserted freeze is placed at the preheader. +void CanonicalizeFreezeInLoopsImpl::InsertFreezeAndForgetFromSCEV(Use &U) { + auto *PH = L->getLoopPreheader(); + + auto *UserI = dyn_cast(U.getUser()); + auto *ValueToFr = U.get(); + assert(UserI && L->contains(UserI->getParent())); + if (isGuaranteedNotToBeUndefOrPoison(ValueToFr, UserI, &DT)) + return; + + LLVM_DEBUG(dbgs() << "canonfr: inserting freeze:\n"); + LLVM_DEBUG(dbgs() << "\tUser: " << *U.getUser() << "\n"); + LLVM_DEBUG(dbgs() << "\tOperand: " << *U.get() << "\n"); + + U.set(new FreezeInst(ValueToFr, ValueToFr->getName() + ".frozen", + PH->getTerminator())); + + SE.forgetValue(UserI); +} + +bool CanonicalizeFreezeInLoopsImpl::run() { + // The loop should be in LoopSimplify form. + if (!L->isLoopSimplifyForm()) + return false; + + // InstsFromPHIs' keys are instructions that are reachable from (auxiliary) + // induction PHIs. + // InstsFromPHIs[I] maintains a set of instructions that are I's operands and + // they're on paths from any of (auxiliary) induction PHIs to I. + DenseMap> InstsFromPHIs; + + // To push freezes out of L, find + // (1) instructions to drop flags like nsw/nuw + // (2) the old freeze instructions to remove + // (3) uses that will be replaced with frozen values + SmallSet InstsToDropFlags; + SmallVector FIsToRemove; + SmallVector UsesToInsertFr; + + SmallSet AuxIndPHIs; + for (auto &PHI : L->getHeader()->phis()) { + if (!L->isAuxiliaryInductionVariable(PHI, SE)) + continue; + + auto StepPair = getStepInstFrom(&PHI); + BinaryOperator *StepInst = StepPair.first; + if (!StepInst) { + // The stepping instruction has unknown form. + // Ignore this PHI. + continue; + } + + Use &Step = StepInst->getOperandUse(StepPair.second); + if (auto *StepI = dyn_cast(Step.get())) { + if (L->contains(StepI->getParent())) { + // The step value is inside the loop. Freezing step value will introduce + // another freeze into the loop, so skip this PHI. + continue; + } + } + + LLVM_DEBUG(dbgs() << "canonfr: AuxIndPHI found: " << PHI << "\n"); + AuxIndPHIs.insert(&PHI); + + getInstsReachableFromPHI(PHI, InstsFromPHIs); + } + + // For each freeze inst that is reached from PHI, check whether it can be + // pushed out of the loop. + for (const auto &Pair : InstsFromPHIs) { + auto *FI = dyn_cast(Pair.first); + if (!FI) + continue; + + LLVM_DEBUG(dbgs() << "canonfr: Freeze found: " << *FI << "\n"); + + // Traverse from FI to phis, and collect values to freeze & instructions to + // drop flags like nsw. + SmallVector ToInsertFr; + SmallSet DropFlags; + if (!CollectUsesToFreezeAndInstsToDropFlags(InstsFromPHIs, AuxIndPHIs, FI, + ToInsertFr, DropFlags)) { + // FI cannot be hoisted. + LLVM_DEBUG(dbgs() << "canonfr: cannot be hoisted: " << *FI << "\n"); + continue; + } + + FIsToRemove.push_back(FI); + for (auto *U : ToInsertFr) + UsesToInsertFr.push_back(U); + for (auto *I : DropFlags) + InstsToDropFlags.insert(I); + } + + if (FIsToRemove.empty()) { + // Nothing to do. + assert(!AuxIndPHIs.empty() || + (UsesToInsertFr.empty() && InstsToDropFlags.empty())); + return false; + } + + assert(!AuxIndPHIs.empty() && !UsesToInsertFr.empty() && + !InstsToDropFlags.empty()); + + // Drop flags like nsw/nuw. + for (auto *Inst : InstsToDropFlags) { + if (isGuaranteedNotToBeUndefOrPoison(Inst, Inst, &DT)) + continue; + // Drop any flag from the instruction. + // NOTE: if canHandleInst is expanded to support more ops, + // this part should be updated as well. + LLVM_DEBUG(dbgs() << "canonfr: drop flags: " << *Inst << "\n"); + Inst->dropPoisonGeneratingFlags(); + SE.forgetValue(Inst); + } + + // Freeze values. This includes initial value of ind PHI. + for (auto *U : UsesToInsertFr) + InsertFreezeAndForgetFromSCEV(*U); + + // Finally, remove the old freeze instructions. + for (auto *FI : FIsToRemove) { + LLVM_DEBUG(dbgs() << "canonfr: removing " << *FI << "\n"); + SE.forgetValue(FI); + FI->replaceAllUsesWith(FI->getOperand(0)); + FI->eraseFromParent(); + } + + return true; +} + +CanonicalizeFreezeInLoops::CanonicalizeFreezeInLoops() : LoopPass(ID) { + initializeCanonicalizeFreezeInLoopsPass(*PassRegistry::getPassRegistry()); +} + +void CanonicalizeFreezeInLoops::getAnalysisUsage(AnalysisUsage &AU) const { + AU.addPreservedID(LoopSimplifyID); + AU.addRequired(); + AU.addPreserved(); + AU.addRequiredID(LoopSimplifyID); + AU.addRequired(); + AU.addPreserved(); + AU.addRequired(); + AU.addPreserved(); +} + +bool CanonicalizeFreezeInLoops::runOnLoop(Loop *L, LPPassManager &) { + if (skipLoop(L)) + return false; + + auto &SE = getAnalysis().getSE(); + auto &DT = getAnalysis().getDomTree(); + return CanonicalizeFreezeInLoopsImpl(L, SE, DT).run(); +} + +PreservedAnalyses +CanonicalizeFreezeInLoopsPass::run(Loop &L, LoopAnalysisManager &AM, + LoopStandardAnalysisResults &AR, + LPMUpdater &U) { + if (!CanonicalizeFreezeInLoopsImpl(&L, AR.SE, AR.DT).run()) + return PreservedAnalyses::all(); + + return getLoopPassPreservedAnalyses(); +} + +INITIALIZE_PASS_BEGIN(CanonicalizeFreezeInLoops, "canon-freeze", + "Canonicalize Freeze Instructions in Loops", false, false) +INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass) +INITIALIZE_PASS_DEPENDENCY(ScalarEvolutionWrapperPass) +INITIALIZE_PASS_DEPENDENCY(LoopSimplify) +INITIALIZE_PASS_END(CanonicalizeFreezeInLoops, "canon-freeze", + "Canonicalize Freeze Instructions in Loops", false, false) + +Pass *llvm::createCanonicalizeFreezeInLoopsPass() { + return new CanonicalizeFreezeInLoops(); +} + +char CanonicalizeFreezeInLoops::ID = 0; \ No newline at end of file diff --git a/llvm/lib/Transforms/Utils/Utils.cpp b/llvm/lib/Transforms/Utils/Utils.cpp --- a/llvm/lib/Transforms/Utils/Utils.cpp +++ b/llvm/lib/Transforms/Utils/Utils.cpp @@ -26,6 +26,7 @@ initializeAddDiscriminatorsLegacyPassPass(Registry); initializeBreakCriticalEdgesPass(Registry); initializeCanonicalizeAliasesLegacyPassPass(Registry); + initializeCanonicalizeFreezeInLoopsPass(Registry); initializeInstNamerPass(Registry); initializeLCSSAWrapperPassPass(Registry); initializeLibCallsShrinkWrapLegacyPassPass(Registry); diff --git a/llvm/test/Transforms/CanonicalizeFreezeInLoops/func_from_mcf_r.ll b/llvm/test/Transforms/CanonicalizeFreezeInLoops/func_from_mcf_r.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/CanonicalizeFreezeInLoops/func_from_mcf_r.ll @@ -0,0 +1,85 @@ +; RUN: opt < %s -canon-freeze -S | FileCheck %s +source_filename = "ld-temp.o" +target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" +target triple = "aarch64-unknown-linux-gnu" + +%struct.arc = type { i32 } +%struct.g = type { i64, %struct.arc, i64, i64, i64 } + +@_MergedGlobals = private global <{ i64, %struct.arc*, %struct.g }> zeroinitializer, align 8 + +@m = internal alias i64, getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 0) +@h = internal alias %struct.arc*, getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 1) +@j = internal alias %struct.g, getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 2) + +; Function Attrs: nofree norecurse nounwind +define dso_local i32 @main() local_unnamed_addr #0 { +bb: + %tmp = load i64, i64* getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 2, i32 0), align 8 + %tmp1 = icmp sgt i64 %tmp, 0 + br i1 %tmp1, label %bb2, label %bb35 + +bb2: ; preds = %bb + %tmp3 = load i64, i64* getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 0), align 8 + %tmp4 = load %struct.arc*, %struct.arc** getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 1), align 8 +; CHECK: %tmp3.frozen = freeze i64 %tmp3 + br label %bb5 + +bb5: ; preds = %bb28, %bb2 + %tmp6 = phi %struct.arc* [ %tmp4, %bb2 ], [ %tmp31, %bb28 ] + %tmp7 = phi i64 [ %tmp3, %bb2 ], [ %tmp12, %bb28 ] +; CHECK: %tmp7 = phi i64 [ %tmp3.frozen, %bb2 ], [ %tmp12, %bb28 ] + %tmp8 = phi i64 [ 0, %bb2 ], [ %tmp11, %bb28 ] + %tmp9 = trunc i64 %tmp7 to i32 + %tmp10 = getelementptr inbounds %struct.arc, %struct.arc* %tmp6, i64 0, i32 0 + store i32 %tmp9, i32* %tmp10, align 4 + %tmp11 = add nuw nsw i64 %tmp8, 1 + %tmp12 = add nsw i64 %tmp7, 1 +; CHECK: %tmp12 = add i64 %tmp7, 1 + store i64 %tmp12, i64* getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 0), align 8 + %tmp13 = load i64, i64* inttoptr (i64 16 to i64*), align 16 + %tmp14 = freeze i64 %tmp12 +; CHECK-NOT: %tmp14 = freeze i64 %tmp12 + %tmp15 = freeze i64 %tmp13 + %tmp16 = sdiv i64 %tmp14, %tmp15 + %tmp17 = mul i64 %tmp16, %tmp15 + %tmp18 = sub i64 %tmp14, %tmp17 + %tmp19 = load i64, i64* inttoptr (i64 24 to i64*), align 8 + %tmp20 = icmp sgt i64 %tmp18, %tmp19 + %tmp21 = load i64, i64* inttoptr (i64 32 to i64*), align 32 + br i1 %tmp20, label %bb22, label %bb28 + +bb22: ; preds = %bb5 + %tmp23 = mul nsw i64 %tmp21, %tmp19 + %tmp24 = sub nsw i64 %tmp18, %tmp19 + %tmp25 = add nsw i64 %tmp21, -1 + %tmp26 = mul nsw i64 %tmp25, %tmp24 + %tmp27 = add nsw i64 %tmp26, %tmp23 + br label %bb28 + +bb28: ; preds = %bb22, %bb5 + %tmp29 = phi i64 [ %tmp27, %bb22 ], [ %tmp21, %bb5 ] + %tmp30 = add nsw i64 %tmp29, %tmp16 + %tmp31 = getelementptr inbounds %struct.arc, %struct.arc* getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 2, i32 1), i64 %tmp30 + store %struct.arc* %tmp31, %struct.arc** getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 1), align 8 + %tmp32 = load i64, i64* getelementptr inbounds (<{ i64, %struct.arc*, %struct.g }>, <{ i64, %struct.arc*, %struct.g }>* @_MergedGlobals, i32 0, i32 2, i32 0), align 8 + %tmp33 = icmp slt i64 %tmp11, %tmp32 + br i1 %tmp33, label %bb5, label %bb34 + +bb34: ; preds = %bb28 + br label %bb35 + +bb35: ; preds = %bb34, %bb + ret i32 0 +} + +attributes #0 = { nofree norecurse nounwind "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "frame-pointer"="non-leaf" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+neon" "unsafe-fp-math"="false" "use-soft-float"="false" } + +!llvm.ident = !{!0} +!llvm.module.flags = !{!1, !2, !3, !4} + +!0 = !{!"clang version 11.0.0 (https://github.com/llvm/llvm-project.git 49f75132bcdfcbc23010252e04e43fe0278ae1e7)"} +!1 = !{i32 1, !"wchar_size", i32 4} +!2 = !{i32 1, !"ThinLTO", i32 0} +!3 = !{i32 1, !"EnableSplitLTOUnit", i32 1} +!4 = !{i32 1, !"LTOPostLink", i32 1} diff --git a/llvm/test/Transforms/CanonicalizeFreezeInLoops/nonsteps-preserve-flags.ll b/llvm/test/Transforms/CanonicalizeFreezeInLoops/nonsteps-preserve-flags.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/CanonicalizeFreezeInLoops/nonsteps-preserve-flags.ll @@ -0,0 +1,34 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt < %s -canon-freeze -S | FileCheck %s +declare void @call(i32) + +define void @add(i32 %init, i32 %n) { +; CHECK-LABEL: @add( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: [[NONSTEP:%.*]] = mul nsw i32 [[I]], 2 +; CHECK-NEXT: call void @call(i32 [[NONSTEP]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry], [%i.next, %loop ] + %i.next = add nsw i32 %i, 1 + %i.next.fr = freeze i32 %i.next + %nonstep = mul nsw i32 %i, 2 + call void @call(i32 %nonstep) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} diff --git a/llvm/test/Transforms/CanonicalizeFreezeInLoops/onephi.ll b/llvm/test/Transforms/CanonicalizeFreezeInLoops/onephi.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/CanonicalizeFreezeInLoops/onephi.ll @@ -0,0 +1,454 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt < %s -canon-freeze -S | FileCheck %s +; A set of tests that have one phi node +target triple = "aarch64-unknown-linux-gnu" +declare void @call(i32) +declare i32 @get_step() + +define void @add(i32 %init, i32 %n) { +; CHECK-LABEL: @add( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry], [%i.next, %loop ] + %i.next = add i32 %i, 1 + %i.next.fr = freeze i32 %i.next + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @add_comm(i32 %init, i32 %n) { +; CHECK-LABEL: @add_comm( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 1, [[I]] +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry ], [ %i.next, %loop ] + %i.next = add i32 1, %i + %i.next.fr = freeze i32 %i.next + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @add_multiuses(i32 %init, i32 %n) { +; CHECK-LABEL: @add_multiuses( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry ], [ %i.next, %loop ] + %i.next = add i32 %i, 1 + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @add_multiuses2(i32 %init, i32 %n) { +; CHECK-LABEL: @add_multiuses2( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry ], [ %i.next, %loop ] + %i.next = add i32 %i, 1 + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %i.next.fr2 = freeze i32 %i.next + call void @call(i32 %i.next.fr2) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @add_flags(i32 %init, i32 %n) { +; CHECK-LABEL: @add_flags( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry ], [ %i.next, %loop ] + %i.next = add nuw nsw i32 %i, 1 + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @add_ind(i32 %init, i32 %n) { +; CHECK-LABEL: @add_ind( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: [[I_FR_NEXT:%.*]] = add nuw nsw i32 [[I]], 1 +; CHECK-NEXT: call void @call(i32 [[I_FR_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_FR_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry ], [ %i.next, %loop ] + %i.next = add nuw nsw i32 %i, 1 + %i.fr = freeze i32 %i + %i.fr.next = add nuw nsw i32 %i.fr, 1 + call void @call(i32 %i.fr.next) + %cond = icmp eq i32 %i.fr.next, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +; Negative test +define void @add_ind_frozen(i32 %init, i32 %n) { +; CHECK-LABEL: @add_ind_frozen( +; CHECK-NEXT: entry: +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT:%.*]], [[ENTRY:%.*]] ], [ [[I_NEXT_FR:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_FR:%.*]] = freeze i32 [[I]] +; CHECK-NEXT: [[I_NEXT_FR]] = add nuw nsw i32 [[I_FR]], 1 +; CHECK-NEXT: call void @call(i32 [[I_NEXT_FR]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT_FR]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [%init, %entry], [%i.next.fr, %loop] + %i.fr = freeze i32 %i + %i.next.fr = add nuw nsw i32 %i.fr, 1 + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @add_flags_not_compared(i32 %init, i32 %n) { +; CHECK-LABEL: @add_flags_not_compared( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry ], [ %i.next, %loop ] + %i.next = add nuw nsw i32 %i, 1 + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +; Negative test +define void @add_flags_not_compared_stepinst(i32 %init, i32 %n) { +; CHECK-LABEL: @add_flags_not_compared_stepinst( +; CHECK-NEXT: entry: +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT:%.*]], [[ENTRY:%.*]] ], [ [[I_NEXT_FR:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT:%.*]] = add nuw nsw i32 [[I]], 1 +; CHECK-NEXT: [[I_NEXT_FR]] = freeze i32 [[I_NEXT]] +; CHECK-NEXT: call void @call(i32 [[I_NEXT_FR]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry ], [ %i.next.fr, %loop ] + %i.next = add nuw nsw i32 %i, 1 + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +; Negative test +; If pushing freeze through icmp is needed, this should be enabled. +; There is no correctness issue in pushing freeze into icmp here, just it's +; being conservative right now. +define void @add_flags_stepinst_frozen(i32 %init, i32 %n) { +; CHECK-LABEL: @add_flags_stepinst_frozen( +; CHECK-NEXT: entry: +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT:%.*]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add nuw nsw i32 [[I]], 1 +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: [[COND_FR:%.*]] = freeze i1 [[COND]] +; CHECK-NEXT: br i1 [[COND_FR]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %init, %entry ], [ %i.next, %loop ] + %i.next = add nuw nsw i32 %i, 1 + call void @call(i32 %i.next) + %cond = icmp eq i32 %i.next, %n + %cond.fr = freeze i1 %cond + br i1 %cond.fr, label %loop, label %exit + +exit: + ret void +} + +define void @sub(i32 %init, i32 %n) { +; CHECK-LABEL: @sub( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = sub i32 [[I]], 1 +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [%init, %entry], [%i.next, %loop] + %i.next = sub nuw nsw i32 %i, 1 + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @init_const(i32 %n) { +; CHECK-LABEL: @init_const( +; CHECK-NEXT: entry: +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ 0, [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ 0, %entry ], [ %i.next, %loop ] + %i.next = add nuw nsw i32 %i, 1 + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @step_init_arg(i32 %init, i32 %n, i32 %step) { +; CHECK-LABEL: @step_init_arg( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[STEP_FROZEN:%.*]] = freeze i32 [[STEP:%.*]] +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], [[STEP_FROZEN]] +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [%init, %entry], [%i.next, %loop] + %i.next = add nuw nsw i32 %i, %step + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @step_init_inst(i32 %n) { +; CHECK-LABEL: @step_init_inst( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[STEP:%.*]] = call i32 @get_step() +; CHECK-NEXT: [[INIT:%.*]] = call i32 @get_step() +; CHECK-NEXT: [[STEP_FROZEN:%.*]] = freeze i32 [[STEP]] +; CHECK-NEXT: [[INIT_FROZEN:%.*]] = freeze i32 [[INIT]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], [[STEP_FROZEN]] +; CHECK-NEXT: call void @call(i32 [[I_NEXT]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + %step = call i32 @get_step() + %init = call i32 @get_step() + br label %loop + +loop: + %i = phi i32 [%init, %entry], [%i.next, %loop] + %i.next = add nuw nsw i32 %i, %step + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +; Negative test +define void @step_inst(i32 %init, i32 %n) { +; CHECK-LABEL: @step_inst( +; CHECK-NEXT: entry: +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[INIT:%.*]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add nuw nsw i32 [[I]], [[I]] +; CHECK-NEXT: [[I_NEXT_FR:%.*]] = freeze i32 [[I_NEXT]] +; CHECK-NEXT: call void @call(i32 [[I_NEXT_FR]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT_FR]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [%init, %entry], [%i.next, %loop] + %i.next = add nuw nsw i32 %i, %i + %i.next.fr = freeze i32 %i.next + call void @call(i32 %i.next.fr) + %cond = icmp eq i32 %i.next.fr, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} diff --git a/llvm/test/Transforms/CanonicalizeFreezeInLoops/phis.ll b/llvm/test/Transforms/CanonicalizeFreezeInLoops/phis.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/CanonicalizeFreezeInLoops/phis.ll @@ -0,0 +1,115 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt < %s -canon-freeze -S | FileCheck %s +; A set of tests that have several phi nodes +target triple = "aarch64-unknown-linux-gnu" +declare void @call(i32) +declare i32 @call2() + +define void @onephi_used(i32 %n, i32 %i.init, i32 %j.init) { +; CHECK-LABEL: @onephi_used( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[I_INIT_FROZEN:%.*]] = freeze i32 [[I_INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[I_INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[J:%.*]] = phi i32 [ [[J_INIT:%.*]], [[ENTRY]] ], [ [[J_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: [[J_NEXT]] = add nuw nsw i32 [[J]], -2 +; CHECK-NEXT: call void @call(i32 [[I]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop +loop: + %i = phi i32 [ %i.init, %entry ], [ %i.next, %loop ] + %j = phi i32 [ %j.init, %entry ], [ %j.next, %loop ] + %i.next = add nuw nsw i32 %i, 1 + %j.next = add nuw nsw i32 %j, -2 + %i.fr = freeze i32 %i + call void @call(i32 %i.fr) + %cond = icmp eq i32 %i.next, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +define void @twophis_used(i32 %n, i32 %i.init, i32 %j.init) { +; CHECK-LABEL: @twophis_used( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[J_INIT_FROZEN:%.*]] = freeze i32 [[J_INIT:%.*]] +; CHECK-NEXT: [[I_INIT_FROZEN:%.*]] = freeze i32 [[I_INIT:%.*]] +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[I_INIT_FROZEN]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[J:%.*]] = phi i32 [ [[J_INIT_FROZEN]], [[ENTRY]] ], [ [[J_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add i32 [[I]], 1 +; CHECK-NEXT: [[J_NEXT]] = add i32 [[J]], -2 +; CHECK-NEXT: [[IJ:%.*]] = add i32 [[I]], [[J]] +; CHECK-NEXT: call void @call(i32 [[IJ]]) +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop +loop: + %i = phi i32 [ %i.init, %entry ], [ %i.next, %loop ] + %j = phi i32 [ %j.init, %entry ], [ %j.next, %loop ] + %i.next = add nuw nsw i32 %i, 1 + %j.next = add nuw nsw i32 %j, -2 + %ij = add i32 %i, %j + %ij.fr = freeze i32 %ij + call void @call(i32 %ij.fr) + %cond = icmp eq i32 %i.next, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +} + +; Negative test +define void @nonindphi_used(i32 %n, i32 %i.init, i32 %j.init, i32 %k.init) { +; CHECK-LABEL: @nonindphi_used( +; CHECK-NEXT: entry: +; CHECK-NEXT: br label [[LOOP:%.*]] +; CHECK: loop: +; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[I_INIT:%.*]], [[ENTRY:%.*]] ], [ [[I_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[J:%.*]] = phi i32 [ [[J_INIT:%.*]], [[ENTRY]] ], [ [[J_NEXT:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[K:%.*]] = phi i32 [ [[K_INIT:%.*]], [[ENTRY]] ], [ [[ANY:%.*]], [[LOOP]] ] +; CHECK-NEXT: [[I_NEXT]] = add nuw nsw i32 [[I]], 1 +; CHECK-NEXT: [[J_NEXT]] = add nuw nsw i32 [[J]], -2 +; CHECK-NEXT: [[IJ:%.*]] = add i32 [[I]], [[J]] +; CHECK-NEXT: [[IJK:%.*]] = add i32 [[IJ]], [[K]] +; CHECK-NEXT: [[IJK_FR:%.*]] = freeze i32 [[IJK]] +; CHECK-NEXT: call void @call(i32 [[IJK_FR]]) +; CHECK-NEXT: [[ANY]] = call i32 @call2() +; CHECK-NEXT: [[COND:%.*]] = icmp eq i32 [[I_NEXT]], [[N:%.*]] +; CHECK-NEXT: br i1 [[COND]], label [[LOOP]], label [[EXIT:%.*]] +; CHECK: exit: +; CHECK-NEXT: ret void +; +entry: + br label %loop + +loop: + %i = phi i32 [ %i.init, %entry ], [ %i.next, %loop ] + %j = phi i32 [ %j.init, %entry ], [ %j.next, %loop ] + %k = phi i32 [ %k.init, %entry ], [ %any, %loop ] + %i.next = add nuw nsw i32 %i, 1 + %j.next = add nuw nsw i32 %j, -2 + %ij = add i32 %i, %j + %ijk = add i32 %ij, %k + %ijk.fr = freeze i32 %ijk + call void @call(i32 %ijk.fr) + %any = call i32 @call2() + %cond = icmp eq i32 %i.next, %n + br i1 %cond, label %loop, label %exit + +exit: + ret void +}