//===- FunctionPropertiesAnalysisTest.cpp - Function Properties 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/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 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 DT; std::unique_ptr 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 makeLLVMModule(LLVMContext &C, const char *IR) { SMDiagnostic Err; std::unique_ptr 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(&I)) if (!Name || CB->getName() == Name) return CB; return nullptr; } }; TEST_F(FunctionPropertiesAnalysisTest, BasicTest) { LLVMContext C; std::unique_ptr 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); // 2 Users: top is one. The other is added because @branches is not internal, // so it may have external callers. 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 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 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 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 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(FPI.BasicBlockCount), F1->size()); EXPECT_EQ(static_cast(FPI.TotalInstructionCount), F1->getInstructionCount()); } TEST_F(FunctionPropertiesAnalysisTest, InvokeUnreachableHandler) { LLVMContext C; std::unique_ptr 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(FPI.BasicBlockCount), F1->size() - 1); EXPECT_EQ(static_cast(FPI.TotalInstructionCount), F1->getInstructionCount() - 2); EXPECT_EQ(FPI, FunctionPropertiesInfo::getFunctionPropertiesInfo(*F1, FAM)); } TEST_F(FunctionPropertiesAnalysisTest, Rethrow) { LLVMContext C; std::unique_ptr 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(FPI.BasicBlockCount), F1->size() - 1); EXPECT_EQ(static_cast(FPI.TotalInstructionCount), F1->getInstructionCount() - 2); EXPECT_EQ(FPI, FunctionPropertiesInfo::getFunctionPropertiesInfo(*F1, FAM)); } TEST_F(FunctionPropertiesAnalysisTest, LPadChanges) { LLVMContext C; std::unique_ptr 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(FPI.BasicBlockCount), F1->size() - 1); EXPECT_EQ(static_cast(FPI.TotalInstructionCount), F1->getInstructionCount() - 2); EXPECT_EQ(FPI, FunctionPropertiesInfo::getFunctionPropertiesInfo(*F1, FAM)); } TEST_F(FunctionPropertiesAnalysisTest, LPadChangesConditional) { LLVMContext C; std::unique_ptr 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(FPI.BasicBlockCount), F1->size() - 1); EXPECT_EQ(static_cast(FPI.TotalInstructionCount), F1->getInstructionCount() - 2); EXPECT_EQ(FPI, FunctionPropertiesInfo::getFunctionPropertiesInfo(*F1, FAM)); } TEST_F(FunctionPropertiesAnalysisTest, InlineSameLoopBB) { LLVMContext C; std::unique_ptr 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 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 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"); // The outcome of inlining will be that lpad becomes unreachable. The landing // pad of the invoke inherited from f2 will land on a new bb which will branch // to a bb containing the body of lpad. 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); } } // end anonymous namespace