Skip to content

Commit 7ecee99

Browse files
committed
[sil-combine] Implement a new class SingleBlockOwnedForwardingInstFolder to help eliminate chains of owned forwarding insts and use it to update upcast and unchecked_ref_cast for ownership.
b644c80f90fb7099ec956bb44065b50e432c5146 caused all owned forwarding instructions to be sunk to their uses in the exact cases where we could eliminate the parent forwarding inst (namely that the value we want to fold has no non-debug, non-consuming users). So I was able to implement this just by implementing a single basic block algorithm that works via a planner struct using said canonicalization. One initializes the planner struct with the instruction that is going to either be eliminated or have its forwarding operand set. Then one adds each of the individual chains that lead to the use that we wish to fold, each time checking that we can eliminate the instruction. Once the user has added all of the intermediate forwarding instructions, by construction (see paragraph above), we know we can optimize. So we eliminate all intermediate values and then depending on whether the user called the set value or replace value method we either set front's operand to be the passed in value or we RAUW/erase front with that value. It is important to note that before we do one of those two operations, front's operand is undef, so we need to perform one of these two operations.
1 parent 279d058 commit 7ecee99

File tree

4 files changed

+548
-83
lines changed

4 files changed

+548
-83
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,13 @@ class SILInstruction
468468
/// Return the array of operands for this instruction.
469469
ArrayRef<Operand> getAllOperands() const;
470470

471+
/// Return the ith operand of this instruction.
472+
///
473+
/// Equivalent to performing getAllOperands()[index];
474+
const Operand &getOperandRef(unsigned index) const {
475+
return getAllOperands()[index];
476+
}
477+
471478
/// Return the array of type dependent operands for this instruction.
472479
///
473480
/// Type dependent operands are hidden operands, i.e. not part of the SIL
@@ -497,6 +504,11 @@ class SILInstruction
497504
/// Return the array of mutable operands for this instruction.
498505
MutableArrayRef<Operand> getAllOperands();
499506

507+
/// Return the ith mutable operand of this instruction.
508+
///
509+
/// Equivalent to performing getAllOperands()[index];
510+
Operand &getOperandRef(unsigned index) { return getAllOperands()[index]; }
511+
500512
/// Return the array of mutable type dependent operands for this instruction.
501513
MutableArrayRef<Operand> getTypeDependentOperands();
502514

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,12 @@ class SILCombiner :
194194
return Worklist.replaceInstUsesWith(I, V);
195195
}
196196

197+
/// Perform use->set(value) and add use->user to the worklist.
198+
void setUseValue(Operand *use, SILValue value) {
199+
use->set(value);
200+
Worklist.add(use->getUser());
201+
}
202+
197203
// This method is to be used when a value is found to be dead,
198204
// replaceable with another preexisting expression. Here we add all
199205
// uses of oldValue to the worklist, replace all uses of oldValue
@@ -347,9 +353,26 @@ class SILCombiner :
347353
bool optimizeIdentityCastComposition(ApplyInst *FInverse,
348354
StringRef FInverseName, StringRef FName);
349355

350-
private:
356+
/// Let \p user and \p value be two forwarding single value instructions with
357+
/// the property that \p user, through potentially a chain of forwarding
358+
/// instructions.
359+
///
360+
/// Let \p user and \p value be two forwarding single value instructions with
361+
/// the property that \p value is the value that \p user forwards. In this
362+
/// case, this helper routine will eliminate \p value if it can rewrite user
363+
/// in terms of \p newValue. This is intended to handle cases where we have
364+
/// completely different types so we need to actually create a new instruction
365+
/// with a different result type.
366+
///
367+
/// \param newValueGenerator Generator that produces the new value to
368+
/// use. Conditionally called if we can perform the optimization.
369+
SILInstruction *tryFoldComposedUnaryForwardingInstChain(
370+
SingleValueInstruction *user, SingleValueInstruction *value,
371+
function_ref<SILValue()> newValueGenerator);
372+
351373
InstModCallbacks &getInstModCallbacks() { return instModCallbacks; }
352374

375+
private:
353376
// Build concrete existential information using findInitExistential.
354377
Optional<ConcreteOpenedExistentialInfo>
355378
buildConcreteOpenedExistentialInfo(Operand &ArgOperand);

lib/SILOptimizer/SILCombiner/SILCombinerCastVisitors.cpp

Lines changed: 197 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
2222
#include "swift/SILOptimizer/Analysis/ValueTracking.h"
2323
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
24+
#include "swift/SILOptimizer/Utils/DebugOptUtils.h"
2425
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
2526
#include "llvm/ADT/DenseMap.h"
2627
#include "llvm/ADT/SmallPtrSet.h"
@@ -95,16 +96,120 @@ SILCombiner::visitRefToRawPointerInst(RefToRawPointerInst *rrpi) {
9596
return nullptr;
9697
}
9798

98-
SILInstruction *SILCombiner::visitUpcastInst(UpcastInst *UCI) {
99-
if (UCI->getFunction()->hasOwnership())
99+
namespace {
100+
101+
/// A folder object for sequences of forwarding instructions that forward owned
102+
/// ownership. Is used to detect if we can delete the intermediate forwarding
103+
/// instructions without ownership issues and then allows the user to either
104+
/// delete all of the rest of the forwarding instructions and then replace front
105+
/// with a new value or set front's operand to a new value.
106+
class SingleBlockOwnedForwardingInstFolder {
107+
SmallVector<SingleValueInstruction *, 4> rest;
108+
SILCombiner &SC;
109+
SingleValueInstruction *front;
110+
111+
public:
112+
SingleBlockOwnedForwardingInstFolder(
113+
SILCombiner &SC, SingleValueInstruction *instructionToFold)
114+
: SC(SC), front(instructionToFold) {
115+
// If our initial instruction to fold isn't owned, set it to nullptr to
116+
// indicate invalid.
117+
if (SILValue(instructionToFold).getOwnershipKind() != OwnershipKind::Owned)
118+
front = nullptr;
119+
}
120+
121+
bool isValid() const { return bool(front); }
122+
123+
bool add(SingleValueInstruction *next) {
124+
assert(isValid());
125+
if (SILValue(next).getOwnershipKind() != OwnershipKind::Owned)
126+
return false;
127+
128+
if (next->getSingleUse()) {
129+
rest.push_back(next);
130+
return true;
131+
}
132+
133+
if (front->getParent() != next->getParent()) {
134+
return false;
135+
}
136+
137+
// Otherwise, since the two values are in the same block and we want to
138+
// optimize only if our original value doesn't have any non-debug uses, we
139+
// know that our value can only have a single non-debug use, the consuming
140+
// user. So if we are not in that situation, bail.
141+
if (!hasOneNonDebugUse(next))
142+
return false;
143+
144+
rest.push_back(next);
145+
return true;
146+
}
147+
148+
/// Delete all forwarding uses and then RAUW front with newValue.
149+
SingleValueInstruction *optimizeWithReplacement(SILValue newValue) && {
150+
// NOTE: Even though after running cleanup rest, front now has its
151+
// forwarding operand set to Undef, we haven't touched its result. So it is
152+
// safe to RAUW.
153+
cleanupRest();
154+
SC.replaceValueUsesWith(front, newValue);
155+
return nullptr;
156+
}
157+
158+
/// Delete all forwarding uses and then set front's first operand to be \p
159+
/// newValue.
160+
SingleValueInstruction *optimizeWithSetValue(SILValue newValue) && {
161+
cleanupRest();
162+
assert(isa<SILUndef>(front->getOperand(0)));
163+
front->setOperand(0, newValue);
164+
SC.setUseValue(&front->getOperandRef(0), newValue);
100165
return nullptr;
166+
}
167+
168+
private:
169+
/// From backwards -> forwards, for each instruction in rest, delete all of
170+
/// its debug uses and then set its single remaining use to be SILUndef.
171+
///
172+
/// This means that after this runs front's forwarding operand is now
173+
/// SILUndef.
174+
void cleanupRest() & {
175+
// We process backwards -> forwards. This cleans up everything but the front
176+
// value.
177+
while (!rest.empty()) {
178+
auto *inst = rest.pop_back_val();
179+
deleteAllDebugUses(inst, SC.getInstModCallbacks());
180+
auto *next = inst->getSingleUse();
181+
assert(next);
182+
assert(rest.empty() || bool(next->getUser() == rest.back()));
183+
next->set(SILUndef::get(next->get()->getType(), inst->getModule()));
184+
SC.eraseInstFromFunction(*inst);
185+
}
186+
}
187+
};
101188

102-
// Ref to raw pointer consumption of other ref casts.
189+
} // namespace
190+
191+
SILInstruction *SILCombiner::visitUpcastInst(UpcastInst *uci) {
192+
auto operand = uci->getOperand();
193+
194+
// %operandUpcast = upcast %0 : $X->Y
195+
// %upcastInst = upcast %operandUpcast : $Y->Z
196+
//
197+
// %operandUpcast = upcast %0 : $X->Y
198+
// %1 = upcast %0 : $X->Z
103199
//
104-
// (upcast (upcast x)) -> (upcast x)
105-
if (auto *Op = dyn_cast<UpcastInst>(UCI->getOperand())) {
106-
UCI->setOperand(Op->getOperand());
107-
return Op->use_empty() ? eraseInstFromFunction(*Op) : nullptr;
200+
// If operandUpcast does not have any further uses, we delete it.
201+
if (auto *operandAsUpcast = dyn_cast<UpcastInst>(operand)) {
202+
if (operand.getOwnershipKind() != OwnershipKind::Owned) {
203+
uci->setOperand(operandAsUpcast->getOperand());
204+
return operandAsUpcast->use_empty()
205+
? eraseInstFromFunction(*operandAsUpcast)
206+
: nullptr;
207+
}
208+
SingleBlockOwnedForwardingInstFolder folder(*this, uci);
209+
if (folder.add(operandAsUpcast)) {
210+
return std::move(folder).optimizeWithSetValue(
211+
operandAsUpcast->getOperand());
212+
}
108213
}
109214

110215
return nullptr;
@@ -282,35 +387,92 @@ SILCombiner::visitUncheckedAddrCastInst(UncheckedAddrCastInst *UADCI) {
282387
}
283388

284389
SILInstruction *
285-
SILCombiner::visitUncheckedRefCastInst(UncheckedRefCastInst *URCI) {
286-
if (URCI->getFunction()->hasOwnership())
287-
return nullptr;
390+
SILCombiner::visitUncheckedRefCastInst(UncheckedRefCastInst *urci) {
391+
// %0 = unchecked_ref_cast %x : $X->Y
392+
// %1 = unchecked_ref_cast %0 : $Y->Z
393+
//
394+
// ->
395+
//
396+
// %0 = unchecked_ref_cast %x : $X->Y
397+
// %1 = unchecked_ref_cast %x : $X->Z
398+
//
399+
// NOTE: For owned values, we only perform this optimization if we can
400+
// guarantee that we can eliminate the initial unchecked_ref_cast.
401+
if (auto *otherURCI = dyn_cast<UncheckedRefCastInst>(urci->getOperand())) {
402+
SILValue otherURCIOp = otherURCI->getOperand();
403+
if (otherURCIOp.getOwnershipKind() != OwnershipKind::Owned) {
404+
return Builder.createUncheckedRefCast(urci->getLoc(), otherURCIOp,
405+
urci->getType());
406+
}
407+
SingleBlockOwnedForwardingInstFolder folder(*this, urci);
408+
if (folder.add(otherURCI)) {
409+
auto *newValue = Builder.createUncheckedRefCast(
410+
urci->getLoc(), otherURCIOp, urci->getType());
411+
return std::move(folder).optimizeWithReplacement(newValue);
412+
}
413+
}
288414

289-
// (unchecked-ref-cast (unchecked-ref-cast x X->Y) Y->Z)
290-
// ->
291-
// (unchecked-ref-cast x X->Z)
292-
if (auto *OtherURCI = dyn_cast<UncheckedRefCastInst>(URCI->getOperand()))
293-
return Builder.createUncheckedRefCast(URCI->getLoc(),
294-
OtherURCI->getOperand(),
295-
URCI->getType());
296-
297-
// (unchecked_ref_cast (upcast x X->Y) Y->Z) -> (unchecked_ref_cast x X->Z)
298-
if (auto *UI = dyn_cast<UpcastInst>(URCI->getOperand()))
299-
return Builder.createUncheckedRefCast(URCI->getLoc(),
300-
UI->getOperand(),
301-
URCI->getType());
302-
303-
if (URCI->getType() != URCI->getOperand()->getType() &&
304-
URCI->getType().isExactSuperclassOf(URCI->getOperand()->getType()))
305-
return Builder.createUpcast(URCI->getLoc(), URCI->getOperand(),
306-
URCI->getType());
307-
308-
// (unchecked_ref_cast (open_existential_ref (init_existential_ref X))) ->
309-
// (unchecked_ref_cast X)
310-
if (auto *OER = dyn_cast<OpenExistentialRefInst>(URCI->getOperand()))
311-
if (auto *IER = dyn_cast<InitExistentialRefInst>(OER->getOperand()))
312-
return Builder.createUncheckedRefCast(URCI->getLoc(), IER->getOperand(),
313-
URCI->getType());
415+
// %0 = upcast %x : $X->Y
416+
// %1 = unchecked_ref_cast %0 : $Y->Z
417+
//
418+
// ->
419+
//
420+
// %0 = upcast %x : $X->Y
421+
// %1 = unchecked_ref_cast %x : $X->Z
422+
//
423+
// NOTE: For owned values, we only perform this optimization if we can
424+
// guarantee that we can eliminate the upcast.
425+
if (auto *ui = dyn_cast<UpcastInst>(urci->getOperand())) {
426+
SILValue uiOp = ui->getOperand();
427+
428+
if (uiOp.getOwnershipKind() != OwnershipKind::Owned) {
429+
return Builder.createUncheckedRefCast(urci->getLoc(), uiOp,
430+
urci->getType());
431+
}
432+
433+
SingleBlockOwnedForwardingInstFolder folder(*this, urci);
434+
if (folder.add(ui)) {
435+
auto *newValue =
436+
Builder.createUncheckedRefCast(urci->getLoc(), uiOp, urci->getType());
437+
return std::move(folder).optimizeWithReplacement(newValue);
438+
}
439+
}
440+
441+
// This is an exact transform where we are replacing urci with an upcast on
442+
// the same value. So from an ownership perspective because both instructions
443+
// are forwarding and we are eliminating urci, we are safe.
444+
if (urci->getType() != urci->getOperand()->getType() &&
445+
urci->getType().isExactSuperclassOf(urci->getOperand()->getType()))
446+
return Builder.createUpcast(urci->getLoc(), urci->getOperand(),
447+
urci->getType());
448+
449+
// %0 = init_existential_ref %x : $X -> Existential
450+
// %1 = open_existential_ref %0 : $Existential -> @opened() Existential
451+
// %2 = unchecked_ref_cast %1
452+
//
453+
// ->
454+
//
455+
// %0 = init_existential_ref %x : $X -> Existential
456+
// %1 = open_existential_ref %0 : $Existential -> @opened() Existential
457+
// %2 = unchecked_ref_cast %x
458+
//
459+
// NOTE: When we have an owned value, we only perform this optimization if we
460+
// can remove both the open_existential_ref and the init_existential_ref.
461+
if (auto *oer = dyn_cast<OpenExistentialRefInst>(urci->getOperand())) {
462+
if (auto *ier = dyn_cast<InitExistentialRefInst>(oer->getOperand())) {
463+
if (ier->getOwnershipKind() != OwnershipKind::Owned) {
464+
return Builder.createUncheckedRefCast(urci->getLoc(), ier->getOperand(),
465+
urci->getType());
466+
}
467+
468+
SingleBlockOwnedForwardingInstFolder folder(*this, urci);
469+
if (folder.add(oer) && folder.add(ier)) {
470+
auto *newValue = Builder.createUncheckedRefCast(
471+
urci->getLoc(), ier->getOperand(), urci->getType());
472+
return std::move(folder).optimizeWithReplacement(newValue);
473+
}
474+
}
475+
}
314476

315477
return nullptr;
316478
}

0 commit comments

Comments
 (0)