Skip to content

Commit

Permalink
SILGen: Correctly emit vtables when an override is more visible than …
Browse files Browse the repository at this point in the history
…the base

If an override B.f() is more visible than a base method A.f(), it is
possible that an override C.f() of B.f() cannot see the original method
A.f().

In this case, we would encounter linker errors if we referenced the
method descriptor or method dispatch thunk for A.f().

Make this work by treating B.f() as the least derived method in this
case, and ensuring that the vtable thunk for B.f() dispatches through
the vtable again.

Fixes <rdar://problem/48330571>, <https://bugs.swift.org/browse/SR-10648>.
  • Loading branch information
slavapestov committed Jun 1, 2019
1 parent f196227 commit 0c2b62f
Show file tree
Hide file tree
Showing 12 changed files with 495 additions and 16 deletions.
5 changes: 5 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5728,6 +5728,11 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
return Bits.AbstractFunctionDecl.NeedsNewVTableEntry;
}

bool isEffectiveLinkageMoreVisibleThan(ValueDecl *other) const {
return (std::min(getEffectiveAccess(), AccessLevel::Public) >
std::min(other->getEffectiveAccess(), AccessLevel::Public));
}

bool isSynthesized() const {
return Bits.AbstractFunctionDecl.Synthesized;
}
Expand Down
30 changes: 24 additions & 6 deletions include/swift/SIL/SILVTableVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ template <class T> class SILVTableVisitor {
void maybeAddMethod(FuncDecl *fd) {
assert(!fd->hasClangNode());

SILDeclRef constant(fd, SILDeclRef::Kind::Func);
maybeAddEntry(constant, constant.requiresNewVTableEntry());
maybeAddEntry(SILDeclRef(fd, SILDeclRef::Kind::Func));
}

void maybeAddConstructor(ConstructorDecl *cd) {
Expand All @@ -97,19 +96,38 @@ template <class T> class SILVTableVisitor {
// The initializing entry point for designated initializers is only
// necessary for super.init chaining, which is sufficiently constrained
// to never need dynamic dispatch.
SILDeclRef constant(cd, SILDeclRef::Kind::Allocator);
maybeAddEntry(constant, constant.requiresNewVTableEntry());
maybeAddEntry(SILDeclRef(cd, SILDeclRef::Kind::Allocator));
}

void maybeAddEntry(SILDeclRef declRef, bool needsNewEntry) {
void maybeAddEntry(SILDeclRef declRef) {
// Introduce a new entry if required.
if (needsNewEntry)
if (declRef.requiresNewVTableEntry())
asDerived().addMethod(declRef);

// Update any existing entries that it overrides.
auto nextRef = declRef;
while ((nextRef = nextRef.getNextOverriddenVTableEntry())) {
auto baseRef = nextRef.getOverriddenVTableEntry();

// If A.f() is overridden by B.f() which is overridden by
// C.f(), it's possible that C.f() is not visible from C.
// In this case, we pretend that B.f() is the least derived
// method with a vtable entry in the override chain.
//
// This works because we detect the possibility of this
// happening when we emit B.f() and do two things:
// - B.f() always gets a new vtable entry, even if it is
// ABI compatible with A.f()
// - The vtable thunk for the override of A.f() in B does a
// vtable dispatch to the implementation of B.f() for the
// concrete subclass, so a subclass of B only needs to
// replace the vtable entry for B.f(); a call to A.f()
// will correctly dispatch to the implementation of B.f()
// in the subclass.
if (!baseRef.getDecl()->isAccessibleFrom(
declRef.getDecl()->getDeclContext()))
break;

asDerived().addMethodOverride(baseRef, declRef);
nextRef = baseRef;
}
Expand Down
6 changes: 6 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6184,6 +6184,12 @@ static bool requiresNewVTableEntry(const AbstractFunctionDecl *decl) {
}
}

// If the base is less visible than the override, we might need a vtable
// entry since callers of the override might not be able to see the base
// at all.
if (decl->isEffectiveLinkageMoreVisibleThan(base))
return true;

// If the method overrides something, we only need a new entry if the
// override has a more general AST type. However an abstraction
// change is OK; we don't want to add a whole new vtable entry just
Expand Down
2 changes: 2 additions & 0 deletions lib/SIL/SILDeclRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,8 @@ SILDeclRef SILDeclRef::getNextOverriddenVTableEntry() const {
if (overridden.kind == SILDeclRef::Kind::Initializer) {
return SILDeclRef();
}

// Overrides of @objc dynamic declarations are not in the vtable.
if (overridden.getDecl()->isObjCDynamic()) {
return SILDeclRef();
}
Expand Down
7 changes: 6 additions & 1 deletion lib/SILGen/SILGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -682,12 +682,17 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
/// \param inputOrigType Abstraction pattern of base class method
/// \param inputSubstType Formal AST type of base class method
/// \param outputSubstType Formal AST type of derived class method
/// \param baseLessVisibleThanDerived If true, the thunk does a
/// double dispatch to the derived method's vtable entry, so that if
/// the derived method has an override that cannot access the base,
/// calls to the base dispatch to the correct method.
void emitVTableThunk(SILDeclRef base,
SILDeclRef derived,
SILFunction *implFn,
AbstractionPattern inputOrigType,
CanAnyFunctionType inputSubstType,
CanAnyFunctionType outputSubstType);
CanAnyFunctionType outputSubstType,
bool baseLessVisibleThanDerived);

//===--------------------------------------------------------------------===//
// Control flow
Expand Down
20 changes: 17 additions & 3 deletions lib/SILGen/SILGenPoly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3546,7 +3546,8 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
SILFunction *implFn,
AbstractionPattern inputOrigType,
CanAnyFunctionType inputSubstType,
CanAnyFunctionType outputSubstType) {
CanAnyFunctionType outputSubstType,
bool baseLessVisibleThanDerived) {
auto fd = cast<AbstractFunctionDecl>(derived.getDecl());

SILLocation loc(fd);
Expand All @@ -3558,7 +3559,12 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
SmallVector<ManagedValue, 8> thunkArgs;
collectThunkParams(loc, thunkArgs);

auto derivedFTy = SGM.Types.getConstantInfo(derived).SILFnType;
CanSILFunctionType derivedFTy;
if (baseLessVisibleThanDerived) {
derivedFTy = SGM.Types.getConstantOverrideType(derived);
} else {
derivedFTy = SGM.Types.getConstantInfo(derived).SILFnType;
}

SubstitutionMap subs;
if (auto *genericEnv = fd->getGenericEnvironment()) {
Expand Down Expand Up @@ -3610,7 +3616,15 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
forwardFunctionArguments(*this, loc, derivedFTy, substArgs, args);

// Create the call.
auto derivedRef = B.createFunctionRefFor(loc, implFn);
SILValue derivedRef;
if (baseLessVisibleThanDerived) {
// See the comment in SILVTableVisitor.h under maybeAddMethod().
auto selfValue = thunkArgs.back().getValue();
auto derivedTy = SGM.Types.getConstantOverrideType(derived);
derivedRef = emitClassMethodRef(loc, selfValue, derived, derivedTy);
} else {
derivedRef = B.createFunctionRefFor(loc, implFn);
}

SILValue result;

Expand Down
23 changes: 17 additions & 6 deletions lib/SILGen/SILGenType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
SILDeclRef derived, SILDeclRef base) {
assert(base.kind == derived.kind);

auto *baseDecl = base.getDecl();
auto *derivedDecl = derived.getDecl();
auto *baseDecl = cast<AbstractFunctionDecl>(base.getDecl());
auto *derivedDecl = cast<AbstractFunctionDecl>(derived.getDecl());

// Note: We intentionally don't support extension members here.
//
Expand Down Expand Up @@ -80,8 +80,11 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
// If the member is dynamic, reference its dynamic dispatch thunk so that
// it will be redispatched, funneling the method call through the runtime
// hook point.
if (derivedDecl->isObjCDynamic()
&& derived.kind != SILDeclRef::Kind::Allocator) {
bool usesObjCDynamicDispatch =
(derivedDecl->isObjCDynamic() &&
derived.kind != SILDeclRef::Kind::Allocator);

if (usesObjCDynamicDispatch) {
implFn = getDynamicThunk(derived, Types.getConstantInfo(derived).SILFnType);
implLinkage = SILLinkage::Public;
} else {
Expand All @@ -93,6 +96,12 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
if (derived == base)
return SILVTable::Entry(base, implFn, implKind, implLinkage);

// If the base method is less visible than the derived method, we need
// a thunk.
bool baseLessVisibleThanDerived =
(derivedDecl->isEffectiveLinkageMoreVisibleThan(baseDecl) &&
!usesObjCDynamicDispatch);

// Determine the derived thunk type by lowering the derived type against the
// abstraction pattern of the base.
auto baseInfo = Types.getConstantInfo(base);
Expand All @@ -104,7 +113,8 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
// The override member type is semantically a subtype of the base
// member type. If the override is ABI compatible, we do not need
// a thunk.
if (M.Types.checkFunctionForABIDifferences(derivedInfo.SILFnType,
if (!baseLessVisibleThanDerived &&
M.Types.checkFunctionForABIDifferences(derivedInfo.SILFnType,
overrideInfo.SILFnType)
== TypeConverter::ABIDifference::Trivial)
return SILVTable::Entry(base, implFn, implKind, implLinkage);
Expand Down Expand Up @@ -143,7 +153,8 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
SILGenFunction(*this, *thunk, theClass)
.emitVTableThunk(base, derived, implFn, basePattern,
overrideInfo.LoweredType,
derivedInfo.LoweredType);
derivedInfo.LoweredType,
baseLessVisibleThanDerived);

return SILVTable::Entry(base, thunk, implKind, implLinkage);
}
Expand Down
22 changes: 22 additions & 0 deletions test/Interpreter/Inputs/vtables_multifile_2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

open class Base {
public init() {}

fileprivate func privateMethod() -> Int {
return 1
}
}

open class Derived : Base {
open override func privateMethod() -> Int {
return super.privateMethod() + 1
}
}

public func callBaseMethod(_ b: Base) -> Int {
return b.privateMethod()
}

public func callDerivedMethod(_ d: Derived) -> Int {
return d.privateMethod()
}
38 changes: 38 additions & 0 deletions test/Interpreter/vtables_multifile.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// RUN: %empty-directory(%t)

// RUN: %target-build-swift-dylib(%t/%target-library-name(vtables_multifile_2)) -enable-library-evolution %S/Inputs/vtables_multifile_2.swift -emit-module -emit-module-path %t/vtables_multifile_2.swiftmodule
// RUN: %target-codesign %t/%target-library-name(vtables_multifile_2)

// RUN: %target-build-swift %s -L %t -I %t -lvtables_multifile_2 -o %t/main %target-rpath(%t)
// RUN: %target-codesign %t/main

// RUN: %target-run %t/main %t/%target-library-name(vtables_multifile_2)

// REQUIRES: executable_test

import StdlibUnittest
import vtables_multifile_2

var VTableTestSuite = TestSuite("VTable")

open class OtherDerived : Derived {
open override func privateMethod() -> Int {
return super.privateMethod() + 1
}
}

VTableTestSuite.test("Base") {
expectEqual(1, callBaseMethod(Base()))
}

VTableTestSuite.test("Derived") {
expectEqual(2, callBaseMethod(Derived()))
expectEqual(2, callDerivedMethod(Derived()))
}

VTableTestSuite.test("OtherDerived") {
expectEqual(3, callBaseMethod(OtherDerived()))
expectEqual(3, callDerivedMethod(OtherDerived()))
}

runAllTests()
117 changes: 117 additions & 0 deletions test/SILGen/Inputs/vtables_multifile_2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
open class OtherDerived : Derived {
internal override func privateMethod1() {
super.privateMethod1()
}
internal override func privateMethod2(_ arg: AnyObject?) {
super.privateMethod2(arg)
}
internal override func privateMethod3(_ arg: Int?) {
super.privateMethod3(arg)
}
internal override func privateMethod4(_ arg: Int) {
super.privateMethod4(arg)
}
}

// --
// Super method calls directly reference the superclass method
// --

// CHECK-LABEL: sil hidden [ossa] @$s17vtables_multifile12OtherDerivedC14privateMethod1yyF : $@convention(method) (@guaranteed OtherDerived) -> () {
// CHECK: bb0(%0 : @guaranteed $OtherDerived):
// CHECK: [[SELF:%.*]] = copy_value %0 : $OtherDerived
// CHECK-NEXT: [[SUPER:%.*]] = upcast [[SELF]] : $OtherDerived to $Derived
// CHECK: [[METHOD:%.*]] = function_ref @$s17vtables_multifile7DerivedC14privateMethod1yyF : $@convention(method) (@guaranteed Derived) -> () // user: %5
// CHECK-NEXT: apply [[METHOD:%.*]]([[SUPER]]) : $@convention(method) (@guaranteed Derived) -> ()
// CHECK-NEXT: destroy_value [[SUPER]] : $Derived
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]] : $()
// CHECK-NEXT:}

// CHECK-LABEL: sil hidden_external @$s17vtables_multifile7DerivedC14privateMethod1yyF : $@convention(method) (@guaranteed Derived) -> ()

// CHECK-LABEL: sil hidden [ossa] @$s17vtables_multifile12OtherDerivedC14privateMethod2yyyXlSgF : $@convention(method) (@guaranteed Optional<AnyObject>, @guaranteed OtherDerived) -> () {
// CHECK: bb0(%0 : @guaranteed $Optional<AnyObject>, %1 : @guaranteed $OtherDerived):
// CHECK: [[SELF:%.*]] = copy_value %1 : $OtherDerived
// CHECK-NEXT: [[SUPER:%.*]] = upcast [[SELF]] : $OtherDerived to $Derived
// CHECK: [[METHOD:%.*]] = function_ref @$s17vtables_multifile7DerivedC14privateMethod2yyyXlSgF : $@convention(method) (@guaranteed Optional<AnyObject>, @guaranteed Derived) -> ()
// CHECK-NEXT: apply [[METHOD]](%0, [[SUPER]]) : $@convention(method) (@guaranteed Optional<AnyObject>, @guaranteed Derived) -> ()
// CHECK-NEXT: destroy_value [[SUPER]] : $Derived
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]] : $()
// CHECK-NEXT: }

// CHECK-LABEL: sil hidden_external @$s17vtables_multifile7DerivedC14privateMethod2yyyXlSgF : $@convention(method) (@guaranteed Optional<AnyObject>, @guaranteed Derived) -> ()
// CHECK: bb0(%0 : $Optional<Int>, %1 : @guaranteed $OtherDerived):
// CHECK: [[SELF:%.*]] = copy_value %1 : $OtherDerived
// CHECK-NEXT: [[SUPER:%.*]] = upcast [[SELF]] : $OtherDerived to $Derived
// CHECK: [[METHOD:%.*]] = function_ref @$s17vtables_multifile7DerivedC14privateMethod3yySiSgF : $@convention(method) (Optional<Int>, @guaranteed Derived) -> ()
// CHECK-NEXT: apply [[METHOD]](%0, [[SUPER]]) : $@convention(method) (Optional<Int>, @guaranteed Derived) -> ()
// CHECK-NEXT: destroy_value [[SUPER]] : $Derived
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]] : $()
// CHECK-NEXT: }

// CHECK-LABEL: sil hidden_external @$s17vtables_multifile7DerivedC14privateMethod3yySiSgF : $@convention(method) (Optional<Int>, @guaranteed Derived) -> ()
// CHECK: bb0(%0 : $Int, %1 : @guaranteed $OtherDerived):
// CHECK: [[SELF:%.*]] = copy_value %1 : $OtherDerived
// CHECK-NEXT: [[SUPER:%.*]] = upcast [[SELF]] : $OtherDerived to $Derived
// CHECK: [[METHOD:%.*]] = function_ref @$s17vtables_multifile7DerivedC14privateMethod4yySiF : $@convention(method) (Int, @guaranteed Derived) -> ()
// CHECK-NEXT: apply [[METHOD]](%0, [[SUPER]]) : $@convention(method) (Int, @guaranteed Derived) -> ()
// CHECK-NEXT: destroy_value [[SUPER]] : $Derived
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]] : $()
// CHECK-NEXT: }

// CHECK-LABEL: sil hidden_external @$s17vtables_multifile7DerivedC14privateMethod4yySiF : $@convention(method) (Int, @guaranteed Derived) -> ()

// --
// VTable thunks for methods of Base redispatch to methods of Derived
// --

// CHECK-LABEL: sil private [ossa] @$s17vtables_multifile7DerivedC14privateMethod1yyFAA4BaseCAD33_63E5F2521A3C787F5F9EFD57FB9237EALLyyFTV : $@convention(method) (@guaranteed Derived) -> () {
// CHECK: bb0(%0 : @guaranteed $Derived):
// CHECK-NEXT: [[METHOD:%.*]] = class_method %0 : $Derived, #Derived.privateMethod1!1 : (Derived) -> () -> (), $@convention(method) (@guaranteed Derived) -> ()
// CHECK-NEXT: apply [[METHOD]](%0) : $@convention(method) (@guaranteed Derived) -> ()
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]] : $()
// CHECK-NEXT: }

// CHECK-LABEL: sil private [ossa] @$s17vtables_multifile7DerivedC14privateMethod2yyyXlSgFAA4BaseCAD33_63E5F2521A3C787F5F9EFD57FB9237EALLyyyXlFTV : $@convention(method) (@guaranteed Optional<AnyObject>, @guaranteed Derived) -> () {
// CHECK: bb0(%0 : @guaranteed $Optional<AnyObject>, %1 : @guaranteed $Derived):
// CHECK-NEXT: [[METHOD:%.*]] = class_method %1 : $Derived, #Derived.privateMethod2!1 : (Derived) -> (AnyObject?) -> (), $@convention(method) (@guaranteed Optional<AnyObject>, @guaranteed Derived) -> ()
// CHECK-NEXT: apply [[METHOD]](%0, %1) : $@convention(method) (@guaranteed Optional<AnyObject>, @guaranteed Derived) -> ()
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]] : $()
// CHECK-NEXT: }

// CHECK-LABEL: sil private [ossa] @$s17vtables_multifile7DerivedC14privateMethod3yySiSgFAA4BaseCAD33_63E5F2521A3C787F5F9EFD57FB9237EALLyySiFTV : $@convention(method) (Int, @guaranteed Derived) -> () {
// CHECK: bb0(%0 : $Int, %1 : @guaranteed $Derived):
// CHECK-NEXT: [[ARG:%.*]] = enum $Optional<Int>, #Optional.some!enumelt.1, %0 : $Int
// CHECK-NEXT: [[METHOD:%.*]] = class_method %1 : $Derived, #Derived.privateMethod3!1 : (Derived) -> (Int?) -> (), $@convention(method) (Optional<Int>, @guaranteed Derived) -> ()
// CHECK-NEXT: apply %3(%2, %1) : $@convention(method) (Optional<Int>, @guaranteed Derived) -> ()
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]] : $()
// CHECK-NEXT: }

// CHECK-LABEL: sil private [ossa] @$s17vtables_multifile7DerivedC14privateMethod4yySiFAA4BaseCAD33_63E5F2521A3C787F5F9EFD57FB9237EALLyyxFTV : $@convention(method) (@in_guaranteed Int, @guaranteed Derived) -> () {
// CHECK: bb0(%0 : $*Int, %1 : @guaranteed $Derived):
// CHECK-NEXT: [[ARG:%.*]] = load [trivial] %0 : $*Int
// CHECK-NEXT: [[METHOD:%.*]] = class_method %1 : $Derived, #Derived.privateMethod4!1 : (Derived) -> (Int) -> (), $@convention(method) (Int, @guaranteed Derived) -> ()
// CHECK-NEXT: apply %3(%2, %1) : $@convention(method) (Int, @guaranteed Derived) -> ()
// CHECK-NEXT: [[RESULT:%.*]] = tuple ()
// CHECK-NEXT: return [[RESULT]] : $()
// CHECK-NEXT: }

// CHECK-LABEL: sil_vtable [serialized] OtherDerived {
// CHECK-NEXT: #Base.privateMethod1!1: <T> (Base<T>) -> () -> () : hidden @$s17vtables_multifile7DerivedC14privateMethod1yyFAA4BaseCAD33_63E5F2521A3C787F5F9EFD57FB9237EALLyyFTV [inherited] // vtable thunk for Base.privateMethod1() dispatching to Derived.privateMethod1()
// CHECK-NEXT: #Base.privateMethod2!1: <T> (Base<T>) -> (AnyObject) -> () : hidden @$s17vtables_multifile7DerivedC14privateMethod2yyyXlSgFAA4BaseCAD33_63E5F2521A3C787F5F9EFD57FB9237EALLyyyXlFTV [inherited] // vtable thunk for Base.privateMethod2(_:) dispatching to Derived.privateMethod2(_:)
// CHECK-NEXT: #Base.privateMethod3!1: <T> (Base<T>) -> (Int) -> () : hidden @$s17vtables_multifile7DerivedC14privateMethod3yySiSgFAA4BaseCAD33_63E5F2521A3C787F5F9EFD57FB9237EALLyySiFTV [inherited] // vtable thunk for Base.privateMethod3(_:) dispatching to Derived.privateMethod3(_:)
// CHECK-NEXT: #Base.privateMethod4!1: <T> (Base<T>) -> (T) -> () : hidden @$s17vtables_multifile7DerivedC14privateMethod4yySiFAA4BaseCAD33_63E5F2521A3C787F5F9EFD57FB9237EALLyyxFTV [inherited] // vtable thunk for Base.privateMethod4(_:) dispatching to Derived.privateMethod4(_:)
// CHECK-NEXT: #Base.init!allocator.1: <T> (Base<T>.Type) -> () -> Base<T> : @$s17vtables_multifile12OtherDerivedCACycfC [override] // OtherDerived.__allocating_init()
// CHECK-NEXT: #Derived.privateMethod1!1: (Derived) -> () -> () : @$s17vtables_multifile12OtherDerivedC14privateMethod1yyF [override] // OtherDerived.privateMethod1()
// CHECK-NEXT: #Derived.privateMethod2!1: (Derived) -> (AnyObject?) -> () : @$s17vtables_multifile12OtherDerivedC14privateMethod2yyyXlSgF [override] // OtherDerived.privateMethod2(_:)
// CHECK-NEXT: #Derived.privateMethod3!1: (Derived) -> (Int?) -> () : @$s17vtables_multifile12OtherDerivedC14privateMethod3yySiSgF [override] // OtherDerived.privateMethod3(_:)
// CHECK-NEXT: #Derived.privateMethod4!1: (Derived) -> (Int) -> () : @$s17vtables_multifile12OtherDerivedC14privateMethod4yySiF [override] // OtherDerived.privateMethod4(_:)
// CHECK-NEXT: #OtherDerived.deinit!deallocator.1: @$s17vtables_multifile12OtherDerivedCfD // OtherDerived.__deallocating_deinit
// CHECK-NEXT:}
Loading

0 comments on commit 0c2b62f

Please sign in to comment.