Skip to content

Commit 47ee847

Browse files
authored
Merge pull request #71025 from jckarter/borrowing-switch-1
SILGen: Always match noncopyable values by borrowing.
2 parents e63a887 + 960938f commit 47ee847

File tree

8 files changed

+163
-45
lines changed

8 files changed

+163
-45
lines changed

include/swift/Basic/Features.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,9 @@ EXPERIMENTAL_FEATURE(TransferringArgsAndResults, true)
273273
// Enable `@preconcurrency` attribute on protocol conformances.
274274
EXPERIMENTAL_FEATURE(PreconcurrencyConformances, false)
275275

276+
// Allow for `switch` of noncopyable values to be borrowing or consuming.
277+
EXPERIMENTAL_FEATURE(BorrowingSwitch, true)
278+
276279
#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
277280
#undef EXPERIMENTAL_FEATURE
278281
#undef UPCOMING_FEATURE

include/swift/SIL/SILInstruction.h

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6736,8 +6736,14 @@ class InjectEnumAddrInst
67366736
}
67376737
};
67386738

6739-
/// Invalidate an enum value and take ownership of its payload data
6740-
/// without moving it in memory.
6739+
/// Project an enum's payload data without checking the case of the enum or
6740+
/// moving it in memory.
6741+
///
6742+
/// For some classes of enum, this is a destructive operation that invalidates
6743+
/// the enum, particularly in cases where the layout algorithm can potentially
6744+
/// use the common spare bits out of the payloads of a multi-payload enum
6745+
/// to store the tag without allocating additional space. The `isDestructive`
6746+
/// static method returns true for enums where this is potentially the case.
67416747
class UncheckedTakeEnumDataAddrInst
67426748
: public UnaryInstructionBase<SILInstructionKind::UncheckedTakeEnumDataAddrInst,
67436749
SingleValueInstruction>
@@ -6755,6 +6761,15 @@ class UncheckedTakeEnumDataAddrInst
67556761
}
67566762

67576763
public:
6764+
// Returns true if the projection operation is possibly destructive for
6765+
// instances of the given enum declaration.
6766+
static bool isDestructive(EnumDecl *forEnum, SILModule &M);
6767+
6768+
// Returns true if this projection operation is possibly destructive.
6769+
bool isDestructive() const {
6770+
return isDestructive(Element->getParentEnum(), getModule());
6771+
}
6772+
67586773
EnumElementDecl *getElement() const { return Element; }
67596774

67606775
unsigned getCaseIndex() {
@@ -7025,6 +7040,7 @@ class TupleExtractInst
70257040
ValueOwnershipKind forwardingOwnershipKind)
70267041
: UnaryInstructionBase(DebugLoc, Operand, ResultTy,
70277042
forwardingOwnershipKind) {
7043+
assert(Operand->getType().castTo<TupleType>());
70287044
sharedUInt32().TupleExtractInst.fieldNo = FieldNo;
70297045
}
70307046

lib/AST/ASTPrinter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3938,6 +3938,8 @@ static bool usesFeaturePreconcurrencyConformances(Decl *decl) {
39383938
return false;
39393939
}
39403940

3941+
static bool usesFeatureBorrowingSwitch(Decl *decl) { return false; }
3942+
39413943
/// Suppress the printing of a particular feature.
39423944
static void suppressingFeature(PrintOptions &options, Feature feature,
39433945
llvm::function_ref<void()> action) {

lib/SIL/IR/SILInstruction.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,6 +1070,10 @@ MemoryBehavior SILInstruction::getMemoryBehavior() const {
10701070
}
10711071
llvm_unreachable("Covered switch isn't covered?!");
10721072
}
1073+
1074+
// TODO: An UncheckedTakeEnumDataAddr instruction has no memory behavior if
1075+
// it is nondestructive. Setting this currently causes LICM to miscompile
1076+
// because access paths do not account for enum projections.
10731077

10741078
switch (getKind()) {
10751079
#define FULL_INST(CLASS, TEXTUALNAME, PARENT, MEMBEHAVIOR, RELEASINGBEHAVIOR) \
@@ -1878,6 +1882,35 @@ DestroyValueInst::getNonescapingClosureAllocation() const {
18781882
}
18791883
}
18801884

1885+
bool
1886+
UncheckedTakeEnumDataAddrInst::isDestructive(EnumDecl *forEnum, SILModule &M) {
1887+
// We only potentially use spare bit optimization when an enum is always
1888+
// loadable.
1889+
auto sig = forEnum->getGenericSignature().getCanonicalSignature();
1890+
if (SILType::isAddressOnly(forEnum->getDeclaredInterfaceType()->getReducedType(sig),
1891+
M.Types, sig,
1892+
TypeExpansionContext::minimal())) {
1893+
return false;
1894+
}
1895+
1896+
// We only overlap spare bits with valid payload values when an enum has
1897+
// multiple payloads.
1898+
bool sawPayloadCase = false;
1899+
for (auto element : forEnum->getAllElements()) {
1900+
if (element->hasAssociatedValues()) {
1901+
if (sawPayloadCase) {
1902+
// TODO: If the associated value's type is always visibly empty then it
1903+
// would get laid out like a no-payload case.
1904+
return true;
1905+
} else {
1906+
sawPayloadCase = true;
1907+
}
1908+
}
1909+
}
1910+
1911+
return false;
1912+
}
1913+
18811914
#ifndef NDEBUG
18821915

18831916
//---

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -689,9 +689,7 @@ struct ImmutableAddressUseVerifier {
689689
}
690690
break;
691691
case SILInstructionKind::UncheckedTakeEnumDataAddrInst: {
692-
auto type =
693-
cast<UncheckedTakeEnumDataAddrInst>(inst)->getOperand()->getType();
694-
if (type.getOptionalObjectType()) {
692+
if (!cast<UncheckedTakeEnumDataAddrInst>(inst)->isDestructive()) {
695693
for (auto result : inst->getResults()) {
696694
llvm::copy(result->getUses(), std::back_inserter(worklist));
697695
}

lib/SILGen/ManagedValue.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,9 +469,6 @@ class ConsumableManagedValue {
469469
/*implicit*/ ConsumableManagedValue(ManagedValue value,
470470
CastConsumptionKind finalConsumption)
471471
: Value(value), FinalConsumption(finalConsumption) {
472-
assert((value.getType().isObject() ||
473-
finalConsumption != CastConsumptionKind::BorrowAlways) &&
474-
"Can not borrow always a value");
475472
assert((value.getType().isAddress() ||
476473
finalConsumption != CastConsumptionKind::CopyOnSuccess) &&
477474
"Can not copy on success a value.");

lib/SILGen/SILGenPattern.cpp

Lines changed: 104 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2058,15 +2058,21 @@ void PatternMatchEmission::emitEnumElementDispatch(
20582058
// After this point we now that we must have an address only type.
20592059
assert(src.getType().isAddressOnly(SGF.F) &&
20602060
"Should have an address only type here");
2061+
assert(!UncheckedTakeEnumDataAddrInst::isDestructive(src.getType().getEnumOrBoundGenericEnum(),
2062+
SGF.getModule()) &&
2063+
"address only enum projection should never be destructive");
20612064

20622065
CanType sourceType = rows[0].Pattern->getType()->getCanonicalType();
20632066

20642067
// Collect the cases and specialized rows.
20652068
CaseBlocks blocks{SGF, rows, sourceType, SGF.B.getInsertionBB()};
20662069

2067-
// We lack a SIL instruction to nondestructively project data from an
2070+
// We (used to) lack a SIL instruction to nondestructively project data from an
20682071
// address-only enum, so we can only do so in place if we're allowed to take
20692072
// the source always. Copy the source if we can't.
2073+
//
2074+
// TODO: This should no longer be necessary now that we guarantee that
2075+
// potentially address-only enums never use spare bit optimization.
20702076
switch (src.getFinalConsumption()) {
20712077
case CastConsumptionKind::TakeAlways:
20722078
case CastConsumptionKind::CopyOnSuccess:
@@ -2086,8 +2092,6 @@ void PatternMatchEmission::emitEnumElementDispatch(
20862092
}
20872093

20882094
// Emit the switch_enum_addr instruction.
2089-
//
2090-
// NOTE: switch_enum_addr does not actually consume the underlying value.
20912095
SGF.B.createSwitchEnumAddr(loc, src.getValue(), blocks.getDefaultBlock(),
20922096
blocks.getCaseBlocks(), blocks.getCounts(),
20932097
defaultCaseCount);
@@ -2171,22 +2175,21 @@ void PatternMatchEmission::emitEnumElementDispatch(
21712175
ManagedValue eltValue;
21722176
// We can only project destructively from an address-only enum, so
21732177
// copy the value if we can't consume it.
2174-
// TODO: Should have a more efficient way to copy payload
2175-
// nondestructively from an enum.
2178+
// TODO: Copying should be avoidable now that we guarantee that address-
2179+
// only enums never use spare bit optimization.
21762180
switch (eltConsumption) {
21772181
case CastConsumptionKind::TakeAlways: {
21782182
auto finalValue = src.getFinalManagedValue();
21792183
eltValue = SGF.B.createUncheckedTakeEnumDataAddr(loc, finalValue,
21802184
eltDecl, eltTy);
21812185
break;
21822186
}
2183-
case CastConsumptionKind::BorrowAlways:
2184-
// If we reach this point, we know that we have a loadable
2185-
// element type from an enum with mixed address
2186-
// only/loadable cases. Since we had an address only type,
2187-
// we assume that we will not have BorrowAlways since
2188-
// address only types do not support BorrowAlways.
2189-
llvm_unreachable("not allowed");
2187+
case CastConsumptionKind::BorrowAlways: {
2188+
eltValue = ManagedValue::forBorrowedAddressRValue(
2189+
SGF.B.createUncheckedTakeEnumDataAddr(loc, src.getValue(),
2190+
eltDecl, eltTy));
2191+
break;
2192+
}
21902193
case CastConsumptionKind::CopyOnSuccess: {
21912194
auto temp = SGF.emitTemporary(loc, SGF.getTypeLowering(src.getType()));
21922195
SGF.B.createCopyAddr(loc, src.getValue(), temp->getAddress(), IsNotTake,
@@ -2212,12 +2215,22 @@ void PatternMatchEmission::emitEnumElementDispatch(
22122215
if (eltTL->isLoadable()) {
22132216
// If we do not have a loadable value, just use getManagedSubobject
22142217
// Load a loadable data value.
2215-
if (eltConsumption == CastConsumptionKind::CopyOnSuccess) {
2218+
switch (eltConsumption) {
2219+
case CastConsumptionKind::CopyOnSuccess:
22162220
eltValue = SGF.B.createLoadBorrow(loc, eltValue);
22172221
eltConsumption = CastConsumptionKind::BorrowAlways;
2218-
} else {
2219-
assert(eltConsumption == CastConsumptionKind::TakeAlways);
2222+
break;
2223+
2224+
case CastConsumptionKind::TakeAlways:
22202225
eltValue = SGF.B.createLoadTake(loc, eltValue);
2226+
break;
2227+
2228+
case CastConsumptionKind::BorrowAlways:
2229+
eltValue = SGF.B.createLoadBorrow(loc, eltValue);
2230+
break;
2231+
2232+
case CastConsumptionKind::TakeOnSuccess:
2233+
llvm_unreachable("not possible");
22212234
}
22222235
origCMV = {eltValue, eltConsumption};
22232236
} else {
@@ -2847,33 +2860,88 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
28472860
ManagedValue subjectMV = emitRValueAsSingleValue(
28482861
S->getSubjectExpr(), SGFContext::AllowGuaranteedPlusZero);
28492862

2863+
llvm::Optional<ValueOwnership> noncopyableSwitchOwnership;
2864+
28502865
// Inline constructor for subject.
28512866
auto subject = ([&]() -> ConsumableManagedValue {
2852-
// If we have a noImplicitCopy value, ensure plus one and convert
2853-
// it. Switches always consume move only values.
2854-
//
2855-
// NOTE: We purposely do not do this for pure move only types since for them
2856-
// we emit everything at +0 and then run the BorrowToDestructure transform
2857-
// upon them. The reason that we do this is that internally to
2858-
// SILGenPattern, we always attempt to move from +1 -> +0 meaning that even
2859-
// if we start at +1, we will go back to +0 given enough patterns to go
2860-
// through. It is simpler to just let SILGenPattern do what it already wants
2861-
// to do, rather than fight it or try to resusitate the "fake owned borrow"
2862-
// path that we still use for address only types (and that we want to delete
2863-
// once we have opaque values).
2864-
if (subjectMV.getType().isMoveOnly() && subjectMV.getType().isObject()) {
2865-
if (subjectMV.getType().isMoveOnlyWrapped()) {
2866-
subjectMV = B.createOwnedMoveOnlyWrapperToCopyableValue(
2867-
S, subjectMV.ensurePlusOne(*this, S));
2868-
} else {
2869-
// If we have a pure move only type and it is owned, borrow it so that
2870-
// BorrowToDestructure can handle it.
2871-
if (subjectMV.getOwnershipKind() == OwnershipKind::Owned) {
2867+
if (subjectMV.getType().isMoveOnly()) {
2868+
if (getASTContext().LangOpts.hasFeature(Feature::BorrowingSwitch)) {
2869+
// Determine the overall ownership behavior of the switch, based on the
2870+
// patterns' ownership behavior.
2871+
auto ownership = ValueOwnership::Shared;
2872+
for (auto caseLabel : S->getCases()) {
2873+
for (auto item : caseLabel->getCaseLabelItems()) {
2874+
ownership = std::max(ownership, item.getPattern()->getOwnership());
2875+
}
2876+
}
2877+
noncopyableSwitchOwnership = ownership;
2878+
2879+
// Based on the ownership behavior, prepare the subject.
2880+
// The pattern match itself will always be performed on a borrow, to
2881+
// ensure that the order of pattern evaluation doesn't prematurely
2882+
// consume or modify the value until we commit to a match. But if the
2883+
// match consumes the value, then we need a +1 value to go back to in
2884+
// order to consume the parts we match to, so we force a +1 value then
2885+
// borrow that for the pattern match.
2886+
switch (ownership) {
2887+
case ValueOwnership::Default:
2888+
llvm_unreachable("invalid");
2889+
2890+
case ValueOwnership::Shared:
2891+
if (subjectMV.getType().isAddress() &&
2892+
subjectMV.getType().isLoadable(F)) {
2893+
// Load a borrow if the type is loadable.
2894+
subjectMV = B.createLoadBorrow(S, subjectMV);
2895+
} else if (!subjectMV.isPlusZero()) {
2896+
subjectMV = subjectMV.borrow(*this, S);
2897+
}
2898+
return {subjectMV, CastConsumptionKind::BorrowAlways};
2899+
2900+
case ValueOwnership::InOut:
2901+
// TODO: mutating switches
2902+
llvm_unreachable("not implemented");
2903+
2904+
case ValueOwnership::Owned:
2905+
// Make sure we own the subject value.
2906+
subjectMV = subjectMV.ensurePlusOne(*this, S);
2907+
if (subjectMV.getType().isAddress() &&
2908+
subjectMV.getType().isLoadable(F)) {
2909+
// Move the value into memory if it's loadable.
2910+
subjectMV = B.createLoadTake(S, subjectMV);
2911+
}
2912+
// Perform the pattern match on a borrow of the subject.
28722913
subjectMV = subjectMV.borrow(*this, S);
2914+
return {subjectMV, CastConsumptionKind::BorrowAlways};
2915+
}
2916+
llvm_unreachable("unhandled value ownership");
2917+
} else {
2918+
// If we have a noImplicitCopy value, ensure plus one and convert
2919+
// it. Switches always consume move only values.
2920+
//
2921+
// NOTE: We purposely do not do this for pure move only types since for them
2922+
// we emit everything at +0 and then run the BorrowToDestructure transform
2923+
// upon them. The reason that we do this is that internally to
2924+
// SILGenPattern, we always attempt to move from +1 -> +0 meaning that even
2925+
// if we start at +1, we will go back to +0 given enough patterns to go
2926+
// through. It is simpler to just let SILGenPattern do what it already wants
2927+
// to do, rather than fight it or try to resusitate the "fake owned borrow"
2928+
// path that we still use for address only types (and that we want to delete
2929+
// once we have opaque values).
2930+
if (subjectMV.getType().isObject()) {
2931+
if (subjectMV.getType().isMoveOnlyWrapped()) {
2932+
subjectMV = B.createOwnedMoveOnlyWrapperToCopyableValue(
2933+
S, subjectMV.ensurePlusOne(*this, S));
2934+
} else {
2935+
// If we have a pure move only type and it is owned, borrow it so that
2936+
// BorrowToDestructure can handle it.
2937+
if (subjectMV.getOwnershipKind() == OwnershipKind::Owned) {
2938+
subjectMV = subjectMV.borrow(*this, S);
2939+
}
2940+
}
28732941
}
28742942
}
28752943
}
2876-
2944+
28772945
// If we have a plus one value...
28782946
if (subjectMV.isPlusOne(*this)) {
28792947
// And we have an address that is loadable, perform a load [take].

lib/Sema/MiscDiagnostics.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4112,7 +4112,8 @@ diagnoseMoveOnlyPatternMatchSubject(ASTContext &C,
41124112
if (auto load = dyn_cast<LoadExpr>(subjectExpr)) {
41134113
subjectExpr = load->getSubExpr()->getSemanticsProvidingExpr();
41144114
}
4115-
if (isa<DeclRefExpr>(subjectExpr)) {
4115+
if (!C.LangOpts.hasFeature(Feature::BorrowingSwitch)
4116+
&& isa<DeclRefExpr>(subjectExpr)) {
41164117
C.Diags.diagnose(subjectExpr->getLoc(),
41174118
diag::move_only_pattern_match_not_consumed)
41184119
.fixItInsert(subjectExpr->getStartLoc(), "consume ");

0 commit comments

Comments
 (0)