diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index fbf1709638d3b..58c85675ad2d1 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -62,6 +62,7 @@ set(SWIFT_BENCH_MODULES single-source/Combos single-source/DataBenchmarks single-source/DeadArray + single-source/DevirtualizeProtocolComposition single-source/DictOfArraysToArrayOfDicts single-source/DictTest single-source/DictTest2 diff --git a/benchmark/single-source/DevirtualizeProtocolComposition.swift b/benchmark/single-source/DevirtualizeProtocolComposition.swift new file mode 100644 index 0000000000000..d90bbd1da71e6 --- /dev/null +++ b/benchmark/single-source/DevirtualizeProtocolComposition.swift @@ -0,0 +1,45 @@ +//===--- DevirtualizeProtocolComposition.swift -------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import TestsUtils + +public let DevirtualizeProtocolComposition = [ + BenchmarkInfo(name: "DevirtualizeProtocolComposition", runFunction: run_DevirtualizeProtocolComposition, tags: [.validation, .api]), +] + +protocol Pingable { func ping() -> Int; func pong() -> Int} + +public class Game { + func length() -> Int { return 10 } +} + +public class PingPong: Game { } + +extension PingPong : Pingable { + func ping() -> Int { return 1 } + func pong() -> Int { return 2 } +} + +func playGame(_ x: Game & Pingable) -> Int { + var sum = 0 + for _ in 0..getType().isAddress()); } @@ -817,9 +817,18 @@ struct ConcreteArgumentCopy { SILBuilderWithScope B(apply.getInstruction(), BuilderCtx); auto loc = apply.getLoc(); auto *ASI = B.createAllocStack(loc, CEI.ConcreteValue->getType()); - auto *CAI = B.createCopyAddr(loc, CEI.ConcreteValue, ASI, IsNotTake, - IsInitialization_t::IsInitialization); - return ConcreteArgumentCopy(origArg, CAI); + // If the type is an address, simple copy it. + if (CEI.ConcreteValue->getType().isAddress()) { + B.createCopyAddr(loc, CEI.ConcreteValue, ASI, IsNotTake, + IsInitialization_t::IsInitialization); + } else { + // Otherwise, we probably got the value from the source of a store + // instruction so, create a store into the temporary argument. + B.createStrongRetain(loc, CEI.ConcreteValue, B.getDefaultAtomicity()); + B.createStore(loc, CEI.ConcreteValue, ASI, + StoreOwnershipQualifier::Unqualified); + } + return ConcreteArgumentCopy(origArg, ASI); } }; @@ -850,20 +859,19 @@ SILInstruction *SILCombiner::createApplyWithConcreteType( FullApplySite Apply, const llvm::SmallDenseMap &COAIs, SILBuilderContext &BuilderCtx) { - // Ensure that the callee is polymorphic. assert(Apply.getOrigCalleeType()->isPolymorphic()); // Create the new set of arguments to apply including their substitutions. SubstitutionMap NewCallSubs = Apply.getSubstitutionMap(); SmallVector NewArgs; - bool UpdatedArgs = false; unsigned ArgIdx = 0; // Push the indirect result arguments. for (unsigned EndIdx = Apply.getSubstCalleeConv().getSILArgIndexOfFirstParam(); ArgIdx < EndIdx; ++ArgIdx) { NewArgs.push_back(Apply.getArgument(ArgIdx)); } + // Transform the parameter arguments. SmallVector concreteArgCopies; for (unsigned EndIdx = Apply.getNumArguments(); ArgIdx < EndIdx; ++ArgIdx) { @@ -882,16 +890,18 @@ SILInstruction *SILCombiner::createApplyWithConcreteType( NewArgs.push_back(Apply.getArgument(ArgIdx)); continue; } - UpdatedArgs = true; + // Ensure that we have a concrete value to propagate. assert(CEI.ConcreteValue); + auto argSub = ConcreteArgumentCopy::generate(CEI, Apply, ArgIdx, BuilderCtx); if (argSub) { concreteArgCopies.push_back(*argSub); - NewArgs.push_back(argSub->tempArgCopy->getDest()); - } else + NewArgs.push_back(argSub->tempArg); + } else { NewArgs.push_back(CEI.ConcreteValue); + } // Form a new set of substitutions where the argument is // replaced with a concrete type. @@ -914,7 +924,33 @@ SILInstruction *SILCombiner::createApplyWithConcreteType( }); } - if (!UpdatedArgs) { + // We need to make sure that we can a) update Apply to use the new args and b) + // at least one argument has changed. If no arguments have changed, we need + // to return nullptr. Otherwise, we will have an infinite loop. + auto substTy = + Apply.getCallee() + ->getType() + .substGenericArgs(Apply.getModule(), NewCallSubs, + Apply.getFunction()->getTypeExpansionContext()) + .getAs(); + SILFunctionConventions conv(substTy, + SILModuleConventions(Apply.getModule())); + bool canUpdateArgs = true; + bool madeUpdate = false; + for (unsigned index = 0; index < conv.getNumSILArguments(); ++index) { + // Make sure that *all* the arguments in both the new substitution function + // and our vector of new arguments have the same type. + canUpdateArgs &= + conv.getSILArgumentType(index) == NewArgs[index]->getType(); + // Make sure that we have changed at least one argument. + madeUpdate |= + NewArgs[index]->getType() != Apply.getArgument(index)->getType(); + } + + // If we can't update the args (because of a type mismatch) or the args don't + // change, bail out by removing the instructions we've added and returning + // nullptr. + if (!canUpdateArgs || !madeUpdate) { // Remove any new instructions created while attempting to optimize this // apply. Since the apply was never rewritten, if they aren't removed here, // they will be removed later as dead when visited by SILCombine, causing @@ -961,8 +997,7 @@ SILInstruction *SILCombiner::createApplyWithConcreteType( auto cleanupLoc = RegularLocation::getAutoGeneratedLocation(); for (ConcreteArgumentCopy &argCopy : llvm::reverse(concreteArgCopies)) { cleanupBuilder.createDestroyAddr(cleanupLoc, argCopy.origArg); - cleanupBuilder.createDeallocStack(cleanupLoc, - argCopy.tempArgCopy->getDest()); + cleanupBuilder.createDeallocStack(cleanupLoc, argCopy.tempArg); } } return NewApply.getInstruction(); diff --git a/lib/SILOptimizer/Utils/Existential.cpp b/lib/SILOptimizer/Utils/Existential.cpp index ff2f500583ad4..626c717a640d2 100644 --- a/lib/SILOptimizer/Utils/Existential.cpp +++ b/lib/SILOptimizer/Utils/Existential.cpp @@ -69,9 +69,9 @@ findInitExistentialFromGlobalAddr(GlobalAddrInst *GAI, SILInstruction *Insn) { /// Returns the instruction that initializes the given stack address. This is /// currently either a init_existential_addr, unconditional_checked_cast_addr, -/// or copy_addr (if the instruction initializing the source of the copy cannot -/// be determined). Returns nullptr if the initializer does not dominate the -/// alloc_stack user \p ASIUser. If the value is copied from another stack +/// store, or copy_addr (if the instruction initializing the source of the copy +/// cannot be determined). Returns nullptr if the initializer does not dominate +/// the alloc_stack user \p ASIUser. If the value is copied from another stack /// location, \p isCopied is set to true. /// /// allocStackAddr may either itself be an AllocStackInst or an @@ -111,6 +111,19 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr, } continue; } + if (auto *store = dyn_cast(User)) { + if (store->getDest() == allocStackAddr) { + if (SingleWrite) + return nullptr; + SingleWrite = store; + // When we support OSSA here, we need to insert a new copy of the value + // before `store` (and make sure that the copy is destroyed when + // replacing the apply operand). + assert(store->getOwnershipQualifier() == + StoreOwnershipQualifier::Unqualified); + } + continue; + } if (isa(User)) { if (SingleWrite) return nullptr; @@ -144,6 +157,9 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr, if (BB != allocStackAddr->getParentBlock() && BB != ASIUser->getParent()) return nullptr; + if (auto *store = dyn_cast(SingleWrite)) + return store; + if (auto *IE = dyn_cast(SingleWrite)) return IE; @@ -180,24 +196,6 @@ static SILInstruction *getStackInitInst(SILValue allocStackAddr, return CAI; } -/// Return the address of the value used to initialize the given stack location. -/// If the value originates from init_existential_addr, then it will be a -/// different type than \p allocStackAddr. -static SILValue getAddressOfStackInit(SILValue allocStackAddr, - SILInstruction *ASIUser, bool &isCopied) { - SILInstruction *initI = getStackInitInst(allocStackAddr, ASIUser, isCopied); - if (!initI) - return SILValue(); - - if (auto *IEA = dyn_cast(initI)) - return IEA; - - if (auto *CAI = dyn_cast(initI)) - return CAI->getSrc(); - - return SILValue(); -} - /// Check if the given operand originates from a recognized OpenArchetype /// instruction. If so, return the Opened, otherwise return nullptr. OpenedArchetypeInfo::OpenedArchetypeInfo(Operand &use) { @@ -207,11 +205,17 @@ OpenedArchetypeInfo::OpenedArchetypeInfo(Operand &use) { // Handle: // %opened = open_existential_addr // %instance = alloc $opened - // copy_addr %opened to %stack + // %opened to %stack // %instance - if (auto stackInitVal = - getAddressOfStackInit(instance, user, isOpenedValueCopied)) { - openedVal = stackInitVal; + if (auto *initI = getStackInitInst(instance, user, isOpenedValueCopied)) { + // init_existential_addr isn't handled here because it isn't considered an + // "opened" archtype. init_existential_addr should be handled by + // ConcreteExistentialInfo. + + if (auto *CAI = dyn_cast(initI)) + openedVal = CAI->getSrc(); + if (auto *store = dyn_cast(initI)) + openedVal = store->getSrc(); } } if (auto *Open = dyn_cast(openedVal)) { diff --git a/lib/SILOptimizer/Utils/InstOptUtils.cpp b/lib/SILOptimizer/Utils/InstOptUtils.cpp index 715ba5de296da..542eaff2819f7 100644 --- a/lib/SILOptimizer/Utils/InstOptUtils.cpp +++ b/lib/SILOptimizer/Utils/InstOptUtils.cpp @@ -814,9 +814,6 @@ SILValue swift::castValueToABICompatibleType(SILBuilder *builder, if (srcTy == destTy) return value; - assert(srcTy.isAddress() == destTy.isAddress() - && "Addresses aren't compatible with values"); - if (srcTy.isAddress() && destTy.isAddress()) { // Cast between two addresses and that's it. return builder->createUncheckedAddrCast(loc, value, destTy); diff --git a/test/SILOptimizer/devirtualize_protocol_composition.swift b/test/SILOptimizer/devirtualize_protocol_composition.swift new file mode 100644 index 0000000000000..9c54736ef2177 --- /dev/null +++ b/test/SILOptimizer/devirtualize_protocol_composition.swift @@ -0,0 +1,140 @@ +// RUN: %target-swift-frontend -parse-as-library -O -wmo -emit-sil %s | %FileCheck %s +// RUN: %target-swift-frontend -parse-as-library -Osize -wmo -emit-sil %s | %FileCheck %s + +// REQUIRES: objc_interop + +// This is an end-to-end test to ensure that the optimizer devertualizes +// calls to a protocol composition type. + +public class ClassA { } + +protocol ProtocolA { + func foo() -> Int +} + +protocol ProtocolB { + func bar() -> Int +} + +public class ClassB: ClassA { + func foo() -> Int { + return 10 + } +} + +extension ClassB: ProtocolA { } + +public class ClassC: ClassA { + func foo() -> Int { + return 10 + } +} + +extension ClassC: ProtocolA { } + +public class ClassD { } +public class ClassE : ClassD { + func foo() -> Int { + return 10 + } +} + +extension ClassE: ProtocolA { } + +public class ClassF { + func foo() -> Int { + return 10 + } + + func bar() -> Int { + return 10 + } +} + +extension ClassF: ProtocolA, ProtocolB { } + +public class ClassG { + func foo() -> Int { + return 10 + } + + func bar() -> Int { + return 10 + } +} + +extension ClassG: ProtocolA, ProtocolB { } + +public class ClassH { + typealias type = ClassD +} + +func shouldOptimize1(_ x: ClassA & ProtocolA) -> Int { + return x.foo() +} + +func shouldOptimize2(_ x: ClassD & ProtocolA) -> Int { + return x.foo() +} + +func shouldOptimize3(_ x: ProtocolA & ProtocolB) -> Int { + return x.foo() + x.bar() +} + +func shouldOptimize4(_ x: ProtocolA & ProtocolB) -> Int { + return x.foo() + x.bar() +} + +func shouldOptimize5(_ x: ClassH.type & ProtocolA) -> Int { + return x.foo() +} + +//CHECK: entryPoint1 +//CHECK-NOT: init_existential_ref +//CHECK-NOT: open_existential_ref +//CHECK-NOT: witness_method +//CHECK: return +public func entryPoint1(c: ClassB) -> Int { + return shouldOptimize1(c) +} + +// TODO: create SR -- this causes a crash on master too +//public func entryPoint2(c: ClassC) -> Int { +// return shouldOptimize1(c) +//} + +//CHECK: entryPoint3 +//CHECK-NOT: init_existential_ref +//CHECK-NOT: open_existential_ref +//CHECK-NOT: witness_method +//CHECK: return +public func entryPoint3(c: ClassE) -> Int { + return shouldOptimize2(c) +} + +//CHECK: entryPoint4 +//CHECK-NOT: init_existential_ref +//CHECK-NOT: open_existential_ref +//CHECK-NOT: witness_method +//CHECK: return +public func entryPoint4(c: ClassF) -> Int { + return shouldOptimize3(c) +} + +//CHECK: entryPoint5 +//CHECK-NOT: init_existential_ref +//CHECK-NOT: open_existential_ref +//CHECK-NOT: witness_method +//CHECK: return +public func entryPoint5(c: ClassG) -> Int { + return shouldOptimize4(c) +} + +//CHECK: entryPoint6 +//CHECK-NOT: init_existential_ref +//CHECK-NOT: open_existential_ref +//CHECK-NOT: witness_method +//CHECK: return +public func entryPoint6(c: ClassE) -> Int { + return shouldOptimize5(c) +} diff --git a/test/SILOptimizer/devirtualize_protocol_composition_two_stores.sil b/test/SILOptimizer/devirtualize_protocol_composition_two_stores.sil new file mode 100644 index 0000000000000..cf16ae6c167da --- /dev/null +++ b/test/SILOptimizer/devirtualize_protocol_composition_two_stores.sil @@ -0,0 +1,123 @@ +// RUN: %target-sil-opt -O -wmo -enable-sil-verify-all %s | %FileCheck %s + +// REQUIRES: objc_interop + +sil_stage canonical + +import Builtin +import Swift +import SwiftShims + +public class A { + @objc deinit + init() +} + +public protocol P { + func foo() -> Int64 +} + +public class B : A, P { + public func foo() -> Int64 + @objc deinit + override init() +} + +public class C : A, P { + public func foo() -> Int64 + @objc deinit + override init() +} + +func imp(x: A & P, y: A & P) -> Int64 + +public func test(x: B, y: C) -> Int64 + +// protocol witness for P.foo() in conformance B +sil shared [transparent] [serialized] [thunk] @Pfoo : $@convention(witness_method: P) (@in_guaranteed B) -> Int64 { +// %0 // user: %1 +bb0(%0 : $*B): + %1 = load %0 : $*B // users: %2, %3 + %2 = class_method %1 : $B, #B.foo!1 : (B) -> () -> Int64, $@convention(method) (@guaranteed B) -> Int64 // user: %3 + %3 = apply %2(%1) : $@convention(method) (@guaranteed B) -> Int64 // user: %4 + return %3 : $Int64 // id: %4 +} // end sil function 'Pfoo' + +// B.foo() +sil [noinline] @foo : $@convention(method) (@guaranteed B) -> Int64 { +// %0 // user: %1 +bb0(%0 : $B): + debug_value %0 : $B, let, name "self", argno 1 // id: %1 + %2 = integer_literal $Builtin.Int64, 1 // user: %3 + %3 = struct $Int64 (%2 : $Builtin.Int64) // user: %4 + return %3 : $Int64 // id: %4 +} // end sil function 'foo' + + +// This function calls @impl. The optimizer generates a specialization of impl +// based on the arguments passed to it here. Even though this function isn't +// directly checked, it is essentail for the optimization. +// test(x:y:) +sil @test :$@convention(thin) (@guaranteed B, @guaranteed C) -> Int64 { +// %0 // users: %5, %4, %2 +// %1 // users: %7, %6, %3 +bb0(%0 : $B, %1 : $C): + debug_value %0 : $B, let, name "x", argno 1 // id: %2 + debug_value %1 : $C, let, name "y", argno 2 // id: %3 + strong_retain %0 : $B // id: %4 + %5 = init_existential_ref %0 : $B : $B, $A & P // users: %11, %9 + strong_retain %1 : $C // id: %6 + %7 = init_existential_ref %1 : $C : $C, $A & P // users: %10, %9 + // function_ref imp(x:y:) + %8 = function_ref @impl : $@convention(thin) (@guaranteed A & P, @guaranteed A & P) -> Int64 // user: %9 + %9 = apply %8(%5, %7) : $@convention(thin) (@guaranteed A & P, @guaranteed A & P) -> Int64 // user: %12 + strong_release %7 : $A & P // id: %10 + strong_release %5 : $A & P // id: %11 + return %9 : $Int64 // id: %12 +} // end sil function 'test' + +// We're looking of an optimized spcialization of @impl, not @impl itself. +// CHECK-NOT: @impl +// CHECK-LABEL: sil shared [noinline] @{{.*}}impl{{.*}} +// In the optimization pass we look for uses of alloc_stack (aka %5). +// This test makes sure that we look at the correct uses with the +// correct dominance order (store before apply before dealloc). +// This function will be passed arguments of type B and C for arguments +// %0 and %1 respectively. We want to make sure that we call B's foo method +// and not C's foo method. +sil hidden [noinline] @impl : $@convention(thin) (@guaranteed A & P, @guaranteed A & P) -> Int64 { +bb0(%0 : $A & P, %1 : $A & P): + %2 = open_existential_ref %0 : $A & P to $@opened("A1F1B526-1463-11EA-932F-ACBC329C418B") A & P + %3 = unchecked_ref_cast %1 : $A & P to $@opened("A1F1B526-1463-11EA-932F-ACBC329C418B") A & P + + %5 = alloc_stack $@opened("A1F1B526-1463-11EA-932F-ACBC329C418B") A & P + store %2 to %5 : $*@opened("A1F1B526-1463-11EA-932F-ACBC329C418B") A & P + + // We want to make sure that we picked up on the FIRST store and not the second one. + // class C's foo method is named "bar" whereas class B's foo method is named "foo". + // We want to make sure that we call a function named "foo" not "bar". + // CHECK: [[FN:%[0-9]+]] = function_ref @${{.*}}foo{{.*}} : $@convention(thin) () -> Int64 + %7 = witness_method $@opened("A1F1B526-1463-11EA-932F-ACBC329C418B") A & P, #P.foo!1 : (Self) -> () -> Int64, %2 : $@opened("A1F1B526-1463-11EA-932F-ACBC329C418B") A & P : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> Int64 + // CHECK: apply [[FN]] + %8 = apply %7<@opened("A1F1B526-1463-11EA-932F-ACBC329C418B") A & P>(%5) : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> Int64 + + store %3 to %5 : $*@opened("A1F1B526-1463-11EA-932F-ACBC329C418B") A & P + dealloc_stack %5 : $*@opened("A1F1B526-1463-11EA-932F-ACBC329C418B") A & P + return %8 : $Int64 +// CHECK-LABEL: end sil function {{.*}}impl{{.*}} +} // end sil function 'impl' + +// C.foo() +sil @bar : $@convention(method) (@guaranteed C) -> Int64 + +sil_vtable [serialized] B { + #B.foo!1: (B) -> () -> Int64 : @foo // B.foo() +} + +sil_vtable [serialized] C { + #C.foo!1: (C) -> () -> Int64 : @bar +} + +sil_witness_table [serialized] B: P module run { + method #P.foo!1: (Self) -> () -> Int64 : @Pfoo // protocol witness for P.foo() in conformance B +} diff --git a/test/SILOptimizer/existential_specializer_indirect_class.sil b/test/SILOptimizer/existential_specializer_indirect_class.sil index 384931e2f09a4..7f0ea56c56d81 100644 --- a/test/SILOptimizer/existential_specializer_indirect_class.sil +++ b/test/SILOptimizer/existential_specializer_indirect_class.sil @@ -1,4 +1,4 @@ -// RUN: %target-sil-opt -wmo -enable-sil-verify-all -emit-sorted-sil %s -enable-existential-specializer -existential-specializer -inline -sil-combine -generic-specializer -devirtualizer 2>&1 | %FileCheck %s +// RUN: %target-sil-opt -O -wmo -enable-sil-verify-all -sil-disable-pass=DeadFunctionElimination %s | %FileCheck %s sil_stage canonical @@ -8,74 +8,64 @@ protocol ClassProtocol: AnyObject { func method() } class C: ClassProtocol { func method() {} } -// CHECK-LABEL: sil shared @$s28test_indirect_class_protocolTf4e_n : $@convention(thin) <τ_0_0 where τ_0_0 : ClassProtocol> (@in τ_0_0) -> () -sil hidden @test_indirect_class_protocol : $@convention(thin) (@in ClassProtocol) -> () { +// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @test_indirect_class_protocol : $@convention(thin) (@in ClassProtocol) -> () +sil @test_indirect_class_protocol : $@convention(thin) (@in ClassProtocol) -> () { // CHECK-NEXT: // -// CHECK-NEXT: bb0(%0 : $*τ_0_0): -// CHECK-NEXT: %1 = load %0 -// CHECK-NEXT: %2 = init_existential_ref %1 -// CHECK-NEXT: %3 = alloc_stack $ClassProtocol -// CHECK-NEXT: store %2 to %3 +// CHECK-NEXT: bb0(%0 : $*ClassProtocol): bb0(%0 : $*ClassProtocol): - // CHECK-NEXT: destroy_addr %3 + // CHECK-NEXT: %1 = load %0 + // CHECK-NEXT: strong_release %1 destroy_addr %0 : $*ClassProtocol - // CHECK-NEXT: dealloc_stack %3 // CHECK-NEXT: return undef return undef : $() } +// Check that all the opened types are optimized away in the specialization of test_indirect_class_protocol_guaranteed // CHECK-LABEL: sil shared @$s39test_indirect_class_protocol_guaranteedTf4e_n : $@convention(thin) <τ_0_0 where τ_0_0 : ClassProtocol> (@in_guaranteed τ_0_0) -> () -sil hidden @test_indirect_class_protocol_guaranteed : $@convention(thin) (@in_guaranteed ClassProtocol) -> () { // CHECK-NEXT: // // CHECK-NEXT: bb0(%0 : $*τ_0_0): -// CHECK-NEXT: %1 = load %0 -// CHECK-NEXT: %2 = init_existential_ref %1 -// CHECK-NEXT: %3 = alloc_stack $ClassProtocol -// CHECK-NEXT: store %2 to %3 + // CHECK-NEXT: [[INPUT:%1]] = load %0 + // CHECK-NEXT: [[METHOD:%.*]] = witness_method $C, #ClassProtocol.method + // CHECK-NEXT: [[ARG:%.*]] = unchecked_ref_cast [[INPUT]] + // CHECK-NEXT: apply [[METHOD]]([[ARG]]) + // CHECK-NEXT: return undef + +sil @test_indirect_class_protocol_guaranteed : $@convention(thin) (@in_guaranteed ClassProtocol) -> () { bb0(%0 : $*ClassProtocol): - // CHECK-NEXT: %5 = load %3 : $*ClassProtocol %1 = load %0 : $*ClassProtocol - // CHECK-NEXT: %6 = open_existential_ref %5 %2 = open_existential_ref %1 : $ClassProtocol to $@opened("ABCDEF01-ABCD-ABCD-ABCD-ABCDEFABCDEF") ClassProtocol - // CHECK-NEXT: %7 = witness_method $C, #ClassProtocol.method %f = witness_method $@opened("ABCDEF01-ABCD-ABCD-ABCD-ABCDEFABCDEF") ClassProtocol, #ClassProtocol.method!1 : (Self) -> () -> (), %2 : $@opened("ABCDEF01-ABCD-ABCD-ABCD-ABCDEFABCDEF") ClassProtocol : $@convention(witness_method : ClassProtocol) (@guaranteed Self) -> () - // CHECK-NEXT: %8 = unchecked_ref_cast %6 - // CHECK-NEXT: %9 = apply %7(%8) apply %f<@opened("ABCDEF01-ABCD-ABCD-ABCD-ABCDEFABCDEF") ClassProtocol>(%2) : $@convention(witness_method : ClassProtocol) (@guaranteed Self) -> () - // CHECK-NEXT: dealloc_stack %3 - // CHECK-NEXT: return undef return undef : $() } -// CHECK-LABEL: sil hidden @invoke_indirect_class_protocol -sil hidden @invoke_indirect_class_protocol : $@convention(thin) (@guaranteed C) -> () { +// Check that a specialization of test_indirect_class_protocol is created. +// CHECK-LABEL: sil shared [signature_optimized_thunk] [always_inline] @$s28test_indirect_class_protocolTf4e_n4main1CC_Tg5 : $@convention(thin) (@owned C) -> () +// CHECK-NEXT: // +// CHECK-NEXT: bb0(%0 : $C): +// CHECK-NEXT: strong_release %0 +// CHECK-NEXT: return undef +// CHECK-LABEL: end sil function '$s28test_indirect_class_protocolTf4e_n4main1CC_Tg5' + +// Check the generated specialization of test_indirect_class_protocol +// CHECK-LABEL: sil shared @$s28test_indirect_class_protocolTf4e_n4main1CC_Tg5Tf4d_n : $@convention(thin) () -> () +// Make sure *everything* was inlined / optimized away +// CHECK-NEXT: bb0: +// CHECK-NEXT: return undef + +sil @invoke_indirect_class_protocol : $@convention(thin) (@guaranteed C) -> () { bb0(%0 : $C): %1 = init_existential_ref %0 : $C : $C, $ClassProtocol - // CHECK: [[INPUT:%.*]] = alloc_stack $ClassProtocol %z = alloc_stack $ClassProtocol retain_value %1 : $ClassProtocol store %1 to %z : $*ClassProtocol - // CHECK: [[SPECIALIZATION:%.*]] = function_ref @$s39test_indirect_class_protocol_guaranteedTf4e_n %f = function_ref @test_indirect_class_protocol_guaranteed : $@convention(thin) (@in_guaranteed ClassProtocol) -> () - // CHECK-NEXT: [[INPUT_LOAD:%.*]] = load [[INPUT]] - // CHECK-NEXT: [[INPUT_OPEN:%.*]] = open_existential_ref [[INPUT_LOAD]] : $ClassProtocol to $[[OPENED_TYPE:@opened(.*) ClassProtocol]] - // CHECK-NEXT: [[INPUT_OPEN_BUF:%.*]] = alloc_stack $[[OPENED_TYPE]] - // CHECK-NEXT: store [[INPUT_OPEN]] to [[INPUT_OPEN_BUF]] - // CHECK-NEXT: apply [[SPECIALIZATION]]<[[OPENED_TYPE]]>([[INPUT_OPEN_BUF]]) apply %f(%z) : $@convention(thin) (@in_guaranteed ClassProtocol) -> () - // CHECK-NEXT: dealloc_stack [[INPUT_OPEN_BUF]] - // CHECK: [[SPECIALIZATION:%.*]] = function_ref @$s28test_indirect_class_protocolTf4e_n %g = function_ref @test_indirect_class_protocol : $@convention(thin) (@in ClassProtocol) -> () - // CHECK-NEXT: [[INPUT_LOAD:%.*]] = load [[INPUT]] - // CHECK-NEXT: [[INPUT_OPEN:%.*]] = open_existential_ref [[INPUT_LOAD]] : $ClassProtocol to $[[OPENED_TYPE:@opened(.*) ClassProtocol]] - // CHECK-NEXT: [[INPUT_OPEN_BUF:%.*]] = alloc_stack $[[OPENED_TYPE]] - // CHECK-NEXT: store [[INPUT_OPEN]] to [[INPUT_OPEN_BUF]] - // CHECK-NEXT: apply [[SPECIALIZATION]]<[[OPENED_TYPE]]>([[INPUT_OPEN_BUF]]) apply %g(%z) : $@convention(thin) (@in ClassProtocol) -> () - // CHECK-NEXT: dealloc_stack [[INPUT_OPEN_BUF]] dealloc_stack %z : $*ClassProtocol return undef : $()