Skip to content

Commit e49374d

Browse files
author
Jessica Paquette
committed
Add remarks describing when a pass changes the IR instruction count of a module
This patch adds a remark which tells the user when a pass changes the number of IR instructions in a module. It can be enabled by using -Rpass-analysis=size-info. The point of this is to make it easier to collect statistics on how passes modify programs in terms of code size. This is similar in concept to timing reports, but using a remark-based interface makes it easy to diff changes over multiple compilations of the same program. By adding functionality like this, we can see * Which passes impact code size the most * How passes impact code size at different optimization levels * Which pass might have contributed the most to an overall code size regression The patch lives in the legacy pass manager, but since it's simply emitting remarks, it shouldn't be too difficult to adapt the functionality to the new pass manager as well. This can also be adapted to handle MachineInstr counts in code gen passes. https://reviews.llvm.org/D38768 llvm-svn: 332739
1 parent b809fc3 commit e49374d

File tree

9 files changed

+273
-4
lines changed

9 files changed

+273
-4
lines changed

llvm/include/llvm/IR/Function.h

+5
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ class Function : public GlobalObject, public ilist_node<Function> {
141141
// Provide fast operand accessors.
142142
DECLARE_TRANSPARENT_OPERAND_ACCESSORS(Value);
143143

144+
/// Returns the number of non-debug IR instructions in this function.
145+
/// This is equivalent to the sum of the sizes of each basic block contained
146+
/// within this function.
147+
unsigned getInstructionCount();
148+
144149
/// Returns the FunctionType for me.
145150
FunctionType *getFunctionType() const {
146151
return cast<FunctionType>(getValueType());

llvm/include/llvm/IR/LegacyPassManagers.h

+9
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,15 @@ class PMDataManager {
403403
InheritedAnalysis[Index++] = (*I)->getAvailableAnalysis();
404404
}
405405

406+
/// Set the initial size of the module if the user has specified that they
407+
/// want remarks for size.
408+
/// Returns 0 if the remark was not requested.
409+
unsigned initSizeRemarkInfo(Module &M);
410+
411+
/// Emit a remark signifying that the number of IR instructions in the module
412+
/// changed.
413+
void emitInstrCountChangedRemark(Pass *P, Module &M, unsigned CountBefore);
414+
406415
protected:
407416
// Top level manager.
408417
PMTopLevelManager *TPM;

llvm/include/llvm/IR/Module.h

+5
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,11 @@ class Module {
207207
/// @returns the module identifier as a string
208208
const std::string &getModuleIdentifier() const { return ModuleID; }
209209

210+
/// Returns the number of non-debug IR instructions in the module.
211+
/// This is equivalent to the sum of the IR instruction counts of each
212+
/// function contained in the module.
213+
unsigned getInstructionCount();
214+
210215
/// Get the module's original source file name. When compiling from
211216
/// bitcode, this is taken from a bitcode record where it was recorded.
212217
/// For other compiles it is the same as the ModuleID, which would

llvm/lib/Analysis/CallGraphSCCPass.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ bool CGPassManager::RunPassOnSCC(Pass *P, CallGraphSCC &CurSCC,
120120
bool &DevirtualizedCall) {
121121
bool Changed = false;
122122
PMDataManager *PM = P->getAsPMDataManager();
123+
Module &M = CG.getModule();
123124

124125
if (!PM) {
125126
CallGraphSCCPass *CGSP = (CallGraphSCCPass*)P;
@@ -130,7 +131,12 @@ bool CGPassManager::RunPassOnSCC(Pass *P, CallGraphSCC &CurSCC,
130131

131132
{
132133
TimeRegion PassTimer(getPassTimer(CGSP));
134+
unsigned InstrCount = initSizeRemarkInfo(M);
133135
Changed = CGSP->runOnSCC(CurSCC);
136+
137+
// If the pass modified the module, it may have modified the instruction
138+
// count of the module. Try emitting a remark.
139+
emitInstrCountChangedRemark(P, M, InstrCount);
134140
}
135141

136142
// After the CGSCCPass is done, when assertions are enabled, use

llvm/lib/Analysis/LoopPass.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ void LPPassManager::markLoopAsDeleted(Loop &L) {
151151
bool LPPassManager::runOnFunction(Function &F) {
152152
auto &LIWP = getAnalysis<LoopInfoWrapperPass>();
153153
LI = &LIWP.getLoopInfo();
154+
Module &M = *F.getParent();
154155
#if 0
155156
DominatorTree *DT = &getAnalysis<DominatorTreeWrapperPass>().getDomTree();
156157
#endif
@@ -200,8 +201,9 @@ bool LPPassManager::runOnFunction(Function &F) {
200201
{
201202
PassManagerPrettyStackEntry X(P, *CurrentLoop->getHeader());
202203
TimeRegion PassTimer(getPassTimer(P));
203-
204+
unsigned InstrCount = initSizeRemarkInfo(M);
204205
Changed |= P->runOnLoop(CurrentLoop, *this);
206+
emitInstrCountChangedRemark(P, M, InstrCount);
205207
}
206208

207209
if (Changed)

llvm/lib/IR/Function.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,14 @@ LLVMContext &Function::getContext() const {
194194
return getType()->getContext();
195195
}
196196

197+
unsigned Function::getInstructionCount() {
198+
unsigned NumInstrs = 0;
199+
for (BasicBlock &BB : BasicBlocks)
200+
NumInstrs += std::distance(BB.instructionsWithoutDebug().begin(),
201+
BB.instructionsWithoutDebug().end());
202+
return NumInstrs;
203+
}
204+
197205
void Function::removeFromParent() {
198206
getParent()->getFunctionList().remove(getIterator());
199207
}

llvm/lib/IR/LegacyPassManager.cpp

+66-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
#include "llvm/IR/LegacyPassManager.h"
1515
#include "llvm/ADT/Statistic.h"
16+
#include "llvm/IR/DiagnosticInfo.h"
1617
#include "llvm/IR/IRPrintingPasses.h"
1718
#include "llvm/IR/LLVMContext.h"
1819
#include "llvm/IR/LegacyPassManagers.h"
@@ -134,8 +135,65 @@ bool PMDataManager::isPassDebuggingExecutionsOrMore() const {
134135
return PassDebugging >= Executions;
135136
}
136137

138+
unsigned PMDataManager::initSizeRemarkInfo(Module &M) {
139+
// Only calculate getInstructionCount if the size-info remark is requested.
140+
if (M.getContext().getDiagHandlerPtr()->isAnalysisRemarkEnabled("size-info"))
141+
return M.getInstructionCount();
142+
return 0;
143+
}
144+
145+
void PMDataManager::emitInstrCountChangedRemark(Pass *P, Module &M,
146+
unsigned CountBefore) {
147+
// Did the user request the remark? If not, quit.
148+
if (!M.getContext().getDiagHandlerPtr()->isAnalysisRemarkEnabled("size-info"))
149+
return;
150+
151+
// We need a function containing at least one basic block in order to output
152+
// remarks. Since it's possible that the first function in the module doesn't
153+
// actually contain a basic block, we have to go and find one that's suitable
154+
// for emitting remarks.
155+
auto It = std::find_if(M.begin(), M.end(),
156+
[](const Function &Fn) { return !Fn.empty(); });
137157

158+
// Didn't find a function. Quit.
159+
if (It == M.end())
160+
return;
161+
162+
// We found a function containing at least one basic block.
163+
Function *F = &*It;
138164

165+
// How many instructions are in the module now?
166+
unsigned CountAfter = M.getInstructionCount();
167+
168+
// If there was no change, don't emit a remark.
169+
if (CountBefore == CountAfter)
170+
return;
171+
172+
// If it's a pass manager, don't emit a remark. (This hinges on the assumption
173+
// that the only passes that return non-null with getAsPMDataManager are pass
174+
// managers.) The reason we have to do this is to avoid emitting remarks for
175+
// CGSCC passes.
176+
if (P->getAsPMDataManager())
177+
return;
178+
179+
// Compute a possibly negative delta between the instruction count before
180+
// running P, and after running P.
181+
int64_t Delta = (int64_t)CountAfter - (int64_t)CountBefore;
182+
183+
BasicBlock &BB = *F->begin();
184+
OptimizationRemarkAnalysis R("size-info", "IRSizeChange",
185+
DiagnosticLocation(), &BB);
186+
// FIXME: Move ore namespace to DiagnosticInfo so that we can use it. This
187+
// would let us use NV instead of DiagnosticInfoOptimizationBase::Argument.
188+
R << DiagnosticInfoOptimizationBase::Argument("Pass", P->getPassName())
189+
<< ": IR instruction count changed from "
190+
<< DiagnosticInfoOptimizationBase::Argument("IRInstrsBefore", CountBefore)
191+
<< " to "
192+
<< DiagnosticInfoOptimizationBase::Argument("IRInstrsAfter", CountAfter)
193+
<< "; Delta: "
194+
<< DiagnosticInfoOptimizationBase::Argument("DeltaInstrCount", Delta);
195+
F->getContext().diagnose(R); // Not using ORE for layering reasons.
196+
}
139197

140198
void PassManagerPrettyStackEntry::print(raw_ostream &OS) const {
141199
if (!V && !M)
@@ -1284,6 +1342,7 @@ bool BBPassManager::runOnFunction(Function &F) {
12841342
return false;
12851343

12861344
bool Changed = doInitialization(F);
1345+
Module &M = *F.getParent();
12871346

12881347
for (BasicBlock &BB : F)
12891348
for (unsigned Index = 0; Index < getNumContainedPasses(); ++Index) {
@@ -1299,8 +1358,9 @@ bool BBPassManager::runOnFunction(Function &F) {
12991358
// If the pass crashes, remember this.
13001359
PassManagerPrettyStackEntry X(BP, BB);
13011360
TimeRegion PassTimer(getPassTimer(BP));
1302-
1361+
unsigned InstrCount = initSizeRemarkInfo(M);
13031362
LocalChanged |= BP->runOnBasicBlock(BB);
1363+
emitInstrCountChangedRemark(BP, M, InstrCount);
13041364
}
13051365

13061366
Changed |= LocalChanged;
@@ -1500,7 +1560,7 @@ bool FPPassManager::runOnFunction(Function &F) {
15001560
return false;
15011561

15021562
bool Changed = false;
1503-
1563+
Module &M = *F.getParent();
15041564
// Collect inherited analysis from Module level pass manager.
15051565
populateInheritedAnalysis(TPM->activeStack);
15061566

@@ -1516,8 +1576,9 @@ bool FPPassManager::runOnFunction(Function &F) {
15161576
{
15171577
PassManagerPrettyStackEntry X(FP, F);
15181578
TimeRegion PassTimer(getPassTimer(FP));
1519-
1579+
unsigned InstrCount = initSizeRemarkInfo(M);
15201580
LocalChanged |= FP->runOnFunction(F);
1581+
emitInstrCountChangedRemark(FP, M, InstrCount);
15211582
}
15221583

15231584
Changed |= LocalChanged;
@@ -1594,7 +1655,9 @@ MPPassManager::runOnModule(Module &M) {
15941655
PassManagerPrettyStackEntry X(MP, M);
15951656
TimeRegion PassTimer(getPassTimer(MP));
15961657

1658+
unsigned InstrCount = initSizeRemarkInfo(M);
15971659
LocalChanged |= MP->runOnModule(M);
1660+
emitInstrCountChangedRemark(MP, M, InstrCount);
15981661
}
15991662

16001663
Changed |= LocalChanged;

llvm/lib/IR/Module.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,13 @@ unsigned Module::getCodeViewFlag() const {
464464
return cast<ConstantInt>(Val->getValue())->getZExtValue();
465465
}
466466

467+
unsigned Module::getInstructionCount() {
468+
unsigned NumInstrs = 0;
469+
for (Function &F : FunctionList)
470+
NumInstrs += F.getInstructionCount();
471+
return NumInstrs;
472+
}
473+
467474
Comdat *Module::getOrInsertComdat(StringRef Name) {
468475
auto &Entry = *ComdatSymTab.insert(std::make_pair(Name, Comdat())).first;
469476
Entry.second.Name = &Entry;

llvm/test/Other/size-remarks.ll

+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
; Ensure that IR count remarks in the legacy pass manager work.
2+
; What this test should check for:
3+
; * Positive, nonzero sizes before/after
4+
; * Nonzero deltas
5+
; * Sizes are being tracked properly across multiple remarks. E.g, if we have
6+
; original_count_1, final_count_1, and
7+
; original_count_2, final_count_2,
8+
; Then original_count_2 == final_count_1.
9+
10+
; For these remarks, the "function" field in the YAML file doesn't matter.
11+
; Each of the testcases work by combining the output remarks with the
12+
; optimization record emit using -pass-remarks-output. This is done to prevent
13+
; test flakiness wrt instruction counts, but also ensure that the output values
14+
; are equivalent in both outputs.
15+
16+
; RUN: opt < %s -inline -pass-remarks-analysis='size-info' \
17+
; RUN: -pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \
18+
; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=CGSCC
19+
; CGSCC: remark: <unknown>:0:0: Function Integration/Inlining:
20+
; CGSCC-SAME: IR instruction count changed from
21+
; CGSCC-SAME: [[ORIG:[1-9][0-9]*]] to [[FINAL:[1-9][0-9]*]];
22+
; CGSCC-SAME: Delta: [[DELTA:-?[1-9][0-9]*]]
23+
; CGSCC: --- !Analysis
24+
; CGSCC-NEXT: Pass: size-info
25+
; CGSCC-NEXT: Name: IRSizeChange
26+
; CGSCC-NEXT: Function:
27+
; CGSCC-NEXT: Args:
28+
; CGSCC-NEXT: - Pass: Function Integration/Inlining
29+
; CGSCC-NEXT: - String: ': IR instruction count changed from '
30+
; CGSCC-NEXT: - IRInstrsBefore: '[[ORIG]]'
31+
; CGSCC-NEXT: - String: ' to '
32+
; CGSCC-NEXT: - IRInstrsAfter: '[[FINAL]]'
33+
; CGSCC-NEXT: - String: '; Delta: '
34+
; CGSCC-NEXT: - DeltaInstrCount: '[[DELTA]]'
35+
36+
; RUN: opt < %s -instcombine -pass-remarks-analysis='size-info' \
37+
; RUN:-pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \
38+
; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=FUNC
39+
; FUNC: remark: <unknown>:0:0: Combine redundant instructions:
40+
; FUNC-SAME: IR instruction count changed from
41+
; FUNC-SAME: [[SIZE1:[1-9][0-9]*]] to [[SIZE2:[1-9][0-9]*]];
42+
; FUNC-SAME: Delta: [[DELTA1:-?[1-9][0-9]*]]
43+
; FUNC-NEXT: remark: <unknown>:0:0: Combine redundant instructions:
44+
; FUNC-SAME: IR instruction count changed from
45+
; FUNC-SAME: [[SIZE2]] to [[SIZE3:[1-9][0-9]*]];
46+
; FUNC-SAME: Delta: [[DELTA2:-?[1-9][0-9]*]]
47+
; FUNC: --- !Analysis
48+
; FUNC-NEXT: Pass: size-info
49+
; FUNC-NEXT: Name: IRSizeChange
50+
; FUNC-NEXT: Function:
51+
; FUNC-NEXT: Args:
52+
; FUNC-NEXT: - Pass: Combine redundant instructions
53+
; FUNC-NEXT: - String: ': IR instruction count changed from '
54+
; FUNC-NEXT: - IRInstrsBefore: '[[SIZE1]]'
55+
; FUNC-NEXT: - String: ' to '
56+
; FUNC-NEXT: - IRInstrsAfter: '[[SIZE2]]'
57+
; FUNC-NEXT: - String: '; Delta: '
58+
; FUNC-NEXT: - DeltaInstrCount: '[[DELTA1]]'
59+
; FUNC: --- !Analysis
60+
; FUNC-NEXT: Pass: size-info
61+
; FUNC-NEXT: Name: IRSizeChange
62+
; FUNC-NEXT: Function:
63+
; FUNC-NEXT: Args:
64+
; FUNC-NEXT: - Pass: Combine redundant instructions
65+
; FUNC-NEXT: - String: ': IR instruction count changed from '
66+
; FUNC-NEXT: - IRInstrsBefore: '[[SIZE2]]'
67+
; FUNC-NEXT: - String: ' to '
68+
; FUNC-NEXT: - IRInstrsAfter: '[[SIZE3]]'
69+
; FUNC-NEXT: - String: '; Delta: '
70+
; FUNC-NEXT: - DeltaInstrCount: '[[DELTA2]]'
71+
72+
; RUN: opt < %s -globaldce -pass-remarks-analysis='size-info' \
73+
; RUN: -pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \
74+
; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=MODULE
75+
; MODULE: remark:
76+
; MODULE-SAME: Dead Global Elimination:
77+
; MODULE-SAME: IR instruction count changed from
78+
; MODULE-SAME: [[ORIG:[1-9][0-9]*]] to [[FINAL:[1-9][0-9]*]];
79+
; MODULE-SAME: Delta: [[DELTA:-?[1-9][0-9]*]]
80+
; MODULE: --- !Analysis
81+
; MODULE-NEXT: Pass: size-info
82+
; MODULE-NEXT: Name: IRSizeChange
83+
; MODULE-NEXT: Function:
84+
; MODULE-NEXT: Args:
85+
; MODULE-NEXT: - Pass: Dead Global Elimination
86+
; MODULE-NEXT: - String: ': IR instruction count changed from '
87+
; MODULE-NEXT: - IRInstrsBefore: '[[ORIG]]'
88+
; MODULE-NEXT: - String: ' to '
89+
; MODULE-NEXT: - IRInstrsAfter: '[[FINAL]]'
90+
; MODULE-NEXT: - String: '; Delta: '
91+
; MODULE-NEXT: - DeltaInstrCount: '[[DELTA]]'
92+
93+
; RUN: opt < %s -dce -pass-remarks-analysis='size-info' \
94+
; RUN: -pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \
95+
; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=BB
96+
; BB: remark: <unknown>:0:0: Dead Code Elimination:
97+
; BB-SAME: IR instruction count changed from
98+
; BB-SAME: [[ORIG:[1-9][0-9]*]] to [[FINAL:[1-9][0-9]*]];
99+
; BB-SAME: Delta: [[DELTA:-?[1-9][0-9]*]]
100+
; BB: --- !Analysis
101+
; BB-NEXT: Pass: size-info
102+
; BB-NEXT: Name: IRSizeChange
103+
; BB-NEXT: Function:
104+
; BB-NEXT: Args:
105+
; BB-NEXT: - Pass: Dead Code Elimination
106+
; BB-NEXT: - String: ': IR instruction count changed from '
107+
; BB-NEXT: - IRInstrsBefore: '[[ORIG]]'
108+
; BB-NEXT: - String: ' to '
109+
; BB-NEXT: - IRInstrsAfter: '[[FINAL]]'
110+
; BB-NEXT: - String: '; Delta: '
111+
; BB-NEXT: - DeltaInstrCount: '[[DELTA]]'
112+
113+
; RUN: opt < %s -loop-unroll -pass-remarks-analysis='size-info' \
114+
; RUN: -pass-remarks-output=%t.yaml -S -o /dev/null 2> %t; \
115+
; RUN: cat %t %t.yaml | FileCheck %s -check-prefix=LOOP
116+
; LOOP: remark: <unknown>:0:0: Unroll loops:
117+
; LOOP-SAME: IR instruction count changed from
118+
; LOOP-SAME: [[ORIG:[1-9][0-9]*]] to [[FINAL:[1-9][0-9]*]];
119+
; LOOP-SAME: Delta: [[DELTA:-?[1-9][0-9]*]]
120+
; LOOP: --- !Analysis
121+
; LOOP-NEXT: Pass: size-info
122+
; LOOP-NEXT: Name: IRSizeChange
123+
; LOOP-NEXT: Function:
124+
; LOOP-NEXT: Args:
125+
; LOOP-DAG: - Pass: Unroll loops
126+
; LOOP-NEXT: - String: ': IR instruction count changed from '
127+
; LOOP-NEXT: - IRInstrsBefore: '[[ORIG]]'
128+
; LOOP-NEXT: - String: ' to '
129+
; LOOP-NEXT: - IRInstrsAfter: '[[FINAL]]'
130+
; LOOP-NEXT: - String: '; Delta: '
131+
; LOOP-NEXT: - DeltaInstrCount: '[[DELTA]]'
132+
declare i1 ()* @boop()
133+
134+
define internal i1 @pluto() {
135+
%F = call i1 ()* () @boop( )
136+
%c = icmp eq i1 ()* %F, @pluto
137+
ret i1 %c
138+
}
139+
140+
define i32 @foo(i32 %x) {
141+
entry:
142+
%x.addr = alloca i32, align 4
143+
store i32 %x, i32* %x.addr, align 4
144+
%0 = load i32, i32* %x.addr, align 4
145+
ret i32 %0
146+
}
147+
148+
define i32 @bar(i32 %x) {
149+
entry:
150+
%x.addr = alloca i32, align 4
151+
store i32 %x, i32* %x.addr, align 4
152+
%0 = load i32, i32* %x.addr, align 4
153+
%call = call i32 @foo(i32 %0)
154+
br label %for.body
155+
for.body:
156+
%s.06 = phi i32 [ 0, %entry ], [ %add, %for.body ]
157+
%i.05 = phi i32 [ 0, %entry ], [ %inc, %for.body ]
158+
%add = add nsw i32 %i.05, 4
159+
%inc = add nsw i32 %i.05, 1
160+
%exitcond = icmp eq i32 %inc, 16
161+
br i1 %exitcond, label %for.end, label %for.body
162+
for.end:
163+
ret i32 %add
164+
}

0 commit comments

Comments
 (0)