Skip to content

Commit 64340aa

Browse files
authored
Merge pull request #41297 from eeckstein/bisecting-improvements
SILPassManager: improve bisecting for debugging the optimizer
2 parents f417630 + 8fe36ee commit 64340aa

File tree

7 files changed

+223
-134
lines changed

7 files changed

+223
-134
lines changed

docs/DebuggingTheCompiler.md

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -714,39 +714,31 @@ which causes the miscompile.
714714
Currently there is no tool to automatically identify the bad optimization, but
715715
it's quite easy to do this manually:
716716

717-
1. Find the offending optimization with bisecting:
718-
719-
a. Add the compiler option `-Xllvm -sil-opt-pass-count=<n>`, where `<n>`
720-
is the number of optimizations to run.
721-
722-
b. Bisect: find n where the executable crashes, but does not crash
723-
with n-1. First just try n = 10, 100, 1000, 10000, etc. to find
724-
an upper bound). Then can either bisect the invocation by hand or
725-
place the invocation into a script and use
726-
`./llvm-project/llvm/utils/bisect` to automatically bisect
727-
based on the scripts error code. Example invocation:
728-
729-
bisect --start=0 --end=10000 ./invoke_swift_passing_N.sh "%(count)s"
730-
731-
c. Once one finds `n`, Add another option `-Xllvm -sil-print-pass-name`. The output can be
732-
large, so it's best to redirect stderr to a file (`2> output`).
733-
In the output search for the last pass before `stage Address Lowering`.
734-
It should be the `Run #<n-1>`. This line tells you the name of the bad
735-
optimization pass and on which function it run.
736-
737-
2. Get the SIL before and after the bad optimization.
738-
739-
a. Add the compiler option
740-
`-Xllvm -sil-print-function='<function>'`
741-
where `<function>` is the function name (including the preceding `$`).
742-
For example:
743-
`-Xllvm -sil-print-function='$s4test6testityS2iF'`.
744-
Again, the output can be large, so it's best to redirect stderr to a file.
745-
b. From the output, copy the SIL of the function *before* the bad
746-
run into a separate file and the SIL *after* the bad run into a file.
747-
c. Compare both SIL files and try to figure out what the optimization pass
748-
did wrong. To simplify the comparison, it's sometimes helpful to replace
749-
all SIL values (e.g. `%27`) with a constant string (e.g. `%x`).
717+
1. Add the compiler option `-Xllvm -sil-opt-pass-count=<n>`, where `<n>`
718+
is the number of optimizations to run.
719+
720+
2. Bisect: find n where the executable crashes, but does not crash
721+
with n-1. First just try n = 10, 100, 1000, 10000, etc. to find
722+
an upper bound). Then can either bisect the invocation by hand or
723+
place the invocation into a script and use
724+
`./llvm-project/llvm/utils/bisect` to automatically bisect
725+
based on the scripts error code. Example invocation:
726+
727+
bisect --start=0 --end=10000 ./invoke_swift_passing_N.sh "%(count)s"
728+
729+
3. Add another option `-Xllvm -sil-print-last`. The output can be
730+
large, so it's best to redirect stderr to a file (`2> output`).
731+
The output contains the SIL before and after the bad optimization.
732+
733+
4. Copy the two functions from the output into separate files and
734+
compare both files. Try to figure out what the optimization pass
735+
did wrong. To simplify the comparison, it's sometimes helpful to replace
736+
all SIL values (e.g. `%27`) with a constant string (e.g. `%x`).
737+
738+
5. If the bad optimization is SILCombine or SimplifyCFG (which do a lot of
739+
transformations in a single run) it's helpful to continue bisecting on
740+
the sub-pass number. The option `-Xllvm -sil-opt-pass-count=<n>.<m>`
741+
can be used for that, where `m` is the sub-pass number.
750742

751743
### Using git-bisect in the presence of branch forwarding/feature branches
752744

include/swift/SILOptimizer/PassManager/PassManager.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ class SILPassManager {
143143

144144
/// The number of passes run so far.
145145
unsigned NumPassesRun = 0;
146+
unsigned numSubpassesRun = 0;
147+
148+
unsigned maxNumPassesToRun = UINT_MAX;
149+
unsigned maxNumSubpassesToRun = UINT_MAX;
146150

147151
/// For invoking Swift passes.
148152
SwiftPassInvocation swiftPassInvocation;
@@ -352,10 +356,17 @@ class SILPassManager {
352356

353357
void executePassPipelinePlan(const SILPassPipelinePlan &Plan);
354358

359+
bool continueWithNextSubpassRun(SILInstruction *forInst, SILFunction *function,
360+
SILTransform *trans);
361+
355362
static bool isPassDisabled(StringRef passName);
356363
static bool disablePassesForFunction(SILFunction *function);
357364

358365
private:
366+
bool doPrintBefore(SILTransform *T, SILFunction *F);
367+
368+
bool doPrintAfter(SILTransform *T, SILFunction *F, bool PassChangedSIL);
369+
359370
void execute();
360371

361372
/// Add a pass of a specific kind.
@@ -387,7 +398,8 @@ class SILPassManager {
387398
bool analysesUnlocked();
388399

389400
/// Dumps information about the pass with index \p TransIdx to llvm::dbgs().
390-
void dumpPassInfo(const char *Title, SILTransform *Tr, SILFunction *F);
401+
void dumpPassInfo(const char *Title, SILTransform *Tr, SILFunction *F,
402+
int passIdx = -1);
391403

392404
/// Dumps information about the pass with index \p TransIdx to llvm::dbgs().
393405
void dumpPassInfo(const char *Title, unsigned TransIdx,

include/swift/SILOptimizer/PassManager/Transforms.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
namespace swift {
2020
class SILModule;
2121
class SILFunction;
22+
class SILInstruction;
2223
class PrettyStackTraceSILFunctionTransform;
2324

2425
/// The base class for all SIL-level transformations.
@@ -129,6 +130,10 @@ namespace swift {
129130

130131
SILFunction *getFunction() { return F; }
131132

133+
bool continueWithNextSubpassRun(SILInstruction *forInst = nullptr) {
134+
return PM->continueWithNextSubpassRun(forInst, F, this);
135+
}
136+
132137
void invalidateAnalysis(SILAnalysis::InvalidationKind K) {
133138
PM->invalidateAnalysis(F, K);
134139
}

lib/SILOptimizer/PassManager/PassManager.cpp

Lines changed: 62 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,13 @@ llvm::cl::opt<bool> SILPrintPassTime(
5252
"sil-print-pass-time", llvm::cl::init(false),
5353
llvm::cl::desc("Print the execution time of each SIL pass"));
5454

55-
llvm::cl::opt<unsigned> SILNumOptPassesToRun(
56-
"sil-opt-pass-count", llvm::cl::init(UINT_MAX),
57-
llvm::cl::desc("Stop optimizing after <N> optimization passes"));
55+
llvm::cl::opt<bool> SILPrintLast(
56+
"sil-print-last", llvm::cl::init(false),
57+
llvm::cl::desc("Print the last optimized function before and after the last pass"));
58+
59+
llvm::cl::opt<std::string> SILNumOptPassesToRun(
60+
"sil-opt-pass-count", llvm::cl::init(""),
61+
llvm::cl::desc("Stop optimizing after <N> passes or <N>.<M> passes/sub-passes"));
5862

5963
llvm::cl::opt<std::string> SILBreakOnFun(
6064
"sil-break-on-function", llvm::cl::init(""),
@@ -178,7 +182,11 @@ static bool functionSelectionEmpty() {
178182
return SILPrintFunction.empty() && SILPrintFunctions.empty();
179183
}
180184

181-
static bool doPrintBefore(SILTransform *T, SILFunction *F) {
185+
bool SILPassManager::doPrintBefore(SILTransform *T, SILFunction *F) {
186+
if (NumPassesRun == maxNumPassesToRun - 1 && SILPrintLast &&
187+
maxNumSubpassesToRun == UINT_MAX && !isMandatory)
188+
return true;
189+
182190
if (F && !isFunctionSelectedForPrinting(F))
183191
return false;
184192

@@ -201,7 +209,10 @@ static bool doPrintBefore(SILTransform *T, SILFunction *F) {
201209
return false;
202210
}
203211

204-
static bool doPrintAfter(SILTransform *T, SILFunction *F, bool PassChangedSIL) {
212+
bool SILPassManager::doPrintAfter(SILTransform *T, SILFunction *F, bool PassChangedSIL) {
213+
if (NumPassesRun == maxNumPassesToRun - 1 && SILPrintLast && !isMandatory)
214+
return true;
215+
205216
if (F && !isFunctionSelectedForPrinting(F))
206217
return false;
207218

@@ -316,6 +327,22 @@ SILPassManager::SILPassManager(SILModule *M, bool isMandatory,
316327
Analyses.push_back(create##NAME##Analysis(Mod));
317328
#include "swift/SILOptimizer/Analysis/Analysis.def"
318329

330+
if (!SILNumOptPassesToRun.empty()) {
331+
StringRef countsStr = SILNumOptPassesToRun;
332+
bool validFormat = true;
333+
if (countsStr.consumeInteger(10, maxNumPassesToRun))
334+
validFormat = false;
335+
if (countsStr.startswith(".")) {
336+
countsStr = countsStr.drop_front(1);
337+
if (countsStr.consumeInteger(10, maxNumSubpassesToRun))
338+
validFormat = false;
339+
}
340+
if (!validFormat || !countsStr.empty()) {
341+
llvm::errs() << "error: wrong format of -sil-opt-pass-count option\n";
342+
exit(1);
343+
}
344+
}
345+
319346
for (SILAnalysis *A : Analyses) {
320347
A->initialize(this);
321348
}
@@ -329,7 +356,27 @@ SILPassManager::SILPassManager(SILModule *M, bool isMandatory,
329356
bool SILPassManager::continueTransforming() {
330357
if (isMandatory)
331358
return true;
332-
return NumPassesRun < SILNumOptPassesToRun;
359+
return NumPassesRun < maxNumPassesToRun;
360+
}
361+
362+
bool SILPassManager::continueWithNextSubpassRun(SILInstruction *forInst,
363+
SILFunction *function,
364+
SILTransform *trans) {
365+
if (isMandatory)
366+
return true;
367+
if (NumPassesRun != maxNumPassesToRun - 1)
368+
return true;
369+
370+
unsigned subPass = numSubpassesRun++;
371+
372+
if (subPass == maxNumSubpassesToRun - 1 && SILPrintLast) {
373+
dumpPassInfo("*** SIL function before ", trans, function);
374+
if (forInst) {
375+
llvm::dbgs() << " *** sub-pass " << subPass << " for " << *forInst;
376+
}
377+
function->dump(getOptions().EmitVerboseSIL);
378+
}
379+
return subPass < maxNumSubpassesToRun;
333380
}
334381

335382
bool SILPassManager::analysesUnlocked() {
@@ -359,24 +406,20 @@ static bool breakBeforeRunning(StringRef fnName, SILFunctionTransform *SFT) {
359406
}
360407

361408
void SILPassManager::dumpPassInfo(const char *Title, SILTransform *Tr,
362-
SILFunction *F) {
363-
llvm::dbgs() << " " << Title << " #" << NumPassesRun << ", stage "
364-
<< StageName << ", pass : " << Tr->getID()
365-
<< " (" << Tr->getTag() << ")";
409+
SILFunction *F, int passIdx) {
410+
llvm::dbgs() << " " << Title << " #" << NumPassesRun
411+
<< ", stage " << StageName << ", pass";
412+
if (passIdx >= 0)
413+
llvm::dbgs() << ' ' << passIdx;
414+
llvm::dbgs() << ": " << Tr->getID() << " (" << Tr->getTag() << ")";
366415
if (F)
367416
llvm::dbgs() << ", Function: " << F->getName();
368417
llvm::dbgs() << '\n';
369418
}
370419

371420
void SILPassManager::dumpPassInfo(const char *Title, unsigned TransIdx,
372421
SILFunction *F) {
373-
SILTransform *Tr = Transformations[TransIdx];
374-
llvm::dbgs() << " " << Title << " #" << NumPassesRun << ", stage "
375-
<< StageName << ", pass " << TransIdx << ": " << Tr->getID()
376-
<< " (" << Tr->getTag() << ")";
377-
if (F)
378-
llvm::dbgs() << ", Function: " << F->getName();
379-
llvm::dbgs() << '\n';
422+
dumpPassInfo(Title, Transformations[TransIdx], F, (int)TransIdx);
380423
}
381424

382425
bool SILPassManager::isMandatoryFunctionPass(SILFunctionTransform *sft) {
@@ -451,6 +494,7 @@ void SILPassManager::runPassOnFunction(unsigned TransIdx, SILFunction *F) {
451494
updateSILModuleStatsBeforeTransform(F->getModule(), SFT, *this, NumPassesRun);
452495

453496
CurrentPassHasInvalidated = false;
497+
numSubpassesRun = 0;
454498

455499
auto MatchFun = [&](const std::string &Str) -> bool {
456500
return SFT->getTag().contains(Str) || SFT->getID().contains(Str);
@@ -623,6 +667,7 @@ void SILPassManager::runModulePass(unsigned TransIdx) {
623667
updateSILModuleStatsBeforeTransform(*Mod, SMT, *this, NumPassesRun);
624668

625669
CurrentPassHasInvalidated = false;
670+
numSubpassesRun = 0;
626671

627672
if (SILPrintPassName)
628673
dumpPassInfo("Run module pass", TransIdx);

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 56 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,57 @@ class SILCombineCanonicalize final : CanonicalizeInstruction {
161161
}
162162
};
163163

164+
SILCombiner::SILCombiner(SILFunctionTransform *trans,
165+
bool removeCondFails, bool enableCopyPropagation) :
166+
parentTransform(trans),
167+
AA(trans->getPassManager()->getAnalysis<AliasAnalysis>(trans->getFunction())),
168+
DA(trans->getPassManager()->getAnalysis<DominanceAnalysis>()),
169+
PCA(trans->getPassManager()->getAnalysis<ProtocolConformanceAnalysis>()),
170+
CHA(trans->getPassManager()->getAnalysis<ClassHierarchyAnalysis>()),
171+
NLABA(trans->getPassManager()->getAnalysis<NonLocalAccessBlockAnalysis>()),
172+
Worklist("SC"),
173+
deleter(InstModCallbacks()
174+
.onDelete([&](SILInstruction *instToDelete) {
175+
// We allow for users in SILCombine to perform 2 stage
176+
// deletion, so we need to split the erasing of
177+
// instructions from adding operands to the worklist.
178+
eraseInstFromFunction(*instToDelete,
179+
false /* don't add operands */);
180+
})
181+
.onNotifyWillBeDeleted(
182+
[&](SILInstruction *instThatWillBeDeleted) {
183+
Worklist.addOperandsToWorklist(
184+
*instThatWillBeDeleted);
185+
})
186+
.onCreateNewInst([&](SILInstruction *newlyCreatedInst) {
187+
Worklist.add(newlyCreatedInst);
188+
})
189+
.onSetUseValue([&](Operand *use, SILValue newValue) {
190+
use->set(newValue);
191+
Worklist.add(use->getUser());
192+
})),
193+
deadEndBlocks(trans->getFunction()), MadeChange(false),
194+
RemoveCondFails(removeCondFails),
195+
enableCopyPropagation(enableCopyPropagation), Iteration(0),
196+
Builder(*trans->getFunction(), &TrackingList),
197+
FuncBuilder(*trans),
198+
CastOpt(
199+
FuncBuilder, nullptr /*SILBuilderContext*/,
200+
/* ReplaceValueUsesAction */
201+
[&](SILValue Original, SILValue Replacement) {
202+
replaceValueUsesWith(Original, Replacement);
203+
},
204+
/* ReplaceInstUsesAction */
205+
[&](SingleValueInstruction *I, ValueBase *V) {
206+
replaceInstUsesWith(*I, V);
207+
},
208+
/* EraseAction */
209+
[&](SILInstruction *I) { eraseInstFromFunction(*I); }),
210+
deBlocks(trans->getFunction()),
211+
ownershipFixupContext(getInstModCallbacks(), deBlocks),
212+
swiftPassInvocation(trans->getPassManager(),
213+
trans->getFunction(), this) {}
214+
164215
bool SILCombiner::trySinkOwnedForwardingInst(SingleValueInstruction *svi) {
165216
if (auto *consumingUse = svi->getSingleConsumingUse()) {
166217
auto *consumingUser = consumingUse->getUser();
@@ -355,6 +406,9 @@ bool SILCombiner::doOneIteration(SILFunction &F, unsigned Iteration) {
355406
if (I == nullptr)
356407
continue;
357408

409+
if (!parentTransform->continueWithNextSubpassRun(I))
410+
return false;
411+
358412
// Check to see if we can DCE the instruction.
359413
if (isInstructionTriviallyDead(I)) {
360414
LLVM_DEBUG(llvm::dbgs() << "SC: DCE: " << *I << '\n');
@@ -455,7 +509,7 @@ bool SILCombiner::runOnFunction(SILFunction &F) {
455509
StackNesting::fixNesting(&F);
456510
}
457511

458-
// Cleanup the builder and return whether or not we made any changes.
512+
assert(TrackingList.empty() && "TrackingList should be fully processed");
459513
return Changed;
460514
}
461515

@@ -532,33 +586,18 @@ namespace {
532586

533587
class SILCombine : public SILFunctionTransform {
534588

535-
llvm::SmallVector<SILInstruction *, 64> TrackingList;
536-
537589
/// The entry point to the transformation.
538590
void run() override {
539-
auto *AA = PM->getAnalysis<AliasAnalysis>(getFunction());
540-
auto *DA = PM->getAnalysis<DominanceAnalysis>();
541-
auto *PCA = PM->getAnalysis<ProtocolConformanceAnalysis>();
542-
auto *CHA = PM->getAnalysis<ClassHierarchyAnalysis>();
543-
auto *NLABA = PM->getAnalysis<NonLocalAccessBlockAnalysis>();
544-
545591
bool enableCopyPropagation =
546592
getOptions().CopyPropagation == CopyPropagationOption::On;
547593
if (getOptions().EnableOSSAModules) {
548594
enableCopyPropagation =
549595
getOptions().CopyPropagation != CopyPropagationOption::Off;
550596
}
551597

552-
SILOptFunctionBuilder FuncBuilder(*this);
553-
// Create a SILBuilder with a tracking list for newly added
554-
// instructions, which we will periodically move to our worklist.
555-
SILBuilder B(*getFunction(), &TrackingList);
556-
SILCombiner Combiner(this, FuncBuilder, B, AA, DA, PCA, CHA, NLABA,
557-
getOptions().RemoveRuntimeAsserts,
598+
SILCombiner Combiner(this, getOptions().RemoveRuntimeAsserts,
558599
enableCopyPropagation);
559600
bool Changed = Combiner.runOnFunction(*getFunction());
560-
assert(TrackingList.empty() &&
561-
"TrackingList should be fully processed by SILCombiner");
562601

563602
if (Changed) {
564603
// Invalidate everything.

0 commit comments

Comments
 (0)