Skip to content

Commit 18143e2

Browse files
authored
Merge pull request #61393 from atrick/fix-closure-release
Fix unchecked_bitwise_cast to "escape" its operand's ownership
2 parents dfbb604 + 4c83bb2 commit 18143e2

File tree

4 files changed

+88
-14
lines changed

4 files changed

+88
-14
lines changed

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,9 +238,31 @@ OPERAND_OWNERSHIP(PointerEscape, ProjectExistentialBox)
238238
OPERAND_OWNERSHIP(PointerEscape, UncheckedOwnershipConversion)
239239
OPERAND_OWNERSHIP(PointerEscape, ConvertEscapeToNoEscape)
240240

241+
// UncheckedBitwiseCast ownership behaves like RefToUnowned. It produces an
242+
// Unowned values from a non-trivial value, without consuming or borrowing the
243+
// non-trivial value. Unlike RefToUnowned, a bitwise cast works on a compound
244+
// value and may truncate the value. The resulting value is still Unowned and
245+
// should be immediately copied to produce an owned value. These happen for two
246+
// reasons:
247+
//
248+
// (1) A Builtin.reinterpretCast is used on a nontrivial type. If the result is
249+
// non-trivial, then, as part of emitting the cast, SILGen emits a copy_value
250+
// immediately after the unchecked_bitwise_cast for all uses of the cast.
251+
//
252+
// (2) SILGen emits special conversions using SILGenBuilder's
253+
// createUncheckedBitCast utility. For non-trivial types, this emits an
254+
// unchecked_bitwise_cast immediately followed by a copy.
255+
//
256+
// The only thing protecting the lifetime of the Unowned value is the cast
257+
// operand's PointerEscape ownership, which prevents OSSA analysis and blocks
258+
// most optimization of the incoming value.
259+
//
260+
// TODO: Verify that Unowned values are only used by copies and that the cast
261+
// operand's lifetime exceeds the copies.
262+
OPERAND_OWNERSHIP(PointerEscape, UncheckedBitwiseCast)
263+
241264
// Instructions that escape reference bits with unenforced lifetime.
242265
// TODO: verify that BitwiseEscape results always have a trivial type.
243-
OPERAND_OWNERSHIP(BitwiseEscape, UncheckedBitwiseCast)
244266
OPERAND_OWNERSHIP(BitwiseEscape, ValueToBridgeObject)
245267
OPERAND_OWNERSHIP(BitwiseEscape, RefToRawPointer)
246268
OPERAND_OWNERSHIP(BitwiseEscape, UncheckedTrivialBitCast)

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -377,19 +377,26 @@ bool swift::findExtendedUsesOfSimpleBorrowedValue(
377377
return true;
378378
}
379379

380+
// TODO: refactor this with SSAPrunedLiveness::computeLiveness.
380381
bool swift::findUsesOfSimpleValue(SILValue value,
381382
SmallVectorImpl<Operand *> *usePoints) {
382383
for (auto *use : value->getUses()) {
383-
if (use->getOperandOwnership() == OperandOwnership::Borrow) {
384+
switch (use->getOperandOwnership()) {
385+
case OperandOwnership::PointerEscape:
386+
return false;
387+
case OperandOwnership::Borrow:
384388
if (!BorrowingOperand(use).visitScopeEndingUses([&](Operand *end) {
385-
if (end->getOperandOwnership() == OperandOwnership::Reborrow) {
386-
return false;
387-
}
388-
usePoints->push_back(end);
389-
return true;
390-
})) {
389+
if (end->getOperandOwnership() == OperandOwnership::Reborrow) {
390+
return false;
391+
}
392+
usePoints->push_back(end);
393+
return true;
394+
})) {
391395
return false;
392396
}
397+
break;
398+
default:
399+
break;
393400
}
394401
usePoints->push_back(use);
395402
}

test/SILOptimizer/copy_propagation.sil

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,7 @@ bb0(%instance : @owned $C):
875875
// CHECK-NOT: destroy_value
876876
// CHECK-LABEL: } // end sil function 'dont_extend_beyond_nonoverlapping_endaccess_after_destroyvalue_in_consuming_block'
877877
sil [ossa] @dont_extend_beyond_nonoverlapping_endaccess_after_destroyvalue_in_consuming_block : $@convention(thin) () -> () {
878+
bb0:
878879
%instance = apply undef() : $@convention(thin) () -> @owned C
879880
%addr = alloc_stack [lexical] $C, var
880881
%access = begin_access [static] [modify] %addr : $*C
@@ -885,3 +886,52 @@ sil [ossa] @dont_extend_beyond_nonoverlapping_endaccess_after_destroyvalue_in_co
885886
%retval = tuple()
886887
return %retval : $()
887888
}
889+
890+
sil @closureGetter : $@convention(thin) () -> @owned Optional<@Sendable @callee_guaranteed () -> ()>
891+
892+
// This pattern is produced by SILGen doUncheckedConversion.
893+
// rdar://100527903 (Overrelease of optional closure when using shorthand syntax)
894+
//
895+
// Copy propagation cannot handle a PointerEscape.
896+
//
897+
// CHECK-LABEL: sil hidden [ossa] @testBitwiseClosureEscape : $@convention(thin) () -> () {
898+
// CHECK: [[V:%.*]] = apply %0() : $@convention(thin) () -> @owned Optional<@Sendable @callee_guaranteed () -> ()>
899+
// CHECK: [[CP1:%.*]] = copy_value [[V]] : $Optional<@Sendable @callee_guaranteed () -> ()>
900+
// CHECK: destroy_value [[V]] : $Optional<@Sendable @callee_guaranteed () -> ()>
901+
// CHECK: [[CAST:%.*]] = unchecked_bitwise_cast [[CP1]] : $Optional<@Sendable @callee_guaranteed () -> ()> to $Optional<@callee_guaranteed () -> ()>
902+
// CHECK-NEXT: [[CASTCP:%.*]] = copy_value [[CAST]] : $Optional<@callee_guaranteed () -> ()>
903+
// CHECK: store [[CASTCP]] to [init]
904+
// CHECK: destroy_value [[CP1]] : $Optional<@Sendable @callee_guaranteed () -> ()>
905+
// CHECK-LABEL: } // end sil function 'testBitwiseClosureEscape'
906+
sil hidden [ossa] @testBitwiseClosureEscape : $@convention(thin) () -> () {
907+
bb0:
908+
%2 = function_ref @closureGetter : $@convention(thin) () -> @owned Optional<@Sendable @callee_guaranteed () -> ()>
909+
%3 = apply %2() : $@convention(thin) () -> @owned Optional<@Sendable @callee_guaranteed () -> ()>
910+
%13 = copy_value %3 : $Optional<@Sendable @callee_guaranteed () -> ()>
911+
%14 = alloc_stack $Optional<@callee_guaranteed () -> ()>
912+
destroy_value %3 : $Optional<@Sendable @callee_guaranteed () -> ()>
913+
%17 = unchecked_bitwise_cast %13 : $Optional<@Sendable @callee_guaranteed () -> ()> to $Optional<@callee_guaranteed () -> ()>
914+
%18 = copy_value %17 : $Optional<@callee_guaranteed () -> ()>
915+
store %18 to [init] %14 : $*Optional<@callee_guaranteed () -> ()>
916+
destroy_addr %14 : $*Optional<@callee_guaranteed () -> ()>
917+
destroy_value %13 : $Optional<@Sendable @callee_guaranteed () -> ()>
918+
dealloc_stack %14 : $*Optional<@callee_guaranteed () -> ()>
919+
%99 = tuple ()
920+
return %99 : $()
921+
}
922+
923+
// Lexical destroy hoisting cannot handle a PointerEscape.
924+
//
925+
// CHECK-LABEL: sil [ossa] @testNoLexicalHoistEscape : $@convention(thin) (@owned Optional<@Sendable @callee_guaranteed () -> ()>) -> @owned Optional<@callee_guaranteed () -> ()> {
926+
// CHECK: bb0(%0 : @owned $Optional<@Sendable @callee_guaranteed () -> ()>):
927+
// CHECK: [[CAST:%.*]] = unchecked_bitwise_cast %0 : $Optional<@Sendable @callee_guaranteed () -> ()> to $Optional<@callee_guaranteed () -> ()>
928+
// CHECK: [[COPY:%.*]] = copy_value [[CAST]] : $Optional<@callee_guaranteed () -> ()>
929+
// CHECK: destroy_value %0 : $Optional<@Sendable @callee_guaranteed () -> ()>
930+
// CHECK-LABEL: } // end sil function 'testNoLexicalHoistEscape'
931+
sil [ossa] @testNoLexicalHoistEscape : $@convention(thin) (@owned Optional<@Sendable @callee_guaranteed () -> ()>) -> @owned Optional<@callee_guaranteed () -> ()> {
932+
bb0(%0 : @owned $Optional<@Sendable @callee_guaranteed () -> ()>):
933+
%cast = unchecked_bitwise_cast %0 : $Optional<@Sendable @callee_guaranteed () -> ()> to $Optional<@callee_guaranteed () -> ()>
934+
%copy = copy_value %cast : $Optional<@callee_guaranteed () -> ()>
935+
destroy_value %0 : $Optional<@Sendable @callee_guaranteed () -> ()>
936+
return %copy : $Optional<@callee_guaranteed () -> ()>
937+
}

test/SILOptimizer/copy_propagation_borrow.sil

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,12 @@ bb3:
243243
// CHECK: bb1:
244244
// CHECK-NEXT: [[COPY:%.*]] = copy_value [[OUTERCOPY]] : $C
245245
// CHECK-NEXT: apply %{{.*}}([[COPY]]) : $@convention(thin) (@owned C) -> ()
246-
// CHECK-NEXT: destroy_value %0 : $C
247246
// CHECK-NEXT: br bb3
248247
// CHECK: bb2:
249-
// CHECK-NEXT: destroy_value %0 : $C
250248
// CHECK-NEXT: br bb3
251249
// CHECK: bb3:
252250
// CHECK-NEXT: destroy_value [[OUTERCOPY]] : $C
251+
// CHECK-NEXT: destroy_value %0 : $C
253252
// CHECK-LABEL: } // end sil function 'testLocalBorrowPostDomDestroy'
254253
sil [ossa] @testLocalBorrowPostDomDestroy : $@convention(thin) (@owned C) -> () {
255254
bb0(%0 : @owned $C):
@@ -285,11 +284,9 @@ bb3:
285284
// CHECK: bb1:
286285
// CHECK-NEXT: apply %{{.*}}([[OUTERCOPY]]) : $@convention(thin) (@guaranteed C) -> ()
287286
// CHECK-NEXT: apply %{{.*}}([[OUTERCOPY]]) : $@convention(thin) (@owned C) -> ()
288-
// CHECK-NEXT: destroy_value %0 : $C
289287
// CHECK-NEXT: br bb3
290288
// CHECK: bb2:
291289
// CHECK-NEXT: apply %{{.*}}([[OUTERCOPY]]) : $@convention(thin) (@guaranteed C) -> ()
292-
// CHECK-NEXT: destroy_value %0 : $C
293290
// CHECK-NEXT: destroy_value [[OUTERCOPY]] : $C
294291
// CHECK-NEXT: br bb3
295292
// CHECK: bb3:
@@ -328,13 +325,11 @@ bb3:
328325
// CHECK-NEXT: apply %{{.*}}([[OUTERCOPY]]) : $@convention(thin) (@guaranteed C) -> ()
329326
// CHECK-NEXT: [[ARGCOPY:%.*]] = copy_value [[OUTERCOPY]] : $C
330327
// CHECK-NEXT: apply %2([[OUTERCOPY]], [[ARGCOPY:%.*]]) : $@convention(thin) (@owned C, @owned C) -> ()
331-
// CHECK-NEXT: destroy_value %0 : $C
332328
// CHECK-NEXT: br bb3
333329
// CHECK: bb2:
334330
// CHECK-NEXT: apply %{{.*}}([[OUTERCOPY]]) : $@convention(thin) (@guaranteed C) -> ()
335331
//
336332
// This copy would be eliminated if the outer lifetime were also canonicalized (no unchecked_ownership_conversion)
337-
// CHECK-NEXT: destroy_value %0 : $C
338333
// CHECK-NEXT: [[COPY2:%.*]] = copy_value [[OUTERCOPY]] : $C
339334
// CHECK-NEXT: destroy_value [[COPY2]] : $C
340335
// CHECK-NEXT: destroy_value %4 : $C

0 commit comments

Comments
 (0)