diff --git a/llvm/include/llvm/Analysis/IRSimilarityIdentifier.h b/llvm/include/llvm/Analysis/IRSimilarityIdentifier.h --- a/llvm/include/llvm/Analysis/IRSimilarityIdentifier.h +++ b/llvm/include/llvm/Analysis/IRSimilarityIdentifier.h @@ -128,6 +128,16 @@ /// to a less than form. It is None otherwise. Optional RevisedPredicate; + /// This is only relevant if we are wrapping a CallInst. If we are requiring + /// that the function calls have matching names as well as types, and the + /// call is not an indirect call, this will hold the name of the function. If + /// it is an indirect string, it will be the empty string. However, if this + /// requirement is not in place it will be the empty string regardless of the + /// function call type. The value held here is used to create the hash of the + /// instruction, and check to make sure two instructions are close to one + /// another. + Optional CalleeName; + /// 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 @@ -168,6 +178,10 @@ /// instruction. the IRInstructionData must be wrapping a CmpInst. CmpInst::Predicate getPredicate() const; + /// Get the callee name that the call instruction is using for hashing the + /// instruction. The IRInstructionData must be wrapping a CallInst. + StringRef getCalleeName() const; + /// A function that swaps the predicates to their less than form if they are /// in a greater than form. Otherwise, the predicate is unchanged. /// @@ -185,6 +199,21 @@ 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. We may decide not to match by name in order to expand the + /// size of the regions we can match. If a function name has the same type + /// signature, but the different name, the region of code is still almost the + /// same. Since function names can be treated as constants, the name itself + /// could be extrapolated away. However, matching by name provides a + /// specificity and more "identical" code than not matching by name. + /// + /// \param MatchByName - A flag to mark whether we are using the called + /// function name as a differentiating parameter. + void setCalleeName(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 +252,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.CalleeName; 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_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 +377,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 +518,10 @@ // is not an indirect call. InstrType visitCallInst(CallInst &CI) { Function *F = CI.getCalledFunction(); - if (!F || CI.isIndirectCall() || !F->hasName()) + bool IsIndirectCall = CI.isIndirectCall(); + if (IsIndirectCall && !EnableIndirectCalls) + return Illegal; + if (!F && !IsIndirectCall) return Illegal; return Legal; } @@ -498,6 +536,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. @@ -882,9 +924,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 @@ -964,6 +1009,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; diff --git a/llvm/include/llvm/Transforms/IPO/IROutliner.h b/llvm/include/llvm/Transforms/IPO/IROutliner.h --- a/llvm/include/llvm/Transforms/IPO/IROutliner.h +++ b/llvm/include/llvm/Transforms/IPO/IROutliner.h @@ -365,7 +365,10 @@ // that they have a name in these cases. bool visitCallInst(CallInst &CI) { Function *F = CI.getCalledFunction(); - if (!F || CI.isIndirectCall() || !F->hasName()) + bool IsIndirectCall = CI.isIndirectCall(); + if (IsIndirectCall && !EnableIndirectCalls) + return false; + if (!F && !IsIndirectCall) return false; // Returning twice can cause issues with the state of the function call // that were not expected when the function was used, so we do not include @@ -389,6 +392,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. diff --git a/llvm/lib/Analysis/IRSimilarityIdentifier.cpp b/llvm/lib/Analysis/IRSimilarityIdentifier.cpp --- a/llvm/lib/Analysis/IRSimilarityIdentifier.cpp +++ b/llvm/lib/Analysis/IRSimilarityIdentifier.cpp @@ -31,6 +31,17 @@ "across branches for debugging purposes.")); } // namespace llvm +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) { @@ -88,6 +99,15 @@ } } +void IRInstructionData::setCalleeName(bool MatchByName) { + CallInst *CI = dyn_cast(Inst); + assert(CI && "Instruction must be call"); + + CalleeName = ""; + if (!CI->isIndirectCall() && MatchByName) + CalleeName = CI->getCalledFunction()->getName().str(); +} + CmpInst::Predicate IRInstructionData::predicateForConsistency(CmpInst *CI) { switch (CI->getPredicate()) { case CmpInst::FCMP_OGT: @@ -114,10 +134,13 @@ return cast(Inst)->getPredicate(); } -static StringRef getCalledFunctionName(CallInst &CI) { - assert(CI.getCalledFunction() != nullptr && "Called Function is nullptr?"); +StringRef IRInstructionData::getCalleeName() const { + assert(isa(Inst) && + "Can only get a name from a call instruction"); - return CI.getCalledFunction()->getName(); + assert(CalleeName.hasValue() && "CalleeName has not been set"); + + return *CalleeName; } bool IRSimilarity::isClose(const IRInstructionData &A, @@ -172,13 +195,11 @@ }); } - // If the instructions are functions, we make sure that the function name is - // the same. We already know that the types are since is isSameOperationAs is - // true. + // If the instructions are functions calls, we make sure that the function + // name is 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.getCalleeName().str().compare(B.getCalleeName().str()) != 0) return false; } @@ -246,6 +267,9 @@ if (isa(*It)) ID->setBranchSuccessors(BasicBlockToInteger); + if (isa(*It)) + ID->setCalleeName(EnableMatchCallsByName); + // Add to the instruction list bool WasInserted; DenseMap::iterator @@ -1077,6 +1101,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); @@ -1087,6 +1113,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; @@ -1107,7 +1135,8 @@ } bool IRSimilarityIdentifierWrapperPass::doInitialization(Module &M) { - IRSI.reset(new IRSimilarityIdentifier(!DisableBranches)); + IRSI.reset(new IRSimilarityIdentifier(!DisableBranches, !DisableIndirectCalls, + MatchCallsByName)); return false; } @@ -1125,7 +1154,8 @@ IRSimilarityIdentifier IRSimilarityAnalysis::run(Module &M, ModuleAnalysisManager &) { - auto IRSI = IRSimilarityIdentifier(!DisableBranches); + auto IRSI = IRSimilarityIdentifier(!DisableBranches, !DisableIndirectCalls, + MatchCallsByName); IRSI.findSimilarity(M); return IRSI; } diff --git a/llvm/lib/Transforms/IPO/IROutliner.cpp b/llvm/lib/Transforms/IPO/IROutliner.cpp --- a/llvm/lib/Transforms/IPO/IROutliner.cpp +++ b/llvm/lib/Transforms/IPO/IROutliner.cpp @@ -40,6 +40,10 @@ extern cl::opt DisableBranches; } // namespace llvm +// 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), @@ -2519,6 +2523,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(); diff --git a/llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll b/llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll --- a/llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll +++ b/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*); diff --git a/llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll b/llvm/test/Transforms/IROutliner/legal-indirect-calls.ll copy from llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll copy to llvm/test/Transforms/IROutliner/legal-indirect-calls.ll --- a/llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll +++ b/llvm/test/Transforms/IROutliner/legal-indirect-calls.ll @@ -1,24 +1,13 @@ -; 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 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 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) { -; 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_1(i32* [[A]], i32* [[B]], i32* [[C]]) -; CHECK-NEXT: call void [[FUNC:%.*]]() -; 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 @@ -34,16 +23,6 @@ } define void @function2(void()* %func) { -; 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_1(i32* [[A]], i32* [[B]], i32* [[C]]) -; CHECK-NEXT: call void [[FUNC:%.*]]() -; 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 @@ -57,3 +36,36 @@ %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 +; diff --git a/llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll b/llvm/test/Transforms/IROutliner/outlining-call-and-indirect.ll copy from llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll copy to llvm/test/Transforms/IROutliner/outlining-call-and-indirect.ll --- a/llvm/test/Transforms/IROutliner/illegal-indirect-calls.ll +++ b/llvm/test/Transforms/IROutliner/outlining-call-and-indirect.ll @@ -1,24 +1,12 @@ -; 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 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 can outline indirect and regular function calls +; when the type matches when it is not specified that the names must match. -declare void @f1(i32*, i32*); -declare void @f2(i32*, i32*); +declare void @f1(); define void @function1(void()* %func) { -; 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_1(i32* [[A]], i32* [[B]], i32* [[C]]) -; CHECK-NEXT: call void [[FUNC:%.*]]() -; 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 @@ -33,17 +21,7 @@ ret void } -define void @function2(void()* %func) { -; 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_1(i32* [[A]], i32* [[B]], i32* [[C]]) -; CHECK-NEXT: call void [[FUNC:%.*]]() -; CHECK-NEXT: call void @outlined_ir_func_0(i32* [[A]], i32* [[B]], i32* [[C]]) -; CHECK-NEXT: ret void -; +define void @function2() { entry: %a = alloca i32, align 4 %b = alloca i32, align 4 @@ -51,9 +29,42 @@ store i32 2, i32* %a, align 4 store i32 3, i32* %b, align 4 store i32 4, i32* %c, align 4 - call void %func() + 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 +; diff --git a/llvm/test/Transforms/IROutliner/outlining-calls.ll b/llvm/test/Transforms/IROutliner/outlining-calls-names-must-match.ll copy from llvm/test/Transforms/IROutliner/outlining-calls.ll copy to llvm/test/Transforms/IROutliner/outlining-calls-names-must-match.ll --- a/llvm/test/Transforms/IROutliner/outlining-calls.ll +++ b/llvm/test/Transforms/IROutliner/outlining-calls-names-must-match.ll @@ -1,5 +1,5 @@ ; 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 -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. diff --git a/llvm/test/Transforms/IROutliner/outlining-calls.ll b/llvm/test/Transforms/IROutliner/outlining-calls.ll --- a/llvm/test/Transforms/IROutliner/outlining-calls.ll +++ b/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 +; diff --git a/llvm/unittests/Analysis/IRSimilarityIdentifierTest.cpp b/llvm/unittests/Analysis/IRSimilarityIdentifierTest.cpp --- a/llvm/unittests/Analysis/IRSimilarityIdentifierTest.cpp +++ b/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) {