Skip to content

Commit 34c2f92

Browse files
authored
Merge pull request #72954 from jckarter/borrowing-switch-expr-based-ownership-6.0
[6.0] Alternative noncopyable switch design based on expression kind.
2 parents f10c4f4 + ef9f830 commit 34c2f92

9 files changed

+181
-57
lines changed

lib/AST/Pattern.cpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,9 @@ Pattern::getOwnership(
777777
void visitNamedPattern(NamedPattern *p) {
778778
switch (p->getDecl()->getIntroducer()) {
779779
case VarDecl::Introducer::Let:
780+
// `let` defaults to the prevailing ownership of the switch.
781+
break;
782+
780783
case VarDecl::Introducer::Var:
781784
// If the subpattern type is copyable, then we can bind the variable
782785
// by copying without requiring more than a borrow of the original.
@@ -786,7 +789,7 @@ Pattern::getOwnership(
786789
// TODO: An explicit `consuming` binding kind consumes regardless of
787790
// type.
788791

789-
// Noncopyable `let` and `var` consume the bound value to move it into
792+
// Noncopyable `var` consumes the bound value to move it into
790793
// a new independent variable.
791794
increaseOwnership(ValueOwnership::Owned, p);
792795
break;
@@ -797,8 +800,8 @@ Pattern::getOwnership(
797800
break;
798801

799802
case VarDecl::Introducer::Borrowing:
800-
// `borrow` bindings borrow parts of the value in-place so they don't
801-
// need stronger access to the subject value.
803+
// `borrow` bindings borrow parts of the value in-place.
804+
increaseOwnership(ValueOwnership::Shared, p);
802805
break;
803806
}
804807
}

lib/SILGen/SILGenPattern.cpp

+120-2
Original file line numberDiff line numberDiff line change
@@ -3218,12 +3218,123 @@ static void switchCaseStmtSuccessCallback(SILGenFunction &SGF,
32183218
SGF.Cleanups.emitBranchAndCleanups(sharedDest, caseBlock, args);
32193219
}
32203220

3221+
// TODO: Integrate this with findStorageExprForMoveOnly, which does almost the
3222+
// same check.
3223+
static bool isBorrowableSubject(SILGenFunction &SGF,
3224+
Expr *subjectExpr) {
3225+
// Look through forwarding expressions.
3226+
for (;;) {
3227+
subjectExpr = subjectExpr->getValueProvidingExpr();
3228+
3229+
// Look through loads.
3230+
if (auto load = dyn_cast<LoadExpr>(subjectExpr)) {
3231+
subjectExpr = load->getSubExpr();
3232+
continue;
3233+
}
3234+
3235+
// Look through optional force-projections.
3236+
// We can't look through optional evaluations here because wrapping the
3237+
// value up in an Optional at the end needs a copy/move to create the
3238+
// temporary optional.
3239+
if (auto force = dyn_cast<ForceValueExpr>(subjectExpr)) {
3240+
subjectExpr = force->getSubExpr();
3241+
continue;
3242+
}
3243+
3244+
// Look through parens.
3245+
if (auto paren = dyn_cast<ParenExpr>(subjectExpr)) {
3246+
subjectExpr = paren->getSubExpr();
3247+
continue;
3248+
}
3249+
3250+
// Look through `try` and `await`.
3251+
if (auto tryExpr = dyn_cast<TryExpr>(subjectExpr)) {
3252+
subjectExpr = tryExpr->getSubExpr();
3253+
continue;
3254+
}
3255+
if (auto awaitExpr = dyn_cast<AwaitExpr>(subjectExpr)) {
3256+
subjectExpr = awaitExpr->getSubExpr();
3257+
continue;
3258+
}
3259+
3260+
break;
3261+
}
3262+
3263+
// An explicit `borrow` expression requires us to do a borrowing access.
3264+
if (isa<BorrowExpr>(subjectExpr)) {
3265+
return true;
3266+
}
3267+
3268+
AbstractStorageDecl *storage;
3269+
AccessSemantics access;
3270+
3271+
// A subject is potentially borrowable if it's some kind of storage reference.
3272+
if (auto declRef = dyn_cast<DeclRefExpr>(subjectExpr)) {
3273+
storage = dyn_cast<AbstractStorageDecl>(declRef->getDecl());
3274+
access = declRef->getAccessSemantics();
3275+
} else if (auto memberRef = dyn_cast<MemberRefExpr>(subjectExpr)) {
3276+
storage = dyn_cast<AbstractStorageDecl>(memberRef->getMember().getDecl());
3277+
access = memberRef->getAccessSemantics();
3278+
} else if (auto subscript = dyn_cast<SubscriptExpr>(subjectExpr)) {
3279+
storage = dyn_cast<AbstractStorageDecl>(subscript->getMember().getDecl());
3280+
access = subscript->getAccessSemantics();
3281+
} else {
3282+
return false;
3283+
}
3284+
3285+
// If the member being referenced isn't storage, there's no benefit to
3286+
// borrowing it.
3287+
if (!storage) {
3288+
return false;
3289+
}
3290+
3291+
// Check the access strategy used to read the storage.
3292+
auto strategy = storage->getAccessStrategy(access,
3293+
AccessKind::Read,
3294+
SGF.SGM.SwiftModule,
3295+
SGF.F.getResilienceExpansion());
3296+
3297+
switch (strategy.getKind()) {
3298+
case AccessStrategy::Kind::Storage:
3299+
// Accessing storage directly benefits from borrowing.
3300+
return true;
3301+
case AccessStrategy::Kind::DirectToAccessor:
3302+
case AccessStrategy::Kind::DispatchToAccessor:
3303+
// If we use an accessor, the kind of accessor affects whether we get
3304+
// a reference we can borrow or a temporary that will be consumed.
3305+
switch (strategy.getAccessor()) {
3306+
case AccessorKind::Get:
3307+
case AccessorKind::DistributedGet:
3308+
// Get returns an owned value.
3309+
return false;
3310+
case AccessorKind::Read:
3311+
case AccessorKind::Modify:
3312+
case AccessorKind::Address:
3313+
case AccessorKind::MutableAddress:
3314+
// Read, modify, and addressors yield a borrowable reference.
3315+
return true;
3316+
case AccessorKind::Init:
3317+
case AccessorKind::Set:
3318+
case AccessorKind::WillSet:
3319+
case AccessorKind::DidSet:
3320+
llvm_unreachable("should not be involved in a read");
3321+
}
3322+
llvm_unreachable("switch not covered?");
3323+
3324+
case AccessStrategy::Kind::MaterializeToTemporary:
3325+
case AccessStrategy::Kind::DispatchToDistributedThunk:
3326+
return false;
3327+
}
3328+
llvm_unreachable("switch not covered?");
3329+
}
3330+
32213331
void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
32223332
LLVM_DEBUG(llvm::dbgs() << "emitting switch stmt\n";
32233333
S->dump(llvm::dbgs());
32243334
llvm::dbgs() << '\n');
32253335

3226-
auto subjectTy = S->getSubjectExpr()->getType();
3336+
auto subjectExpr = S->getSubjectExpr();
3337+
auto subjectTy = subjectExpr->getType();
32273338
auto subjectLoweredTy = getLoweredType(subjectTy);
32283339
auto subjectLoweredAddress =
32293340
silConv.useLoweredAddresses() && subjectLoweredTy.isAddressOnly(F);
@@ -3244,7 +3355,14 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) {
32443355
if (getASTContext().LangOpts.hasFeature(Feature::BorrowingSwitch)) {
32453356
if (subjectTy->isNoncopyable()) {
32463357
// Determine the overall ownership behavior of the switch, based on the
3247-
// patterns' ownership behavior.
3358+
// subject expression and the patterns' ownership behavior.
3359+
3360+
// If the subject expression is borrowable, then perform the switch as
3361+
// a borrow. (A `consume` expression would render the expression
3362+
// non-borrowable.) Otherwise, perform it as a consume.
3363+
ownership = isBorrowableSubject(*this, subjectExpr)
3364+
? ValueOwnership::Shared
3365+
: ValueOwnership::Owned;
32483366
for (auto caseLabel : S->getCases()) {
32493367
for (auto item : caseLabel->getCaseLabelItems()) {
32503368
ownership = std::max(ownership, item.getPattern()->getOwnership());

stdlib/public/core/Optional.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -846,7 +846,7 @@ public func ?? <T: ~Copyable>(
846846
optional: consuming T?,
847847
defaultValue: @autoclosure () throws -> T?
848848
) rethrows -> T? {
849-
switch optional {
849+
switch consume optional {
850850
case .some(let value):
851851
return value
852852
case .none:

test/SILGen/borrowing_switch_return_on_all_paths.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extension NoncopyableList.Link {
2525
func find(where predicate: (Element)->Bool) -> Maybe<Element> {
2626
switch self {
2727
case .empty: return .none
28-
case .more(_borrowing box):
28+
case .more(let box):
2929
if predicate(box.wrapped.element) { return .some(box.wrapped.element) }
3030
return box.wrapped.next.find(where: predicate)
3131
}

test/SILGen/borrowing_switch_subjects.swift

+10-8
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func borrowParam(x: borrowing Outer) {
2525
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [strict] [no_consume_or_assign]
2626
// CHECK: [[BORROW:%.*]] = begin_borrow [[MARK]]
2727
switch x {
28-
case _borrowing y:
28+
case let y:
2929
// CHECK: apply {{.*}}([[BORROW]])
3030
use(y)
3131
}
@@ -39,7 +39,7 @@ func borrowParam(x: borrowing Outer) {
3939
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [strict] [no_consume_or_assign] [[COPY_INNER]]
4040
// CHECK: [[BORROW:%.*]] = begin_borrow [[MARK]]
4141
switch x.storedInner {
42-
case _borrowing y:
42+
case let y:
4343
// CHECK: apply {{.*}}([[BORROW]])
4444
use(y)
4545
}
@@ -56,22 +56,24 @@ func borrowParam(x: borrowing Outer) {
5656
// CHECK: [[MARK2:%.*]] = mark_unresolved_non_copyable_value [strict] [no_consume_or_assign] [[COPY2]]
5757
// CHECK: [[BORROW2:%.*]] = begin_borrow [[MARK2]]
5858
switch x.readInner {
59-
case _borrowing y:
59+
case let y:
6060
// CHECK: apply {{.*}}([[BORROW2]])
6161
use(y)
6262
}
6363
// CHECK: end_apply [[TOKEN]]
6464
// CHECK: end_borrow [[BORROW_OUTER]]
6565

66+
// `temporary()` is an rvalue, so we
6667
// CHECK: [[FN:%.*]] = function_ref @{{.*}}9temporary
6768
// CHECK: [[TMP:%.*]] = apply [[FN]]()
6869
// CHECK: [[BORROW_OUTER:%.*]] = begin_borrow [fixed] [[TMP]]
69-
// CHECK: [[COPY:%.*]] = copy_value [[BORROW_OUTER]]
70-
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [strict] [no_consume_or_assign] [[COPY]]
71-
// CHECK: [[BORROW:%.*]] = begin_borrow [[MARK]]
70+
// CHECK: end_borrow [[BORROW_OUTER]]
71+
// CHECK: store [[TMP]] to [init] [[Y:%.*]] :
72+
// CHECK: [[MARK:%.*]] = mark_unresolved_non_copyable_value [no_consume_or_assign] [[Y]]
7273
switch temporary() {
73-
case _borrowing y:
74-
// CHECK: apply {{.*}}([[BORROW]])
74+
case let y:
75+
// CHECK: [[LOAD_BORROW:%.*]] = load_borrow [[MARK]]
76+
// CHECK: apply {{.*}}([[LOAD_BORROW]])
7577
use(y)
7678
}
7779
}

0 commit comments

Comments
 (0)