Skip to content

Commit 2fad943

Browse files
committed
[sil-combine] Update convert_function canonicalization for ownership.
Some notes: 1. I moved the identity round-trip case to InstSimplify since that is where optimizations like that are. 2. I did not update in this commit the code that eliminates convert_function when it is only destroyed. In a subsequent commit I am going to implement that in a general way and apply it to all forwarding instructions. 3. I implemented eliminating convert_function with ownership only uses in a utility so that I can reuse it for other similar optimizations in SILCombine.
1 parent 9f95271 commit 2fad943

File tree

8 files changed

+555
-98
lines changed

8 files changed

+555
-98
lines changed

include/swift/SIL/ApplySite.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ class ApplySite {
116116

117117
ApplySiteKind getKind() const { return ApplySiteKind(Inst->getKind()); }
118118

119+
SILInstruction *operator*() const { return Inst; }
120+
SILInstruction *operator->() const { return Inst; }
121+
119122
explicit operator bool() const { return Inst != nullptr; }
120123

121124
SILInstruction *getInstruction() const { return Inst; }

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,11 @@ SILValue
705705
makeNewValueAvailable(SILValue value, SILBasicBlock *inBlock,
706706
JointPostDominanceSetComputer *jointPostDomComputer);
707707

708+
/// Given a forwarding instruction, eliminate it if all of its users are debug
709+
/// instructions and ownership uses.
710+
bool tryEliminateOnlyOwnershipUsedForwardingInst(
711+
SingleValueInstruction *forwardingInst, InstModCallbacks &callbacks);
712+
708713
} // end namespace swift
709714

710715
#endif

include/swift/SILOptimizer/Utils/OwnershipOptUtils.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#ifndef SWIFT_SILOPTIMIZER_UTILS_OWNERSHIPOPTUTILS_H
2020
#define SWIFT_SILOPTIMIZER_UTILS_OWNERSHIPOPTUTILS_H
2121

22+
#include "swift/Basic/Defer.h"
2223
#include "swift/SIL/BasicBlockUtils.h"
2324
#include "swift/SIL/OwnershipUtils.h"
2425
#include "swift/SIL/SILModule.h"
@@ -76,6 +77,17 @@ struct OwnershipFixupContext {
7677
extraAddressFixupInfo.intPtrOp = InteriorPointerOperand();
7778
}
7879

80+
/// Gets access to the joint post dominance computer and clears it after \p
81+
/// callback.
82+
template <typename ResultTy>
83+
ResultTy withJointPostDomComputer(
84+
function_ref<ResultTy(JointPostDominanceSetComputer &)> callback) {
85+
// Make sure we clear the joint post dom computer after callback.
86+
SWIFT_DEFER { jointPostDomSetComputer.clear(); };
87+
// Then return callback passing in the computer.
88+
return callback(jointPostDomSetComputer);
89+
}
90+
7991
private:
8092
/// Helper method called to determine if we discovered we needed interior
8193
/// pointer fixups while simplifying.
@@ -129,6 +141,37 @@ class OwnershipRAUWHelper {
129141
SILValue newValue);
130142
};
131143

144+
/// A utility composed ontop of OwnershipFixupContext that knows how to replace
145+
/// a single use of a value with another value with a different ownership. We
146+
/// allow for the values to have different types.
147+
///
148+
/// NOTE: When not in OSSA, this just performs a normal set use, so this code is
149+
/// safe to use with all code.
150+
class OwnershipReplaceSingleUseHelper {
151+
OwnershipFixupContext *ctx;
152+
Operand *use;
153+
SILValue newValue;
154+
155+
public:
156+
OwnershipReplaceSingleUseHelper()
157+
: ctx(nullptr), use(nullptr), newValue(nullptr) {}
158+
159+
/// Return an instance of this class if we support replacing \p use->get()
160+
/// with \p newValue.
161+
///
162+
/// NOTE: For now we only support objects, not addresses so addresses will
163+
/// always yield an invalid helper.
164+
OwnershipReplaceSingleUseHelper(OwnershipFixupContext &ctx, Operand *use,
165+
SILValue newValue);
166+
167+
/// Returns true if this helper was initialized into a valid state.
168+
operator bool() const { return isValid(); }
169+
bool isValid() const { return bool(ctx) && bool(use) && bool(newValue); }
170+
171+
/// Perform the actual RAUW.
172+
SILBasicBlock::iterator perform();
173+
};
174+
132175
} // namespace swift
133176

134177
#endif

lib/SILOptimizer/Analysis/SimplifyInstruction.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ namespace {
7070
SILValue visitPointerToThinFunctionInst(PointerToThinFunctionInst *PTTFI);
7171
SILValue visitBeginAccessInst(BeginAccessInst *BAI);
7272
SILValue visitMetatypeInst(MetatypeInst *MTI);
73+
SILValue visitConvertFunctionInst(ConvertFunctionInst *cfi);
7374

7475
SILValue simplifyOverflowBuiltin(BuiltinInst *BI);
7576
};
@@ -482,6 +483,19 @@ SILValue InstSimplifier::visitBeginAccessInst(BeginAccessInst *BAI) {
482483
return SILValue();
483484
}
484485

486+
SILValue InstSimplifier::visitConvertFunctionInst(ConvertFunctionInst *cfi) {
487+
// Eliminate round trip convert_function. Non round-trip is performed in
488+
// SILCombine.
489+
//
490+
// (convert_function Y->X (convert_function x X->Y)) -> x
491+
SILValue convertedValue = lookThroughOwnershipInsts(cfi->getConverted());
492+
if (auto *subCFI = dyn_cast<ConvertFunctionInst>(convertedValue))
493+
if (subCFI->getConverted()->getType() == cfi->getType())
494+
return lookThroughOwnershipInsts(subCFI->getConverted());
495+
496+
return SILValue();
497+
}
498+
485499
SILValue InstSimplifier::visitMetatypeInst(MetatypeInst *MI) {
486500
auto metaType = MI->getType().castTo<MetatypeType>();
487501
auto instanceType = metaType.getInstanceType();

lib/SILOptimizer/SILCombiner/SILCombinerCastVisitors.cpp

Lines changed: 111 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -978,10 +978,8 @@ SILInstruction *SILCombiner::visitConvertEscapeToNoEscapeInst(
978978
SILType::getPrimitiveObjectType(NewTy));
979979
}
980980

981-
SILInstruction *SILCombiner::visitConvertFunctionInst(ConvertFunctionInst *CFI) {
982-
if (CFI->getFunction()->hasOwnership())
983-
return nullptr;
984-
981+
SILInstruction *
982+
SILCombiner::visitConvertFunctionInst(ConvertFunctionInst *cfi) {
985983
// If this conversion only changes substitutions, then rewrite applications
986984
// of the converted function as applications of the original.
987985
//
@@ -990,84 +988,124 @@ SILInstruction *SILCombiner::visitConvertFunctionInst(ConvertFunctionInst *CFI)
990988
//
991989
// TODO: We could generalize this to handle other ABI-compatible cases, by
992990
// inserting the necessary casts around the arguments.
993-
if (CFI->onlyConvertsSubstitutions()) {
994-
auto usei = CFI->use_begin();
995-
while (usei != CFI->use_end()) {
996-
auto use = *usei++;
997-
auto user = use->getUser();
998-
if (isa<ApplySite>(user) && use->getOperandNumber() == 0) {
999-
auto applySite = ApplySite(user);
1000-
// If this is a partial_apply, insert a convert_function back to the
1001-
// original result type.
1002-
1003-
if (auto pa = dyn_cast<PartialApplyInst>(user)) {
1004-
auto partialApplyTy = pa->getType();
1005-
Builder.setInsertionPoint(std::next(pa->getIterator()));
1006-
1007-
SmallVector<SILValue, 4> args(pa->getArguments().begin(),
1008-
pa->getArguments().end());
1009-
1010-
auto newPA = Builder.createPartialApply(pa->getLoc(),
1011-
CFI->getConverted(),
1012-
pa->getSubstitutionMap(),
1013-
args,
1014-
pa->getFunctionType()->getCalleeConvention());
1015-
auto newConvert = Builder.createConvertFunction(pa->getLoc(),
1016-
newPA, partialApplyTy,
1017-
false);
1018-
pa->replaceAllUsesWith(newConvert);
1019-
eraseInstFromFunction(*pa);
1020-
1021-
continue;
1022-
}
1023-
991+
if (cfi->onlyConvertsSubstitutions()) {
992+
SmallVector<Operand *, 32> worklist(cfi->getUses());
993+
while (!worklist.empty()) {
994+
auto *use = worklist.pop_back_val();
995+
auto *user = use->getUser();
996+
997+
// Look through begin_borrow and copy_value.
998+
if (isa<BeginBorrowInst>(user) || isa<CopyValueInst>(user)) {
999+
for (auto result : user->getResults())
1000+
for (auto *resultUse : result->getUses())
1001+
worklist.push_back(resultUse);
1002+
continue;
1003+
}
1004+
1005+
if (!isa<ApplySite>(user) || use->getOperandNumber() != 0)
1006+
continue;
1007+
1008+
if (auto fas = FullApplySite::isa(user)) {
10241009
// For full apply sites, we only need to replace the `convert_function`
10251010
// with the original value.
1026-
use->set(CFI->getConverted());
1027-
applySite.setSubstCalleeType(
1028-
CFI->getConverted()->getType().castTo<SILFunctionType>());
1011+
//
1012+
// OWNERSHIP DISCUSSION: We know that cfi is forwarding, so we know that
1013+
// if cfi is not owned, then we know that cfi->getConverted() must be
1014+
// valid at applySite and also that the applySite does not consume a
1015+
// value. In such a case, just perform the change and continue.
1016+
SILValue newValue = cfi->getConverted();
1017+
if (newValue.getOwnershipKind() != OwnershipKind::Owned &&
1018+
newValue.getOwnershipKind() != OwnershipKind::Guaranteed) {
1019+
instModCallbacks.setUseValue(use, newValue);
1020+
fas.setSubstCalleeType(newValue->getType().castTo<SILFunctionType>());
1021+
continue;
1022+
}
1023+
1024+
// Otherwise, we need to use the OwnershipReplaceSingleUseHelper since
1025+
// we have been looking through ownership forwarding insts and newValue
1026+
// may be a value with a different lifetime from our original value
1027+
// beyond the initial base value.
1028+
OwnershipReplaceSingleUseHelper helper(ownershipFixupContext, use,
1029+
newValue);
1030+
if (!helper)
1031+
continue;
1032+
helper.perform();
1033+
fas.setSubstCalleeType(newValue->getType().castTo<SILFunctionType>());
1034+
continue;
1035+
}
1036+
1037+
// If this is a partial_apply, insert a convert_function back to the
1038+
// original result type.
1039+
auto *pa = dyn_cast<PartialApplyInst>(user);
1040+
if (!pa)
1041+
continue;
1042+
1043+
auto partialApplyTy = pa->getType();
1044+
if (!hasOwnership()) {
1045+
SILBuilderWithScope localBuilder(std::next(pa->getIterator()), Builder);
1046+
SmallVector<SILValue, 4> args(pa->getArguments().begin(),
1047+
pa->getArguments().end());
1048+
1049+
auto newPA = Builder.createPartialApply(
1050+
pa->getLoc(), cfi->getConverted(), pa->getSubstitutionMap(), args,
1051+
pa->getFunctionType()->getCalleeConvention());
1052+
auto newConvert = Builder.createConvertFunction(pa->getLoc(), newPA,
1053+
partialApplyTy, false);
1054+
replaceInstUsesWith(*pa, newConvert);
1055+
eraseInstFromFunction(*pa);
1056+
continue;
10291057
}
1058+
1059+
OwnershipRAUWHelper helper(ownershipFixupContext, pa,
1060+
cfi->getConverted());
1061+
if (!helper)
1062+
continue;
1063+
SmallVector<SILValue, 4> args(pa->getArguments().begin(),
1064+
pa->getArguments().end());
1065+
auto newValue = withJointPostDomComputer<SILValue>([&](auto &j) {
1066+
return makeCopiedValueAvailable(cfi->getConverted(), pa->getParent(),
1067+
&j);
1068+
});
1069+
1070+
SILBuilderWithScope localBuilder(std::next(pa->getIterator()), Builder);
1071+
auto *newPA = localBuilder.createPartialApply(
1072+
pa->getLoc(), newValue, pa->getSubstitutionMap(), args,
1073+
pa->getFunctionType()->getCalleeConvention());
1074+
if (!use->isLifetimeEnding()) {
1075+
localBuilder.emitDestroyValueOperation(pa->getLoc(), newValue);
1076+
}
1077+
auto *newConvert = localBuilder.createConvertFunction(
1078+
pa->getLoc(), newPA, partialApplyTy, false);
1079+
// We need to end the lifetime of the convert_function/partial_apply since
1080+
// the helper assumes that ossa is correct upon input.
1081+
localBuilder.emitDestroyValueOperation(pa->getLoc(), newConvert);
1082+
helper.perform(newConvert);
10301083
}
10311084
}
1032-
1085+
10331086
// (convert_function (convert_function x)) => (convert_function x)
1034-
if (auto subCFI = dyn_cast<ConvertFunctionInst>(CFI->getConverted())) {
1035-
// If we convert the function type back to itself, we can replace the
1036-
// conversion completely.
1037-
if (subCFI->getConverted()->getType() == CFI->getType()) {
1038-
CFI->replaceAllUsesWith(subCFI->getConverted());
1039-
eraseInstFromFunction(*CFI);
1040-
return nullptr;
1087+
if (auto *subCFI = dyn_cast<ConvertFunctionInst>(cfi->getConverted())) {
1088+
// We handle the case of an identity conversion in inst simplify, so if we
1089+
// see this pattern then we know that we don't have a round trip and thus
1090+
// should just bypass the intermediate conversion.
1091+
if (cfi->getOwnershipKind() != OwnershipKind::Owned) {
1092+
cfi->getOperandRef().set(subCFI->getConverted());
1093+
// Return cfi to show we changed it.
1094+
return cfi;
10411095
}
1042-
1043-
// Otherwise, we can still bypass the intermediate conversion.
1044-
CFI->getOperandRef().set(subCFI->getConverted());
1045-
}
1046-
1047-
// Replace a convert_function that only has refcounting uses with its
1048-
// operand.
1049-
auto anyNonRefCountUse =
1050-
std::any_of(CFI->use_begin(),
1051-
CFI->use_end(),
1052-
[](Operand *Use) {
1053-
return !isa<RefCountingInst>(Use->getUser());
1054-
});
1055-
1056-
if (anyNonRefCountUse)
1057-
return nullptr;
10581096

1059-
// Replace all retain/releases on convert_function by retain/releases on
1060-
// its argument. This is required to preserve the lifetime of its argument,
1061-
// which could be e.g. a partial_apply instruction capturing some further
1062-
// arguments.
1063-
auto Converted = CFI->getConverted();
1064-
while (!CFI->use_empty()) {
1065-
auto *Use = *(CFI->use_begin());
1066-
assert(Use->getUser()->getResults().empty() &&
1067-
"Did not expect user with a result!");
1068-
Use->set(Converted);
1097+
// If we have an owned value, we can only perform this optimization if the
1098+
// convert_function is in the same block to ensure that we know we will
1099+
// eliminate the convert_function. Otherwise we may be breaking up a
1100+
// forwarding chain in favor of additional ARC traffic which isn't
1101+
// canonical.
1102+
SingleBlockOwnedForwardingInstFolder folder(*this, cfi);
1103+
if (folder.add(subCFI))
1104+
return std::move(folder).optimizeWithSetValue(subCFI->getConverted());
10691105
}
10701106

1071-
eraseInstFromFunction(*CFI);
1107+
// Replace a convert_function that only has refcounting uses with its
1108+
// operand.
1109+
tryEliminateOnlyOwnershipUsedForwardingInst(cfi, instModCallbacks);
10721110
return nullptr;
10731111
}

lib/SILOptimizer/Utils/InstOptUtils.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2024,3 +2024,40 @@ SILValue swift::makeNewValueAvailable(
20242024

20252025
return controlEqCopy ? controlEqCopy : value;
20262026
}
2027+
2028+
bool swift::tryEliminateOnlyOwnershipUsedForwardingInst(
2029+
SingleValueInstruction *forwardingInst, InstModCallbacks &callbacks) {
2030+
if (!OwnershipForwardingMixin::isa(forwardingInst) ||
2031+
isa<AllArgOwnershipForwardingSingleValueInst>(forwardingInst))
2032+
return false;
2033+
2034+
SmallVector<Operand *, 32> worklist(getNonDebugUses(forwardingInst));
2035+
while (!worklist.empty()) {
2036+
auto *use = worklist.pop_back_val();
2037+
auto *user = use->getUser();
2038+
2039+
if (isa<EndBorrowInst>(user) || isa<DestroyValueInst>(user) ||
2040+
isa<RefCountingInst>(user))
2041+
continue;
2042+
2043+
if (isa<CopyValueInst>(user) || isa<BeginBorrowInst>(user)) {
2044+
for (auto result : user->getResults())
2045+
for (auto *resultUse : getNonDebugUses(result))
2046+
worklist.push_back(resultUse);
2047+
continue;
2048+
}
2049+
2050+
return false;
2051+
}
2052+
2053+
// Now that we know we can perform our transform, set all uses of
2054+
// forwardingInst to be used of its operand and then delete \p forwardingInst.
2055+
auto newValue = forwardingInst->getOperand(0);
2056+
while (!forwardingInst->use_empty()) {
2057+
auto *use = *(forwardingInst->use_begin());
2058+
use->set(newValue);
2059+
}
2060+
2061+
callbacks.deleteInst(forwardingInst);
2062+
return true;
2063+
}

0 commit comments

Comments
 (0)