Skip to content

Commit 2d61e3d

Browse files
authored
Merge pull request #73531 from jckarter/forwarding-force-unwrap
SILGen: Treat Optional `x!` force unwrapping as a forwarding operation.
2 parents b2bc7a0 + 82e566a commit 2d61e3d

File tree

8 files changed

+237
-46
lines changed

8 files changed

+237
-46
lines changed

lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,9 +1137,18 @@ void FieldSensitivePrunedLiveRange<LivenessWithDefs>::computeBoundary(
11371137
for (SILBasicBlock *succBB : block->getSuccessors()) {
11381138
if (FieldSensitivePrunedLiveBlocks::isDead(
11391139
getBlockLiveness(succBB, index))) {
1140-
PRUNED_LIVENESS_LOG(llvm::dbgs() << "Marking succBB as boundary edge: bb"
1141-
<< succBB->getDebugID() << '\n');
1142-
boundary.getBoundaryEdgeBits(succBB).set(index);
1140+
// If the basic block ends in unreachable, don't consider it a
1141+
// boundary.
1142+
// TODO: Should also do this if the block's successors all always
1143+
// end in unreachable too.
1144+
if (isa<UnreachableInst>(succBB->getTerminator())) {
1145+
PRUNED_LIVENESS_LOG(llvm::dbgs() << "succBB ends in unreachable, skipping as boundary edge: bb"
1146+
<< succBB->getDebugID() << '\n');
1147+
} else {
1148+
PRUNED_LIVENESS_LOG(llvm::dbgs() << "Marking succBB as boundary edge: bb"
1149+
<< succBB->getDebugID() << '\n');
1150+
boundary.getBoundaryEdgeBits(succBB).set(index);
1151+
}
11431152
}
11441153
}
11451154
asImpl().findBoundariesInBlock(block, index, /*isLiveOut*/ true,

lib/SILGen/SILGenApply.cpp

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3223,41 +3223,54 @@ static StorageRefResult findStorageReferenceExprForBorrow(Expr *e) {
32233223

32243224
Expr *SILGenFunction::findStorageReferenceExprForMoveOnly(Expr *argExpr,
32253225
StorageReferenceOperationKind kind) {
3226+
ForceValueExpr *forceUnwrap = nullptr;
3227+
// Check for a force unwrap. This might show up inside or outside of the
3228+
// load.
3229+
if (auto *fu = dyn_cast<ForceValueExpr>(argExpr)) {
3230+
forceUnwrap = fu;
3231+
argExpr = fu->getSubExpr();
3232+
}
3233+
32263234
// If there's a load around the outer part of this arg expr, look past it.
32273235
bool sawLoad = false;
32283236
if (auto *li = dyn_cast<LoadExpr>(argExpr)) {
32293237
argExpr = li->getSubExpr();
32303238
sawLoad = true;
32313239
}
32323240

3241+
// Check again for a force unwrap before the load.
3242+
if (auto *fu = dyn_cast<ForceValueExpr>(argExpr)) {
3243+
forceUnwrap = fu;
3244+
argExpr = fu->getSubExpr();
3245+
}
3246+
32333247
// If we're consuming instead, then the load _must_ have been there.
32343248
if (kind == StorageReferenceOperationKind::Consume && !sawLoad)
32353249
return nullptr;
32363250

3237-
// If we did not see a load and our argExpr is a
3238-
// declref_expr, return nullptr. We have an object not something that will be
3239-
// in memory. This can happen with classes or with values captured by a
3240-
// closure.
3241-
//
3242-
// NOTE: If we see a member_ref_expr from a decl_ref_expr, we still process it
3243-
// since the declref_expr could be from a class.
3251+
// TODO: This section should be removed eventually. Decl refs should not be
3252+
// handled different from other storage. Removing it breaks some things
3253+
// currently.
32443254
if (!sawLoad) {
32453255
if (auto *declRef = dyn_cast<DeclRefExpr>(argExpr)) {
32463256
assert(!declRef->getType()->is<LValueType>() &&
32473257
"Shouldn't ever have an lvalue type here!");
3248-
3249-
// Proceed if the storage references a global or static let.
3250-
// TODO: We should treat any storage reference as a borrow, it seems, but
3251-
// that currently disrupts what the move checker expects. It would also
3252-
// be valuable to borrow copyable global lets, but this is a targeted
3253-
// fix to allow noncopyable globals to work properly.
3254-
bool isGlobal = false;
3255-
if (auto vd = dyn_cast<VarDecl>(declRef->getDecl())) {
3256-
isGlobal = vd->isGlobalStorage();
3257-
}
3258-
3259-
if (!isGlobal) {
3260-
return nullptr;
3258+
3259+
// Proceed if the storage reference is a force unwrap.
3260+
if (!forceUnwrap) {
3261+
// Proceed if the storage references a global or static let.
3262+
// TODO: We should treat any storage reference as a borrow, it seems, but
3263+
// that currently disrupts what the move checker expects. It would also
3264+
// be valuable to borrow copyable global lets, but this is a targeted
3265+
// fix to allow noncopyable globals to work properly.
3266+
bool isGlobal = false;
3267+
if (auto vd = dyn_cast<VarDecl>(declRef->getDecl())) {
3268+
isGlobal = vd->isGlobalStorage();
3269+
}
3270+
3271+
if (!isGlobal) {
3272+
return nullptr;
3273+
}
32613274
}
32623275
}
32633276
}
@@ -3302,6 +3315,10 @@ Expr *SILGenFunction::findStorageReferenceExprForMoveOnly(Expr *argExpr,
33023315
}
33033316
if (!isMoveOnly)
33043317
return nullptr;
3318+
3319+
if (forceUnwrap) {
3320+
return forceUnwrap;
3321+
}
33053322

33063323
return result.getTransitiveRoot();
33073324
}

lib/SILGen/SILGenConvert.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,16 +199,21 @@ SILGenFunction::emitPreconditionOptionalHasValue(SILLocation loc,
199199
auto someDecl = getASTContext().getOptionalSomeDecl();
200200
auto noneDecl = getASTContext().getOptionalNoneDecl();
201201

202-
// If we have an object, make sure the object is at +1. All switch_enum of
203-
// objects is done at +1.
204202
bool isAddress = optional.getType().isAddress();
203+
bool isBorrow = !optional.isPlusOneOrTrivial(*this);
205204
SwitchEnumInst *switchEnum = nullptr;
206205
if (isAddress) {
207206
// We forward in the creation routine for
208207
// unchecked_take_enum_data_addr. switch_enum_addr is a +0 operation.
209208
B.createSwitchEnumAddr(loc, optional.getValue(),
210209
/*defaultDest*/ nullptr,
211210
{{someDecl, contBB}, {noneDecl, failBB}});
211+
} else if (isBorrow) {
212+
hadCleanup = false;
213+
hadLValue = false;
214+
switchEnum = B.createSwitchEnum(loc, optional.getValue(),
215+
/*defaultDest*/ nullptr,
216+
{{someDecl, contBB}, {noneDecl, failBB}});
212217
} else {
213218
optional = optional.ensurePlusOne(*this, loc);
214219
hadCleanup = true;

lib/SILGen/SILGenLValue.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,9 @@ namespace {
948948
ManagedValue base) && override {
949949
// Assert that the optional value is present and return the projected out
950950
// payload.
951+
if (isConsumeAccess(getTypeData().getAccessKind())) {
952+
base = base.ensurePlusOne(SGF, loc);
953+
}
951954
return SGF.emitPreconditionOptionalHasValue(loc, base, isImplicitUnwrap);
952955
}
953956

@@ -4355,6 +4358,16 @@ getOptionalObjectTypeData(SILGenFunction &SGF, SGFAccessKind accessKind,
43554358
LValue SILGenLValue::visitForceValueExpr(ForceValueExpr *e,
43564359
SGFAccessKind accessKind,
43574360
LValueOptions options) {
4361+
// Since Sema doesn't reason about borrows, a borrowed force expr
4362+
// might end up type checked with the load inside of the force.
4363+
auto subExpr = e->getSubExpr();
4364+
if (auto load = dyn_cast<LoadExpr>(subExpr)) {
4365+
assert((isBorrowAccess(accessKind) || isConsumeAccess(accessKind))
4366+
&& "should only see a (force_value (load)) lvalue as part of a "
4367+
"borrow or consume");
4368+
subExpr = load->getSubExpr();
4369+
}
4370+
43584371
// Like BindOptional, this is a read even if we only write to the result.
43594372
// (But it's unnecessary to use a force this way!)
43604373
LValue lv = visitRec(e->getSubExpr(),

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1713,9 +1713,6 @@ struct CopiedLoadBorrowEliminationVisitor
17131713
useWorklist.push_back(use);
17141714
}
17151715

1716-
// If we have a switch_enum, we always need to convert it to a load
1717-
// [copy] since we need to destructure through it.
1718-
shouldConvertToLoadCopy |= isa<SwitchEnumInst>(nextUse->getUser());
17191716
continue;
17201717
}
17211718
case OperandOwnership::Borrow:

test/SILGen/vtable_thunks.swift

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,18 +145,16 @@ class H: G {
145145
// CHECK-LABEL: sil private [thunk] [ossa] @$s13vtable_thunks1DC3iuo{{[_0-9a-zA-Z]*}}FTV
146146
// CHECK: bb0([[X:%.*]] : @guaranteed $B, [[Y:%.*]] : @guaranteed $Optional<B>, [[Z:%.*]] : @guaranteed $B, [[W:%.*]] : @guaranteed $D):
147147
// CHECK: [[WRAP_X:%.*]] = enum $Optional<B>, #Optional.some!enumelt, [[X]] : $B
148-
// CHECK: [[Y_COPY:%.*]] = copy_value [[Y]]
149-
// CHECK: switch_enum [[Y_COPY]] : $Optional<B>, case #Optional.some!enumelt: [[SOME_BB:bb[0-9]+]], case #Optional.none!enumelt: [[NONE_BB:bb[0-9]+]]
148+
// CHECK: switch_enum [[Y]] : $Optional<B>, case #Optional.some!enumelt: [[SOME_BB:bb[0-9]+]], case #Optional.none!enumelt: [[NONE_BB:bb[0-9]+]]
150149

151150
// CHECK: [[NONE_BB]]:
152151
// CHECK: [[DIAGNOSE_UNREACHABLE_FUNC:%.*]] = function_ref @$ss30_diagnoseUnexpectedNilOptional{{.*}}
153152
// CHECK: apply [[DIAGNOSE_UNREACHABLE_FUNC]]
154153
// CHECK: unreachable
155154

156-
// CHECK: [[SOME_BB]]([[UNWRAP_Y:%.*]] : @owned $B):
157-
// CHECK: [[BORROWED_UNWRAP_Y:%.*]] = begin_borrow [[UNWRAP_Y]]
155+
// CHECK: [[SOME_BB]]([[UNWRAP_Y:%.*]] : @guaranteed $B):
158156
// CHECK: [[THUNK_FUNC:%.*]] = function_ref @$s13vtable_thunks1DC3iuo{{.*}}
159-
// CHECK: [[RES:%.*]] = apply [[THUNK_FUNC]]([[WRAP_X]], [[BORROWED_UNWRAP_Y]], [[Z]], [[W]])
157+
// CHECK: [[RES:%.*]] = apply [[THUNK_FUNC]]([[WRAP_X]], [[UNWRAP_Y]], [[Z]], [[W]])
160158
// CHECK: [[WRAP_RES:%.*]] = enum $Optional<B>, {{.*}} [[RES]]
161159
// CHECK: return [[WRAP_RES]]
162160

test/SILOptimizer/discard_checking.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ func globalThrowingFn() throws {}
1616
struct Basics: ~Copyable {
1717
consuming func test1(_ b: Bool) {
1818
guard b else {
19-
fatalError("bah!") // expected-error {{must consume 'self' before exiting method that discards self}}
19+
return // expected-error {{must consume 'self' before exiting method that discards self}}
2020
}
2121
discard self // expected-note {{discarded self here}}
2222
}
2323

2424
consuming func test1_fixed(_ b: Bool) {
2525
guard b else {
2626
_ = consume self
27-
fatalError("bah!")
27+
return
2828
}
2929
discard self
3030
}
@@ -33,7 +33,7 @@ struct Basics: ~Copyable {
3333
repeat {
3434
switch c {
3535
case .red:
36-
fatalError("bah!")
36+
return
3737
case .blue:
3838
throw E.someError
3939
case .green:
@@ -49,7 +49,7 @@ struct Basics: ~Copyable {
4949
switch c {
5050
case .red:
5151
discard self
52-
fatalError("bah!")
52+
return
5353
case .blue:
5454
discard self
5555
throw E.someError
@@ -145,7 +145,7 @@ struct Basics: ~Copyable {
145145
if case .red = c {
146146
discard self // expected-note {{discarded self here}}
147147
}
148-
fatalError("oh no") // expected-error {{must consume 'self' before exiting method that discards self}}
148+
return // expected-error {{must consume 'self' before exiting method that discards self}}
149149
}
150150

151151
consuming func test7_fixed(_ c: Color) throws {
@@ -154,17 +154,17 @@ struct Basics: ~Copyable {
154154
return
155155
}
156156
_ = consume self
157-
fatalError("oh no")
157+
return
158158
}
159159

160160
consuming func test8(_ c: Color) throws {
161161
if case .red = c {
162162
discard self // expected-note {{discarded self here}}
163163
}
164164
if case .blue = c {
165-
fatalError("hi") // expected-error {{must consume 'self' before exiting method that discards self}}
165+
return
166166
}
167-
}
167+
} // expected-error {{must consume 'self' before exiting method that discards self}}
168168

169169
consuming func test8_stillMissingAConsume1(_ c: Color) throws {
170170
if case .red = c {
@@ -173,7 +173,7 @@ struct Basics: ~Copyable {
173173
}
174174
if case .blue = c {
175175
_ = consume self
176-
fatalError("hi")
176+
return
177177
}
178178
} // expected-error {{must consume 'self' before exiting method that discards self}}
179179

@@ -183,7 +183,7 @@ struct Basics: ~Copyable {
183183
return
184184
}
185185
if case .blue = c {
186-
fatalError("hi") // expected-error {{must consume 'self' before exiting method that discards self}}
186+
return // expected-error {{must consume 'self' before exiting method that discards self}}
187187
}
188188
_ = consume self
189189
}
@@ -195,7 +195,7 @@ struct Basics: ~Copyable {
195195
}
196196
if case .blue = c {
197197
_ = consume self
198-
fatalError("hi")
198+
return
199199
}
200200
_ = consume self
201201
}
@@ -407,7 +407,7 @@ struct Basics: ~Copyable {
407407
case 2:
408408
return // expected-error {{must consume 'self' before exiting method that discards self}}
409409
case 3:
410-
fatalError("no") // expected-error {{must consume 'self' before exiting method that discards self}}
410+
return // expected-error {{must consume 'self' before exiting method that discards self}}
411411
case 4:
412412
globalConsumingFn(self)
413413
default:
@@ -568,7 +568,7 @@ struct Money: ~Copyable {
568568

569569
consuming func spend(_ charge: Int) throws -> Money {
570570
guard charge > 0 else {
571-
fatalError("can't charge a negative amount!") // expected-error {{must consume 'self' before exiting method that discards self}}
571+
return Money(balance: balance) // expected-error {{must consume 'self' before exiting method that discards self}}
572572
}
573573

574574
if balance < charge {

0 commit comments

Comments
 (0)