Index: llvm/include/llvm/Analysis/IRSimilarityIdentifier.h =================================================================== --- llvm/include/llvm/Analysis/IRSimilarityIdentifier.h +++ llvm/include/llvm/Analysis/IRSimilarityIdentifier.h @@ -128,6 +128,10 @@ /// to a less than form. It is None otherwise. Optional RevisedPredicate; + /// This is only relevant if we are wrapping a CallInst and we are requiring + // that the name of the function called be the same. + Optional CallInstName; + /// This structure holds the distances of how far "ahead of" or "behind" the /// target blocks of a branch, or the incoming blocks of a phi nodes are. /// If the value is negative, it means that the block was registered before @@ -185,6 +189,16 @@ void setBranchSuccessors(DenseMap &BasicBlockToInteger); + /// For an IRInstructionData containing a CallInst, set the function name + /// appropriately. This will be an empty string if it is an indirect call, + /// or we are not matching by name of the called function. It will be the + /// name of the function if \p MatchByName is true and it is not an indirect + /// call. + /// + /// \param MatchByName - A flag to mark whether we are using the called + /// function name as a differentiating parameter. + void setFunctionName(bool MatchByName = true); + /// Hashes \p Value based on its opcode, types, and operand types. /// Two IRInstructionData instances produce the same hash when they perform /// the same operation. @@ -223,12 +237,14 @@ llvm::hash_value(ID.Inst->getType()), llvm::hash_value(ID.getPredicate()), llvm::hash_combine_range(OperTypes.begin(), OperTypes.end())); - else if (CallInst *CI = dyn_cast(ID.Inst)) + else if (CallInst *CI = dyn_cast(ID.Inst)) { + std::string FunctionName = *ID.CallInstName; return llvm::hash_combine( - llvm::hash_value(ID.Inst->getOpcode()), - llvm::hash_value(ID.Inst->getType()), - llvm::hash_value(CI->getCalledFunction()->getName().str()), - llvm::hash_combine_range(OperTypes.begin(), OperTypes.end())); + llvm::hash_value(ID.Inst->getOpcode()), + llvm::hash_value(ID.Inst->getType()), + llvm::hash_value(FunctionName), + llvm::hash_combine_range(OperTypes.begin(), OperTypes.end())); + } return llvm::hash_combine( llvm::hash_value(ID.Inst->getOpcode()), llvm::hash_value(ID.Inst->getType()), @@ -346,6 +362,10 @@ /// to be considered for similarity. bool HaveLegalRange = false; + /// Marks whether we should use exact function names, as well as types to + /// find similarity between calls. + bool EnableMatchCallsByName = false; + /// This allocator pointer is in charge of holding on to the IRInstructionData /// so it is not deallocated until whatever external tool is using it is done /// with the information. @@ -483,7 +503,9 @@ // is not an indirect call. InstrType visitCallInst(CallInst &CI) { Function *F = CI.getCalledFunction(); - if (!F || CI.isIndirectCall() || !F->hasName()) + if (CI.isIndirectCall() && !EnableIndirectCalls) + return Illegal; + if (!F && !CI.isIndirectCall()) return Illegal; return Legal; } @@ -498,6 +520,10 @@ // The flag variable that lets the classifier know whether we should // allow branches to be checked for similarity. bool EnableBranches = false; + + // The flag variable that lets the classifier know whether we should + // allow indirect calls to be considered legal instructions. + bool EnableIndirectCalls = false; }; /// Maps an Instruction to a member of InstrType. @@ -884,9 +910,12 @@ /// analyzing the module. class IRSimilarityIdentifier { public: - IRSimilarityIdentifier(bool MatchBranches = true) + IRSimilarityIdentifier(bool MatchBranches = true, + bool MatchIndirectCalls = true, + bool MatchCallsWithName = false) : Mapper(&InstDataAllocator, &InstDataListAllocator), - EnableBranches(MatchBranches) {} + EnableBranches(MatchBranches), EnableIndirectCalls(MatchIndirectCalls), + EnableMatchingCallsByName(MatchCallsWithName) {} private: /// Map the instructions in the module to unsigned integers, using mapping @@ -966,6 +995,15 @@ /// similarity, or only look within basic blocks. bool EnableBranches = true; + /// The flag variable that marks whether we allow indirect calls to be checked + /// for similarity, or exclude them as a legal instruction. + bool EnableIndirectCalls = true; + + /// The flag variable that marks whether we allow calls to be marked as + /// similar if they do not have the same name, only the same calling + /// convention, attributes and type signature. + bool EnableMatchingCallsByName = true; + /// The SimilarityGroups found with the most recent run of \ref /// findSimilarity. None if there is no recent run. Optional SimilarityCandidates; Index: llvm/include/llvm/Transforms/IPO/IROutliner.h =================================================================== --- llvm/include/llvm/Transforms/IPO/IROutliner.h +++ llvm/include/llvm/Transforms/IPO/IROutliner.h @@ -358,7 +358,9 @@ // that they have a name in these cases. bool visitCallInst(CallInst &CI) { Function *F = CI.getCalledFunction(); - if (!F || CI.isIndirectCall() || !F->hasName()) + if (CI.isIndirectCall() && !EnableIndirectCalls) + return false; + if (!F && !CI.isIndirectCall()) return false; return true; } @@ -377,6 +379,10 @@ // The flag variable that marks whether we should allow branch instructions // to be outlined. bool EnableBranches = false; + + // The flag variable that marks whether we should allow indirect calls + // to be outlined. + bool EnableIndirectCalls = true; }; /// A InstVisitor used to exclude certain instructions from being outlined. Index: llvm/lib/Analysis/IRSimilarityIdentifier.cpp =================================================================== --- llvm/lib/Analysis/IRSimilarityIdentifier.cpp +++ llvm/lib/Analysis/IRSimilarityIdentifier.cpp @@ -29,6 +29,16 @@ cl::desc("disable similarity matching, and outlining, " "across branches for debugging purposes.")); +cl::opt + DisableIndirectCalls("no-ir-sim-indirect-calls", cl::init(false), + cl::ReallyHidden, + cl::desc("disable outlining indirect calls.")); + +cl::opt + MatchCallsByName("ir-sim-calls-by-name", cl::init(false), cl::ReallyHidden, + cl::desc("only allow matching call instructions if the " + "name and type signature match.")); + IRInstructionData::IRInstructionData(Instruction &I, bool Legality, IRInstructionDataList &IDList) : Inst(&I), Legal(Legality), IDL(&IDList) { @@ -86,6 +96,15 @@ } } +void IRInstructionData::setFunctionName(bool MatchByName) { + CallInst *CI = dyn_cast(Inst); + assert(CI && "Instruction must be call"); + + CallInstName = ""; + if (!CI->isIndirectCall() && MatchByName) + CallInstName = CI->getCalledFunction()->getName().str(); +} + CmpInst::Predicate IRInstructionData::predicateForConsistency(CmpInst *CI) { switch (CI->getPredicate()) { case CmpInst::FCMP_OGT: @@ -112,12 +131,6 @@ return cast(Inst)->getPredicate(); } -static StringRef getCalledFunctionName(CallInst &CI) { - assert(CI.getCalledFunction() != nullptr && "Called Function is nullptr?"); - - return CI.getCalledFunction()->getName(); -} - bool IRSimilarity::isClose(const IRInstructionData &A, const IRInstructionData &B) { @@ -174,9 +187,7 @@ // the same. We already know that the types are since is isSameOperationAs is // true. if (isa(A.Inst) && isa(B.Inst)) { - CallInst *CIA = cast(A.Inst); - CallInst *CIB = cast(B.Inst); - if (getCalledFunctionName(*CIA).compare(getCalledFunctionName(*CIB)) != 0) + if (A.CallInstName->compare(*B.CallInstName) != 0) return false; } @@ -243,6 +254,9 @@ if (isa(*It)) ID->setBranchSuccessors(BasicBlockToInteger); + + if (isa(*It)) + ID->setFunctionName(EnableMatchCallsByName); // Add to the instruction list bool WasInserted; @@ -1074,6 +1088,8 @@ std::vector InstrList; std::vector IntegerMapping; Mapper.InstClassifier.EnableBranches = this->EnableBranches; + Mapper.InstClassifier.EnableIndirectCalls = EnableIndirectCalls; + Mapper.EnableMatchCallsByName = EnableMatchingCallsByName; populateMapper(Modules, InstrList, IntegerMapping); findCandidates(InstrList, IntegerMapping); @@ -1084,6 +1100,8 @@ SimilarityGroupList &IRSimilarityIdentifier::findSimilarity(Module &M) { resetSimilarityCandidates(); Mapper.InstClassifier.EnableBranches = this->EnableBranches; + Mapper.InstClassifier.EnableIndirectCalls = EnableIndirectCalls; + Mapper.EnableMatchCallsByName = EnableMatchingCallsByName; std::vector InstrList; std::vector IntegerMapping; @@ -1104,7 +1122,8 @@ } bool IRSimilarityIdentifierWrapperPass::doInitialization(Module &M) { - IRSI.reset(new IRSimilarityIdentifier(!DisableBranches)); + IRSI.reset(new IRSimilarityIdentifier(!DisableBranches, !DisableIndirectCalls, + MatchCallsByName)); return false; } @@ -1122,7 +1141,8 @@ IRSimilarityIdentifier IRSimilarityAnalysis::run(Module &M, ModuleAnalysisManager &) { - auto IRSI = IRSimilarityIdentifier(!DisableBranches); + auto IRSI = IRSimilarityIdentifier(!DisableBranches, !DisableIndirectCalls, + MatchCallsByName); IRSI.findSimilarity(M); return IRSI; } Index: llvm/lib/Transforms/IPO/IROutliner.cpp =================================================================== --- llvm/lib/Transforms/IPO/IROutliner.cpp +++ llvm/lib/Transforms/IPO/IROutliner.cpp @@ -38,6 +38,10 @@ // matching and outlining. extern cl::opt DisableBranches; +// A command flag to be used for debugging to indirect calls from similarity +// matching and outlining. +extern cl::opt DisableIndirectCalls; + // Set to true if the user wants the ir outliner to run on linkonceodr linkage // functions. This is false by default because the linker can dedupe linkonceodr // functions. Since the outliner is confined to a single module (modulo LTO), @@ -2468,6 +2472,7 @@ unsigned IROutliner::doOutline(Module &M) { // Find the possible similarity sections. InstructionClassifier.EnableBranches = !DisableBranches; + InstructionClassifier.EnableIndirectCalls = !DisableIndirectCalls; IRSimilarityIdentifier &Identifier = getIRSI(M); SimilarityGroupList &SimilarityCandidates = *Identifier.getSimilarity(); Index: llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll =================================================================== --- llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll +++ llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll @@ -1,9 +1,8 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py -; RUN: opt -S -verify -iroutliner -ir-outlining-no-cost < %s | FileCheck %s +; RUN: opt -S -verify -iroutliner -ir-outlining-no-cost --no-ir-sim-indirect-calls < %s | FileCheck %s -; This test checks that we do not outline indirect calls. We cannot guarantee -; that we have the same name in these cases, so two indirect calls cannot -; be considered similar. +; This test checks that we do not outline indirect calls when it is specified +; that we should not. declare void @f1(i32*, i32*); declare void @f2(i32*, i32*); Index: llvm/test/Transforms/IROutliner/legal-indirect-calls.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/IROutliner/legal-indirect-calls.ll @@ -0,0 +1,71 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --include-generated-funcs +; RUN: opt -S -verify -iroutliner -ir-outlining-no-cost < %s | FileCheck %s + +; This test checks that we do outline indirect calls when it is not specified +; that we should not. + +declare void @f1(i32*, i32*); +declare void @f2(i32*, i32*); + +define void @function1(void()* %func) { +entry: + %a = alloca i32, align 4 + %b = alloca i32, align 4 + %c = alloca i32, align 4 + store i32 2, i32* %a, align 4 + store i32 3, i32* %b, align 4 + store i32 4, i32* %c, align 4 + call void %func() + %al = load i32, i32* %a + %bl = load i32, i32* %b + %cl = load i32, i32* %c + ret void +} + +define void @function2(void()* %func) { +entry: + %a = alloca i32, align 4 + %b = alloca i32, align 4 + %c = alloca i32, align 4 + store i32 2, i32* %a, align 4 + store i32 3, i32* %b, align 4 + store i32 4, i32* %c, align 4 + call void %func() + %al = load i32, i32* %a + %bl = load i32, i32* %b + %cl = load i32, i32* %c + ret void +} +; CHECK-LABEL: @function1( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]], void ()* [[FUNC:%.*]]) +; CHECK-NEXT: ret void +; +; +; CHECK-LABEL: @function2( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]], void ()* [[FUNC:%.*]]) +; CHECK-NEXT: ret void +; +; +; CHECK-LABEL: define internal void @outlined_ir_func_0( +; CHECK-NEXT: newFuncRoot: +; CHECK-NEXT: br label [[ENTRY_TO_OUTLINE:%.*]] +; CHECK: entry_to_outline: +; CHECK-NEXT: store i32 2, i32* [[TMP0:%.*]], align 4 +; CHECK-NEXT: store i32 3, i32* [[TMP1:%.*]], align 4 +; CHECK-NEXT: store i32 4, i32* [[TMP2:%.*]], align 4 +; CHECK-NEXT: call void [[TMP3:%.*]]() +; CHECK-NEXT: [[AL:%.*]] = load i32, i32* [[TMP0]], align 4 +; CHECK-NEXT: [[BL:%.*]] = load i32, i32* [[TMP1]], align 4 +; CHECK-NEXT: [[CL:%.*]] = load i32, i32* [[TMP2]], align 4 +; CHECK-NEXT: br label [[ENTRY_AFTER_OUTLINE_EXITSTUB:%.*]] +; CHECK: entry_after_outline.exitStub: +; CHECK-NEXT: ret void +; Index: llvm/test/Transforms/IROutliner/outlining-call-and-indirect.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/IROutliner/outlining-call-and-indirect.ll @@ -0,0 +1,70 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --include-generated-funcs +; RUN: opt -S -verify -iroutliner -ir-outlining-no-cost < %s | FileCheck %s + +; This test checks that we do can outline indirect and regular function calls +; when the type matches when it is not specified that the names must match. + +declare void @f1(); + +define void @function1(void()* %func) { +entry: + %a = alloca i32, align 4 + %b = alloca i32, align 4 + %c = alloca i32, align 4 + store i32 2, i32* %a, align 4 + store i32 3, i32* %b, align 4 + store i32 4, i32* %c, align 4 + call void %func() + %al = load i32, i32* %a + %bl = load i32, i32* %b + %cl = load i32, i32* %c + ret void +} + +define void @function2() { +entry: + %a = alloca i32, align 4 + %b = alloca i32, align 4 + %c = alloca i32, align 4 + store i32 2, i32* %a, align 4 + store i32 3, i32* %b, align 4 + store i32 4, i32* %c, align 4 + call void @f1() + %al = load i32, i32* %a + %bl = load i32, i32* %b + %cl = load i32, i32* %c + ret void +} +; CHECK-LABEL: @function1( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]], void ()* [[FUNC:%.*]]) +; CHECK-NEXT: ret void +; +; +; CHECK-LABEL: @function2( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]], void ()* @f1) +; CHECK-NEXT: ret void +; +; +; CHECK-LABEL: define internal void @outlined_ir_func_0( +; CHECK-NEXT: newFuncRoot: +; CHECK-NEXT: br label [[ENTRY_TO_OUTLINE:%.*]] +; CHECK: entry_to_outline: +; CHECK-NEXT: store i32 2, i32* [[TMP0:%.*]], align 4 +; CHECK-NEXT: store i32 3, i32* [[TMP1:%.*]], align 4 +; CHECK-NEXT: store i32 4, i32* [[TMP2:%.*]], align 4 +; CHECK-NEXT: call void [[TMP3:%.*]]() +; CHECK-NEXT: [[AL:%.*]] = load i32, i32* [[TMP0]], align 4 +; CHECK-NEXT: [[BL:%.*]] = load i32, i32* [[TMP1]], align 4 +; CHECK-NEXT: [[CL:%.*]] = load i32, i32* [[TMP2]], align 4 +; CHECK-NEXT: br label [[ENTRY_AFTER_OUTLINE_EXITSTUB:%.*]] +; CHECK: entry_after_outline.exitStub: +; CHECK-NEXT: ret void +; Index: llvm/test/Transforms/IROutliner/outlining-calls-names-must-match.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/IROutliner/outlining-calls-names-must-match.ll @@ -0,0 +1,93 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; RUN: opt -S -verify -iroutliner -ir-outlining-no-cost -ir-sim-calls-by-name < %s | FileCheck %s + +; This test checks that we do can outline calls, but only if they have the same +; function type and the same name. + +declare void @f1(i32*, i32*); +declare void @f2(i32*, i32*); + +define void @function1() { +; CHECK-LABEL: @function1( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]]) +; CHECK-NEXT: ret void +; +entry: + %a = alloca i32, align 4 + %b = alloca i32, align 4 + %c = alloca i32, align 4 + store i32 2, i32* %a, align 4 + store i32 3, i32* %b, align 4 + store i32 4, i32* %c, align 4 + call void @f1(i32* %a, i32* %b) + %al = load i32, i32* %a + %bl = load i32, i32* %b + %cl = load i32, i32* %c + ret void +} + +define void @function2() { +; CHECK-LABEL: @function2( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]]) +; CHECK-NEXT: ret void +; +entry: + %a = alloca i32, align 4 + %b = alloca i32, align 4 + %c = alloca i32, align 4 + store i32 2, i32* %a, align 4 + store i32 3, i32* %b, align 4 + store i32 4, i32* %c, align 4 + call void @f1(i32* %a, i32* %b) + %al = load i32, i32* %a + %bl = load i32, i32* %b + %cl = load i32, i32* %c + ret void +} + +define void @function3() { +; CHECK-LABEL: @function3( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: store i32 2, i32* [[A]], align 4 +; CHECK-NEXT: store i32 3, i32* [[B]], align 4 +; CHECK-NEXT: store i32 4, i32* [[C]], align 4 +; CHECK-NEXT: call void @f2(i32* [[A]], i32* [[B]]) +; CHECK-NEXT: [[AL:%.*]] = load i32, i32* [[A]], align 4 +; CHECK-NEXT: [[BL:%.*]] = load i32, i32* [[B]], align 4 +; CHECK-NEXT: [[CL:%.*]] = load i32, i32* [[C]], align 4 +; CHECK-NEXT: ret void +; +entry: + %a = alloca i32, align 4 + %b = alloca i32, align 4 + %c = alloca i32, align 4 + store i32 2, i32* %a, align 4 + store i32 3, i32* %b, align 4 + store i32 4, i32* %c, align 4 + call void @f2(i32* %a, i32* %b) + %al = load i32, i32* %a + %bl = load i32, i32* %b + %cl = load i32, i32* %c + ret void +} + +; CHECK: define internal void @outlined_ir_func_0(i32* [[ARG0:%.*]], i32* [[ARG1:%.*]], i32* [[ARG2:%.*]]) +; CHECK: entry_to_outline: +; CHECK-NEXT: store i32 2, i32* [[ARG0]], align 4 +; CHECK-NEXT: store i32 3, i32* [[ARG1]], align 4 +; CHECK-NEXT: store i32 4, i32* [[ARG2]], align 4 +; CHECK-NEXT: call void @f1(i32* [[ARG0]], i32* [[ARG1]]) +; CHECK-NEXT: [[AL:%.*]] = load i32, i32* [[ARG0]], align 4 +; CHECK-NEXT: [[BL:%.*]] = load i32, i32* [[ARG1]], align 4 +; CHECK-NEXT: [[CL:%.*]] = load i32, i32* [[ARG2]], align 4 Index: llvm/test/Transforms/IROutliner/outlining-calls.ll =================================================================== --- llvm/test/Transforms/IROutliner/outlining-calls.ll +++ llvm/test/Transforms/IROutliner/outlining-calls.ll @@ -1,4 +1,4 @@ -; NOTE: Assertions have been autogenerated by utils/update_test_checks.py +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --include-generated-funcs ; RUN: opt -S -verify -iroutliner -ir-outlining-no-cost < %s | FileCheck %s ; This test checks that we do can outline calls, but only if they have the same @@ -8,14 +8,6 @@ declare void @f2(i32*, i32*); define void @function1() { -; CHECK-LABEL: @function1( -; CHECK-NEXT: entry: -; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 -; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 -; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 -; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]]) -; CHECK-NEXT: ret void -; entry: %a = alloca i32, align 4 %b = alloca i32, align 4 @@ -31,14 +23,6 @@ } define void @function2() { -; CHECK-LABEL: @function2( -; CHECK-NEXT: entry: -; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 -; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 -; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 -; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]]) -; CHECK-NEXT: ret void -; entry: %a = alloca i32, align 4 %b = alloca i32, align 4 @@ -54,20 +38,6 @@ } define void @function3() { -; CHECK-LABEL: @function3( -; CHECK-NEXT: entry: -; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 -; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 -; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 -; CHECK-NEXT: store i32 2, i32* [[A]], align 4 -; CHECK-NEXT: store i32 3, i32* [[B]], align 4 -; CHECK-NEXT: store i32 4, i32* [[C]], align 4 -; CHECK-NEXT: call void @f2(i32* [[A]], i32* [[B]]) -; CHECK-NEXT: [[AL:%.*]] = load i32, i32* [[A]], align 4 -; CHECK-NEXT: [[BL:%.*]] = load i32, i32* [[B]], align 4 -; CHECK-NEXT: [[CL:%.*]] = load i32, i32* [[C]], align 4 -; CHECK-NEXT: ret void -; entry: %a = alloca i32, align 4 %b = alloca i32, align 4 @@ -82,12 +52,45 @@ ret void } -; CHECK: define internal void @outlined_ir_func_0(i32* [[ARG0:%.*]], i32* [[ARG1:%.*]], i32* [[ARG2:%.*]]) -; CHECK: entry_to_outline: -; CHECK-NEXT: store i32 2, i32* [[ARG0]], align 4 -; CHECK-NEXT: store i32 3, i32* [[ARG1]], align 4 -; CHECK-NEXT: store i32 4, i32* [[ARG2]], align 4 -; CHECK-NEXT: call void @f1(i32* [[ARG0]], i32* [[ARG1]]) -; CHECK-NEXT: [[AL:%.*]] = load i32, i32* [[ARG0]], align 4 -; CHECK-NEXT: [[BL:%.*]] = load i32, i32* [[ARG1]], align 4 -; CHECK-NEXT: [[CL:%.*]] = load i32, i32* [[ARG2]], align 4 +; CHECK-LABEL: @function1( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]], void (i32*, i32*)* @f1) +; CHECK-NEXT: ret void +; +; +; CHECK-LABEL: @function2( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]], void (i32*, i32*)* @f1) +; CHECK-NEXT: ret void +; +; +; CHECK-LABEL: @function3( +; CHECK-NEXT: entry: +; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[B:%.*]] = alloca i32, align 4 +; CHECK-NEXT: [[C:%.*]] = alloca i32, align 4 +; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]], void (i32*, i32*)* @f2) +; CHECK-NEXT: ret void +; +; +; CHECK-LABEL: define internal void @outlined_ir_func_0( +; CHECK-NEXT: newFuncRoot: +; CHECK-NEXT: br label [[ENTRY_TO_OUTLINE:%.*]] +; CHECK: entry_to_outline: +; CHECK-NEXT: store i32 2, i32* [[TMP0:%.*]], align 4 +; CHECK-NEXT: store i32 3, i32* [[TMP1:%.*]], align 4 +; CHECK-NEXT: store i32 4, i32* [[TMP2:%.*]], align 4 +; CHECK-NEXT: call void [[TMP3:%.*]](i32* [[TMP0]], i32* [[TMP1]]) +; CHECK-NEXT: [[AL:%.*]] = load i32, i32* [[TMP0]], align 4 +; CHECK-NEXT: [[BL:%.*]] = load i32, i32* [[TMP1]], align 4 +; CHECK-NEXT: [[CL:%.*]] = load i32, i32* [[TMP2]], align 4 +; CHECK-NEXT: br label [[ENTRY_AFTER_OUTLINE_EXITSTUB:%.*]] +; CHECK: entry_after_outline.exitStub: +; CHECK-NEXT: ret void +; Index: llvm/unittests/Analysis/IRSimilarityIdentifierTest.cpp =================================================================== --- llvm/unittests/Analysis/IRSimilarityIdentifierTest.cpp +++ llvm/unittests/Analysis/IRSimilarityIdentifierTest.cpp @@ -933,8 +933,8 @@ ASSERT_NE(UnsignedVec[0], UnsignedVec[1]); } -// Checks that indirect call instructions are mapped to be illegal since we -// cannot guarantee the same function in two different cases. +// Checks that indirect call instructions are mapped to be illegal when it is +// specified to disallow them. TEST(IRInstructionMapper, CallsIllegalIndirect) { StringRef ModuleString = R"( define i32 @f(void()* %func) { @@ -951,12 +951,39 @@ SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); + Mapper.InstClassifier.EnableIndirectCalls = false; getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); ASSERT_EQ(UnsignedVec.size(), static_cast(0)); } +// Checks that indirect call instructions are mapped to be legal when it is not +// specified to disallow them. +TEST(IRInstructionMapper, CallsLegalIndirect) { + StringRef ModuleString = R"( + define i32 @f(void()* %func) { + bb0: + call void %func() + call void %func() + ret i32 0 + })"; + LLVMContext Context; + std::unique_ptr M = makeLLVMModule(Context, ModuleString); + + std::vector InstrList; + std::vector UnsignedVec; + + SpecificBumpPtrAllocator InstDataAllocator; + SpecificBumpPtrAllocator IDLAllocator; + IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); + Mapper.InstClassifier.EnableIndirectCalls = true; + getVectors(*M, Mapper, InstrList, UnsignedVec); + + ASSERT_EQ(InstrList.size(), UnsignedVec.size()); + ASSERT_EQ(UnsignedVec.size(), static_cast(3)); +} + // Checks that a call instruction is mapped to be legal. Here we check that // a call with the same name, and same types are mapped to the same // value. @@ -986,8 +1013,8 @@ } // Here we check that a calls with different names, but the same arguments types -// are mapped to different value. -TEST(IRInstructionMapper, CallsSameArgTypeDifferentName) { +// are mapped to different value when specified that the name must match. +TEST(IRInstructionMapper, CallsSameArgTypeDifferentNameDisallowed) { StringRef ModuleString = R"( declare i32 @f1(i32, i32) declare i32 @f2(i32, i32) @@ -1006,6 +1033,7 @@ SpecificBumpPtrAllocator InstDataAllocator; SpecificBumpPtrAllocator IDLAllocator; IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); + Mapper.EnableMatchCallsByName = true; getVectors(*M, Mapper, InstrList, UnsignedVec); ASSERT_EQ(InstrList.size(), UnsignedVec.size()); @@ -1013,6 +1041,35 @@ ASSERT_NE(UnsignedVec[0], UnsignedVec[1]); } +// Here we check that a calls with different names, but the same arguments types +// are mapped to the same value when it is not specifed that they must match. +TEST(IRInstructionMapper, CallsSameArgTypeDifferentName) { + StringRef ModuleString = R"( + declare i32 @f1(i32, i32) + declare i32 @f2(i32, i32) + define i32 @f(i32 %a, i32 %b) { + bb0: + %0 = call i32 @f1(i32 %a, i32 %b) + %1 = call i32 @f2(i32 %a, i32 %b) + ret i32 0 + })"; + LLVMContext Context; + std::unique_ptr M = makeLLVMModule(Context, ModuleString); + + std::vector InstrList; + std::vector UnsignedVec; + + SpecificBumpPtrAllocator InstDataAllocator; + SpecificBumpPtrAllocator IDLAllocator; + IRInstructionMapper Mapper(&InstDataAllocator, &IDLAllocator); + Mapper.EnableMatchCallsByName = false; + getVectors(*M, Mapper, InstrList, UnsignedVec); + + ASSERT_EQ(InstrList.size(), UnsignedVec.size()); + ASSERT_EQ(UnsignedVec.size(), static_cast(3)); + ASSERT_EQ(UnsignedVec[0], UnsignedVec[1]); +} + // Here we check that a calls with different names, and different arguments // types are mapped to different value. TEST(IRInstructionMapper, CallsDifferentArgTypeDifferentName) {