Skip to content

[silgenpattern] Change all loadable types to always go through the ownership preserving tuple code. #19840

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 58 additions & 40 deletions lib/SILGen/SILGenPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,10 +465,10 @@ class PatternMatchEmission {
void emitSpecializedDispatch(ClauseMatrix &matrix, ArgArray args,
unsigned &lastRow, unsigned column,
const FailureHandler &failure);
void emitTupleDispatchWithOwnership(ArrayRef<RowToSpecialize> rows,
ConsumableManagedValue src,
const SpecializationHandler &handleSpec,
const FailureHandler &failure);
void emitTupleObjectDispatch(ArrayRef<RowToSpecialize> rows,
ConsumableManagedValue src,
const SpecializationHandler &handleSpec,
const FailureHandler &failure);
void emitTupleDispatch(ArrayRef<RowToSpecialize> rows,
ConsumableManagedValue src,
const SpecializationHandler &handleSpec,
Expand Down Expand Up @@ -738,15 +738,19 @@ forwardIntoSubtree(SILGenFunction &SGF, SILLocation loc,
ManagedValue outerMV = outerCMV.getFinalManagedValue();
if (!outerMV.hasCleanup()) return outerCMV;

assert(outerCMV.getFinalConsumption() != CastConsumptionKind::CopyOnSuccess
&& "copy-on-success value with cleanup?");
auto consumptionKind = outerCMV.getFinalConsumption();
(void)consumptionKind;
assert((consumptionKind == CastConsumptionKind::TakeAlways ||
consumptionKind == CastConsumptionKind::TakeOnSuccess) &&
"non-+1 consumption with a cleanup?");
scope.pushCleanupState(outerMV.getCleanup(),
CleanupState::PersistentlyActive);

// If SILOwnership is enabled, we always forward down values as borrows that
// are copied on success.
if (SGF.F.getModule().getOptions().EnableSILOwnership) {
return {outerMV.borrow(SGF, loc), CastConsumptionKind::CopyOnSuccess};
// If SILOwnership is enabled and we have an object, borrow instead of take on
// success.
if (SGF.F.getModule().getOptions().EnableSILOwnership &&
outerMV.getType().isObject()) {
return {outerMV.borrow(SGF, loc), CastConsumptionKind::BorrowAlways};
}

// Success means that we won't end up in the other branch,
Expand All @@ -766,9 +770,6 @@ static void forwardIntoIrrefutableSubtree(SILGenFunction &SGF,

assert(outerCMV.getFinalConsumption() != CastConsumptionKind::CopyOnSuccess
&& "copy-on-success value with cleanup?");
assert((!SGF.F.getModule().getOptions().EnableSILOwnership ||
outerCMV.getFinalConsumption() == CastConsumptionKind::TakeAlways) &&
"When semantic sil is enabled, we should never see TakeOnSuccess");
scope.pushCleanupState(outerMV.getCleanup(),
CleanupState::PersistentlyActive);

Expand Down Expand Up @@ -899,17 +900,9 @@ class ArgUnforwarder {

static bool requiresUnforwarding(SILGenFunction &SGF,
ConsumableManagedValue operand) {
if (SGF.F.getModule().getOptions().EnableSILOwnership) {
assert(operand.getFinalConsumption() !=
CastConsumptionKind::TakeOnSuccess &&
"When compiling with sil ownership take on success is disabled");
// No unforwarding is needed, we always borrow/copy.
return false;
}

return (operand.hasCleanup() &&
operand.getFinalConsumption()
== CastConsumptionKind::TakeOnSuccess);
return operand.hasCleanup() &&
operand.getFinalConsumption()
== CastConsumptionKind::TakeOnSuccess;
}

/// Given that an aggregate was divided into a set of borrowed
Expand Down Expand Up @@ -1356,9 +1349,6 @@ getManagedSubobject(SILGenFunction &SGF, SILValue value,
return {ManagedValue::forUnmanaged(value), consumption};
case CastConsumptionKind::TakeAlways:
case CastConsumptionKind::TakeOnSuccess:
assert((!SGF.F.getModule().getOptions().EnableSILOwnership ||
consumption != CastConsumptionKind::TakeOnSuccess) &&
"TakeOnSuccess should never be used when sil ownership is enabled");
return {SGF.emitManagedRValueWithCleanup(value, valueTL), consumption};
}
}
Expand All @@ -1381,7 +1371,7 @@ emitReabstractedSubobject(SILGenFunction &SGF, SILLocation loc,
SGF.emitOrigToSubstValue(loc, mv, abstraction, substFormalType));
}

void PatternMatchEmission::emitTupleDispatchWithOwnership(
void PatternMatchEmission::emitTupleObjectDispatch(
ArrayRef<RowToSpecialize> rows, ConsumableManagedValue src,
const SpecializationHandler &handleCase,
const FailureHandler &outerFailure) {
Expand Down Expand Up @@ -1409,8 +1399,7 @@ void PatternMatchEmission::emitTupleDispatchWithOwnership(
destructured.push_back({v, src.getFinalConsumption()});
});

// Break down the values.
// Recurse.
// Since we did all of our work at +0, we just send down the outer failure.
handleCase(destructured, specializedRows, outerFailure);
}

Expand All @@ -1421,13 +1410,45 @@ void PatternMatchEmission::
emitTupleDispatch(ArrayRef<RowToSpecialize> rows, ConsumableManagedValue src,
const SpecializationHandler &handleCase,
const FailureHandler &outerFailure) {
if (SGF.getOptions().EnableSILOwnership && src.getType().isObject()) {
return emitTupleDispatchWithOwnership(rows, src, handleCase, outerFailure);
}
auto firstPat = rows[0].Pattern;
auto sourceType = cast<TupleType>(firstPat->getType()->getCanonicalType());
SILLocation loc = firstPat;

// If our source is an address that is loadable, perform a load_borrow.
if (src.getType().isAddress() && src.getType().isLoadable(SGF.getModule())) {
src = {SGF.B.createLoadBorrow(loc, src.getFinalManagedValue()),
CastConsumptionKind::BorrowAlways};
}

// Then if we have an object...
if (src.getType().isObject()) {
// Make sure that if we ahve a copy_on_success, non-trivial value that we do
// not have a value with @owned ownership.
assert((!src.getType().isTrivial(SGF.getModule()) ||
src.getFinalConsumption() != CastConsumptionKind::CopyOnSuccess ||
src.getOwnershipKind() != ValueOwnershipKind::Owned) &&
"@owned value without cleanup + copy_on_success");

// If we have are asked to perform TakeOnSuccess, borrow the value instead.
//
// The reason why do this for TakeOnSuccess is that we want to not have to
// deal with unforwarding of aggregate tuples in failing cases since that
// causes ownership invariants to be violated since we already forwarded the
// aggregate to create cleanups on its elements.
//
// In contrast, we do still want to allow for TakeAlways variants to not
// need to borrow, so we do not borrow if we take always.
if (!src.getType().isTrivial(SGF.getModule()) &&
src.getFinalConsumption() == CastConsumptionKind::TakeOnSuccess) {
src = {src.getFinalManagedValue().borrow(SGF, loc),
CastConsumptionKind::BorrowAlways};
}

// Then perform a forward or reborrow destructure on the object.
return emitTupleObjectDispatch(rows, src, handleCase, outerFailure);
}

auto sourceType = cast<TupleType>(firstPat->getType()->getCanonicalType());

SILValue v = src.getFinalManagedValue().forward(SGF);
SmallVector<ConsumableManagedValue, 4> destructured;

Expand Down Expand Up @@ -1882,8 +1903,6 @@ void PatternMatchEmission::emitEnumElementDispatch(
break;

case CastConsumptionKind::TakeOnSuccess:
assert(!SGF.F.getModule().getOptions().EnableSILOwnership &&
"TakeOnSuccess is not supported when compiling with ownership");
// If any of the specialization cases is refutable, we must copy.
if (!blocks.hasAnyRefutableCase())
break;
Expand Down Expand Up @@ -1966,8 +1985,6 @@ void PatternMatchEmission::emitEnumElementDispatch(
auto eltConsumption = src.getFinalConsumption();
if (caseInfo.Irrefutable &&
eltConsumption == CastConsumptionKind::TakeOnSuccess) {
assert(!SGF.F.getModule().getOptions().EnableSILOwnership &&
"TakeOnSuccess is not supported when compiling with ownership");
eltConsumption = CastConsumptionKind::TakeAlways;
}

Expand Down Expand Up @@ -2760,8 +2777,9 @@ void SILGenFunction::emitCatchDispatch(DoCatchStmt *S, ManagedValue exn,
// Set up an initial clause matrix.
ClauseMatrix clauseMatrix(clauseRows);
ConsumableManagedValue subject;
if (F.getModule().getOptions().EnableSILOwnership) {
subject = {exn.borrow(*this, S), CastConsumptionKind::CopyOnSuccess};
if (F.getModule().getOptions().EnableSILOwnership &&
exn.getType().isObject()) {
subject = {exn.borrow(*this, S), CastConsumptionKind::BorrowAlways};
} else {
subject = {exn, CastConsumptionKind::TakeOnSuccess};
}
Expand Down
3 changes: 1 addition & 2 deletions test/SILGen/indirect_enum.swift
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,7 @@ func switchTreeA<T>(_ x: TreeA<T>) {
// CHECK: [[BRANCH_CASE]]([[NODE_BOX:%.*]] : @owned $<τ_0_0> { var (left: TreeA<τ_0_0>, right: TreeA<τ_0_0>) } <T>):
// CHECK: [[TUPLE_ADDR:%.*]] = project_box [[NODE_BOX]]
// CHECK: [[TUPLE:%.*]] = load_borrow [[TUPLE_ADDR]]
// CHECK: [[LEFT:%.*]] = tuple_extract [[TUPLE]] {{.*}}, 0
// CHECK: [[RIGHT:%.*]] = tuple_extract [[TUPLE]] {{.*}}, 1
// CHECK: ([[LEFT:%.*]], [[RIGHT:%.*]]) = destructure_tuple [[TUPLE]]
// CHECK: switch_enum [[LEFT]] : $TreeA<T>,
// CHECK: case #TreeA.Leaf!enumelt.1: [[LEAF_CASE_LEFT:bb[0-9]+]],
// CHECK: default [[FAIL_LEFT:bb[0-9]+]]
Expand Down
12 changes: 4 additions & 8 deletions test/SILGen/switch.swift
Original file line number Diff line number Diff line change
Expand Up @@ -705,8 +705,7 @@ func test_union_1(u: MaybePair) {

// CHECK: [[IS_BOTH]]([[TUP:%.*]] : @owned $(Int, String)):
case .Both:
// CHECK: tuple_extract [[TUP]] : $(Int, String), 0
// CHECK: [[TUP_STR:%.*]] = tuple_extract [[TUP]] : $(Int, String), 1
// CHECK: ({{%.*}}, [[TUP_STR:%.*]]) = destructure_tuple [[TUP]]
// CHECK: destroy_value [[TUP_STR]] : $String
// CHECK: function_ref @$s6switch1dyyF
// CHECK: br [[CONT]]
Expand Down Expand Up @@ -915,9 +914,7 @@ enum Foo { case A, B }
// CHECK-LABEL: sil hidden @$s6switch05test_A11_two_unions1x1yyAA3FooO_AFtF
func test_switch_two_unions(x: Foo, y: Foo) {
// CHECK: [[T0:%.*]] = tuple (%0 : $Foo, %1 : $Foo)
// CHECK: [[X:%.*]] = tuple_extract [[T0]] : $(Foo, Foo), 0
// CHECK: [[Y:%.*]] = tuple_extract [[T0]] : $(Foo, Foo), 1

// CHECK: ([[X:%.*]], [[Y:%.*]]) = destructure_tuple [[T0]]
// CHECK: switch_enum [[Y]] : $Foo, case #Foo.A!enumelt: [[IS_CASE1:bb[0-9]+]], default [[IS_NOT_CASE1:bb[0-9]+]]

switch (x, y) {
Expand Down Expand Up @@ -980,8 +977,7 @@ func rdar14835992<T, U>(t: Rdar14835992, tt: T, uu: U) {
enum ABC { case A, B, C }

// CHECK-LABEL: sil hidden @$s6switch18testTupleWildcardsyyAA3ABCO_ADtF
// CHECK: [[X:%.*]] = tuple_extract {{%.*}} : $(ABC, ABC), 0
// CHECK: [[Y:%.*]] = tuple_extract {{%.*}} : $(ABC, ABC), 1
// CHECK: ([[X:%.*]], [[Y:%.*]]) = destructure_tuple {{%.*}} : $(ABC, ABC)
// CHECK: switch_enum [[X]] : $ABC, case #ABC.A!enumelt: [[X_A:bb[0-9]+]], default [[X_NOT_A:bb[0-9]+]]
// CHECK: [[X_A]]:
// CHECK: function_ref @$s6switch1ayyF
Expand Down Expand Up @@ -1022,7 +1018,7 @@ func testLabeledScalarPayload(_ lsp: LabeledScalarPayload) -> Any {
// CHECK: switch_enum {{%.*}}, case #LabeledScalarPayload.Payload!enumelt.1: bb1
switch lsp {
// CHECK: bb1([[TUPLE:%.*]] : @trivial $(name: Int)):
// CHECK: [[X:%.*]] = tuple_extract [[TUPLE]]
// CHECK: [[X:%.*]] = destructure_tuple [[TUPLE]]
// CHECK: [[ANY_X_ADDR:%.*]] = init_existential_addr {{%.*}}, $Int
// CHECK: store [[X]] to [trivial] [[ANY_X_ADDR]]
case let .Payload(x):
Expand Down
11 changes: 6 additions & 5 deletions test/SILGen/switch_isa.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ func guardFn(_ l: D, _ r: D) -> Bool { return true }
// CHECK: [[ARG0_COPY:%.*]] = copy_value [[ARG0]]
// CHECK: [[ARG1_COPY:%.*]] = copy_value [[ARG1]]
// CHECK: [[TUP:%.*]] = tuple ([[ARG0_COPY:%.*]] : $B, [[ARG1_COPY:%.*]] : $B)
// TODO: This should be destructures not tuple extracts.
// CHECK: [[TUP_1:%.*]] = tuple_extract [[TUP]] : $(B, B), 0
// CHECK: [[TUP_2:%.*]] = tuple_extract [[TUP]] : $(B, B), 1
// CHECK: [[BORROWED_TUP:%.*]] = begin_borrow [[TUP]]
// CHECK: ([[TUP_1:%.*]], [[TUP_2:%.*]]) = destructure_tuple [[BORROWED_TUP]]
// CHECK: checked_cast_br [[TUP_1]] : $B to $D, [[R_CAST_YES:bb[0-9]+]], [[R_CAST_NO:bb[0-9]+]]
//
// CHECK: [[R_CAST_YES]]([[R:%.*]] : @guaranteed $D):
Expand All @@ -74,8 +73,8 @@ func guardFn(_ l: D, _ r: D) -> Bool { return true }
// CHECK-NEXT: destroy_value [[R2]]
// CHECK-NEXT: end_borrow [[L]]
// CHECK-NEXT: end_borrow [[R]]
// CHECK-NEXT: destroy_value [[TUP_2]]
// CHECK-NEXT: destroy_value [[TUP_1]]
// CHECK-NEXT: end_borrow [[BORROWED_TUP]]
// CHECK-NEXT: destroy_value [[TUP]]
// CHECK-NEXT: br [[EXIT:bb[0-9]+]]
//
// CHECK: [[GUARD_NO]]:
Expand All @@ -84,12 +83,14 @@ func guardFn(_ l: D, _ r: D) -> Bool { return true }
// TODO: Infer end_borrow from the input begin_borrow. This should be eliminated.
// CHECK-NEXT: end_borrow [[L]]
// CHECK-NEXT: end_borrow [[R]]
// CHECK-NEXT: end_borrow [[BORROWED_TUP]]
// CHECK-NEXT: br [[CONT:bb[0-9]+]]
//
// CHECK: [[L_CAST_NO]]([[LFAIL:%.*]] : @guaranteed $B):
// CHECK-NEXT: end_borrow [[LFAIL]]
// CHECK-NEXT: destroy_value [[R2]]
// CHECK-NEXT: end_borrow [[R]]
// CHECK-NEXT: end_borrow [[BORROWED_TUP]]
// CHECK-NEXT: br [[CONT]]
//
// CHECK: [[CONT]]:
Expand Down
2 changes: 2 additions & 0 deletions test/SILGen/switch_ownership.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func test_switch_two_trivial_unions(x: Foo, y: Foo) {
// CHECK: unreachable
}
}
// CHECK: } // end sil function '$s6switch05test_A19_two_trivial_unions1x1yyAA3FooO_AFtF'

// CHECK-LABEL: sil hidden @$s6switch05test_A22_two_nontrivial_unions1x1yyAA13NonTrivialFooO_AFtF : $@convention(thin) (@guaranteed NonTrivialFoo, @guaranteed NonTrivialFoo) -> () {
func test_switch_two_nontrivial_unions(x: NonTrivialFoo, y: NonTrivialFoo) {
Expand Down Expand Up @@ -98,3 +99,4 @@ func test_switch_two_nontrivial_unions(x: NonTrivialFoo, y: NonTrivialFoo) {
// CHECK: unreachable
}
}
// CHECK: } // end sil function '$s6switch05test_A22_two_nontrivial_unions1x1yyAA13NonTrivialFooO_AFtF'
56 changes: 22 additions & 34 deletions test/SILGen/switch_var.swift
Original file line number Diff line number Diff line change
Expand Up @@ -618,20 +618,16 @@ func test_multiple_patterns3() {
switch f {
// CHECK: switch_enum {{%.*}} : $Foo, case #Foo.A!enumelt.1: [[A:bb[0-9]+]], case #Foo.B!enumelt.1: [[B:bb[0-9]+]], case #Foo.C!enumelt.1: [[C:bb[0-9]+]]
case .A(let x, let n), .B(let n, let x), .C(_, let x, let n):
// CHECK: [[A]]({{%.*}} : @trivial $(Int, Double)):
// CHECK: [[A_X:%.*]] = tuple_extract
// CHECK: [[A_N:%.*]] = tuple_extract
// CHECK: [[A]]([[A_TUP:%.*]] : @trivial $(Int, Double)):
// CHECK: ([[A_X:%.*]], [[A_N:%.*]]) = destructure_tuple [[A_TUP]]
// CHECK: br [[CASE_BODY:bb[0-9]+]]([[A_X]] : $Int, [[A_N]] : $Double)

// CHECK: [[B]]({{%.*}} : @trivial $(Double, Int)):
// CHECK: [[B_N:%.*]] = tuple_extract
// CHECK: [[B_X:%.*]] = tuple_extract
// CHECK: [[B]]([[B_TUP:%.*]] : @trivial $(Double, Int)):
// CHECK: ([[B_N:%.*]], [[B_X:%.*]]) = destructure_tuple [[B_TUP]]
// CHECK: br [[CASE_BODY]]([[B_X]] : $Int, [[B_N]] : $Double)

// CHECK: [[C]]({{%.*}} : @trivial $(Int, Int, Double)):
// CHECK: [[C__:%.*]] = tuple_extract
// CHECK: [[C_X:%.*]] = tuple_extract
// CHECK: [[C_N:%.*]] = tuple_extract
// CHECK: [[C]]([[C_TUP:%.*]] : @trivial $(Int, Int, Double)):
// CHECK: ([[C__:%.*]], [[C_X:%.*]], [[C_N:%.*]]) = destructure_tuple [[C_TUP]]
// CHECK: br [[CASE_BODY]]([[C_X]] : $Int, [[C_N]] : $Double)

// CHECK: [[CASE_BODY]]([[BODY_X:%.*]] : @trivial $Int, [[BODY_N:%.*]] : @trivial $Double):
Expand All @@ -652,27 +648,23 @@ func test_multiple_patterns4() {
switch b {
// CHECK: switch_enum {{%.*}} : $Bar, case #Bar.Y!enumelt.1: [[Y:bb[0-9]+]], case #Bar.Z!enumelt.1: [[Z:bb[0-9]+]]
case .Y(.A(let x, _), _), .Y(.B(_, let x), _), .Y(.C, let x), .Z(let x, _):
// CHECK: [[Y]]({{%.*}} : @trivial $(Foo, Int)):
// CHECK: [[Y_F:%.*]] = tuple_extract
// CHECK: [[Y_X:%.*]] = tuple_extract
// CHECK: [[Y]]([[Y_TUP:%.*]] : @trivial $(Foo, Int)):
// CHECK: ([[Y_F:%.*]], [[Y_X:%.*]]) = destructure_tuple [[Y_TUP]]
// CHECK: switch_enum [[Y_F]] : $Foo, case #Foo.A!enumelt.1: [[A:bb[0-9]+]], case #Foo.B!enumelt.1: [[B:bb[0-9]+]], case #Foo.C!enumelt.1: [[C:bb[0-9]+]]

// CHECK: [[A]]({{%.*}} : @trivial $(Int, Double)):
// CHECK: [[A_X:%.*]] = tuple_extract
// CHECK: [[A_N:%.*]] = tuple_extract
// CHECK: [[A]]([[A_TUP:%.*]] : @trivial $(Int, Double)):
// CHECK: ([[A_X:%.*]], [[A_N:%.*]]) = destructure_tuple [[A_TUP]]
// CHECK: br [[CASE_BODY:bb[0-9]+]]([[A_X]] : $Int)

// CHECK: [[B]]({{%.*}} : @trivial $(Double, Int)):
// CHECK: [[B_N:%.*]] = tuple_extract
// CHECK: [[B_X:%.*]] = tuple_extract
// CHECK: [[B]]([[B_TUP:%.*]] : @trivial $(Double, Int)):
// CHECK: ([[B_N:%.*]], [[B_X:%.*]]) = destructure_tuple [[B_TUP]]
// CHECK: br [[CASE_BODY]]([[B_X]] : $Int)

// CHECK: [[C]]({{%.*}} : @trivial $(Int, Int, Double)):
// CHECK: br [[CASE_BODY]]([[Y_X]] : $Int)

// CHECK: [[Z]]({{%.*}} : @trivial $(Int, Foo)):
// CHECK: [[Z_X:%.*]] = tuple_extract
// CHECK: [[Z_F:%.*]] = tuple_extract
// CHECK: [[Z]]([[Z_TUP:%.*]] : @trivial $(Int, Foo)):
// CHECK: ([[Z_X:%.*]], [[Z_F:%.*]]) = destructure_tuple [[Z_TUP]]
// CHECK: br [[CASE_BODY]]([[Z_X]] : $Int)

// CHECK: [[CASE_BODY]]([[BODY_X:%.*]] : @trivial $Int):
Expand All @@ -690,27 +682,23 @@ func test_multiple_patterns5() {
switch b {
// CHECK: switch_enum {{%.*}} : $Bar, case #Bar.Y!enumelt.1: [[Y:bb[0-9]+]], case #Bar.Z!enumelt.1: [[Z:bb[0-9]+]]
case .Y(.A(var x, _), _), .Y(.B(_, var x), _), .Y(.C, var x), .Z(var x, _):
// CHECK: [[Y]]({{%.*}} : @trivial $(Foo, Int)):
// CHECK: [[Y_F:%.*]] = tuple_extract
// CHECK: [[Y_X:%.*]] = tuple_extract
// CHECK: [[Y]]([[Y_TUP:%.*]] : @trivial $(Foo, Int)):
// CHECK: ([[Y_F:%.*]], [[Y_X:%.*]]) = destructure_tuple [[Y_TUP]]
// CHECK: switch_enum [[Y_F]] : $Foo, case #Foo.A!enumelt.1: [[A:bb[0-9]+]], case #Foo.B!enumelt.1: [[B:bb[0-9]+]], case #Foo.C!enumelt.1: [[C:bb[0-9]+]]

// CHECK: [[A]]({{%.*}} : @trivial $(Int, Double)):
// CHECK: [[A_X:%.*]] = tuple_extract
// CHECK: [[A_N:%.*]] = tuple_extract
// CHECK: [[A]]([[A_TUP:%.*]] : @trivial $(Int, Double)):
// CHECK: ([[A_X:%.*]], [[A_N:%.*]]) = destructure_tuple [[A_TUP]]
// CHECK: br [[CASE_BODY:bb[0-9]+]]([[A_X]] : $Int)

// CHECK: [[B]]({{%.*}} : @trivial $(Double, Int)):
// CHECK: [[B_N:%.*]] = tuple_extract
// CHECK: [[B_X:%.*]] = tuple_extract
// CHECK: [[B]]([[B_TUP:%.*]] : @trivial $(Double, Int)):
// CHECK: ([[B_N:%.*]], [[B_X:%.*]]) = destructure_tuple [[B_TUP]]
// CHECK: br [[CASE_BODY]]([[B_X]] : $Int)

// CHECK: [[C]]({{%.*}} : @trivial $(Int, Int, Double)):
// CHECK: br [[CASE_BODY]]([[Y_X]] : $Int)

// CHECK: [[Z]]({{%.*}} : @trivial $(Int, Foo)):
// CHECK: [[Z_X:%.*]] = tuple_extract
// CHECK: [[Z_F:%.*]] = tuple_extract
// CHECK: [[Z]]([[Z_TUP:%.*]] : @trivial $(Int, Foo)):
// CHECK: ([[Z_X:%.*]], [[Z_F:%.*]]) = destructure_tuple [[Z_TUP]]
// CHECK: br [[CASE_BODY]]([[Z_X]] : $Int)

// CHECK: [[CASE_BODY]]([[BODY_X:%.*]] : @trivial $Int):
Expand Down