Skip to content

Commit 960938f

Browse files
committed
SILGen: Always match noncopyable values by borrowing.
Even if the final pattern ends up consuming the value, the match itself must be nondestructive, because any match condition could fail and cause us to have to go back to the original aggregate. For copyable values, we can always copy our way out of consuming operations, but we don't have that luxury for noncopyable types, so the entire match operation has to be done as a borrow. For address-only enums, this requires codifying part of our tag layout algorithm in SIL, namely that an address-only enum will never use spare bits or other overlapping storage for the enum tag. This allows us to assume that `unchecked_take_enum_data_addr` is safely non-side- effecting and match an address-only noncopyable enum as a borrow. I put TODOs to remove defensive copies from various parts of our copyable enum codegen, as well as to have the instruction report its memory behavior as `None` when the projection is nondestructive, but this disturbs SILGen for existing code in ways SIL passes aren't yet ready for, so I'll leave those as is for now. This patch is enough to get simple examples of noncopyable enum switches to SILGen correctly. Additional work is necessary to stage in the binding step of the pattern match; for a consuming switch, we'll need to end the borrow(s) and then reproject the matched components so we can consume them moving them into the owned bindings. The move-only checker also needs to be updated because it currently always tries to convert a switch into a consuming operation.
1 parent 1da24ec commit 960938f

File tree

8 files changed

+163
-45
lines changed

8 files changed

+163
-45
lines changed

include/swift/Basic/Features.def

+3
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

+18-2
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

+2
Original file line numberDiff line numberDiff line change
@@ -3940,6 +3940,8 @@ static bool usesFeaturePreconcurrencyConformances(Decl *decl) {
39403940
return false;
39413941
}
39423942

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

lib/SIL/IR/SILInstruction.cpp

+33
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

+1-3
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

-3
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

+104-36
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

+2-1
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)