#include "llvm/Analysis/FunctionPropertiesAnalysis.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/AsmParser/Parser.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Passes/StandardInstrumentations.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "gtest/gtest.h"
#include <cstring>
using namespace llvm;
namespace {
class FunctionPropertiesAnalysisTest : public testing::Test {
public:
FunctionPropertiesAnalysisTest() {
FAM.registerPass([&] { return DominatorTreeAnalysis(); });
FAM.registerPass([&] { return LoopAnalysis(); });
FAM.registerPass([&] { return PassInstrumentationAnalysis(); });
}
protected:
std::unique_ptr<DominatorTree> DT;
std::unique_ptr<LoopInfo> LI;
FunctionAnalysisManager FAM;
FunctionPropertiesInfo buildFPI(Function &F) {
return FunctionPropertiesInfo::getFunctionPropertiesInfo(F, FAM);
}
void invalidate(Function &F) {
PreservedAnalyses PA = PreservedAnalyses::none();
FAM.invalidate(F, PA);
}
std::unique_ptr<Module> makeLLVMModule(LLVMContext &C, const char *IR) {
SMDiagnostic Err;
std::unique_ptr<Module> Mod = parseAssemblyString(IR, Err, C);
if (!Mod)
Err.print("MLAnalysisTests", errs());
return Mod;
}
CallBase* findCall(Function& F, const char* Name = nullptr) {
for (auto &BB : F)
for (auto &I : BB )
if (auto *CB = dyn_cast<CallBase>(&I))
if (!Name || CB->getName() == Name)
return CB;
return nullptr;
}
};
TEST_F(FunctionPropertiesAnalysisTest, BasicTest) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
declare i32 @f1(i32)
declare i32 @f2(i32)
define i32 @branches(i32) {
%cond = icmp slt i32 %0, 3
br i1 %cond, label %then, label %else
then:
%ret.1 = call i32 @f1(i32 %0)
br label %last.block
else:
%ret.2 = call i32 @f2(i32 %0)
br label %last.block
last.block:
%ret = phi i32 [%ret.1, %then], [%ret.2, %else]
ret i32 %ret
}
define internal i32 @top() {
%1 = call i32 @branches(i32 2)
%2 = call i32 @f1(i32 %1)
ret i32 %2
}
)IR");
Function *BranchesFunction = M->getFunction("branches");
FunctionPropertiesInfo BranchesFeatures = buildFPI(*BranchesFunction);
EXPECT_EQ(BranchesFeatures.BasicBlockCount, 4);
EXPECT_EQ(BranchesFeatures.BlocksReachedFromConditionalInstruction, 2);
EXPECT_EQ(BranchesFeatures.Uses, 2);
EXPECT_EQ(BranchesFeatures.DirectCallsToDefinedFunctions, 0);
EXPECT_EQ(BranchesFeatures.LoadInstCount, 0);
EXPECT_EQ(BranchesFeatures.StoreInstCount, 0);
EXPECT_EQ(BranchesFeatures.MaxLoopDepth, 0);
EXPECT_EQ(BranchesFeatures.TopLevelLoopCount, 0);
Function *TopFunction = M->getFunction("top");
FunctionPropertiesInfo TopFeatures = buildFPI(*TopFunction);
EXPECT_EQ(TopFeatures.BasicBlockCount, 1);
EXPECT_EQ(TopFeatures.BlocksReachedFromConditionalInstruction, 0);
EXPECT_EQ(TopFeatures.Uses, 0);
EXPECT_EQ(TopFeatures.DirectCallsToDefinedFunctions, 1);
EXPECT_EQ(BranchesFeatures.LoadInstCount, 0);
EXPECT_EQ(BranchesFeatures.StoreInstCount, 0);
EXPECT_EQ(BranchesFeatures.MaxLoopDepth, 0);
EXPECT_EQ(BranchesFeatures.TopLevelLoopCount, 0);
}
TEST_F(FunctionPropertiesAnalysisTest, InlineSameBBSimple) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
define i32 @f1(i32 %a) {
%b = call i32 @f2(i32 %a)
%c = add i32 %b, 2
ret i32 %c
}
define i32 @f2(i32 %a) {
%b = add i32 %a, 1
ret i32 %b
}
)IR");
Function *F1 = M->getFunction("f1");
CallBase* CB = findCall(*F1, "b");
EXPECT_NE(CB, nullptr);
FunctionPropertiesInfo ExpectedInitial;
ExpectedInitial.BasicBlockCount = 1;
ExpectedInitial.TotalInstructionCount = 3;
ExpectedInitial.Uses = 1;
ExpectedInitial.DirectCallsToDefinedFunctions = 1;
FunctionPropertiesInfo ExpectedFinal = ExpectedInitial;
ExpectedFinal.DirectCallsToDefinedFunctions = 0;
auto FPI = buildFPI(*F1);
EXPECT_EQ(FPI, ExpectedInitial);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(FPI, ExpectedFinal);
}
TEST_F(FunctionPropertiesAnalysisTest, InlineSameBBLargerCFG) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
define i32 @f1(i32 %a) {
entry:
%i = icmp slt i32 %a, 0
br i1 %i, label %if.then, label %if.else
if.then:
%b = call i32 @f2(i32 %a)
%c1 = add i32 %b, 2
br label %end
if.else:
%c2 = add i32 %a, 1
br label %end
end:
%ret = phi i32 [%c1, %if.then],[%c2, %if.else]
ret i32 %ret
}
define i32 @f2(i32 %a) {
%b = add i32 %a, 1
ret i32 %b
}
)IR");
Function *F1 = M->getFunction("f1");
CallBase* CB = findCall(*F1, "b");
EXPECT_NE(CB, nullptr);
FunctionPropertiesInfo ExpectedInitial;
ExpectedInitial.BasicBlockCount = 4;
ExpectedInitial.BlocksReachedFromConditionalInstruction = 2;
ExpectedInitial.TotalInstructionCount = 9;
ExpectedInitial.Uses = 1;
ExpectedInitial.DirectCallsToDefinedFunctions = 1;
FunctionPropertiesInfo ExpectedFinal = ExpectedInitial;
ExpectedFinal.DirectCallsToDefinedFunctions = 0;
auto FPI = buildFPI(*F1);
EXPECT_EQ(FPI, ExpectedInitial);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(FPI, ExpectedFinal);
}
TEST_F(FunctionPropertiesAnalysisTest, InlineSameBBLoops) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
define i32 @f1(i32 %a) {
entry:
%i = icmp slt i32 %a, 0
br i1 %i, label %if.then, label %if.else
if.then:
%b = call i32 @f2(i32 %a)
%c1 = add i32 %b, 2
br label %end
if.else:
%c2 = add i32 %a, 1
br label %end
end:
%ret = phi i32 [%c1, %if.then],[%c2, %if.else]
ret i32 %ret
}
define i32 @f2(i32 %a) {
entry:
br label %loop
loop:
%indvar = phi i32 [%indvar.next, %loop], [0, %entry]
%b = add i32 %a, %indvar
%indvar.next = add i32 %indvar, 1
%cond = icmp slt i32 %indvar.next, %a
br i1 %cond, label %loop, label %exit
exit:
ret i32 %b
}
)IR");
Function *F1 = M->getFunction("f1");
CallBase* CB = findCall(*F1, "b");
EXPECT_NE(CB, nullptr);
FunctionPropertiesInfo ExpectedInitial;
ExpectedInitial.BasicBlockCount = 4;
ExpectedInitial.BlocksReachedFromConditionalInstruction = 2;
ExpectedInitial.TotalInstructionCount = 9;
ExpectedInitial.Uses = 1;
ExpectedInitial.DirectCallsToDefinedFunctions = 1;
FunctionPropertiesInfo ExpectedFinal;
ExpectedFinal.BasicBlockCount = 6;
ExpectedFinal.BlocksReachedFromConditionalInstruction = 4;
ExpectedFinal.Uses = 1;
ExpectedFinal.MaxLoopDepth = 1;
ExpectedFinal.TopLevelLoopCount = 1;
ExpectedFinal.TotalInstructionCount = 14;
auto FPI = buildFPI(*F1);
EXPECT_EQ(FPI, ExpectedInitial);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(FPI, ExpectedFinal);
}
TEST_F(FunctionPropertiesAnalysisTest, InvokeSimple) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
declare void @might_throw()
define internal void @callee() {
entry:
call void @might_throw()
ret void
}
define i32 @caller() personality i32 (...)* @__gxx_personality_v0 {
entry:
invoke void @callee()
to label %cont unwind label %exc
cont:
ret i32 0
exc:
%exn = landingpad {i8*, i32}
cleanup
ret i32 1
}
declare i32 @__gxx_personality_v0(...)
)IR");
Function *F1 = M->getFunction("caller");
CallBase* CB = findCall(*F1);
EXPECT_NE(CB, nullptr);
auto FPI = buildFPI(*F1);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(static_cast<size_t>(FPI.BasicBlockCount),
F1->getBasicBlockList().size());
EXPECT_EQ(static_cast<size_t>(FPI.TotalInstructionCount),
F1->getInstructionCount());
}
TEST_F(FunctionPropertiesAnalysisTest, InvokeUnreachableHandler) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
declare void @might_throw()
define internal i32 @callee() personality i32 (...)* @__gxx_personality_v0 {
entry:
invoke void @might_throw()
to label %cont unwind label %exc
cont:
ret i32 0
exc:
%exn = landingpad {i8*, i32}
cleanup
resume { i8*, i32 } %exn
}
define i32 @caller() personality i32 (...)* @__gxx_personality_v0 {
entry:
%X = invoke i32 @callee()
to label %cont unwind label %Handler
cont:
ret i32 %X
Handler:
%exn = landingpad {i8*, i32}
cleanup
ret i32 1
}
declare i32 @__gxx_personality_v0(...)
)IR");
Function *F1 = M->getFunction("caller");
CallBase* CB = findCall(*F1);
EXPECT_NE(CB, nullptr);
auto FPI = buildFPI(*F1);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(static_cast<size_t>(FPI.BasicBlockCount),
F1->getBasicBlockList().size() - 1);
EXPECT_EQ(static_cast<size_t>(FPI.TotalInstructionCount),
F1->getInstructionCount() - 2);
EXPECT_EQ(FPI, FunctionPropertiesInfo::getFunctionPropertiesInfo(*F1, FAM));
}
TEST_F(FunctionPropertiesAnalysisTest, Rethrow) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
declare void @might_throw()
define internal i32 @callee() personality i32 (...)* @__gxx_personality_v0 {
entry:
invoke void @might_throw()
to label %cont unwind label %exc
cont:
ret i32 0
exc:
%exn = landingpad {i8*, i32}
cleanup
resume { i8*, i32 } %exn
}
define i32 @caller() personality i32 (...)* @__gxx_personality_v0 {
entry:
%X = invoke i32 @callee()
to label %cont unwind label %Handler
cont:
ret i32 %X
Handler:
%exn = landingpad {i8*, i32}
cleanup
ret i32 1
}
declare i32 @__gxx_personality_v0(...)
)IR");
Function *F1 = M->getFunction("caller");
CallBase* CB = findCall(*F1);
EXPECT_NE(CB, nullptr);
auto FPI = buildFPI(*F1);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(static_cast<size_t>(FPI.BasicBlockCount),
F1->getBasicBlockList().size() - 1);
EXPECT_EQ(static_cast<size_t>(FPI.TotalInstructionCount),
F1->getInstructionCount() - 2);
EXPECT_EQ(FPI, FunctionPropertiesInfo::getFunctionPropertiesInfo(*F1, FAM));
}
TEST_F(FunctionPropertiesAnalysisTest, LPadChanges) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
declare void @external_func()
@exception_type1 = external global i8
@exception_type2 = external global i8
define internal void @inner() personality i8* null {
invoke void @external_func()
to label %cont unwind label %lpad
cont:
ret void
lpad:
%lp = landingpad i32
catch i8* @exception_type1
resume i32 %lp
}
define void @outer() personality i8* null {
invoke void @inner()
to label %cont unwind label %lpad
cont:
ret void
lpad:
%lp = landingpad i32
cleanup
catch i8* @exception_type2
resume i32 %lp
}
)IR");
Function *F1 = M->getFunction("outer");
CallBase* CB = findCall(*F1);
EXPECT_NE(CB, nullptr);
auto FPI = buildFPI(*F1);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(static_cast<size_t>(FPI.BasicBlockCount),
F1->getBasicBlockList().size() - 1);
EXPECT_EQ(static_cast<size_t>(FPI.TotalInstructionCount),
F1->getInstructionCount() - 2);
EXPECT_EQ(FPI, FunctionPropertiesInfo::getFunctionPropertiesInfo(*F1, FAM));
}
TEST_F(FunctionPropertiesAnalysisTest, LPadChangesConditional) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
declare void @external_func()
@exception_type1 = external global i8
@exception_type2 = external global i8
define internal void @inner() personality i8* null {
invoke void @external_func()
to label %cont unwind label %lpad
cont:
ret void
lpad:
%lp = landingpad i32
catch i8* @exception_type1
resume i32 %lp
}
define void @outer(i32 %a) personality i8* null {
entry:
%i = icmp slt i32 %a, 0
br i1 %i, label %if.then, label %cont
if.then:
invoke void @inner()
to label %cont unwind label %lpad
cont:
ret void
lpad:
%lp = landingpad i32
cleanup
catch i8* @exception_type2
resume i32 %lp
}
)IR");
Function *F1 = M->getFunction("outer");
CallBase* CB = findCall(*F1);
EXPECT_NE(CB, nullptr);
auto FPI = buildFPI(*F1);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(static_cast<size_t>(FPI.BasicBlockCount),
F1->getBasicBlockList().size() - 1);
EXPECT_EQ(static_cast<size_t>(FPI.TotalInstructionCount),
F1->getInstructionCount() - 2);
EXPECT_EQ(FPI, FunctionPropertiesInfo::getFunctionPropertiesInfo(*F1, FAM));
}
TEST_F(FunctionPropertiesAnalysisTest, InlineSameLoopBB) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
declare i32 @a()
declare i32 @b()
define i32 @f1(i32 %a) {
entry:
br label %loop
loop:
%i = call i32 @f2(i32 %a)
%c = icmp slt i32 %i, %a
br i1 %c, label %loop, label %end
end:
%r = phi i32 [%i, %loop], [%a, %entry]
ret i32 %r
}
define i32 @f2(i32 %a) {
%cnd = icmp slt i32 %a, 0
br i1 %cnd, label %then, label %else
then:
%r1 = call i32 @a()
br label %end
else:
%r2 = call i32 @b()
br label %end
end:
%r = phi i32 [%r1, %then], [%r2, %else]
ret i32 %r
}
)IR");
Function *F1 = M->getFunction("f1");
CallBase *CB = findCall(*F1);
EXPECT_NE(CB, nullptr);
FunctionPropertiesInfo ExpectedInitial;
ExpectedInitial.BasicBlockCount = 3;
ExpectedInitial.TotalInstructionCount = 6;
ExpectedInitial.BlocksReachedFromConditionalInstruction = 2;
ExpectedInitial.Uses = 1;
ExpectedInitial.DirectCallsToDefinedFunctions = 1;
ExpectedInitial.MaxLoopDepth = 1;
ExpectedInitial.TopLevelLoopCount = 1;
FunctionPropertiesInfo ExpectedFinal = ExpectedInitial;
ExpectedFinal.BasicBlockCount = 6;
ExpectedFinal.DirectCallsToDefinedFunctions = 0;
ExpectedFinal.BlocksReachedFromConditionalInstruction = 4;
ExpectedFinal.TotalInstructionCount = 12;
auto FPI = buildFPI(*F1);
EXPECT_EQ(FPI, ExpectedInitial);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(FPI, ExpectedFinal);
}
TEST_F(FunctionPropertiesAnalysisTest, Unreachable) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
define i64 @f1(i32 noundef %value) {
entry:
br i1 true, label %cond.true, label %cond.false
cond.true: ; preds = %entry
%conv2 = sext i32 %value to i64
br label %cond.end
cond.false: ; preds = %entry
%call3 = call noundef i64 @f2()
br label %extra
extra:
br label %extra2
extra2:
br label %cond.end
cond.end: ; preds = %cond.false, %cond.true
%cond = phi i64 [ %conv2, %cond.true ], [ %call3, %extra ]
ret i64 %cond
}
define i64 @f2() {
entry:
tail call void @llvm.trap()
unreachable
}
declare void @llvm.trap()
)IR");
Function *F1 = M->getFunction("f1");
CallBase *CB = findCall(*F1);
EXPECT_NE(CB, nullptr);
FunctionPropertiesInfo ExpectedInitial;
ExpectedInitial.BasicBlockCount = 6;
ExpectedInitial.TotalInstructionCount = 9;
ExpectedInitial.BlocksReachedFromConditionalInstruction = 2;
ExpectedInitial.Uses = 1;
ExpectedInitial.DirectCallsToDefinedFunctions = 1;
FunctionPropertiesInfo ExpectedFinal = ExpectedInitial;
ExpectedFinal.BasicBlockCount = 4;
ExpectedFinal.DirectCallsToDefinedFunctions = 0;
ExpectedFinal.TotalInstructionCount = 7;
auto FPI = buildFPI(*F1);
EXPECT_EQ(FPI, ExpectedInitial);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(FPI, ExpectedFinal);
}
TEST_F(FunctionPropertiesAnalysisTest, InvokeSkipLP) {
LLVMContext C;
std::unique_ptr<Module> M = makeLLVMModule(C,
R"IR(
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
define i64 @f1(i32 noundef %value) {
entry:
invoke fastcc void @f2() to label %cont unwind label %lpad
cont:
ret i64 1
lpad:
%lp = landingpad i32 cleanup
br label %ehcleanup
ehcleanup:
resume i32 0
}
define void @f2() {
invoke noundef void @f3() to label %exit unwind label %lpad
exit:
ret void
lpad:
%lp = landingpad i32 cleanup
resume i32 %lp
}
declare void @f3()
)IR");
Function *F1 = M->getFunction("f1");
CallBase *CB = findCall(*F1);
EXPECT_NE(CB, nullptr);
FunctionPropertiesInfo ExpectedInitial;
ExpectedInitial.BasicBlockCount = 4;
ExpectedInitial.TotalInstructionCount = 5;
ExpectedInitial.BlocksReachedFromConditionalInstruction = 0;
ExpectedInitial.Uses = 1;
ExpectedInitial.DirectCallsToDefinedFunctions = 1;
FunctionPropertiesInfo ExpectedFinal = ExpectedInitial;
ExpectedFinal.BasicBlockCount = 6;
ExpectedFinal.DirectCallsToDefinedFunctions = 0;
ExpectedFinal.TotalInstructionCount = 8;
auto FPI = buildFPI(*F1);
EXPECT_EQ(FPI, ExpectedInitial);
FunctionPropertiesUpdater FPU(FPI, *CB);
InlineFunctionInfo IFI;
auto IR = llvm::InlineFunction(*CB, IFI);
EXPECT_TRUE(IR.isSuccess());
invalidate(*F1);
FPU.finish(FAM);
EXPECT_EQ(FPI, ExpectedFinal);
}
}