diff --git a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h index 66a878cc5717f..1cea674c1e3b8 100644 --- a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h +++ b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h @@ -176,6 +176,11 @@ class DIMemoryObjectInfo { MemoryInst->isDerivedClassSelfOnly(); } + /// True if this memory object is the 'self' of a root class init method. + bool isRootClassSelf() const { + return isClassInitSelf() && MemoryInst->isRootSelf(); + } + /// True if this memory object is the 'self' of a non-root class init method. bool isNonRootClassSelf() const { return isClassInitSelf() && !MemoryInst->isRootSelf(); diff --git a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp index ab7a60c936c07..14a63285fb1f3 100644 --- a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp +++ b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp @@ -1072,7 +1072,13 @@ void LifetimeChecker::handleStoreUse(unsigned UseID) { // it for later. Once we've collected all of the conditional init/assigns, // we can insert a single control variable for the memory object for the // whole function. - if (!Use.onlyTouchesTrivialElements(TheMemory)) + // + // For root class initializers, we must keep track of initializations of + // trivial stored properties also, since we need to know when the object + // has been fully initialized when deciding if a strong_release should + // lower to a partial_dealloc_ref. + if (TheMemory.isRootClassSelf() || + !Use.onlyTouchesTrivialElements(TheMemory)) HasConditionalInitAssign = true; return; } @@ -2186,12 +2192,12 @@ static void updateControlVariable(SILLocation Loc, } /// Test a bit in the control variable at the current insertion point. -static SILValue testControlVariable(SILLocation Loc, - unsigned Elt, - SILValue ControlVariableAddr, - Identifier &ShiftRightFn, - Identifier &TruncateFn, - SILBuilder &B) { +static SILValue testControlVariableBit(SILLocation Loc, + unsigned Elt, + SILValue ControlVariableAddr, + Identifier &ShiftRightFn, + Identifier &TruncateFn, + SILBuilder &B) { SILValue ControlVariable = B.createLoad(Loc, ControlVariableAddr, LoadOwnershipQualifier::Trivial); @@ -2224,6 +2230,32 @@ static SILValue testControlVariable(SILLocation Loc, {}, CondVal); } +/// Test if all bits in the control variable are set at the current +/// insertion point. +static SILValue testAllControlVariableBits(SILLocation Loc, + SILValue ControlVariableAddr, + Identifier &CmpEqFn, + SILBuilder &B) { + SILValue ControlVariable = + B.createLoad(Loc, ControlVariableAddr, LoadOwnershipQualifier::Trivial); + + SILValue CondVal = ControlVariable; + CanBuiltinIntegerType IVType = CondVal->getType().castTo(); + + if (IVType->getFixedWidth() == 1) + return CondVal; + + SILValue AllBitsSet = B.createIntegerLiteral(Loc, CondVal->getType(), -1); + if (!CmpEqFn.get()) + CmpEqFn = getBinaryFunction("cmp_eq", CondVal->getType(), + B.getASTContext()); + SILValue Args[] = { CondVal, AllBitsSet }; + + return B.createBuiltin(Loc, CmpEqFn, + SILType::getBuiltinIntegerType(1, B.getASTContext()), + {}, Args); +} + /// handleConditionalInitAssign - This memory object has some stores /// into (some element of) it that is either an init or an assign based on the /// control flow path through the function, or have a destroy event that happens @@ -2285,7 +2317,13 @@ SILValue LifetimeChecker::handleConditionalInitAssign() { // If this ambiguous store is only of trivial types, then we don't need to // do anything special. We don't even need keep the init bit for the // element precise. - if (Use.onlyTouchesTrivialElements(TheMemory)) + // + // For root class initializers, we must keep track of initializations of + // trivial stored properties also, since we need to know when the object + // has been fully initialized when deciding if a strong_release should + // lower to a partial_dealloc_ref. + if (!TheMemory.isRootClassSelf() && + Use.onlyTouchesTrivialElements(TheMemory)) continue; B.setInsertionPoint(Use.Inst); @@ -2324,9 +2362,9 @@ SILValue LifetimeChecker::handleConditionalInitAssign() { // initialization. for (unsigned Elt = Use.FirstElement, e = Elt+Use.NumElements; Elt != e; ++Elt) { - auto CondVal = testControlVariable(Loc, Elt, ControlVariableAddr, - ShiftRightFn, TruncateFn, - B); + auto CondVal = testControlVariableBit(Loc, Elt, ControlVariableAddr, + ShiftRightFn, TruncateFn, + B); SILBasicBlock *TrueBB, *FalseBB, *ContBB; InsertCFGDiamond(CondVal, Loc, B, @@ -2395,7 +2433,7 @@ SILValue LifetimeChecker::handleConditionalInitAssign() { void LifetimeChecker:: handleConditionalDestroys(SILValue ControlVariableAddr) { SILBuilderWithScope B(TheMemory.getUninitializedValue()); - Identifier ShiftRightFn, TruncateFn; + Identifier ShiftRightFn, TruncateFn, CmpEqFn; unsigned NumMemoryElements = TheMemory.getNumElements(); @@ -2465,9 +2503,9 @@ handleConditionalDestroys(SILValue ControlVariableAddr) { // Insert a load of the liveness bitmask and split the CFG into a diamond // right before the destroy_addr, if we haven't already loaded it. - auto CondVal = testControlVariable(Loc, Elt, ControlVariableAddr, - ShiftRightFn, TruncateFn, - B); + auto CondVal = testControlVariableBit(Loc, Elt, ControlVariableAddr, + ShiftRightFn, TruncateFn, + B); SILBasicBlock *ReleaseBlock, *DeallocBlock, *ContBlock; @@ -2486,11 +2524,11 @@ handleConditionalDestroys(SILValue ControlVariableAddr) { // depending on if the self box was initialized or not. auto emitReleaseOfSelfWhenNotConsumed = [&](SILLocation Loc, SILInstruction *Release) { - auto CondVal = testControlVariable(Loc, SelfInitializedElt, - ControlVariableAddr, - ShiftRightFn, - TruncateFn, - B); + auto CondVal = testControlVariableBit(Loc, SelfInitializedElt, + ControlVariableAddr, + ShiftRightFn, + TruncateFn, + B); SILBasicBlock *ReleaseBlock, *ConsumedBlock, *ContBlock; @@ -2522,12 +2560,50 @@ handleConditionalDestroys(SILValue ControlVariableAddr) { // Just conditionally destroy each memory element, and for classes, // also free the partially initialized object. if (!TheMemory.isNonRootClassSelf()) { - destroyMemoryElements(Loc, Availability); - processUninitializedRelease(Release, false, B.getInsertionPoint()); + assert(!Availability.isAllYes() && + "Should not end up here if fully initialized"); + + // For root class initializers, we check if all proeprties were + // dynamically initialized, and if so, treat this as a release of + // an initialized 'self', instead of tearing down the fields + // one by one and deallocating memory. + // + // This is required for correctness, since the condition that + // allows 'self' to escape is that all stored properties were + // initialized. So we cannot deallocate the memory if 'self' may + // have escaped. + // + // This also means the deinitializer will run if all stored + // properties were initialized. + if (TheMemory.isClassInitSelf() && + Availability.hasAny(DIKind::Partial)) { + auto CondVal = testAllControlVariableBits(Loc, ControlVariableAddr, + CmpEqFn, B); + + SILBasicBlock *ReleaseBlock, *DeallocBlock, *ContBlock; + + InsertCFGDiamond(CondVal, Loc, B, + ReleaseBlock, DeallocBlock, ContBlock); + + // If true, self was fully initialized and must be released. + B.setInsertionPoint(ReleaseBlock->begin()); + B.setCurrentDebugScope(ReleaseBlock->begin()->getDebugScope()); + Release->moveBefore(&*B.getInsertionPoint()); + + // If false, self is uninitialized and must be freed. + B.setInsertionPoint(DeallocBlock->begin()); + B.setCurrentDebugScope(DeallocBlock->begin()->getDebugScope()); + destroyMemoryElements(Loc, Availability); + processUninitializedRelease(Release, false, B.getInsertionPoint()); + } else { + destroyMemoryElements(Loc, Availability); + processUninitializedRelease(Release, false, B.getInsertionPoint()); + + // The original strong_release or destroy_addr instruction is + // always dead at this point. + deleteDeadRelease(CDElt.ReleaseID); + } - // The original strong_release or destroy_addr instruction is - // always dead at this point. - deleteDeadRelease(CDElt.ReleaseID); continue; } @@ -2573,11 +2649,11 @@ handleConditionalDestroys(SILValue ControlVariableAddr) { // self.init or super.init may or may not have been called. // We have not yet stored 'self' into the box. - auto CondVal = testControlVariable(Loc, SuperInitElt, - ControlVariableAddr, - ShiftRightFn, - TruncateFn, - B); + auto CondVal = testControlVariableBit(Loc, SuperInitElt, + ControlVariableAddr, + ShiftRightFn, + TruncateFn, + B); SILBasicBlock *ConsumedBlock, *DeallocBlock, *ContBlock; @@ -2607,11 +2683,11 @@ handleConditionalDestroys(SILValue ControlVariableAddr) { // self.init or super.init may or may not have been called. // We may or may have stored 'self' into the box. - auto CondVal = testControlVariable(Loc, SuperInitElt, - ControlVariableAddr, - ShiftRightFn, - TruncateFn, - B); + auto CondVal = testControlVariableBit(Loc, SuperInitElt, + ControlVariableAddr, + ShiftRightFn, + TruncateFn, + B); SILBasicBlock *LiveBlock, *DeallocBlock, *ContBlock; diff --git a/test/Interpreter/failable_initializers_root_class.swift b/test/Interpreter/failable_initializers_root_class.swift new file mode 100644 index 0000000000000..9caa08da0001e --- /dev/null +++ b/test/Interpreter/failable_initializers_root_class.swift @@ -0,0 +1,176 @@ +// RUN: %target-run-simple-swift + +// REQUIRES: executable_test + +import StdlibUnittest + + +var FailableInitTestSuite = TestSuite("FailableInit") + +var deinitCalled = 0 + +func mustFail(f: () -> T?) { + if f() != nil { + preconditionFailure("Didn't fail") + } +} + +func mustSucceed(f: () -> T?) { + if f() == nil { + preconditionFailure("Didn't succeed") + } +} + +class FirstClass { + var x: LifetimeTracked + + init?(n: Int) { + if n == 0 { + return nil + } + + x = LifetimeTracked(0) + + if n == 1 { + return nil + } + } + + deinit { + deinitCalled += 1 + } +} + +FailableInitTestSuite.test("FirstClass") { + deinitCalled = 0 + + mustFail { FirstClass(n: 0) } + expectEqual(0, deinitCalled) + + mustFail { FirstClass(n: 1) } + expectEqual(1, deinitCalled) + + mustSucceed { FirstClass(n: 2) } + expectEqual(2, deinitCalled) +} + +class FirstClassTrivial { + var x: Int + + init?(n: Int) { + if n == 0 { + return nil + } + + x = 0 + + if n == 1 { + return nil + } + } + + deinit { + deinitCalled += 1 + } +} + +FailableInitTestSuite.test("FirstClassTrivial") { + deinitCalled = 0 + + mustFail { FirstClassTrivial(n: 0) } + expectEqual(0, deinitCalled) + + mustFail { FirstClassTrivial(n: 1) } + expectEqual(1, deinitCalled) + + mustSucceed { FirstClassTrivial(n: 2) } + expectEqual(2, deinitCalled) +} + +class SecondClass { + var x: LifetimeTracked + var y: LifetimeTracked + + init?(n: Int) { + if n == 0 { + return nil + } + + x = LifetimeTracked(0) + + if n == 1 { + return nil + } + + y = LifetimeTracked(0) + + if n == 2 { + return nil + } + } + + deinit { + deinitCalled += 1 + } +} + +FailableInitTestSuite.test("SecondClass") { + deinitCalled = 0 + + mustFail { SecondClass(n: 0) } + expectEqual(0, deinitCalled) + + mustFail { SecondClass(n: 1) } + expectEqual(0, deinitCalled) + + mustFail { SecondClass(n: 2) } + expectEqual(1, deinitCalled) + + mustSucceed { SecondClass(n: 3) } + expectEqual(2, deinitCalled) +} + +class SecondClassTrivial { + var x: Int + var y: Int + + init?(n: Int) { + if n == 0 { + return nil + } + + x = 0 + + if n == 1 { + return nil + } + + y = 0 + + if n == 2 { + return nil + } + } + + deinit { + deinitCalled += 1 + } +} + +FailableInitTestSuite.test("SecondClassTrivial") { + deinitCalled = 0 + + mustFail { SecondClassTrivial(n: 0) } + expectEqual(0, deinitCalled) + + mustFail { SecondClassTrivial(n: 1) } + expectEqual(0, deinitCalled) + + mustFail { SecondClassTrivial(n: 2) } + expectEqual(1, deinitCalled) + + mustSucceed { SecondClassTrivial(n: 3) } + expectEqual(2, deinitCalled) +} + +runAllTests() diff --git a/test/SILOptimizer/definite_init_root_class.swift b/test/SILOptimizer/definite_init_root_class.swift new file mode 100644 index 0000000000000..89d304d3c2169 --- /dev/null +++ b/test/SILOptimizer/definite_init_root_class.swift @@ -0,0 +1,214 @@ +// RUN: %target-swift-frontend -emit-sil %s | %FileCheck %s + +class OtherClass {} + +class FirstClass { + var x: OtherClass + + // CHECK-LABEL: sil hidden @$s24definite_init_root_class10FirstClassC1nACSgs5Int32V_tcfc : $@convention(method) (Int32, @owned FirstClass) -> @owned Optional + init?(n: Int32) { + // CHECK: [[CONTROL:%.*]] = alloc_stack $Builtin.Int1 + // CHECK: [[ZERO:%.*]] = integer_literal $Builtin.Int1, 0 + // CHECK: store [[ZERO]] to [[CONTROL]] : $*Builtin.Int1 + + // CHECK: [[ZERO:%.*]] = integer_literal $Builtin.Int32, 0 + // CHECK: [[N:%.*]] = struct_extract %0 : $Int32, #Int32._value + // CHECK: [[CMP:%.*]] = builtin "cmp_eq_Int32"([[N]] : $Builtin.Int32, [[ZERO]] : $Builtin.Int32) : $Builtin.Int1 + // CHECK: cond_br [[CMP]], bb1, bb2 + if n == 0 { + return nil + } + + // CHECK: bb1: + // CHECK: br bb5 + + // CHECK: bb2: + // CHECK: [[METATYPE:%.*]] = metatype $@thick OtherClass.Type + // CHECK: [[INIT:%.*]] = function_ref @$s24definite_init_root_class10OtherClassCACycfC : $@convention(method) (@thick OtherClass.Type) -> @owned OtherClass + // CHECK: [[OTHER:%.*]] = apply [[INIT]]([[METATYPE]]) : $@convention(method) (@thick OtherClass.Type) -> @owned OtherClass + // CHECK: [[X_ADDR:%.*]] = ref_element_addr %1 : $FirstClass, #FirstClass.x + // CHECK: [[X_ACCESS:%.*]] = begin_access [modify] [dynamic] %15 : $*OtherClass + // CHECK: [[ONE:%.*]] = integer_literal $Builtin.Int1, -1 + // CHECK: store [[ONE]] to [[CONTROL]] : $*Builtin.Int1 + // CHECK: store [[OTHER]] to [[X_ACCESS]] : $*OtherClass + // CHECK: end_access [[X_ACCESS]] : $*OtherClass + x = OtherClass() + + // CHECK: [[ONE:%.*]] = integer_literal $Builtin.Int32, 1 + // CHECK: [[N:%.*]] = struct_extract %0 : $Int32, #Int32._value + // CHECK: [[CMP:%.*]] = builtin "cmp_eq_Int32"([[N]] : $Builtin.Int32, [[ONE]] : $Builtin.Int32) : $Builtin.Int1 + // CHECK: cond_br [[CMP]], bb3, bb4 + if n == 1 { + return nil + } + + // CHECK: bb3: + // CHECK: br bb5 + + // CHECK: bb4: + // CHECK: [[RESULT:%.*]] = enum $Optional, #Optional.some!enumelt, %1 : $FirstClass + // CHECK: br bb12([[RESULT]] : $Optional) + + // CHECK: bb5: + // CHECK: [[BIT:%.*]] = load [[CONTROL]] : $*Builtin.Int1 + // CHECK: cond_br [[BIT]], bb6, bb7 + + // CHECK: bb6: + // CHECK: strong_release %1 : $FirstClass + // CHECK: br bb11 + + // CHECK: bb7: + // CHECK: [[BIT:%.*]] = load [[CONTROL]] : $*Builtin.Int1 + // CHECK: cond_br [[BIT]], bb8, bb9 + + // CHECK: bb8: + // CHECK: [[X_ADDR:%.*]] = ref_element_addr %1 : $FirstClass, #FirstClass.x + // CHECK: [[X_ACCESS:%.*]] = begin_access [deinit] [static] [[X_ADDR]] : $*OtherClass + // CHECK: destroy_addr [[X_ACCESS]] : $*OtherClass + // CHECK: end_access [[X_ACCESS]] : $*OtherClass + // CHECK: br bb10 + + // CHECK: bb9: + // CHECK: br bb10 + + // CHECK: [[METATYPE:%.*]] = metatype $@thick FirstClass.Type + // CHECK: dealloc_partial_ref %1 : $FirstClass, [[METATYPE]] : $@thick FirstClass.Type + // CHECK: br bb11 + + // CHECK: bb11: + // CHECK: [[NIL:%.*]] = enum $Optional, #Optional.none!enumelt + // CHECK: br bb12([[NIL]] : $Optional) + + // CHECK: bb12([[RESULT:%.*]] : $Optional): + // CHECK: dealloc_stack [[CONTROL]] : $*Builtin.Int1 + // CHECK: return [[RESULT]] : $Optional + } +} + +class SecondClass { + var x: OtherClass + var y: OtherClass + + // CHECK-LABEL: sil hidden @$s24definite_init_root_class11SecondClassC1nACSgs5Int32V_tcfc : $@convention(method) (Int32, @owned SecondClass) -> @owned Optional { + init?(n: Int32) { + // CHECK: [[CONTROL:%.*]] = alloc_stack $Builtin.Int2 + // CHECK: [[ZERO:%.*]] = integer_literal $Builtin.Int2, 0 + // CHECK: store [[ZERO]] to [[CONTROL]] : $*Builtin.Int2 + + // CHECK: [[ZERO:%.*]] = integer_literal $Builtin.Int32, 0 + // CHECK: [[N:%.*]] = struct_extract %0 : $Int32, #Int32._value + // CHECK: [[CMP:%.*]] = builtin "cmp_eq_Int32"([[N]] : $Builtin.Int32, [[ZERO]] : $Builtin.Int32) : $Builtin.Int1 + // CHECK: cond_br [[CMP]], bb1, bb2 + if n == 0 { + return nil + } + + // CHECK: bb1: + // CHECK: br bb7 + + // CHECK: bb2: + // CHECK: [[METATYPE:%.*]] = metatype $@thick OtherClass.Type + // CHECK: [[INIT:%.*]] = function_ref @$s24definite_init_root_class10OtherClassCACycfC : $@convention(method) (@thick OtherClass.Type) -> @owned OtherClass + // CHECK: [[OTHER:%.*]] = apply [[INIT]]([[METATYPE]]) : $@convention(method) (@thick OtherClass.Type) -> @owned OtherClass + // CHECK: [[X_ADDR:%.*]] = ref_element_addr %1 : $SecondClass, #SecondClass.x + // CHECK: [[X_ACCESS:%.*]] = begin_access [modify] [dynamic] [[X_ADDR]] : $*OtherClass + // CHECK: [[ONE:%.*]] = integer_literal $Builtin.Int2, 1 + // CHECK: store [[ONE]] to [[CONTROL]] : $*Builtin.Int2 + // CHECK: store [[OTHER]] to [[X_ACCESS]] : $*OtherClass + // CHECK: end_access [[X_ACCESS]] : $*OtherClass + x = OtherClass() + + // CHECK: [[ONE:%.*]] = integer_literal $Builtin.Int32, 1 + // CHECK: [[N:%.*]] = struct_extract %0 : $Int32, #Int32._value + // CHECK: [[CMP:%.*]] = builtin "cmp_eq_Int32"([[N]] : $Builtin.Int32, [[ONE]] : $Builtin.Int32) : $Builtin.Int1 + // CHECK: cond_br [[CMP]], bb3, bb4 + if n == 1 { + return nil + } + + // CHECK: bb3: + // CHECK: br bb7 + + // CHECK: bb4: + // CHECK: [[METATYPE:%.*]] = metatype $@thick OtherClass.Type + // CHECK: [[INIT:%.*]] = function_ref @$s24definite_init_root_class10OtherClassCACycfC : $@convention(method) (@thick OtherClass.Type) -> @owned OtherClass + // CHECK: [[OTHER:%.*]] = apply [[INIT]]([[METATYPE]]) : $@convention(method) (@thick OtherClass.Type) -> @owned OtherClass + // CHECK: [[Y_ADDR:%.*]] = ref_element_addr %1 : $SecondClass, #SecondClass.y + // CHECK: [[Y_ACCESS:%.*]] = begin_access [modify] [dynamic] [[Y_ADDR]] : $*OtherClass + // CHECK: [[THREE:%.*]] = integer_literal $Builtin.Int2, -1 + // CHECK: store [[THREE]] to [[CONTROL]] : $*Builtin.Int2 + // CHECK: store [[OTHER]] to [[Y_ACCESS]] : $*OtherClass + // CHECK: end_access [[Y_ACCESS]] : $*OtherClass + y = OtherClass() + + // CHECK: [[TWO:%.*]] = integer_literal $Builtin.Int32, 2 + // CHECK: [[N:%.*]] = struct_extract %0 : $Int32, #Int32._value + // CHECK: [[CMP:%.*]] = builtin "cmp_eq_Int32"([[N]] : $Builtin.Int32, [[TWO]] : $Builtin.Int32) : $Builtin.Int1 + // CHECK: cond_br [[CMP]], bb5, bb6 + if n == 2 { + return nil + } + + // CHECK: bb5: + // CHECK: br bb7 + + // CHECK: bb6: + // CHECK: [[RESULT:%.*]] = enum $Optional, #Optional.some!enumelt, %1 : $SecondClass + // CHECK: br bb17([[RESULT]] : $Optional) + + // CHECK: bb7: + // CHECK: [[BITS:%.*]] = load [[CONTROL]] : $*Builtin.Int2 + // CHECK: [[THREE:%.*]] = integer_literal $Builtin.Int2, -1 + // CHECK: [[BIT:%.*]] = builtin "cmp_eq_Int2"([[BITS]] : $Builtin.Int2, [[THREE]] : $Builtin.Int2) : $Builtin.Int1 + // CHECK: cond_br [[BIT]], bb8, bb9 + + // CHECK: bb8: + // CHECK: strong_release %1 : $SecondClass + // CHECK: br bb16 + + // CHECK: bb9: + // CHECK: [[BITS:%.*]] = load [[CONTROL]] : $*Builtin.Int2 + // CHECK: [[BIT:%.*]] = builtin "trunc_Int2_Int1"([[BITS]] : $Builtin.Int2) : $Builtin.Int1 + // CHECK: cond_br [[BIT]], bb10, bb11 + + // CHECK: bb10: + // CHECK: [[X_ADDR:%.*]] = ref_element_addr %1 : $SecondClass, #SecondClass.x + // CHECK: [[X_ACCESS:%.*]] = begin_access [deinit] [static] [[X_ADDR]] : $*OtherClass + // CHECK: destroy_addr [[X_ACCESS]] : $*OtherClass + // CHECK: end_access [[X_ACCESS]] : $*OtherClass + // CHECK: br bb12 + + // CHECK: bb11: + // CHECK: br bb12 + + // CHECK: bb12: + // CHECK: [[BITS:%.*]] = load [[CONTROL]] : $*Builtin.Int2 + // CHECK: [[ONE:%.*]] = integer_literal $Builtin.Int2, 1 + // CHECK: [[TMP:%.*]] = builtin "lshr_Int2"([[BITS]] : $Builtin.Int2, [[ONE]] : $Builtin.Int2) : $Builtin.Int2 + // CHECK: [[BIT:%.*]] = builtin "trunc_Int2_Int1"([[TMP]] : $Builtin.Int2) : $Builtin.Int1 + // CHECK: cond_br [[BIT]], bb13, bb14 + + // CHECK: bb13: + // CHECK: [[Y_ADDR:%.*]] = ref_element_addr %1 : $SecondClass, #SecondClass.y + // CHECK: [[Y_ACCESS:%.*]] = begin_access [deinit] [static] [[Y_ADDR]] : $*OtherClass + // CHECK: destroy_addr [[Y_ACCESS]] : $*OtherClass + // CHECK: end_access [[Y_ACCESS]] : $*OtherClass + // CHECK: br bb15 + + // CHECK: bb14: + // CHECK: br bb15 + + // CHECK: bb15: + // CHECK: [[METATYPE:%.*]] = metatype $@thick SecondClass.Type + // CHECK: dealloc_partial_ref %1 : $SecondClass, [[METATYPE]] : $@thick SecondClass.Type + // CHECK: br bb16 + + // CHECK: bb16: + // CHECK: [[NIL:%.*]] = enum $Optional, #Optional.none!enumelt + // CHECK: br bb17([[NIL]] : $Optional) + + // CHECK: bb17([[RESULT:%.*]] : $Optional): + // CHECK: dealloc_stack [[CONTROL]] : $*Builtin.Int2 + // CHECK: return [[RESULT]] : $Optional + } +}