Skip to content

Commit 0e696e1

Browse files
committed
SILCombine - Fix worklist logic for OSSA canonicalization
Required before fixing/re-enabling OSSA RAUW utilities. Make sure the SILCombine worklist canonicalizes all the copies and guarantees termination. Run canonicalization on every existing copy_value once and once for every new copy_value added during SILCombine. Only add copies and their uses back to the worklist if canonicalization deleted an instruction. Add tracing for sinking forwaring instructions.
1 parent 8504265 commit 0e696e1

File tree

2 files changed

+87
-53
lines changed

2 files changed

+87
-53
lines changed

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 86 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ bool SILCombiner::trySinkOwnedForwardingInst(SingleValueInstruction *svi) {
175175
[](Operand *use) { return !use->isLifetimeEnding(); }))
176176
return false;
177177

178+
LLVM_DEBUG(llvm::dbgs() << "Sink forwarding: " << *svi << '\n');
179+
178180
// Otherwise, delete all of the debug uses so we don't have to sink them as
179181
// well and then return true so we process svi in its new position.
180182
deleteAllDebugUses(svi, getInstModCallbacks());
@@ -209,6 +211,10 @@ bool SILCombiner::trySinkOwnedForwardingInst(SingleValueInstruction *svi) {
209211
}
210212

211213
auto *newSVI = svi->clone(sviUser);
214+
215+
LLVM_DEBUG(llvm::dbgs()
216+
<< "Sink forwarding: " << *svi << " to " << *newSVI << '\n');
217+
212218
Worklist.add(newSVI);
213219
sviUse->set(newSVI);
214220
}
@@ -220,51 +226,70 @@ bool SILCombiner::trySinkOwnedForwardingInst(SingleValueInstruction *svi) {
220226

221227
/// Canonicalize each extended OSSA lifetime that contains an instruction newly
222228
/// created during this SILCombine iteration.
223-
void SILCombiner::canonicalizeOSSALifetimes() {
229+
///
230+
/// \p currentInst is null if the current instruction was deleted during its
231+
/// SILCombine.
232+
///
233+
/// Avoid endless worklist iteration as follows:
234+
///
235+
/// - Canonicalization only runs on the canonical definition of the visited
236+
/// instruction if it was itself a copy or any new copies were inserted
237+
/// as a result of optimization.
238+
///
239+
/// - Instructions are only added back to the SILCombine worklist when
240+
/// canonicalization deletes an instruction. Only the canonical def being
241+
/// processed and its uses are added rather than arbitrary operands of the
242+
/// deleted instruction. This ensures that an instruction is only added back
243+
/// to the worklist after SILCombine either directly optimized it or created a
244+
/// new copy_value for which it is the canonical def or its use.
245+
void SILCombiner::canonicalizeOSSALifetimes(SILInstruction *currentInst) {
224246
if (!enableCopyPropagation || !Builder.hasOwnership())
225247
return;
226248

227249
SmallSetVector<SILValue, 16> defsToCanonicalize;
228-
for (auto *trackedInst : *Builder.getTrackingList()) {
229-
if (trackedInst->isDeleted())
230-
continue;
231250

232-
if (auto *cvi = dyn_cast<CopyValueInst>(trackedInst)) {
233-
SILValue def = CanonicalizeOSSALifetime::getCanonicalCopiedDef(cvi);
234-
235-
// getCanonicalCopiedDef returns a copy whenever that the copy's source is
236-
// guaranteed. In that case, find the root of the borrowed lifetime. If it
237-
// is a function argument, then a simple guaranteed canonicalization can
238-
// be performed. Canonicalizing other borrow scopes is not handled by
239-
// SILCombine because it's not a single-lifetime canonicalization.
240-
// Instead, SILCombine treats a copy that uses a borrowed value as a
241-
// separate owned live range. Handling the compensation code across the
242-
// borrow scope boundary requires post processing in a particular order.
243-
// The copy propagation pass knows how to handle that. To avoid complexity
244-
// and ensure fast convergence, rewriting borrow scopes should not be
245-
// combined with other unrelated transformations.
246-
if (auto *copy = dyn_cast<CopyValueInst>(def)) {
247-
if (SILValue borrowDef =
248-
CanonicalizeBorrowScope::getCanonicalBorrowedDef(
249-
copy->getOperand())) {
250-
if (isa<SILFunctionArgument>(borrowDef)) {
251-
def = borrowDef;
252-
}
251+
// copyInst was either optimized by a SILCombine visitor or is a copy_value
252+
// produced by the visitor. Find the canonical def.
253+
auto recordCopiedDef = [&defsToCanonicalize](CopyValueInst *copyInst) {
254+
SILValue def = CanonicalizeOSSALifetime::getCanonicalCopiedDef(copyInst);
255+
256+
// getCanonicalCopiedDef returns a copy whenever that the copy's source is
257+
// guaranteed. In that case, find the root of the borrowed lifetime. If it
258+
// is a function argument, then a simple guaranteed canonicalization can be
259+
// performed. Canonicalizing other borrow scopes is not handled by
260+
// SILCombine because it's not a single-lifetime canonicalization. Instead,
261+
// SILCombine treats a copy that uses a borrowed value as a separate owned
262+
// live range. Handling the compensation code across the borrow scope
263+
// boundary requires post processing in a particular order. The copy
264+
// propagation pass knows how to handle that. To avoid complexity and ensure
265+
// fast convergence, rewriting borrow scopes should not be combined with
266+
// other unrelated transformations.
267+
if (auto *copyDef = dyn_cast<CopyValueInst>(def)) {
268+
if (SILValue borrowDef = CanonicalizeBorrowScope::getCanonicalBorrowedDef(
269+
copyDef->getOperand())) {
270+
if (isa<SILFunctionArgument>(borrowDef)) {
271+
def = borrowDef;
253272
}
254273
}
255-
defsToCanonicalize.insert(def);
256274
}
275+
defsToCanonicalize.insert(def);
276+
};
277+
278+
if (auto *copyInst = dyn_cast_or_null<CopyValueInst>(currentInst))
279+
recordCopiedDef(copyInst);
280+
281+
for (auto *trackedInst : *Builder.getTrackingList()) {
282+
if (trackedInst->isDeleted())
283+
continue;
284+
if (auto *copyInst = dyn_cast<CopyValueInst>(trackedInst))
285+
recordCopiedDef(copyInst);
257286
}
258287
if (defsToCanonicalize.empty())
259288
return;
260289

261290
// Remove instructions deleted during canonicalization from SILCombine's
262291
// worklist. CanonicalizeOSSALifetime invalidates operands before invoking
263292
// the deletion callback.
264-
//
265-
// Note: a simpler approach would be to drain the Worklist before
266-
// canonicalizing OSSA, then callbacks could be completely removed from
267-
// CanonicalizeOSSALifetime.
268293
auto canonicalizeCallbacks =
269294
InstModCallbacks().onDelete([this](SILInstruction *instToDelete) {
270295
eraseInstFromFunction(*instToDelete,
@@ -279,23 +304,30 @@ void SILCombiner::canonicalizeOSSALifetimes() {
279304

280305
while (!defsToCanonicalize.empty()) {
281306
SILValue def = defsToCanonicalize.pop_back_val();
282-
if (auto functionArg = dyn_cast<SILFunctionArgument>(def)) {
283-
if (!borrowCanonicalizer.canonicalizeFunctionArgument(functionArg))
284-
continue;
285-
} else if (!canonicalizer.canonicalizeValueLifetime(def)) {
286-
continue;
287-
}
288-
MadeChange = true;
289307

290-
// Canonicalization may rewrite many copies and destroys within a single
291-
// extended lifetime, but the extended lifetime of each canonical def is
292-
// non-overlapping. If def was successfully canonicalized, simply add it
293-
// and its users to the SILCombine worklist.
294-
if (auto *inst = def->getDefiningInstruction()) {
295-
Worklist.add(inst);
308+
deleter.getCallbacks().resetHadCallbackInvocation();
309+
310+
auto canonicalized = [&]() {
311+
if (!deleter.getCallbacks().hadCallbackInvocation())
312+
return;
313+
314+
if (auto *inst = def->getDefiningInstruction()) {
315+
Worklist.add(inst);
316+
}
317+
for (auto *use : def->getUses()) {
318+
Worklist.add(use->getUser());
319+
}
320+
};
321+
322+
if (def->getOwnershipKind() == OwnershipKind::Guaranteed) {
323+
if (auto functionArg = dyn_cast<SILFunctionArgument>(def)) {
324+
if (borrowCanonicalizer.canonicalizeFunctionArgument(functionArg))
325+
canonicalized();
326+
}
327+
continue;
296328
}
297-
for (auto *use : def->getUses()) {
298-
Worklist.add(use->getUser());
329+
if (canonicalizer.canonicalizeValueLifetime(def)) {
330+
canonicalized();
299331
}
300332
}
301333
}
@@ -367,27 +399,29 @@ bool SILCombiner::doOneIteration(SILFunction &F, unsigned Iteration) {
367399
Builder.setInsertionPoint(I);
368400

369401
#ifndef NDEBUG
370-
std::string OrigI;
402+
std::string OrigIStr;
371403
#endif
372-
LLVM_DEBUG(llvm::raw_string_ostream SS(OrigI); I->print(SS);
373-
OrigI = SS.str(););
374-
LLVM_DEBUG(llvm::dbgs() << "SC: Visiting: " << OrigI << '\n');
404+
LLVM_DEBUG(llvm::raw_string_ostream SS(OrigIStr); I->print(SS);
405+
OrigIStr = SS.str(););
406+
LLVM_DEBUG(llvm::dbgs() << "SC: Visiting: " << OrigIStr << '\n');
375407

408+
SILInstruction *currentInst = I;
376409
if (SILInstruction *Result = visit(I)) {
377410
++NumCombined;
378411
// Should we replace the old instruction with a new one?
379412
Worklist.replaceInstructionWithInstruction(I, Result
380413
#ifndef NDEBUG
381-
,
382-
OrigI
414+
,
415+
OrigIStr
383416
#endif
384417
);
418+
currentInst = Result;
385419
MadeChange = true;
386420
}
387421

388422
// Eliminate copies created that this SILCombine iteration may have
389423
// introduced during OSSA-RAUW.
390-
canonicalizeOSSALifetimes();
424+
canonicalizeOSSALifetimes(currentInst->isDeleted() ? nullptr : currentInst);
391425

392426
// Builder's tracking list has been accumulating instructions created by the
393427
// during this SILCombine iteration. To finish this iteration, go through

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ class SILCombiner :
384384

385385
/// Apply CanonicalizeOSSALifetime to the extended lifetime of any copy
386386
/// introduced during SILCombine for an owned value.
387-
void canonicalizeOSSALifetimes();
387+
void canonicalizeOSSALifetimes(SILInstruction *currentInst);
388388

389389
// Optimize concatenation of string literals.
390390
// Constant-fold concatenation of string literals known at compile-time.

0 commit comments

Comments
 (0)