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 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,16 @@ 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. This updates the CountBefore parameter if the instruction count + /// 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 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,8 @@ bool &DevirtualizedCall) { bool Changed = false; PMDataManager *PM = P->getAsPMDataManager(); + Module &M = CG.getModule(); + unsigned InstrCount = initSizeRemarkInfo(M); if (!PM) { CallGraphSCCPass *CGSP = (CallGraphSCCPass*)P; @@ -131,6 +133,11 @@ { TimeRegion PassTimer(getPassTimer(CGSP)); Changed = CGSP->runOnSCC(CurSCC); + + // If the pass modified the module, it may have modified the instruction + // count of the module. Try emitting a remark. + if (Changed) + emitInstrCountChangedRemark(P, M, InstrCount); } // After the CGSCCPass is done, when assertions are enabled, use @@ -154,6 +161,8 @@ { TimeRegion PassTimer(getPassTimer(FPP)); Changed |= FPP->runOnFunction(*F); + if (Changed) + emitInstrCountChangedRemark(P, M, InstrCount); } F->getContext().yield(); } Index: lib/IR/Function.cpp =================================================================== --- lib/IR/Function.cpp +++ lib/IR/Function.cpp @@ -194,6 +194,13 @@ return getType()->getContext(); } +unsigned Function::getInstructionCount() { + int NumInstrs = 0; + for (BasicBlock &BB : BasicBlocks) + NumInstrs += BB.size(); + 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,73 @@ 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. + Function *F = nullptr; + if (!F) { + auto It = std::find_if(M.begin(), M.end(), + [](const Function &Fn) { return Fn.size(); }); + // Didn't find a function. Quit. + if (It == M.end()) + return; + F = &*It; + } + + // If we don't have a valid function, we should bail here. + if (!F) + return; + // 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. + + // We've emitted the remark, meaning there was a change in size. Update the + // instruction count. + CountBefore = CountAfter; +} void PassManagerPrettyStackEntry::print(raw_ostream &OS) const { if (!V && !M) @@ -1285,6 +1351,9 @@ bool Changed = doInitialization(F); + Module &M = *F.getParent(); + unsigned InstrCount = initSizeRemarkInfo(M); + for (BasicBlock &BB : F) for (unsigned Index = 0; Index < getNumContainedPasses(); ++Index) { BasicBlockPass *BP = getContainedPass(Index); @@ -1301,6 +1370,8 @@ TimeRegion PassTimer(getPassTimer(BP)); LocalChanged |= BP->runOnBasicBlock(BB); + if (LocalChanged) + emitInstrCountChangedRemark(BP, M, InstrCount); } Changed |= LocalChanged; @@ -1500,6 +1571,8 @@ return false; bool Changed = false; + Module &M = *(F.getParent()); + unsigned InstrCount = initSizeRemarkInfo(M); // Collect inherited analysis from Module level pass manager. populateInheritedAnalysis(TPM->activeStack); @@ -1518,6 +1591,8 @@ TimeRegion PassTimer(getPassTimer(FP)); LocalChanged |= FP->runOnFunction(F); + if (LocalChanged) + emitInstrCountChangedRemark(FP, M, InstrCount); } Changed |= LocalChanged; @@ -1594,7 +1669,10 @@ PassManagerPrettyStackEntry X(MP, M); TimeRegion PassTimer(getPassTimer(MP)); + unsigned InstrCount = initSizeRemarkInfo(M); LocalChanged |= MP->runOnModule(M); + if (LocalChanged) + 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,157 @@ +; Ensure that IR count remarks in the pass manager work for every type of pass +; that we handle. +; The actual IR code doesn't matter for this test. We just want to ensure that +; we're getting valid size remarks. + +; CGSCC passes. +; 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:[0-9]+]] to [[FINAL:[0-9]+]]; Delta: [[DELTA:[0-9]+]] +; CGSCC: --- !Analysis +; CGSCC-NEXT: Pass: size-info +; CGSCC-NEXT: Name: IRSizeChange +; CGSCC-NEXT: Function: pluto +; 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]]' + +; Function passes. +; 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 [[SIZE1:[0-9]+]] to +; FUNC-SAME: [[SIZE2:[0-9]+]]; Delta: -[[DELTA1:[0-9]+]] +; FUNC: remark: :0:0: Combine redundant instructions: +; FUNC-SAME: IR instruction count changed from [[SIZE2]] to +; FUNC-SAME: [[SIZE3:[0-9]+]]; Delta: -[[DELTA2:[0-9]+]] +; FUNC: remark: :0:0: Combine redundant instructions: +; FUNC-SAME: IR instruction count changed from [[SIZE3]] to [[SIZE4:[0-9]+]]; +; FUNC-SAME: Delta: -[[DELTA3:[0-9]+]] +; FUNC: --- !Analysis +; FUNC-NEXT: Pass: size-info +; FUNC-NEXT: Name: IRSizeChange +; FUNC-NEXT: Function: pluto +; 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: pluto +; 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]]' + +; FUNC: --- !Analysis +; FUNC-NEXT: Pass: size-info +; FUNC-NEXT: Name: IRSizeChange +; FUNC-NEXT: Function: pluto +; FUNC-NEXT: Args: +; FUNC-NEXT: - Pass: Combine redundant instructions +; FUNC-NEXT: - String: ': IR instruction count changed from ' +; FUNC-NEXT: - IRInstrsBefore: '[[SIZE3]]' +; FUNC-NEXT: - String: ' to ' +; FUNC-NEXT: - IRInstrsAfter: '[[SIZE4]]' +; FUNC-NEXT: - String: '; Delta: ' +; FUNC-NEXT: - DeltaInstrCount: '-[[DELTA3]]' + +; Make sure it works for module passes. + +; Next, make sure it works for function passes. +; 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: :0:0: Dead Global Elimination: +; MODULE-SAME: IR instruction count changed from [[ORIG:[0-9]+]] +; MODULE-SAME: to [[FINAL:[0-9]+]]; Delta: -[[DELTA:[0-9]+]] +; MODULE: --- !Analysis +; MODULE-NEXT: Pass: size-info +; MODULE-NEXT: Name: IRSizeChange +; MODULE-NEXT: Function: baz +; 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]]' + +; Basic block passes. +; 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 [[ORIG:[0-9]+]] +; BB-SAME: to [[FINAL:[0-9]+]]; Delta: -[[DELTA:[0-9]+]] +; BB: --- !Analysis +; BB-NEXT: Pass: size-info +; BB-NEXT: Name: IRSizeChange +; BB-NEXT: Function: pluto +; 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]]' + +declare i1 ()* @boop() + +define internal i1 @pluto() { + %F = call i1 ()* () @boop( ) + %c = icmp eq i1 ()* %F, @pluto + ret i1 %c +} + +define void @baz() #0 { + %x.addr = alloca i32, align 4 + ret void +} + +define i32 @foo(i32 %x, i32 %y) #0 { +entry: + %x.addr = alloca i32, align 4 + %y.addr = alloca i32, align 4 + store i32 %x, i32* %x.addr, align 4 + store i32 %y, i32* %y.addr, align 4 + %0 = load i32, i32* %x.addr, align 4 + %1 = load i32, i32* %y.addr, align 4 + %add = add nsw i32 %0, %1 + ret i32 %add +} + +define i32 @bar(i32 %j) #0 { +entry: + %j.addr = alloca i32, align 4 + store i32 %j, i32* %j.addr, align 4 + %0 = load i32, i32* %j.addr, align 4 + %1 = load i32, i32* %j.addr, align 4 + %sub = sub nsw i32 %1, 2 + %call = call i32 @foo(i32 %0, i32 %sub) + ret i32 %call +}