blob: ce002e9239960317165da5a0066d59d496a9b2f7 [file] [log] [blame]
//===- LoopUtilsTest.cpp - Unit tests for LoopUtils -----------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/Utils/LoopUtils.h"
#include "llvm/Analysis/AssumptionCache.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/ScalarEvolution.h"
#include "llvm/Analysis/TargetLibraryInfo.h"
#include "llvm/AsmParser/Parser.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/SourceMgr.h"
#include "gtest/gtest.h"
using namespace llvm;
static std::unique_ptr<Module> parseIR(LLVMContext &C, const char *IR) {
SMDiagnostic Err;
std::unique_ptr<Module> Mod = parseAssemblyString(IR, Err, C);
if (!Mod)
Err.print("LoopUtilsTests", errs());
return Mod;
}
static void run(Module &M, StringRef FuncName,
function_ref<void(Function &F, DominatorTree &DT,
ScalarEvolution &SE, LoopInfo &LI)>
Test) {
Function *F = M.getFunction(FuncName);
DominatorTree DT(*F);
TargetLibraryInfoImpl TLII(M.getTargetTriple());
TargetLibraryInfo TLI(TLII);
AssumptionCache AC(*F);
LoopInfo LI(DT);
ScalarEvolution SE(*F, TLI, AC, DT, LI);
Test(*F, DT, SE, LI);
}
TEST(LoopUtils, DeleteDeadLoopNest) {
LLVMContext C;
std::unique_ptr<Module> M =
parseIR(C, "define void @foo() {\n"
"entry:\n"
" br label %for.i\n"
"for.i:\n"
" %i = phi i64 [ 0, %entry ], [ %inc.i, %for.i.latch ]\n"
" br label %for.j\n"
"for.j:\n"
" %j = phi i64 [ 0, %for.i ], [ %inc.j, %for.j ]\n"
" %inc.j = add nsw i64 %j, 1\n"
" %cmp.j = icmp slt i64 %inc.j, 100\n"
" br i1 %cmp.j, label %for.j, label %for.k.preheader\n"
"for.k.preheader:\n"
" br label %for.k\n"
"for.k:\n"
" %k = phi i64 [ %inc.k, %for.k ], [ 0, %for.k.preheader ]\n"
" %inc.k = add nsw i64 %k, 1\n"
" %cmp.k = icmp slt i64 %inc.k, 100\n"
" br i1 %cmp.k, label %for.k, label %for.i.latch\n"
"for.i.latch:\n"
" %inc.i = add nsw i64 %i, 1\n"
" %cmp.i = icmp slt i64 %inc.i, 100\n"
" br i1 %cmp.i, label %for.i, label %for.end\n"
"for.end:\n"
" ret void\n"
"}\n");
run(*M, "foo",
[&](Function &F, DominatorTree &DT, ScalarEvolution &SE, LoopInfo &LI) {
assert(LI.begin() != LI.end() && "Expecting loops in function F");
Loop *L = *LI.begin();
assert(L && L->getName() == "for.i" && "Expecting loop for.i");
deleteDeadLoop(L, &DT, &SE, &LI);
assert(DT.verify(DominatorTree::VerificationLevel::Fast) &&
"Expecting valid dominator tree");
LI.verify(DT);
assert(LI.begin() == LI.end() &&
"Expecting no loops left in function F");
SE.verify();
Function::iterator FI = F.begin();
BasicBlock *Entry = &*(FI++);
assert(Entry->getName() == "entry" && "Expecting BasicBlock entry");
const BranchInst *BI = dyn_cast<BranchInst>(Entry->getTerminator());
assert(BI && "Expecting valid branch instruction");
EXPECT_EQ(BI->getNumSuccessors(), (unsigned)1);
EXPECT_EQ(BI->getSuccessor(0)->getName(), "for.end");
});
}
TEST(LoopUtils, IsKnownPositiveInLoopTest) {
LLVMContext C;
std::unique_ptr<Module> M =
parseIR(C, "define void @foo(i32 %n, i1 %c) {\n"
"entry:\n"
" %is.positive = icmp sgt i32 %n, 0\n"
" br i1 %is.positive, label %loop, label %exit\n"
"loop:\n"
" br i1 %c, label %loop, label %exit\n"
"exit:\n"
" ret void\n"
"}\n");
run(*M, "foo",
[&](Function &F, DominatorTree &DT, ScalarEvolution &SE, LoopInfo &LI) {
assert(LI.begin() != LI.end() && "Expecting loops in function F");
Loop *L = *LI.begin();
assert(L && L->getName() == "loop" && "Expecting loop 'loop'");
auto *Arg = F.getArg(0);
const SCEV *ArgSCEV = SE.getSCEV(Arg);
EXPECT_EQ(isKnownPositiveInLoop(ArgSCEV, L, SE), true);
});
}
TEST(LoopUtils, IsKnownNonPositiveInLoopTest) {
LLVMContext C;
std::unique_ptr<Module> M =
parseIR(C, "define void @foo(i32 %n, i1 %c) {\n"
"entry:\n"
" %is.non.positive = icmp sle i32 %n, 0\n"
" br i1 %is.non.positive, label %loop, label %exit\n"
"loop:\n"
" br i1 %c, label %loop, label %exit\n"
"exit:\n"
" ret void\n"
"}\n");
run(*M, "foo",
[&](Function &F, DominatorTree &DT, ScalarEvolution &SE, LoopInfo &LI) {
assert(LI.begin() != LI.end() && "Expecting loops in function F");
Loop *L = *LI.begin();
assert(L && L->getName() == "loop" && "Expecting loop 'loop'");
auto *Arg = F.getArg(0);
const SCEV *ArgSCEV = SE.getSCEV(Arg);
EXPECT_EQ(isKnownNonPositiveInLoop(ArgSCEV, L, SE), true);
});
}
// The inner and outer loop here share a latch. Because any loop metadata must
// be attached to that latch, loop metadata cannot distinguish between the two
// loops. Until that problem is solved (by moving loop metadata to loops'
// header blocks instead), getLoopEstimatedTripCount and
// setLoopEstimatedTripCount must refuse to operate on at least one of the two
// loops. They choose to reject the outer loop here because the latch does not
// exit it.
TEST(LoopUtils, nestedLoopSharedLatchEstimatedTripCount) {
LLVMContext C;
std::unique_ptr<Module> M =
parseIR(C, "declare i1 @f()\n"
"declare i1 @g()\n"
"define void @foo() {\n"
"entry:\n"
" br label %outer\n"
"outer:\n"
" %c0 = call i1 @f()"
" br i1 %c0, label %inner, label %exit, !prof !0\n"
"inner:\n"
" %c1 = call i1 @g()"
" br i1 %c1, label %inner, label %outer, !prof !1\n"
"exit:\n"
" ret void\n"
"}\n"
"!0 = !{!\"branch_weights\", i32 100, i32 1}\n"
"!1 = !{!\"branch_weights\", i32 4, i32 1}\n"
"\n");
run(*M, "foo",
[&](Function &F, DominatorTree &DT, ScalarEvolution &SE, LoopInfo &LI) {
assert(LI.end() - LI.begin() == 1 && "Expected one outer loop");
Loop *Outer = *LI.begin();
assert(Outer->end() - Outer->begin() == 1 && "Expected one inner loop");
Loop *Inner = *Outer->begin();
// Even before llvm.loop.estimated_trip_count is added to either loop,
// getLoopEstimatedTripCount rejects the outer loop.
EXPECT_EQ(getLoopEstimatedTripCount(Inner), 5);
EXPECT_EQ(getLoopEstimatedTripCount(Outer), std::nullopt);
// setLoopEstimatedTripCount for the inner loop does not affect
// getLoopEstimatedTripCount for the outer loop.
EXPECT_EQ(setLoopEstimatedTripCount(Inner, 100), true);
EXPECT_EQ(getLoopEstimatedTripCount(Inner), 100);
EXPECT_EQ(getLoopEstimatedTripCount(Outer), std::nullopt);
// setLoopEstimatedTripCount rejects the outer loop.
EXPECT_EQ(setLoopEstimatedTripCount(Outer, 999), false);
EXPECT_EQ(getLoopEstimatedTripCount(Inner), 100);
EXPECT_EQ(getLoopEstimatedTripCount(Outer), std::nullopt);
});
}