Index: include/llvm/IR/Function.h =================================================================== --- include/llvm/IR/Function.h +++ include/llvm/IR/Function.h @@ -141,6 +141,11 @@ // Provide fast operand accessors. DECLARE_TRANSPARENT_OPERAND_ACCESSORS(Value); + /// Returns the number of non-debug IR instructions in this function. + /// This is equivalent to the sum of the sizes of each basic block contained + /// within this function. + unsigned getInstructionCount(); + /// Returns the FunctionType for me. FunctionType *getFunctionType() const { return cast(getValueType()); Index: include/llvm/IR/LegacyPassManagers.h =================================================================== --- include/llvm/IR/LegacyPassManagers.h +++ include/llvm/IR/LegacyPassManagers.h @@ -403,6 +403,15 @@ InheritedAnalysis[Index++] = (*I)->getAvailableAnalysis(); } + /// Set the initial size of the module if the user has specified that they + /// want remarks for size. + /// Returns 0 if the remark was not requested. + unsigned initSizeRemarkInfo(Module &M); + + /// Emit a remark signifying that the number of IR instructions in the module + /// changed. + void emitInstrCountChangedRemark(Pass *P, Module &M, unsigned CountBefore); + protected: // Top level manager. PMTopLevelManager *TPM; Index: include/llvm/IR/Module.h =================================================================== --- include/llvm/IR/Module.h +++ include/llvm/IR/Module.h @@ -207,6 +207,11 @@ /// @returns the module identifier as a string const std::string &getModuleIdentifier() const { return ModuleID; } + /// Returns the number of non-debug IR instructions in the module. + /// This is equivalent to the sum of the IR instruction counts of each + /// function contained in the module. + unsigned getInstructionCount(); + /// Get the module's original source file name. When compiling from /// bitcode, this is taken from a bitcode record where it was recorded. /// For other compiles it is the same as the ModuleID, which would Index: lib/Analysis/CallGraphSCCPass.cpp =================================================================== --- lib/Analysis/CallGraphSCCPass.cpp +++ lib/Analysis/CallGraphSCCPass.cpp @@ -120,6 +120,7 @@ bool &DevirtualizedCall) { bool Changed = false; PMDataManager *PM = P->getAsPMDataManager(); + Module &M = CG.getModule(); if (!PM) { CallGraphSCCPass *CGSP = (CallGraphSCCPass*)P; @@ -130,7 +131,12 @@ { TimeRegion PassTimer(getPassTimer(CGSP)); + unsigned InstrCount = initSizeRemarkInfo(M); Changed = CGSP->runOnSCC(CurSCC); + + // If the pass modified the module, it may have modified the instruction + // count of the module. Try emitting a remark. + emitInstrCountChangedRemark(P, M, InstrCount); } // After the CGSCCPass is done, when assertions are enabled, use Index: lib/Analysis/LoopPass.cpp =================================================================== --- lib/Analysis/LoopPass.cpp +++ lib/Analysis/LoopPass.cpp @@ -151,6 +151,7 @@ bool LPPassManager::runOnFunction(Function &F) { auto &LIWP = getAnalysis(); LI = &LIWP.getLoopInfo(); + Module &M = *F.getParent(); #if 0 DominatorTree *DT = &getAnalysis().getDomTree(); #endif @@ -200,8 +201,9 @@ { PassManagerPrettyStackEntry X(P, *CurrentLoop->getHeader()); TimeRegion PassTimer(getPassTimer(P)); - + unsigned InstrCount = initSizeRemarkInfo(M); Changed |= P->runOnLoop(CurrentLoop, *this); + emitInstrCountChangedRemark(P, M, InstrCount); } if (Changed) Index: lib/IR/Function.cpp =================================================================== --- lib/IR/Function.cpp +++ lib/IR/Function.cpp @@ -194,6 +194,14 @@ return getType()->getContext(); } +unsigned Function::getInstructionCount() { + unsigned NumInstrs = 0; + for (BasicBlock &BB : BasicBlocks) + NumInstrs += std::distance(BB.instructionsWithoutDebug().begin(), + BB.instructionsWithoutDebug().end()); + return NumInstrs; +} + void Function::removeFromParent() { getParent()->getFunctionList().remove(getIterator()); } Index: lib/IR/LegacyPassManager.cpp =================================================================== --- lib/IR/LegacyPassManager.cpp +++ lib/IR/LegacyPassManager.cpp @@ -13,6 +13,7 @@ #include "llvm/IR/LegacyPassManager.h" #include "llvm/ADT/Statistic.h" +#include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/IRPrintingPasses.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/LegacyPassManagers.h" @@ -134,8 +135,65 @@ return PassDebugging >= Executions; } +unsigned PMDataManager::initSizeRemarkInfo(Module &M) { + // Only calculate getInstructionCount if the size-info remark is requested. + if (M.getContext().getDiagHandlerPtr()->isAnalysisRemarkEnabled("size-info")) + return M.getInstructionCount(); + return 0; +} + +void PMDataManager::emitInstrCountChangedRemark(Pass *P, Module &M, + unsigned CountBefore) { + // Did the user request the remark? If not, quit. + if (!M.getContext().getDiagHandlerPtr()->isAnalysisRemarkEnabled("size-info")) + return; + + // We need a function containing at least one basic block in order to output + // remarks. Since it's possible that the first function in the module doesn't + // actually contain a basic block, we have to go and find one that's suitable + // for emitting remarks. + auto It = std::find_if(M.begin(), M.end(), + [](const Function &Fn) { return !Fn.empty(); }); + + // Didn't find a function. Quit. + if (It == M.end()) + return; + + // We found a function containing at least one basic block. + Function *F = &*It; + + // How many instructions are in the module now? + unsigned CountAfter = M.getInstructionCount(); + // If there was no change, don't emit a remark. + if (CountBefore == CountAfter) + return; + + // If it's a pass manager, don't emit a remark. (This hinges on the assumption + // that the only passes that return non-null with getAsPMDataManager are pass + // managers.) The reason we have to do this is to avoid emitting remarks for + // CGSCC passes. + if (P->getAsPMDataManager()) + return; + // Compute a possibly negative delta between the instruction count before + // running P, and after running P. + int64_t Delta = (int64_t)CountAfter - (int64_t)CountBefore; + + BasicBlock &BB = *F->begin(); + OptimizationRemarkAnalysis R("size-info", "IRSizeChange", + DiagnosticLocation(), &BB); + // FIXME: Move ore namespace to DiagnosticInfo so that we can use it. This + // would let us use NV instead of DiagnosticInfoOptimizationBase::Argument. + R << DiagnosticInfoOptimizationBase::Argument("Pass", P->getPassName()) + << ": IR instruction count changed from " + << DiagnosticInfoOptimizationBase::Argument("IRInstrsBefore", CountBefore) + << " to " + << DiagnosticInfoOptimizationBase::Argument("IRInstrsAfter", CountAfter) + << "; Delta: " + << DiagnosticInfoOptimizationBase::Argument("DeltaInstrCount", Delta); + F->getContext().diagnose(R); // Not using ORE for layering reasons. +} void PassManagerPrettyStackEntry::print(raw_ostream &OS) const { if (!V && !M) @@ -1284,6 +1342,7 @@ return false; bool Changed = doInitialization(F); + Module &M = *F.getParent(); for (BasicBlock &BB : F) for (unsigned Index = 0; Index < getNumContainedPasses(); ++Index) { @@ -1299,8 +1358,9 @@ // If the pass crashes, remember this. PassManagerPrettyStackEntry X(BP, BB); TimeRegion PassTimer(getPassTimer(BP)); - + unsigned InstrCount = initSizeRemarkInfo(M); LocalChanged |= BP->runOnBasicBlock(BB); + emitInstrCountChangedRemark(BP, M, InstrCount); } Changed |= LocalChanged; @@ -1500,7 +1560,7 @@ return false; bool Changed = false; - + Module &M = *F.getParent(); // Collect inherited analysis from Module level pass manager. populateInheritedAnalysis(TPM->activeStack); @@ -1516,8 +1576,9 @@ { PassManagerPrettyStackEntry X(FP, F); TimeRegion PassTimer(getPassTimer(FP)); - + unsigned InstrCount = initSizeRemarkInfo(M); LocalChanged |= FP->runOnFunction(F); + emitInstrCountChangedRemark(FP, M, InstrCount); } Changed |= LocalChanged; @@ -1594,7 +1655,9 @@ PassManagerPrettyStackEntry X(MP, M); TimeRegion PassTimer(getPassTimer(MP)); + unsigned InstrCount = initSizeRemarkInfo(M); LocalChanged |= MP->runOnModule(M); + emitInstrCountChangedRemark(MP, M, InstrCount); } Changed |= LocalChanged; Index: lib/IR/Module.cpp =================================================================== --- lib/IR/Module.cpp +++ lib/IR/Module.cpp @@ -464,6 +464,13 @@ return cast(Val->getValue())->getZExtValue(); } +unsigned Module::getInstructionCount() { + unsigned NumInstrs = 0; + for (Function &F : FunctionList) + NumInstrs += F.getInstructionCount(); + return NumInstrs; +} + Comdat *Module::getOrInsertComdat(StringRef Name) { auto &Entry = *ComdatSymTab.insert(std::make_pair(Name, Comdat())).first; Entry.second.Name = &Entry; Index: test/Other/size-remarks.ll =================================================================== --- /dev/null +++ test/Other/size-remarks.ll @@ -0,0 +1,164 @@ +; Ensure that IR count remarks in the legacy pass manager work. +; What this test should check for: +; * Positive, nonzero sizes before/after +; * Nonzero deltas +; * Sizes are being tracked properly across multiple remarks. E.g, if we have +; original_count_1, final_count_1, and +; original_count_2, final_count_2, +; Then original_count_2 == final_count_1. + +; For these remarks, the "function" field in the YAML file doesn't matter. +; Each of the testcases work by combining the output remarks with the +; optimization record emit using -pass-remarks-output. This is done to prevent +; test flakiness wrt instruction counts, but also ensure that the output values +; are equivalent in both outputs. + +; RUN: opt < %s -inline -pass-remarks-analysis='size-info' \ +; RUN: -pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \ +; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=CGSCC +; CGSCC: remark: :0:0: Function Integration/Inlining: +; CGSCC-SAME: IR instruction count changed from +; CGSCC-SAME: [[ORIG:[1-9][0-9]*]] to [[FINAL:[1-9][0-9]*]]; +; CGSCC-SAME: Delta: [[DELTA:-?[1-9][0-9]*]] +; CGSCC: --- !Analysis +; CGSCC-NEXT: Pass: size-info +; CGSCC-NEXT: Name: IRSizeChange +; CGSCC-NEXT: Function: +; CGSCC-NEXT: Args: +; CGSCC-NEXT: - Pass: Function Integration/Inlining +; CGSCC-NEXT: - String: ': IR instruction count changed from ' +; CGSCC-NEXT: - IRInstrsBefore: '[[ORIG]]' +; CGSCC-NEXT: - String: ' to ' +; CGSCC-NEXT: - IRInstrsAfter: '[[FINAL]]' +; CGSCC-NEXT: - String: '; Delta: ' +; CGSCC-NEXT: - DeltaInstrCount: '[[DELTA]]' + +; RUN: opt < %s -instcombine -pass-remarks-analysis='size-info' \ +; RUN:-pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \ +; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=FUNC +; FUNC: remark: :0:0: Combine redundant instructions: +; FUNC-SAME: IR instruction count changed from +; FUNC-SAME: [[SIZE1:[1-9][0-9]*]] to [[SIZE2:[1-9][0-9]*]]; +; FUNC-SAME: Delta: [[DELTA1:-?[1-9][0-9]*]] +; FUNC-NEXT: remark: :0:0: Combine redundant instructions: +; FUNC-SAME: IR instruction count changed from +; FUNC-SAME: [[SIZE2]] to [[SIZE3:[1-9][0-9]*]]; +; FUNC-SAME: Delta: [[DELTA2:-?[1-9][0-9]*]] +; FUNC: --- !Analysis +; FUNC-NEXT: Pass: size-info +; FUNC-NEXT: Name: IRSizeChange +; FUNC-NEXT: Function: +; FUNC-NEXT: Args: +; FUNC-NEXT: - Pass: Combine redundant instructions +; FUNC-NEXT: - String: ': IR instruction count changed from ' +; FUNC-NEXT: - IRInstrsBefore: '[[SIZE1]]' +; FUNC-NEXT: - String: ' to ' +; FUNC-NEXT: - IRInstrsAfter: '[[SIZE2]]' +; FUNC-NEXT: - String: '; Delta: ' +; FUNC-NEXT: - DeltaInstrCount: '[[DELTA1]]' +; FUNC: --- !Analysis +; FUNC-NEXT: Pass: size-info +; FUNC-NEXT: Name: IRSizeChange +; FUNC-NEXT: Function: +; FUNC-NEXT: Args: +; FUNC-NEXT: - Pass: Combine redundant instructions +; FUNC-NEXT: - String: ': IR instruction count changed from ' +; FUNC-NEXT: - IRInstrsBefore: '[[SIZE2]]' +; FUNC-NEXT: - String: ' to ' +; FUNC-NEXT: - IRInstrsAfter: '[[SIZE3]]' +; FUNC-NEXT: - String: '; Delta: ' +; FUNC-NEXT: - DeltaInstrCount: '[[DELTA2]]' + +; RUN: opt < %s -globaldce -pass-remarks-analysis='size-info' \ +; RUN: -pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \ +; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=MODULE +; MODULE: remark: +; MODULE-SAME: Dead Global Elimination: +; MODULE-SAME: IR instruction count changed from +; MODULE-SAME: [[ORIG:[1-9][0-9]*]] to [[FINAL:[1-9][0-9]*]]; +; MODULE-SAME: Delta: [[DELTA:-?[1-9][0-9]*]] +; MODULE: --- !Analysis +; MODULE-NEXT: Pass: size-info +; MODULE-NEXT: Name: IRSizeChange +; MODULE-NEXT: Function: +; MODULE-NEXT: Args: +; MODULE-NEXT: - Pass: Dead Global Elimination +; MODULE-NEXT: - String: ': IR instruction count changed from ' +; MODULE-NEXT: - IRInstrsBefore: '[[ORIG]]' +; MODULE-NEXT: - String: ' to ' +; MODULE-NEXT: - IRInstrsAfter: '[[FINAL]]' +; MODULE-NEXT: - String: '; Delta: ' +; MODULE-NEXT: - DeltaInstrCount: '[[DELTA]]' + +; RUN: opt < %s -dce -pass-remarks-analysis='size-info' \ +; RUN: -pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \ +; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=BB +; BB: remark: :0:0: Dead Code Elimination: +; BB-SAME: IR instruction count changed from +; BB-SAME: [[ORIG:[1-9][0-9]*]] to [[FINAL:[1-9][0-9]*]]; +; BB-SAME: Delta: [[DELTA:-?[1-9][0-9]*]] +; BB: --- !Analysis +; BB-NEXT: Pass: size-info +; BB-NEXT: Name: IRSizeChange +; BB-NEXT: Function: +; BB-NEXT: Args: +; BB-NEXT: - Pass: Dead Code Elimination +; BB-NEXT: - String: ': IR instruction count changed from ' +; BB-NEXT: - IRInstrsBefore: '[[ORIG]]' +; BB-NEXT: - String: ' to ' +; BB-NEXT: - IRInstrsAfter: '[[FINAL]]' +; BB-NEXT: - String: '; Delta: ' +; BB-NEXT: - DeltaInstrCount: '[[DELTA]]' + +; RUN: opt < %s -loop-unroll -pass-remarks-analysis='size-info' \ +; RUN: -pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \ +; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=LOOP +; LOOP: remark: :0:0: Unroll loops: +; LOOP-SAME: IR instruction count changed from +; LOOP-SAME: [[ORIG:[1-9][0-9]*]] to [[FINAL:[1-9][0-9]*]]; +; LOOP-SAME: Delta: [[DELTA:-?[1-9][0-9]*]] +; LOOP: --- !Analysis +; LOOP-NEXT: Pass: size-info +; LOOP-NEXT: Name: IRSizeChange +; LOOP-NEXT: Function: +; LOOP-NEXT: Args: +; LOOP-DAG: - Pass: Unroll loops +; LOOP-NEXT: - String: ': IR instruction count changed from ' +; LOOP-NEXT: - IRInstrsBefore: '[[ORIG]]' +; LOOP-NEXT: - String: ' to ' +; LOOP-NEXT: - IRInstrsAfter: '[[FINAL]]' +; LOOP-NEXT: - String: '; Delta: ' +; LOOP-NEXT: - DeltaInstrCount: '[[DELTA]]' +declare i1 ()* @boop() + +define internal i1 @pluto() { + %F = call i1 ()* () @boop( ) + %c = icmp eq i1 ()* %F, @pluto + ret i1 %c +} + +define i32 @foo(i32 %x) { +entry: + %x.addr = alloca i32, align 4 + store i32 %x, i32* %x.addr, align 4 + %0 = load i32, i32* %x.addr, align 4 + ret i32 %0 +} + +define i32 @bar(i32 %x) { +entry: + %x.addr = alloca i32, align 4 + store i32 %x, i32* %x.addr, align 4 + %0 = load i32, i32* %x.addr, align 4 + %call = call i32 @foo(i32 %0) + br label %for.body +for.body: + %s.06 = phi i32 [ 0, %entry ], [ %add, %for.body ] + %i.05 = phi i32 [ 0, %entry ], [ %inc, %for.body ] + %add = add nsw i32 %i.05, 4 + %inc = add nsw i32 %i.05, 1 + %exitcond = icmp eq i32 %inc, 16 + br i1 %exitcond, label %for.end, label %for.body +for.end: + ret i32 %add +}