2020-05-27 18:33:46 +02:00
|
|
|
//===- DDGTest.cpp - DDGAnalysis unit tests -------------------------------===//
|
|
|
|
//
|
|
|
|
// 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/Analysis/DDG.h"
|
|
|
|
#include "llvm/Analysis/AliasAnalysis.h"
|
2020-12-02 17:53:17 +01:00
|
|
|
#include "llvm/Analysis/AssumptionCache.h"
|
2020-05-27 18:33:46 +02:00
|
|
|
#include "llvm/Analysis/BasicAliasAnalysis.h"
|
2020-06-17 16:24:59 +02:00
|
|
|
#include "llvm/Analysis/LoopInfo.h"
|
2020-05-27 18:33:46 +02:00
|
|
|
#include "llvm/Analysis/ScalarEvolution.h"
|
|
|
|
#include "llvm/Analysis/TargetLibraryInfo.h"
|
|
|
|
#include "llvm/AsmParser/Parser.h"
|
|
|
|
#include "llvm/IR/Dominators.h"
|
|
|
|
#include "llvm/Support/SourceMgr.h"
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
|
|
|
|
using namespace llvm;
|
|
|
|
|
|
|
|
/// Build the DDG analysis for a loop and run the given test \p Test.
|
|
|
|
static void runTest(Module &M, StringRef FuncName,
|
|
|
|
function_ref<void(Function &F, LoopInfo &LI,
|
|
|
|
DependenceInfo &DI, ScalarEvolution &SE)>
|
|
|
|
Test) {
|
|
|
|
auto *F = M.getFunction(FuncName);
|
|
|
|
ASSERT_NE(F, nullptr) << "Could not find " << FuncName;
|
|
|
|
|
|
|
|
TargetLibraryInfoImpl TLII;
|
|
|
|
TargetLibraryInfo TLI(TLII);
|
|
|
|
AssumptionCache AC(*F);
|
|
|
|
DominatorTree DT(*F);
|
|
|
|
LoopInfo LI(DT);
|
|
|
|
ScalarEvolution SE(*F, TLI, AC, DT, LI);
|
|
|
|
AAResults AA(TLI);
|
|
|
|
DependenceInfo DI(F, &AA, &SE, &LI);
|
|
|
|
Test(*F, LI, DI, SE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static std::unique_ptr<Module> makeLLVMModule(LLVMContext &Context,
|
|
|
|
const char *ModuleStr) {
|
|
|
|
SMDiagnostic Err;
|
|
|
|
return parseAssemblyString(ModuleStr, Err, Context);
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(DDGTest, getDependencies) {
|
|
|
|
const char *ModuleStr =
|
|
|
|
"target datalayout = \"e-m:e-i64:64-n32:64\"\n"
|
|
|
|
"target triple = \"powerpc64le-unknown-linux-gnu\"\n"
|
|
|
|
"\n"
|
|
|
|
"define dso_local void @foo(i32 signext %n, i32* noalias %A, i32* "
|
|
|
|
"noalias %B) {\n"
|
|
|
|
"entry:\n"
|
|
|
|
" %cmp1 = icmp sgt i32 %n, 0\n"
|
|
|
|
" br i1 %cmp1, label %for.body.preheader, label %for.end\n"
|
|
|
|
"\n"
|
|
|
|
"for.body.preheader:\n"
|
|
|
|
" %wide.trip.count = zext i32 %n to i64\n"
|
|
|
|
" br label %for.body\n"
|
|
|
|
" \n"
|
|
|
|
" for.body:\n"
|
|
|
|
" %indvars.iv = phi i64 [ 0, %for.body.preheader ], [ "
|
|
|
|
"%indvars.iv.next, %for.body ]\n"
|
|
|
|
" %arrayidx = getelementptr inbounds i32, i32* %A, i64 %indvars.iv\n"
|
|
|
|
" %0 = trunc i64 %indvars.iv to i32\n"
|
|
|
|
" store i32 %0, i32* %arrayidx, align 4\n"
|
|
|
|
" %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1\n"
|
|
|
|
" %arrayidx2 = getelementptr inbounds i32, i32* %A, i64 "
|
|
|
|
"%indvars.iv.next\n"
|
|
|
|
" %1 = load i32, i32* %arrayidx2, align 4\n"
|
|
|
|
" %add3 = add nsw i32 %1, 1\n"
|
|
|
|
" %arrayidx5 = getelementptr inbounds i32, i32* %B, i64 %indvars.iv\n"
|
|
|
|
" store i32 %add3, i32* %arrayidx5, align 4\n"
|
|
|
|
" %exitcond = icmp ne i64 %indvars.iv.next, %wide.trip.count\n"
|
|
|
|
" br i1 %exitcond, label %for.body, label %for.end.loopexit\n"
|
|
|
|
"\n"
|
|
|
|
"for.end.loopexit:\n"
|
|
|
|
" br label %for.end\n"
|
|
|
|
"\n"
|
|
|
|
"for.end:\n"
|
|
|
|
" ret void\n"
|
|
|
|
"}\n";
|
|
|
|
|
|
|
|
LLVMContext Context;
|
|
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleStr);
|
|
|
|
|
|
|
|
runTest(
|
|
|
|
*M, "foo",
|
|
|
|
[&](Function &F, LoopInfo &LI, DependenceInfo &DI, ScalarEvolution &SE) {
|
|
|
|
Loop *L = *LI.begin();
|
|
|
|
assert(L && "expected the loop to be identified.");
|
|
|
|
|
|
|
|
DataDependenceGraph DDG(*L, LI, DI);
|
|
|
|
|
|
|
|
// Collect all the nodes that have an outgoing memory edge
|
|
|
|
// while collecting all memory edges as well. There should
|
|
|
|
// only be one node with an outgoing memory edge and there
|
|
|
|
// should only be one memory edge in the entire graph.
|
|
|
|
std::vector<DDGNode *> DependenceSourceNodes;
|
|
|
|
std::vector<DDGEdge *> MemoryEdges;
|
|
|
|
for (DDGNode *N : DDG) {
|
|
|
|
for (DDGEdge *E : *N) {
|
|
|
|
bool SourceAdded = false;
|
|
|
|
if (E->isMemoryDependence()) {
|
|
|
|
MemoryEdges.push_back(E);
|
|
|
|
if (!SourceAdded) {
|
|
|
|
DependenceSourceNodes.push_back(N);
|
|
|
|
SourceAdded = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
EXPECT_EQ(DependenceSourceNodes.size(), 1ull);
|
|
|
|
EXPECT_EQ(MemoryEdges.size(), 1ull);
|
|
|
|
|
|
|
|
DataDependenceGraph::DependenceList DL;
|
|
|
|
DDG.getDependencies(*DependenceSourceNodes.back(),
|
|
|
|
MemoryEdges.back()->getTargetNode(), DL);
|
|
|
|
|
|
|
|
EXPECT_EQ(DL.size(), 1ull);
|
|
|
|
EXPECT_TRUE(DL.back()->isAnti());
|
|
|
|
EXPECT_EQ(DL.back()->getLevels(), 1u);
|
|
|
|
EXPECT_NE(DL.back()->getDistance(1), nullptr);
|
|
|
|
EXPECT_EQ(DL.back()->getDistance(1),
|
|
|
|
SE.getOne(DL.back()->getDistance(1)->getType()));
|
|
|
|
});
|
|
|
|
}
|
2021-01-07 16:31:11 +01:00
|
|
|
|
|
|
|
/// Test to make sure that when pi-blocks are formed, multiple edges of the same
|
|
|
|
/// kind and direction are collapsed into a single edge.
|
|
|
|
/// In the test below, %loadASubI belongs to an outside node, which has input
|
|
|
|
/// dependency with multiple load instructions in the pi-block containing
|
|
|
|
/// %loadBSubI. We expect a single memory dependence edge from the outside node
|
|
|
|
/// to this pi-block. The pi-block also contains %add and %add7 both of which
|
|
|
|
/// feed a phi in an outside node. We expect a single def-use edge from the
|
|
|
|
/// pi-block to the node containing that phi.
|
|
|
|
TEST(DDGTest, avoidDuplicateEdgesToFromPiBlocks) {
|
|
|
|
const char *ModuleStr =
|
|
|
|
"target datalayout = \"e-m:e-i64:64-n32:64-v256:256:256-v512:512:512\"\n"
|
|
|
|
"\n"
|
|
|
|
"define void @foo(float* noalias %A, float* noalias %B, float* noalias "
|
|
|
|
"%C, float* noalias %D, i32 signext %n) {\n"
|
|
|
|
"entry:\n"
|
|
|
|
" %cmp1 = icmp sgt i32 %n, 0\n"
|
|
|
|
" br i1 %cmp1, label %for.body.preheader, label %for.end\n"
|
|
|
|
"\n"
|
|
|
|
"for.body.preheader: ; preds = %entry\n"
|
|
|
|
" %wide.trip.count = zext i32 %n to i64\n"
|
|
|
|
" br label %for.body\n"
|
|
|
|
"\n"
|
|
|
|
"for.body: ; preds = "
|
|
|
|
"%for.body.preheader, %if.end\n"
|
|
|
|
" %indvars.iv = phi i64 [ 0, %for.body.preheader ], [ %indvars.iv.next, "
|
|
|
|
"%if.end ]\n"
|
|
|
|
" %arrayidx = getelementptr inbounds float, float* %A, i64 %indvars.iv\n"
|
|
|
|
" %loadASubI = load float, float* %arrayidx, align 4\n"
|
|
|
|
" %arrayidx2 = getelementptr inbounds float, float* %B, i64 "
|
|
|
|
"%indvars.iv\n"
|
|
|
|
" %loadBSubI = load float, float* %arrayidx2, align 4\n"
|
|
|
|
" %add = fadd fast float %loadASubI, %loadBSubI\n"
|
|
|
|
" %arrayidx4 = getelementptr inbounds float, float* %A, i64 "
|
|
|
|
"%indvars.iv\n"
|
|
|
|
" store float %add, float* %arrayidx4, align 4\n"
|
|
|
|
" %arrayidx6 = getelementptr inbounds float, float* %A, i64 "
|
|
|
|
"%indvars.iv\n"
|
|
|
|
" %0 = load float, float* %arrayidx6, align 4\n"
|
|
|
|
" %add7 = fadd fast float %0, 1.000000e+00\n"
|
|
|
|
" %indvars.iv.next = add nuw nsw i64 %indvars.iv, 1\n"
|
|
|
|
" %arrayidx10 = getelementptr inbounds float, float* %B, i64 "
|
|
|
|
"%indvars.iv.next\n"
|
|
|
|
" store float %add7, float* %arrayidx10, align 4\n"
|
|
|
|
" %arrayidx12 = getelementptr inbounds float, float* %A, i64 "
|
|
|
|
"%indvars.iv\n"
|
|
|
|
" %1 = load float, float* %arrayidx12, align 4\n"
|
|
|
|
" %cmp13 = fcmp fast ogt float %1, 1.000000e+02\n"
|
|
|
|
" br i1 %cmp13, label %if.then, label %if.else\n"
|
|
|
|
"\n"
|
|
|
|
"if.then: ; preds = %for.body\n"
|
|
|
|
" br label %if.end\n"
|
|
|
|
"\n"
|
|
|
|
"if.else: ; preds = %for.body\n"
|
|
|
|
" br label %if.end\n"
|
|
|
|
"\n"
|
|
|
|
"if.end: ; preds = %if.else, "
|
|
|
|
"%if.then\n"
|
|
|
|
" %ff.0 = phi float [ %add, %if.then ], [ %add7, %if.else ]\n"
|
|
|
|
" store float %ff.0, float* %C, align 4\n"
|
|
|
|
" %exitcond = icmp ne i64 %indvars.iv.next, %wide.trip.count\n"
|
|
|
|
" br i1 %exitcond, label %for.body, label %for.end.loopexit\n"
|
|
|
|
"\n"
|
|
|
|
"for.end.loopexit: ; preds = %if.end\n"
|
|
|
|
" br label %for.end\n"
|
|
|
|
"\n"
|
|
|
|
"for.end: ; preds = "
|
|
|
|
"%for.end.loopexit, %entry\n"
|
|
|
|
" ret void\n"
|
|
|
|
"}\n";
|
|
|
|
|
|
|
|
LLVMContext Context;
|
|
|
|
std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleStr);
|
|
|
|
|
|
|
|
runTest(
|
|
|
|
*M, "foo",
|
|
|
|
[&](Function &F, LoopInfo &LI, DependenceInfo &DI, ScalarEvolution &SE) {
|
|
|
|
Loop *L = *LI.begin();
|
|
|
|
assert(L && "expected the loop to be identified.");
|
|
|
|
|
|
|
|
DataDependenceGraph DDG(*L, LI, DI);
|
|
|
|
|
|
|
|
const DDGNode *LoadASubI = nullptr;
|
|
|
|
for (DDGNode *N : DDG) {
|
|
|
|
if (!isa<SimpleDDGNode>(N))
|
|
|
|
continue;
|
|
|
|
SmallVector<Instruction *, 8> IList;
|
|
|
|
N->collectInstructions([](const Instruction *I) { return true; },
|
|
|
|
IList);
|
|
|
|
if (llvm::any_of(IList, [](Instruction *I) {
|
|
|
|
return I->getName() == "loadASubI";
|
|
|
|
})) {
|
|
|
|
LoadASubI = N;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert(LoadASubI && "Did not find load of A[i]");
|
|
|
|
|
|
|
|
const PiBlockDDGNode *PiBlockWithBSubI = nullptr;
|
|
|
|
for (DDGNode *N : DDG) {
|
|
|
|
if (!isa<PiBlockDDGNode>(N))
|
|
|
|
continue;
|
|
|
|
for (DDGNode *M : cast<PiBlockDDGNode>(N)->getNodes()) {
|
|
|
|
SmallVector<Instruction *, 8> IList;
|
|
|
|
M->collectInstructions([](const Instruction *I) { return true; },
|
|
|
|
IList);
|
|
|
|
if (llvm::any_of(IList, [](Instruction *I) {
|
|
|
|
return I->getName() == "loadBSubI";
|
|
|
|
})) {
|
|
|
|
PiBlockWithBSubI = static_cast<PiBlockDDGNode *>(N);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (PiBlockWithBSubI)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
assert(PiBlockWithBSubI &&
|
|
|
|
"Did not find pi-block containing load of B[i]");
|
|
|
|
|
|
|
|
const DDGNode *FFPhi = nullptr;
|
|
|
|
for (DDGNode *N : DDG) {
|
|
|
|
if (!isa<SimpleDDGNode>(N))
|
|
|
|
continue;
|
|
|
|
SmallVector<Instruction *, 8> IList;
|
|
|
|
N->collectInstructions([](const Instruction *I) { return true; },
|
|
|
|
IList);
|
|
|
|
if (llvm::any_of(IList, [](Instruction *I) {
|
|
|
|
return I->getName() == "ff.0";
|
|
|
|
})) {
|
|
|
|
FFPhi = N;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
assert(FFPhi && "Did not find ff.0 phi instruction");
|
|
|
|
|
|
|
|
// Expect a single memory edge from '%0 = A[i]' to the pi-block. This
|
|
|
|
// means the duplicate incoming memory edges are removed during pi-block
|
|
|
|
// formation.
|
|
|
|
SmallVector<DDGEdge *, 4> EL;
|
|
|
|
LoadASubI->findEdgesTo(*PiBlockWithBSubI, EL);
|
|
|
|
unsigned NumMemoryEdges = llvm::count_if(
|
|
|
|
EL, [](DDGEdge *Edge) { return Edge->isMemoryDependence(); });
|
|
|
|
EXPECT_EQ(NumMemoryEdges, 1ull);
|
|
|
|
|
|
|
|
/// Expect a single def-use edge from the pi-block to '%ff.0 = phi...`.
|
|
|
|
/// This means the duplicate outgoing def-use edges are removed during
|
|
|
|
/// pi-block formation.
|
|
|
|
EL.clear();
|
|
|
|
PiBlockWithBSubI->findEdgesTo(*FFPhi, EL);
|
|
|
|
NumMemoryEdges =
|
|
|
|
llvm::count_if(EL, [](DDGEdge *Edge) { return Edge->isDefUse(); });
|
|
|
|
EXPECT_EQ(NumMemoryEdges, 1ull);
|
|
|
|
});
|
|
|
|
}
|