Files
swift-mirror/test/SILGen/noncopyable_loadable_is_pattern.swift
Tim Kientzle e628b58ba9 [any ~Copyable] Use the new is machinery (almost) always
Until now, the new SIL instruction form and runtime have been used only for a
specific shape of `is` testing of noncopyable existentials.  But the machinery
is much more general than that and carries potential performance benefits for
all `is` tests.  Previous work has generalized the runtime support,
clarified the Sema diagnostics, and allowed IRGen to selectively
use the new runtime only when the deployment target permits.

Now, this PR changes the SILGen casting decision logic to actually use the new
SIL `checked_cast_addr_br test_only` for almost all `is` tests.  After this,
most `is` forms will use the new SIL instructions and (with a suitably new
runtime) benefit from the new runtime entry point which can in most cases check
whether a conversion is possible without actually doing the conversion.

The guard now rests on two conditions:

- The cast must be one IRGen performs indirectly, since `test_only` rides on
  `checked_cast_addr_br`.
- The source must not need reabstraction, because the test reads it where it
  already lies rather than reabstracting to the most general pattern first.

This includes a small fix to the `MoveOnlyWrappedTypeEliminator` since the
new SIL form is now visible there.

And of course, a fair bit of test churn.

TODO: Remaining work for the `is` overhaul includes: Fixing some remaining bugs
in noncopyable type handling; converting more of the runtime logic to support
pure test-only operation; ironing out a few OpaqueValues issues; and
reconsidering how this interacts with the very few cast forms permitted in
Embedded Swift.
2026-10-19 12:10:04 -07:00

85 lines
4.0 KiB
Swift

// RUN: %target-swift-emit-silgen %s -target %target-future-triple -enable-experimental-feature NoncopyableCasting -verify | %FileCheck %s
// REQUIRES: swift_feature_NoncopyableCasting
// A `case is T` subject does not have to arrive as an address. A loadable
// non-`Copyable` type -- a concrete struct, not an existential -- reaches the
// pattern emitter as a value, and the ordinary lowering would answer the cast
// by materialising that value into a destination buffer, which copies. For a
// copyable subject that is fine and the ordinary lowering keeps it; for this
// one the copy is illegal, so the subject is borrowed into a temporary and
// read in place by the non-consuming test.
//
// What that buys is visible in the instruction itself: `test_only` takes no
// destination operand, so there is one `alloc_stack` (the borrow temporary)
// instead of two (source buffer plus cast destination).
//
// Without this, the three spellings of the same question disagreed: `switch`
// and `if case` both produced "copy of noncopyable typed value", while the
// expression form `x is Big` is rejected outright by Sema.
protocol P: ~Copyable {}
protocol Q {}
struct Small: P, ~Copyable { var a: Int }
struct Big: P, ~Copyable { var a, b, c: Int }
// CHECK-LABEL: sil hidden [ossa] @$s31noncopyable_loadable_is_pattern9viaSwitchySbAA5SmallVF :
// CHECK: bb0(%0 : @guaranteed $Small):
// CHECK: [[TEMP:%[0-9]+]] = alloc_stack $Small
// CHECK: [[BORROW:%[0-9]+]] = store_borrow {{%[0-9]+}} to [[TEMP]]
// The test reads the borrow in place: no destination operand, so no second
// buffer for the cast to write a copy of the subject into.
// CHECK-NOT: alloc_stack
// CHECK: checked_cast_addr_br test_only Small in [[BORROW]] to Big, bb1, bb2
// CHECK: } // end sil function
func viaSwitch(_ x: borrowing Small) -> Bool {
switch x {
case is Big: return true // expected-warning {{cast from 'Small' to unrelated type 'Big' always fails}}
default: return false
}
}
// The same question in `if case` form reaches the same lowering.
// CHECK-LABEL: sil hidden [ossa] @$s31noncopyable_loadable_is_pattern9viaIfCaseySbAA5SmallVF :
// CHECK: [[BORROW:%[0-9]+]] = store_borrow
// CHECK: checked_cast_addr_br test_only Small in [[BORROW]] to Big, bb1, bb2
// CHECK: } // end sil function
func viaIfCase(_ x: borrowing Small) -> Bool {
if case is Big = x { return true } // expected-warning {{cast from 'Small' to unrelated type 'Big' always fails}}
return false
}
// A consuming subject arrives as a value too, and must not be consumed merely
// to be asked about.
// CHECK-LABEL: sil hidden [ossa] @$s31noncopyable_loadable_is_pattern12viaConsumingySbAA5SmallVnF :
// CHECK: bb0(%0 : @owned $Small):
// CHECK: [[BORROW:%[0-9]+]] = store_borrow
// CHECK: checked_cast_addr_br test_only Small in [[BORROW]] to Big, bb1, bb2
// CHECK: } // end sil function
func viaConsuming(_ x: consuming Small) -> Bool {
switch x {
case is Big: return true // expected-warning {{cast from 'Small' to unrelated type 'Big' always fails}}
default: return false
}
}
struct CopyableSmall: Q { var a: Int }
struct CopyableBig: Q { var a, b, c: Int }
// A copyable subject keeps the ordinary lowering -- a destination buffer and a
// copying cast. Nothing here widens the non-consuming test to subjects that
// never needed it.
// CHECK-LABEL: sil hidden [ossa] @$s31noncopyable_loadable_is_pattern8copyableySbAA13CopyableSmallVF :
// CHECK-NOT: test_only
// CHECK: [[SRC:%[0-9]+]] = alloc_stack $CopyableSmall
// CHECK: [[DEST:%[0-9]+]] = alloc_stack $CopyableBig
// CHECK: checked_cast_addr_br copy_on_success CopyableSmall in [[SRC]] to CopyableBig in [[DEST]], bb1, bb2
// CHECK: } // end sil function
func copyable(_ x: CopyableSmall) -> Bool {
switch x {
case is CopyableBig: return true // expected-warning {{cast from 'CopyableSmall' to unrelated type 'CopyableBig' always fails}}
default: return false
}
}