diff --git a/llvm/include/llvm/IR/ModuleSummaryIndex.h b/llvm/include/llvm/IR/ModuleSummaryIndex.h --- a/llvm/include/llvm/IR/ModuleSummaryIndex.h +++ b/llvm/include/llvm/IR/ModuleSummaryIndex.h @@ -42,8 +42,18 @@ #include #include +namespace Opt { +enum MergeLevelEnum { none, size, all }; +} + namespace llvm { +// This is for now until I figure out a better place to put this declaration in. +// Compute a similarity metric for function F. Useful for merging functions. +unsigned profileFunction(const Function *F); +bool isComparisonCandidate(const Function *F); +bool isAliasCapable(const Function* G); + namespace yaml { template struct MappingTraits; @@ -567,7 +577,7 @@ std::vector(), std::vector(), std::vector(), - std::vector()); + std::vector(), 0); } /// A dummy node to reference external functions that aren't in the index @@ -590,6 +600,7 @@ std::vector CallGraphEdgeList; std::unique_ptr TIdInfo; + unsigned SimilarityHash; public: FunctionSummary(GVFlags Flags, unsigned NumInsts, FFlags FunFlags, @@ -599,10 +610,12 @@ std::vector TypeTestAssumeVCalls, std::vector TypeCheckedLoadVCalls, std::vector TypeTestAssumeConstVCalls, - std::vector TypeCheckedLoadConstVCalls) + std::vector TypeCheckedLoadConstVCalls, + unsigned SimHash) : GlobalValueSummary(FunctionKind, Flags, std::move(Refs)), InstCount(NumInsts), FunFlags(FunFlags), EntryCount(EntryCount), - CallGraphEdgeList(std::move(CGEdges)) { + CallGraphEdgeList(std::move(CGEdges)), + SimilarityHash(SimHash) { if (!TypeTests.empty() || !TypeTestAssumeVCalls.empty() || !TypeCheckedLoadVCalls.empty() || !TypeTestAssumeConstVCalls.empty() || !TypeCheckedLoadConstVCalls.empty()) @@ -620,6 +633,9 @@ return GVS->getSummaryKind() == FunctionKind; } + /// Get the SimilarityHash of this function. + unsigned similarityHash() { return SimilarityHash; } + /// Get function summary flags. FFlags fflags() const { return FunFlags; } diff --git a/llvm/include/llvm/IR/ModuleSummaryIndexYAML.h b/llvm/include/llvm/IR/ModuleSummaryIndexYAML.h --- a/llvm/include/llvm/IR/ModuleSummaryIndexYAML.h +++ b/llvm/include/llvm/IR/ModuleSummaryIndexYAML.h @@ -229,7 +229,7 @@ std::move(FSum.TypeTestAssumeVCalls), std::move(FSum.TypeCheckedLoadVCalls), std::move(FSum.TypeTestAssumeConstVCalls), - std::move(FSum.TypeCheckedLoadConstVCalls))); + std::move(FSum.TypeCheckedLoadConstVCalls), 0)); } } static void output(IO &io, GlobalValueSummaryMapTy &V) { diff --git a/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h b/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h --- a/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h +++ b/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h @@ -256,7 +256,7 @@ /// devirt target names for any locals that were exported. void updateIndexWPDForExports( ModuleSummaryIndex &Summary, - function_ref isExported, + StringMap &ExportLists, std::map> &LocalWPDTargetsMap); } // end namespace llvm diff --git a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp --- a/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp +++ b/llvm/lib/Analysis/ModuleSummaryAnalysis.cpp @@ -551,12 +551,14 @@ // FIXME: refactor this to use the same code that inliner is using. // Don't try to import functions with noinline attribute. F.getAttributes().hasFnAttribute(Attribute::NoInline)}; + // SimHash of 0 means just a placeholder. + unsigned SimHash = llvm::profileFunction(&F); auto FuncSummary = std::make_unique( Flags, NumInsts, FunFlags, /*EntryCount=*/0, std::move(Refs), CallGraphEdges.takeVector(), TypeTests.takeVector(), TypeTestAssumeVCalls.takeVector(), TypeCheckedLoadVCalls.takeVector(), TypeTestAssumeConstVCalls.takeVector(), - TypeCheckedLoadConstVCalls.takeVector()); + TypeCheckedLoadConstVCalls.takeVector(), SimHash); if (NonRenamableLocal) CantBePromoted.insert(F.getGUID()); Index.addGlobalValueSummary(F, std::move(FuncSummary)); @@ -798,7 +800,7 @@ ArrayRef{}, ArrayRef{}, ArrayRef{}, - ArrayRef{}); + ArrayRef{}, 0); Index.addGlobalValueSummary(*GV, std::move(Summary)); } else { std::unique_ptr Summary = diff --git a/llvm/lib/AsmParser/LLParser.cpp b/llvm/lib/AsmParser/LLParser.cpp --- a/llvm/lib/AsmParser/LLParser.cpp +++ b/llvm/lib/AsmParser/LLParser.cpp @@ -8153,7 +8153,7 @@ std::move(TypeIdInfo.TypeTestAssumeVCalls), std::move(TypeIdInfo.TypeCheckedLoadVCalls), std::move(TypeIdInfo.TypeTestAssumeConstVCalls), - std::move(TypeIdInfo.TypeCheckedLoadConstVCalls)); + std::move(TypeIdInfo.TypeCheckedLoadConstVCalls), 0); FS->setModulePath(ModulePath); diff --git a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp --- a/llvm/lib/Bitcode/Reader/BitcodeReader.cpp +++ b/llvm/lib/Bitcode/Reader/BitcodeReader.cpp @@ -5785,9 +5785,9 @@ } const uint64_t Version = Record[0]; const bool IsOldProfileFormat = Version == 1; - if (Version < 1 || Version > 7) + if (Version < 1 || Version > 8) return error("Invalid summary version " + Twine(Version) + - ". Version should be in the range [1-7]."); + ". Version should be in the range [1-8]."); Record.clear(); // Keep around the last seen summary to be used when we see an optional @@ -5847,12 +5847,12 @@ break; } // FS_PERMODULE: [valueid, flags, instcount, fflags, numrefs, - // numrefs x valueid, n x (valueid)] + // numrefs x valueid, simhash, n x (valueid)] // FS_PERMODULE_PROFILE: [valueid, flags, instcount, fflags, numrefs, - // numrefs x valueid, + // numrefs x valueid, simhash, // n x (valueid, hotness)] // FS_PERMODULE_RELBF: [valueid, flags, instcount, fflags, numrefs, - // numrefs x valueid, + // numrefs x valueid, simhash, // n x (valueid, relblockfreq)] case bitc::FS_PERMODULE: case bitc::FS_PERMODULE_RELBF: @@ -5861,6 +5861,7 @@ uint64_t RawFlags = Record[1]; unsigned InstCount = Record[2]; uint64_t RawFunFlags = 0; + unsigned SimHash = 0; unsigned NumRefs = Record[3]; unsigned NumRORefs = 0, NumWORefs = 0; int RefListStartIndex = 4; @@ -5874,6 +5875,10 @@ if (Version >= 7) { NumWORefs = Record[6]; RefListStartIndex = 7; + if (Version >= 8) { + SimHash = Record[7]; + RefListStartIndex = 8; + } } } } @@ -5901,7 +5906,7 @@ std::move(PendingTypeTestAssumeVCalls), std::move(PendingTypeCheckedLoadVCalls), std::move(PendingTypeTestAssumeConstVCalls), - std::move(PendingTypeCheckedLoadConstVCalls)); + std::move(PendingTypeCheckedLoadConstVCalls), SimHash); PendingTypeTests.clear(); PendingTypeTestAssumeVCalls.clear(); PendingTypeCheckedLoadVCalls.clear(); @@ -5993,9 +5998,9 @@ break; } // FS_COMBINED: [valueid, modid, flags, instcount, fflags, numrefs, - // numrefs x valueid, n x (valueid)] + // numrefs x valueid, simhash, n x (valueid)] // FS_COMBINED_PROFILE: [valueid, modid, flags, instcount, fflags, numrefs, - // numrefs x valueid, n x (valueid, hotness)] + // numrefs x valueid, simhash, n x (valueid, hotness)] case bitc::FS_COMBINED: case bitc::FS_COMBINED_PROFILE: { unsigned ValueID = Record[0]; @@ -6003,6 +6008,7 @@ uint64_t RawFlags = Record[2]; unsigned InstCount = Record[3]; uint64_t RawFunFlags = 0; + unsigned SimHash = 0; uint64_t EntryCount = 0; unsigned NumRefs = Record[4]; unsigned NumRORefs = 0, NumWORefs = 0; @@ -6023,6 +6029,10 @@ RefListStartIndex = 9; NumWORefs = Record[8]; NumRORefsOffset = 2; + if (Version >= 8) { + SimHash = Record[9]; + RefListStartIndex = 10; + } } } NumRORefs = Record[RefListStartIndex - NumRORefsOffset]; @@ -6048,7 +6058,7 @@ std::move(PendingTypeTestAssumeVCalls), std::move(PendingTypeCheckedLoadVCalls), std::move(PendingTypeTestAssumeConstVCalls), - std::move(PendingTypeCheckedLoadConstVCalls)); + std::move(PendingTypeCheckedLoadConstVCalls), SimHash); PendingTypeTests.clear(); PendingTypeTestAssumeVCalls.clear(); PendingTypeCheckedLoadVCalls.clear(); diff --git a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp --- a/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp +++ b/llvm/lib/Bitcode/Writer/BitcodeWriter.cpp @@ -3667,6 +3667,7 @@ NameVals.push_back(FS->refs().size()); NameVals.push_back(SpecialRefCnts.first); // rorefcnt NameVals.push_back(SpecialRefCnts.second); // worefcnt + NameVals.push_back(FS->similarityHash()); // simhash for (auto &RI : FS->refs()) NameVals.push_back(VE.getValueID(RI.getValue())); @@ -3740,7 +3741,7 @@ // Current version for the summary. // This is bumped whenever we introduce changes in the way some record are // interpreted, like flags for instance. -static const uint64_t INDEX_VERSION = 7; +static const uint64_t INDEX_VERSION = 8; /// Emit the per-module summary section alongside the rest of /// the module's bitcode. @@ -3786,6 +3787,7 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // numrefs Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // rorefcnt Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // worefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // simhash // numrefs x valueid, n x (valueid, hotness) Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); @@ -3804,6 +3806,7 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // numrefs Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // rorefcnt Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // worefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // simhash // numrefs x valueid, n x (valueid [, rel_block_freq]) Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); @@ -3928,6 +3931,7 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // numrefs Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // rorefcnt Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // worefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // simhash // numrefs x valueid, n x (valueid) Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); @@ -3945,6 +3949,7 @@ Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // numrefs Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // rorefcnt Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 4)); // worefcnt + Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); // simhash // numrefs x valueid, n x (valueid, hotness) Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Array)); Abbv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 8)); @@ -4052,6 +4057,7 @@ NameVals.push_back(0); // numrefs NameVals.push_back(0); // rorefcnt NameVals.push_back(0); // worefcnt + NameVals.push_back(FS->similarityHash()); // simhash unsigned Count = 0, RORefCnt = 0, WORefCnt = 0; for (auto &RI : FS->refs()) { diff --git a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp --- a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp +++ b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp @@ -80,10 +80,8 @@ namespace { -// Default to using all available threads in the system, but using only one -// thred per core, as indicated by the usage of -// heavyweight_hardware_concurrency() below. -static cl::opt ThreadCount("threads", cl::init(0)); +static cl::opt + ThreadCount("threads", cl::init(llvm::heavyweight_hardware_concurrency())); // Simple helper to save temporary files for debug. static void saveTempBitcode(const Module &TheModule, StringRef TempDir, diff --git a/llvm/lib/Transforms/IPO/MergeSimilarFunctions.cpp b/llvm/lib/Transforms/IPO/MergeSimilarFunctions.cpp --- a/llvm/lib/Transforms/IPO/MergeSimilarFunctions.cpp +++ b/llvm/lib/Transforms/IPO/MergeSimilarFunctions.cpp @@ -31,6 +31,7 @@ #include "llvm/IR/Instructions.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" +#include "llvm/IR/ModuleSummaryIndex.h" #include "llvm/IR/Operator.h" #include "llvm/IR/ValueHandle.h" #include "llvm/Pass.h" @@ -54,10 +55,6 @@ STATISTIC(NumSimilarFunctionsMerged, "Number of similar functions merged"); -static cl::opt MergeMinInsts( - "mergesimilarfunc-min-insts", cl::Hidden, cl::init(4), - cl::desc("Min instructions required to even consider single block fns")); - static cl::opt MergeDifferingMinInsts( "mergesimilarfunc-diff-min-insts", cl::Hidden, cl::init(15), cl::desc("Min instructions required to try merging differing functions")); @@ -73,9 +70,8 @@ static cl::opt OptPrintMerges("mergesimilarfunc-print-merges", cl::Hidden, cl::init(false)); -static cl::opt UseGlobalAliases( - "mergesimilarfunc-global-aliases", cl::Hidden, cl::init(false), - cl::desc("Enable writing alias by enabling global aliases")); +extern cl::opt UseGlobalAliases; +extern cl::opt MergeMinInsts; void PrintMerges(const char *Desc, Function *Old, Function *New) { if (OptPrintMerges) { @@ -84,20 +80,17 @@ } } -// Minimize the name pollution caused by the enum values. + namespace Opt { -enum MergeLevelEnum { none, size, all }; -static cl::opt MergeLevel( - "mergesimilarfunc-level", cl::Hidden, cl::ZeroOrMore, - cl::desc("Level of function merging:"), cl::init(size), - cl::values(clEnumVal(none, "function merging disabled"), - clEnumVal(size, "only try to merge functions that are optimized " - "for size"), - clEnumVal(all, "attempt to merge all similar functions"))); + extern cl::opt MergeLevel; } static const char *MERGED_SUFFIX = "__merged"; +namespace llvm { +unsigned profileFunction(const Function *F); +} +/* /// Returns the type id for a type to be hashed. We turn pointer types into /// integers here because the actual compare logic below considers pointers and /// integers of the same size as equal. @@ -110,7 +103,8 @@ /// Creates a hash-code for the function which is the same for any two /// functions that will compare equal, without looking at the instructions /// inside the function. -static unsigned profileFunction(const Function *F) { +namespace llvm { +unsigned profileFunction(const Function *F) { FunctionType *FTy = F->getFunctionType(); FoldingSetNodeID ID; @@ -124,7 +118,7 @@ ID.AddInteger(getTypeIDForHash(FTy->getParamType(i))); return ID.ComputeHash(); } - +}*/ /// Replace Inst1 by a switch statement that executes Inst1 or one of Inst2s /// depending on the value of SwitchVal. If a value in Inst2s is NULL, it @@ -1120,35 +1114,6 @@ FunctionsToCompare.clear(); } -static bool isAliasCapable(Function* G) { - return - UseGlobalAliases && G->hasGlobalUnnamedAddr() - && (G->hasExternalLinkage() || G->hasLocalLinkage() || G->hasWeakLinkage()); -} - -static bool isComparisonCandidate(Function *F) { - if (Opt::MergeLevel == Opt::size) { - // Only consider functions that are to be optimized for size. - // By default, that is all functions at -Os/-Oz and nothing at -O2. - bool Os = F->getAttributes(). - hasAttribute(AttributeList::FunctionIndex, Attribute::OptimizeForSize); - bool Oz = F->getAttributes(). - hasAttribute(AttributeList::FunctionIndex, Attribute::MinSize); - if (!Os && !Oz) - return false; - } - - // Ignore declarations and tiny functions - no point in merging those - if (F->isDeclaration()) return false; - if (F->getName().endswith(MERGED_SUFFIX)) return false; - if (F->hasAvailableExternallyLinkage()) return false; - if (F->hasFnAttribute(Attribute::AlwaysInline)) return false; - if (F->size() == 1 && F->begin()->size() < MergeMinInsts) - return isAliasCapable(F); - - return true; -} - void MergeRegistry::defer(Function *F) { if (isComparisonCandidate(F)) Deferred.push_back(F); @@ -1168,9 +1133,11 @@ Function *F = dyn_cast_or_null(V); if (!F) continue; if (InsertedFuncs.find(F) != InsertedFuncs.end()) continue; - if (!isComparisonCandidate(F)) continue; unsigned Hash = profileFunction(F); + // Hash code of 0 is just a placeholder. + if (Hash == 0) + continue; FunctionsToCompare[Hash].push_front(F); InsertedFuncs.insert(F); @@ -1200,10 +1167,11 @@ void MergeRegistry::remove(Function *F, bool Reanalyze/*=true*/) { // There is no need to remove a function that is not already // in a bucket. - if (!isComparisonCandidate(F)) - return; - unsigned Hash = profileFunction(F); + // Hash code of 0 is just a placeholder. + if (Hash == 0) + return; + std::list &Bucket = FunctionsToCompare[Hash]; removeFromBucket(F, Bucket); diff --git a/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp b/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp --- a/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp +++ b/llvm/lib/Transforms/IPO/PassManagerBuilder.cpp @@ -149,10 +149,6 @@ "enable-order-file-instrumentation", cl::init(false), cl::Hidden, cl::desc("Enable order file instrumentation (default = off)")); -static cl::opt - EnableMatrix("enable-matrix", cl::init(false), cl::Hidden, - cl::desc("Enable lowering of the matrix intrinsics")); - static cl::opt EnableMergeSimilarFunctions( "enable-merge-sim-functions", cl::init(false), cl::Hidden, cl::desc("Enable the Function merging pass (default = on)")); @@ -515,6 +511,11 @@ MPM.add(createGlobalDCEPass()); } + if (EnableMergeSimilarFunctions) { + auto *Summary = (ImportSummary ? ImportSummary : ExportSummary); + MPM.add(createMergeSimilarFunctionsPass(Summary)); + } + addExtensionsToPM(EP_EnabledOnOptLevel0, MPM); if (PrepareForLTO || PrepareForThinLTO) { @@ -829,8 +830,10 @@ if (MergeFunctions) MPM.add(createMergeFunctionsPass()); - if (EnableMergeSimilarFunctions) - MPM.add(createMergeSimilarFunctionsPass()); + if (EnableMergeSimilarFunctions) { + auto *Summary = (ImportSummary ? ImportSummary : ExportSummary); + MPM.add(createMergeSimilarFunctionsPass(Summary)); + } // LoopSink pass sinks instructions hoisted by LICM, which serves as a // canonicalization pass that enables other optimizations. As a result, @@ -1059,8 +1062,10 @@ // currently it damages debug info. if (MergeFunctions) PM.add(createMergeFunctionsPass()); - if (EnableMergeSimilarFunctions) - PM.add(createMergeSimilarFunctionsPass()); + if (EnableMergeSimilarFunctions) { + auto *Summary = (ImportSummary ? ImportSummary : ExportSummary); + PM.add(createMergeSimilarFunctionsPass(Summary)); + } } void PassManagerBuilder::populateThinLTOPassManager( diff --git a/llvm/test/Transforms/MergeSimilarFunc/merge-debug-info.ll b/llvm/test/Transforms/MergeSimilarFunc/merge-debug-info.ll --- a/llvm/test/Transforms/MergeSimilarFunc/merge-debug-info.ll +++ b/llvm/test/Transforms/MergeSimilarFunc/merge-debug-info.ll @@ -163,14 +163,14 @@ !10 = !DIDerivedType(tag: DW_TAG_member, name: "set", scope: !6, file: !1, line: 6, baseType: !11, size: 32, align: 32, offset: 32) !11 = !DIBasicType(name: "int", size: 32, align: 32, encoding: DW_ATE_signed) !12 = !DIDerivedType(tag: DW_TAG_member, name: "get", scope: !6, file: !1, line: 7, baseType: !11, size: 32, align: 32, offset: 64) -!14 = distinct !DISubprogram(name: "bar", scope: !1, file: !1, line: 10, type: !15, isLocal: false, isDefinition: true, scopeLine: 10, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !17) +!14 = distinct !DISubprogram(name: "bar", scope: !1, file: !1, line: 10, type: !15, isLocal: false, isDefinition: true, scopeLine: 10, flags: DIFlagPrototyped, isOptimized: true, unit: !0) !15 = !DISubroutineType(types: !16) !16 = !{!11, !9} !17 = !{!18, !19, !20} !18 = !DILocalVariable(name: "b", arg: 1, scope: !14, file: !1, line: 10, type: !9) !19 = !DILocalVariable(name: "res", scope: !14, file: !1, line: 11, type: !11) !20 = !DILocalVariable(name: "ee", scope: !14, file: !1, line: 12, type: !4) -!21 = distinct !DISubprogram(name: "bar1", scope: !1, file: !1, line: 24, type: !15, isLocal: false, isDefinition: true, scopeLine: 24, flags: DIFlagPrototyped, isOptimized: true, unit: !0, variables: !22) +!21 = distinct !DISubprogram(name: "bar1", scope: !1, file: !1, line: 24, type: !15, isLocal: false, isDefinition: true, scopeLine: 24, flags: DIFlagPrototyped, isOptimized: true, unit: !0) !22 = !{!23, !24, !25} !23 = !DILocalVariable(name: "b", arg: 1, scope: !21, file: !1, line: 24, type: !9) !24 = !DILocalVariable(name: "res", scope: !21, file: !1, line: 25, type: !11)