From e6249576eafc39de27029596a7c163fcc82284ca Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Mon, 31 Aug 2020 18:20:22 -0400 Subject: [PATCH 1/3] DI: Rename testControlVariable() to testControlVariableBit() --- .../Mandatory/DefiniteInitialization.cpp | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp index ab7a60c936c07..da7370b39ec39 100644 --- a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp +++ b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp @@ -2186,12 +2186,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); @@ -2324,9 +2324,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, @@ -2465,9 +2465,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 +2486,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; @@ -2573,11 +2573,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 +2607,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; From df0a8b374601d9d0b7f3d66bc515ffbad4132ad0 Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Mon, 31 Aug 2020 22:06:35 -0400 Subject: [PATCH 2/3] DI: Track of initialization state of trivial fields in root class initializers While we don't need to destroy these fields, we still need to know when the class is _semantically_ fully initialized, since this will determine if we're going to release the class, running the deinitializer, or if we're going to deallocate the memory for the instance directly. --- .../Mandatory/DIMemoryUseCollector.h | 5 +++++ .../Mandatory/DefiniteInitialization.cpp | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) 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 da7370b39ec39..41ba220949ef2 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; } @@ -2285,7 +2291,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); From 233eeea6cb025b04e112c723a27113a94f81c79e Mon Sep 17 00:00:00 2001 From: Slava Pestov Date: Mon, 31 Aug 2020 18:27:53 -0400 Subject: [PATCH 3/3] DI: Correctly handle conditional destroy of 'self' in root class In a designated initializer of a non-root class, 'self' becomes fully initialized after the 'super.init' call, at which point escaping uses of 'self' become valid, and releases of 'self' are lowered to a 'strong_release' instruction, which runs the deinitializer. In a root class, 'self' becomes fully initialized after all stored properties have been initialized, at which point escaping uses of 'self' become valid. However, DI would still lower a conditional destroy of 'self' by destroying any initialized stored properties and freeing the object with 'dealloc_partial_ref'. This is incorrect, because 'self' may have escaped. In the non-conditional destroy case, we correctly lowered the release to a 'strong_release' if all stored properties are known to be initialized. Fix DI to handle the conditional destroy case by first checking if all bits in the control variable are set, and releasing the instance with 'strong_release' if so. The 'dealloc_partial_ref' is only emitted if not at least one stored property was not initialized. This ensures that we don't deallocate an instance that may have escaped. Fixes , , , . --- .../Mandatory/DefiniteInitialization.cpp | 76 ++++++- .../failable_initializers_root_class.swift | 176 ++++++++++++++ .../definite_init_root_class.swift | 214 ++++++++++++++++++ 3 files changed, 460 insertions(+), 6 deletions(-) create mode 100644 test/Interpreter/failable_initializers_root_class.swift create mode 100644 test/SILOptimizer/definite_init_root_class.swift diff --git a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp index 41ba220949ef2..14a63285fb1f3 100644 --- a/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp +++ b/lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp @@ -2230,6 +2230,32 @@ static SILValue testControlVariableBit(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 @@ -2407,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(); @@ -2534,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; } 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 + } +}