Skip to content

Commit d451c3e

Browse files
authored
Merge pull request #73334 from Snowy1803/unified-lost-vars-statistics
Add compile flag for lost debug variables statistics
2 parents d5aafbc + 711e964 commit d451c3e

File tree

6 files changed

+148
-25
lines changed

6 files changed

+148
-25
lines changed

docs/DebuggingTheCompiler.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,13 @@ with the proper attributes to ensure they'll be available in the debugger. In
302302
particular, if you see `SWIFT_DEBUG_DUMP` in a class declaration, that class
303303
has a `dump()` method you can call.
304304

305+
### Pass statistics
306+
307+
There are options to output a lot of different statistics, including about
308+
SIL passes. More information is available in
309+
[Compiler Performance](CompilerPerformance.md) for the unified statistics, and
310+
[Optimizer Counter Analysis](OptimizerCountersAnalysis.md) for pass counters.
311+
305312
## Debugging and Profiling on SIL level
306313

307314
### SIL source level profiling

docs/HowToUpdateDebugInfo.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,11 +255,18 @@ debug_value %1 : $Int, var, name "pair", type $Pair, expr op_fragment:#Pair.a //
255255

256256
## Rules of thumb
257257
- Optimization passes may never drop a variable entirely. If a variable is
258-
entirely optimized away, an `undef` debug value should still be kept.
258+
entirely optimized away, an `undef` debug value should still be kept. The only
259+
exception is an unreachable function or scope, which is entirely removed.
259260
- A `debug_value` must always describe a correct value for that source variable
260261
at that source location. If a value is only correct on some paths through that
261262
instruction, it must be replaced with `undef`. Debug info never speculates.
262263
- When a SIL instruction is deleted, call salvageDebugInfo(). It will try to
263264
capture the effect of the deleted instruction in a debug expression, so the
264265
location can be preserved. You can also use an `InstructionDeleter` which will
265266
automatically call `salvageDebugInfo`.
267+
268+
> [!Tip]
269+
> To detect when a pass drops a variable, you can use the
270+
> `-Xllvm -sil-stats-lost-variables` to print when a variable is lost by a pass.
271+
> More information about this option is available in
272+
> [Optimizer Counter Analysis](OptimizerCountersAnalysis.md)

docs/OptimizerCountersAnalysis.md

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ The following statistics can be recorded:
5454

5555
* For SILFunctions: the number of SIL basic blocks for each SILFunction, the
5656
number of SIL instructions, the number of SILInstructions of a specific
57-
kind (e.g. a number of alloc_ref instructions)
57+
kind (e.g. a number of alloc_ref instructions), the number of debug
58+
variables
5859

5960
* For SILModules: the number of SIL basic blocks in the SILModule, the number
6061
of SIL instructions, the number of SILFunctions, the number of
@@ -118,6 +119,16 @@ e.g. `-Xllvm -sil-stats-only-instructions=alloc_ref,alloc_stack`. If you need to
118119
collect stats about all kinds of SIL instructions, you can use this syntax:
119120
`-Xllvm -sil-stats-only-instructions=all`.
120121

122+
### Debug variable level counters
123+
A different type of counter is the lost debug variables counter. It is enabled
124+
by using the `-Xllvm -sil-stats-lost-variables` command-line option. It only
125+
tracks statistics about lost variables in SILFunctions. It is not enabled by
126+
any other command-line option, but can be combined with the others. It is not
127+
compatible with thresholds, it always counts lost variables. Note that it does
128+
not track the number of debug variables: it counts the number of debug variables
129+
that were present, but aren't anymore. If a variable changes location or scope,
130+
which is not allowed, it will be counted as lost.
131+
121132
## Configuring which counters changes should be recorded
122133

123134
The user has a possibility to configure a number of thresholds, which control
@@ -181,9 +192,9 @@ And for counter stats it looks like this:
181192
* `function_history` corresponds to the verbose mode of function
182193
counters collection, when changes to the SILFunction counters are logged
183194
unconditionally, without any on-line filtering.
184-
* `CounterName` is typically one of `block`, `inst`, `function`, `memory`,
185-
or `inst_instruction_name` if you collect counters for specific kinds of SIL
186-
instructions.
195+
* `CounterName` is typically one of `block`, `inst`, `function`, `memory`,
196+
`lostvars`, or `inst_instruction_name` if you collect counters for specific
197+
kinds of SIL instructions.
187198
* `Symbol` is e.g. the name of a function
188199
* `StageName` is the name of the current optimizer pipeline stage
189200
* `TransformName` is the name of the current optimizer transformation/pass
@@ -192,6 +203,14 @@ And for counter stats it looks like this:
192203
want to reproduce the result later using
193204
`-Xllvm -sil-opt-pass-count -Xllvm TransformPassNumber`
194205

206+
## Extract Lost Variables per Pass
207+
208+
For lost variables, there is a script to output a CSV with only the amount of
209+
lost variables per pass. You can then easily open the resulting CSV in Numbers
210+
to make graphs.
211+
212+
`utils/process-stats-lost-variables csv_file_with_counters > csv_aggregate`
213+
195214
## Storing the produced statistics into a database
196215

197216
To store the set of produced counters into a database, you can use the
@@ -345,4 +364,3 @@ from Counters C where C.counter = 'inst' and C.kind = 'module'
345364
group by Stage
346365
order by sum(C.Delta);
347366
```
348-

lib/SILOptimizer/Utils/OptimizerStatsUtils.cpp

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@
6868
#include "llvm/Support/CommandLine.h"
6969
#include "llvm/Support/FileSystem.h"
7070
#include "llvm/Support/Process.h"
71-
#include "swift/SIL/SILValue.h"
71+
#include "swift/Basic/SourceManager.h"
72+
#include "swift/SIL/DebugUtils.h"
7273
#include "swift/SIL/SILVisitor.h"
7374
#include "swift/SILOptimizer/Analysis/Analysis.h"
7475
#include "swift/SILOptimizer/PassManager/PassManager.h"
@@ -186,6 +187,11 @@ llvm::cl::opt<bool> SILStatsFunctions(
186187
"sil-stats-functions", llvm::cl::init(false),
187188
llvm::cl::desc("Enable computation of statistics for SIL functions"));
188189

190+
/// Dump statistics about lost debug variables.
191+
llvm::cl::opt<bool> SILStatsLostVariables(
192+
"sil-stats-lost-variables", llvm::cl::init(false),
193+
llvm::cl::desc("Dump lost debug variables stats"));
194+
189195
/// The name of the output file for optimizer counters.
190196
llvm::cl::opt<std::string> SILStatsOutputFile(
191197
"sil-stats-output-file", llvm::cl::init(""),
@@ -272,16 +278,28 @@ struct FunctionStat {
272278
/// Instruction counts per SILInstruction kind.
273279
InstructionCounts InstCounts;
274280

281+
using VarID = std::tuple<const SILDebugScope *, llvm::StringRef,
282+
unsigned, unsigned>;
283+
llvm::StringSet<> VarNames;
284+
llvm::DenseSet<FunctionStat::VarID> DebugVariables;
285+
275286
FunctionStat(SILFunction *F);
276287
FunctionStat() {}
277288

289+
// The DebugVariables set contains pointers to VarNames. Disallow copy.
290+
FunctionStat(const FunctionStat &) = delete;
291+
FunctionStat(FunctionStat &&) = default;
292+
FunctionStat &operator=(const FunctionStat &) = delete;
293+
FunctionStat &operator=(FunctionStat &&) = default;
294+
278295
void print(llvm::raw_ostream &stream) const {
279296
stream << "FunctionStat("
280297
<< "blocks = " << BlockCount << ", Inst = " << InstCount << ")\n";
281298
}
282299

283300
bool operator==(const FunctionStat &rhs) const {
284-
return BlockCount == rhs.BlockCount && InstCount == rhs.InstCount;
301+
return BlockCount == rhs.BlockCount && InstCount == rhs.InstCount
302+
&& DebugVariables == rhs.DebugVariables;
285303
}
286304

287305
bool operator!=(const FunctionStat &rhs) const { return !(*this == rhs); }
@@ -360,14 +378,20 @@ struct ModuleStat {
360378
bool operator!=(const ModuleStat &rhs) const { return !(*this == rhs); }
361379
};
362380

363-
// A helper type to collect the stats about the number of instructions and basic
364-
// blocks.
381+
/// A helper type to collect the stats about a function (instructions, blocks,
382+
/// debug variables).
365383
struct InstCountVisitor : SILInstructionVisitor<InstCountVisitor> {
366384
int BlockCount = 0;
367385
int InstCount = 0;
368386
InstructionCounts &InstCounts;
369387

370-
InstCountVisitor(InstructionCounts &InstCounts) : InstCounts(InstCounts) {}
388+
llvm::StringSet<> &VarNames;
389+
llvm::DenseSet<FunctionStat::VarID> &DebugVariables;
390+
391+
InstCountVisitor(InstructionCounts &InstCounts,
392+
llvm::StringSet<> &VarNames,
393+
llvm::DenseSet<FunctionStat::VarID> &DebugVariables)
394+
: InstCounts(InstCounts), VarNames(VarNames), DebugVariables(DebugVariables) {}
371395

372396
int getBlockCount() const {
373397
return BlockCount;
@@ -385,6 +409,29 @@ struct InstCountVisitor : SILInstructionVisitor<InstCountVisitor> {
385409
void visit(SILInstruction *I) {
386410
++InstCount;
387411
++InstCounts[I->getKind()];
412+
413+
if (!SILStatsLostVariables)
414+
return;
415+
// Check the debug variable.
416+
DebugVarCarryingInst inst(I);
417+
if (!inst)
418+
return;
419+
std::optional<SILDebugVariable> varInfo = inst.getVarInfo();
420+
if (!varInfo)
421+
return;
422+
423+
llvm::StringRef UniqueName = VarNames.insert(varInfo->Name).first->getKey();
424+
if (!varInfo->Loc)
425+
varInfo->Loc = inst->getLoc();
426+
unsigned line = 0, col = 0;
427+
if (varInfo->Loc && varInfo->Loc->getSourceLoc().isValid()) {
428+
std::tie(line, col) = inst->getModule().getSourceManager()
429+
.getPresumedLineAndColumnForLoc(varInfo->Loc->getSourceLoc(), 0);
430+
}
431+
FunctionStat::VarID key(
432+
varInfo->Scope ? varInfo->Scope : inst->getDebugScope(),
433+
UniqueName, line, col);
434+
DebugVariables.insert(key);
388435
}
389436
};
390437

@@ -549,7 +596,7 @@ class NewLineInserter {
549596
std::unique_ptr<llvm::raw_ostream, void(*)(llvm::raw_ostream *)>
550597
stats_output_stream = {nullptr, nullptr};
551598

552-
/// Return the output streamm to be used for logging the collected statistics.
599+
/// Return the output stream to be used for logging the collected statistics.
553600
llvm::raw_ostream &stats_os() {
554601
// Initialize the stream if it is not initialized yet.
555602
if (!stats_output_stream) {
@@ -664,6 +711,18 @@ bool isFirstTimeData(int Old, int New) {
664711
return Old == 0 && New != Old;
665712
}
666713

714+
int computeLostVariables(llvm::DenseSet<FunctionStat::VarID> &Old,
715+
llvm::DenseSet<FunctionStat::VarID> &New) {
716+
unsigned count = 0;
717+
for (auto &var : Old) {
718+
if (New.contains(var))
719+
continue;
720+
// llvm::dbgs() << "Lost variable: " << std::get<1>(var) << "\n";
721+
count++;
722+
}
723+
return count;
724+
}
725+
667726
/// Dump statistics for a SILFunction. It is only used if a user asked to
668727
/// produce detailed stats about transformations of SILFunctions. This
669728
/// information is dumped unconditionally, for each transformation that changed
@@ -710,7 +769,7 @@ void processFuncStatsChanges(SILFunction *F, FunctionStat &OldStat,
710769
TransformationContext &Ctx) {
711770
processFuncStatHistory(F, NewStat, Ctx);
712771

713-
if (!SILStatsFunctions && !SILStatsDumpAll)
772+
if (!SILStatsFunctions && !SILStatsLostVariables && !SILStatsDumpAll)
714773
return;
715774

716775
if (OldStat == NewStat)
@@ -724,6 +783,8 @@ void processFuncStatsChanges(SILFunction *F, FunctionStat &OldStat,
724783
// Compute deltas.
725784
double DeltaBlockCount = computeDelta(OldStat.BlockCount, NewStat.BlockCount);
726785
double DeltaInstCount = computeDelta(OldStat.InstCount, NewStat.InstCount);
786+
int LostVariables = computeLostVariables(OldStat.DebugVariables,
787+
NewStat.DebugVariables);
727788

728789
NewLineInserter nl;
729790

@@ -744,6 +805,11 @@ void processFuncStatsChanges(SILFunction *F, FunctionStat &OldStat,
744805
printCounterChange("function", "inst", DeltaInstCount, OldStat.InstCount,
745806
NewStat.InstCount, Ctx, F->getName());
746807
}
808+
809+
if ((SILStatsDumpAll || SILStatsLostVariables) && LostVariables) {
810+
stats_os() << nl.get();
811+
printCounterValue("function", "lostvars", LostVariables, F->getName(), Ctx);
812+
}
747813
}
748814

749815
/// Process SILModule's statistics changes.
@@ -867,10 +933,10 @@ void OptimizerStatsAnalysis::updateModuleStats(TransformationContext &Ctx) {
867933
auto &FuncStat = getFunctionStat(&F);
868934
FunctionStat NewFuncStat(&F);
869935
processFuncStatHistory(&F, NewFuncStat, Ctx);
870-
// Update function stats.
871-
FuncStat = NewFuncStat;
872936
// Update module stats.
873937
NewModStat.addFunctionStat(NewFuncStat);
938+
// Update function stats.
939+
FuncStat = std::move(NewFuncStat);
874940
}
875941
} else {
876942
// Go only over functions that were changed since the last computation.
@@ -893,20 +959,20 @@ void OptimizerStatsAnalysis::updateModuleStats(TransformationContext &Ctx) {
893959
auto *F = InvalidatedFuncs.back();
894960
InvalidatedFuncs.pop_back();
895961
auto &FuncStat = getFunctionStat(F);
896-
auto OldFuncStat = FuncStat;
962+
auto &OldFuncStat = FuncStat;
897963
FunctionStat NewFuncStat(F);
898964
processFuncStatsChanges(F, OldFuncStat, NewFuncStat, Ctx);
899965
NewModStat.subFunctionStat(OldFuncStat);
900966
NewModStat.addFunctionStat(NewFuncStat);
901-
FuncStat = NewFuncStat;
967+
FuncStat = std::move(NewFuncStat);
902968
}
903969

904970
// Process deleted functions.
905971
while (!DeletedFuncs.empty()) {
906972
auto *F = DeletedFuncs.back();
907973
DeletedFuncs.pop_back();
908974
auto &FuncStat = getFunctionStat(F);
909-
auto OldFuncStat = FuncStat;
975+
auto &OldFuncStat = FuncStat;
910976
FunctionStat NewFuncStat;
911977
processFuncStatsChanges(F, OldFuncStat, NewFuncStat, Ctx);
912978
NewModStat.subFunctionStat(OldFuncStat);
@@ -922,7 +988,7 @@ void OptimizerStatsAnalysis::updateModuleStats(TransformationContext &Ctx) {
922988
FunctionStat NewFuncStat(F);
923989
processFuncStatsChanges(F, OldFuncStat, NewFuncStat, Ctx);
924990
NewModStat.addFunctionStat(NewFuncStat);
925-
FuncStat = NewFuncStat;
991+
FuncStat = std::move(NewFuncStat);
926992
}
927993
}
928994

@@ -939,7 +1005,7 @@ void OptimizerStatsAnalysis::updateModuleStats(TransformationContext &Ctx) {
9391005
}
9401006

9411007
FunctionStat::FunctionStat(SILFunction *F) {
942-
InstCountVisitor V(InstCounts);
1008+
InstCountVisitor V(InstCounts, VarNames, DebugVariables);
9431009
V.visitSILFunction(F);
9441010
BlockCount = V.getBlockCount();
9451011
InstCount = V.getInstCount();
@@ -957,7 +1023,8 @@ void swift::updateSILModuleStatsAfterTransform(SILModule &M,
9571023
SILTransform *Transform,
9581024
SILPassManager &PM,
9591025
int PassNumber, int Duration) {
960-
if (!SILStatsModules && !SILStatsFunctions && !SILStatsDumpAll)
1026+
if (!SILStatsModules && !SILStatsFunctions && !SILStatsLostVariables
1027+
&& !SILStatsDumpAll)
9611028
return;
9621029
TransformationContext Ctx(M, PM, Transform, PassNumber, Duration);
9631030
OptimizerStatsAnalysis *Stats = PM.getAnalysis<OptimizerStatsAnalysis>();

utils/optimizer_counters_to_sql.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,13 @@ def addStatsFromInput(inputFile, db):
9292
for line in inputFile:
9393
# Read next line
9494
# Split into segments
95-
segments = line.split(",")
95+
segments = line.split(", ")
9696
if len(segments) < 6 or not (segments[0] in [
9797
'module', 'function', 'function_history']):
9898
continue
9999
# Trim all values
100-
segments = map(str.strip, segments)
101-
if segments[0] == 'function_history':
100+
segments = list(map(str.strip, segments))
101+
if segments[0] == 'function_history' or segments[1] == 'lostvars':
102102
# Process history records
103103
delta = 0.0
104104
(kind, counter, stage, transform, passnum,
@@ -128,7 +128,7 @@ def processStatsFile(filename, dbname):
128128
filename,
129129
dbname))
130130
db = OptStatsDB(dbname)
131-
with open(filename, "rb") as inputFile:
131+
with open(filename, "r") as inputFile:
132132
addStatsFromInput(inputFile, db)
133133
db.finish()
134134

utils/process-stats-lost-variables

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/awk -f
2+
3+
# This source file is part of the Swift.org open source project
4+
#
5+
# Copyright (c) 2014 - 2020 Apple Inc. and the Swift project authors
6+
# Licensed under Apache License v2.0 with Runtime Library Exception
7+
#
8+
# See https://swift.org/LICENSE.txt for license information
9+
# See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
11+
BEGIN {
12+
FS = ", "
13+
OFS = ", "
14+
print "Pass Name", "Lost Variables"
15+
}
16+
17+
$1 == "function" && $2 == "lostvars" {
18+
stats[$4] += $6
19+
}
20+
21+
END {
22+
for (i in stats)
23+
print i, stats[i]
24+
}

0 commit comments

Comments
 (0)