Skip to content

Commit 540202f

Browse files
committed
[interop][SwiftToCxx] dispatch virtual Swift methods using a relative offset into the vtable for classes deriving from resilient classes
1 parent 327b989 commit 540202f

7 files changed

+251
-10
lines changed

include/swift/IRGen/IRABIDetailsProvider.h

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,9 @@ class IRABIDetailsProvider {
255255
Direct,
256256
/// An indirect call that can be made via a static offset in a vtable.
257257
IndirectVTableStaticOffset,
258+
/// An indirect call that should be made via an offset relative to
259+
/// external base value in a vtable.
260+
IndirectVTableRelativeOffset,
258261
/// The call should be made via the provided thunk function.
259262
Thunk
260263
};
@@ -273,39 +276,68 @@ class IRABIDetailsProvider {
273276
discriminator);
274277
}
275278

279+
static MethodDispatchInfo indirectVTableRelativeOffset(
280+
size_t bitOffset, std::string symbolName,
281+
Optional<PointerAuthDiscriminator> discriminator) {
282+
return MethodDispatchInfo(Kind::IndirectVTableRelativeOffset, bitOffset,
283+
symbolName, discriminator);
284+
}
285+
276286
static MethodDispatchInfo thunk(std::string thunkName) {
277287
return MethodDispatchInfo(Kind::Thunk, 0, thunkName);
278288
}
279289

280290
Kind getKind() const { return kind; }
291+
292+
/// Return the bit offset into the vtable from which the method pointer
293+
/// should be loaded.
281294
size_t getStaticBitOffset() const {
282295
assert(kind == Kind::IndirectVTableStaticOffset);
283296
return bitOffset;
284297
}
285298
Optional<PointerAuthDiscriminator> getPointerAuthDiscriminator() const {
286-
assert(kind == Kind::IndirectVTableStaticOffset);
299+
assert(kind == Kind::IndirectVTableStaticOffset ||
300+
kind == Kind::IndirectVTableRelativeOffset);
287301
return discriminator;
288302
}
289303
StringRef getThunkSymbolName() const {
290304
assert(kind == Kind::Thunk);
291-
return thunkName;
305+
return symbolName;
306+
}
307+
308+
/// Return the bit offset relative to base offset value into the vtable from
309+
/// which the method pointer should be loaded.
310+
size_t getRelativeBitOffset() const {
311+
assert(kind == Kind::IndirectVTableRelativeOffset);
312+
return bitOffset;
313+
}
314+
315+
/// Return the external symbol from which the relative base offset should be
316+
/// loaded.
317+
StringRef getBaseOffsetSymbolName() const {
318+
assert(kind == Kind::IndirectVTableRelativeOffset);
319+
return symbolName;
292320
}
293321

294322
private:
295-
MethodDispatchInfo(Kind kind, size_t bitOffset, std::string thunkName = "",
323+
MethodDispatchInfo(Kind kind, size_t bitOffset, std::string symbolName = "",
296324
Optional<PointerAuthDiscriminator> discriminator = None)
297-
: kind(kind), bitOffset(bitOffset), thunkName(thunkName),
325+
: kind(kind), bitOffset(bitOffset), symbolName(symbolName),
298326
discriminator(discriminator) {}
299327

300328
Kind kind;
301329
size_t bitOffset;
302-
std::string thunkName;
330+
std::string symbolName;
303331
Optional<PointerAuthDiscriminator> discriminator;
304332
};
305333

306334
Optional<MethodDispatchInfo>
307335
getMethodDispatchInfo(const AbstractFunctionDecl *funcDecl);
308336

337+
/// Returns the type of the base offset value located at the specific class
338+
/// base offset symbol.
339+
Type getClassBaseOffsetSymbolType() const;
340+
309341
private:
310342
std::unique_ptr<IRABIDetailsProviderImpl> impl;
311343
};

lib/IRGen/IRABIDetailsProvider.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,11 +256,20 @@ class IRABIDetailsProviderImpl {
256256
/*bitOffset=*/mi->TheOffset.getStaticOffset().getValue(),
257257
getMethodPointerAuthInfo(funcDecl, silDecl));
258258
}
259-
return {};
259+
assert(mi->TheOffset.isDynamic());
260+
return MethodDispatchInfo::indirectVTableRelativeOffset(
261+
mi->TheOffset.getRelativeOffset().getValue(),
262+
LinkEntity::forClassMetadataBaseOffset(parentClass).mangleAsString(),
263+
getMethodPointerAuthInfo(funcDecl, silDecl));
260264
}
261265
llvm_unreachable("invalid kind");
262266
}
263267

268+
Type getClassBaseOffsetSymbolType() const {
269+
return *getPrimitiveTypeFromLLVMType(
270+
silMod->getASTContext(), IGM.ClassMetadataBaseOffsetTy->elements()[0]);
271+
}
272+
264273
Lowering::TypeConverter typeConverter;
265274
// Default silOptions are sufficient, as we don't need to generated SIL.
266275
SILOptions silOpts;
@@ -470,3 +479,7 @@ IRABIDetailsProvider::getMethodDispatchInfo(
470479
const AbstractFunctionDecl *funcDecl) {
471480
return impl->getMethodDispatchInfo(funcDecl);
472481
}
482+
483+
Type IRABIDetailsProvider::getClassBaseOffsetSymbolType() const {
484+
return impl->getClassBaseOffsetSymbolType();
485+
}

lib/PrintAsClang/DeclAndTypePrinter.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,21 @@ class DeclAndTypePrinter::Implementation
14431443
FD, funcABI, resultTy, funcTy, dispatchInfo->getThunkSymbolName(),
14441444
"dispatch thunk for");
14451445
assert(!thunkRepresentation.isUnsupported());
1446+
} else if (dispatchInfo->getKind() ==
1447+
IRABIDetailsProvider::MethodDispatchInfo::Kind::
1448+
IndirectVTableRelativeOffset) {
1449+
// Emit the C signature for the class metadata base offset.
1450+
owningPrinter.interopContext.runIfStubForDeclNotEmitted(
1451+
dispatchInfo->getBaseOffsetSymbolName(), [&] {
1452+
auto baseClassOffsetType =
1453+
owningPrinter.interopContext.getIrABIDetails()
1454+
.getClassBaseOffsetSymbolType();
1455+
os << "SWIFT_EXTERN ";
1456+
ClangSyntaxPrinter(os).printKnownCType(
1457+
baseClassOffsetType, owningPrinter.typeMapping);
1458+
os << ' ' << dispatchInfo->getBaseOffsetSymbolName()
1459+
<< "; // class metadata base offset\n";
1460+
});
14461461
}
14471462
}
14481463
}

lib/PrintAsClang/PrintClangClassType.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,6 @@ void ClangClassTypePrinter::printClassTypeDecl(
5050
ClangSyntaxPrinter(baseQualNameOS)
5151
.printModuleNamespaceQualifiersIfNeeded(parentClass->getModuleContext(),
5252
typeDecl->getModuleContext());
53-
if (!baseQualNameOS.str().empty())
54-
baseQualNameOS << "::";
5553
baseQualNameOS << baseNameOS.str();
5654
} else {
5755
baseClassName = "RefCountedClass";

lib/PrintAsClang/PrintClangFunction.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,7 @@ void DeclAndTypeClangFunctionPrinter::printCxxThunkBody(
11051105
case DispatchKindTy::Direct:
11061106
break;
11071107
case DispatchKindTy::IndirectVTableStaticOffset:
1108+
case DispatchKindTy::IndirectVTableRelativeOffset:
11081109
os << "void ***selfPtr_ = reinterpret_cast<void ***>( "
11091110
"::swift::_impl::_impl_RefCountedClass::getOpaquePointer(*this));"
11101111
"\n";
@@ -1126,8 +1127,15 @@ void DeclAndTypeClangFunctionPrinter::printCxxThunkBody(
11261127
os << " func;\n";
11271128
os << "};\n";
11281129
os << "FTypeAddress *fptrptr_ = reinterpret_cast<FTypeAddress *>(vtable_ "
1129-
"+ "
1130-
<< (dispatchInfo->getStaticBitOffset() / 8) << ");\n";
1130+
"+ ";
1131+
if (dispatchInfo->getKind() == DispatchKindTy::IndirectVTableStaticOffset)
1132+
os << (dispatchInfo->getStaticBitOffset() / 8);
1133+
else
1134+
os << '(' << cxx_synthesis::getCxxImplNamespaceName()
1135+
<< "::" << dispatchInfo->getBaseOffsetSymbolName()
1136+
<< " / sizeof(void *)) + "
1137+
<< (dispatchInfo->getRelativeBitOffset() / 8);
1138+
os << ");\n";
11311139
indirectFunctionVar = StringRef("fptrptr_->func");
11321140
break;
11331141
case DispatchKindTy::Thunk:
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-swift-frontend %S/swift-subclass-of-resilient-class-virtual-method-dispatch.swift -D RESILIENT_MODULE -module-name Class -emit-module -emit-module-path %t/Class.swiftmodule -enable-library-evolution -clang-header-expose-decls=all-public -emit-clang-header-path %t/class.h
4+
5+
// RUN: %target-swift-frontend %S/swift-subclass-of-resilient-class-virtual-method-dispatch.swift -I %t -typecheck -module-name UseClass -clang-header-expose-decls=all-public -emit-clang-header-path %t/useclass.h
6+
7+
// RUN: %target-interop-build-clangxx -c %s -I %t -o %t/swift-class-execution.o
8+
9+
// RUN: %target-interop-build-swift -c %S/swift-subclass-of-resilient-class-virtual-method-dispatch.swift -D RESILIENT_MODULE -o %t/class.o -module-name Class -enable-library-evolution -Xfrontend -entry-point-function-name -Xfrontend swiftMain2
10+
11+
// RUN: %target-interop-build-swift %S/swift-subclass-of-resilient-class-virtual-method-dispatch.swift -I %t -o %t/swift-class-execution -Xlinker %t/swift-class-execution.o -Xlinker %t/class.o -module-name UseClass -Xfrontend -entry-point-function-name -Xfrontend swiftMain
12+
13+
// RUN: %target-codesign %t/swift-class-execution
14+
// RUN: %target-run %t/swift-class-execution | %FileCheck %s
15+
16+
#include "class.h"
17+
#include "useclass.h"
18+
#include <assert.h>
19+
20+
using namespace UseClass;
21+
22+
int main() {
23+
auto derived = createCrossModuleDerivedClass();
24+
Class::BaseClass derivedAsBase = derived;
25+
auto derivedDerived = createCrossModuleDerivedDerivedClass();
26+
CrossModuleDerivedClass derivedDerivedAsDerived = derivedDerived;
27+
28+
{
29+
derived.virtualMethod();
30+
assert(derived.getComputedProp() == -56);
31+
// CHECK: CrossModuleDerivedClass.virtualMethod
32+
}
33+
34+
{
35+
derived.virtualMethodInDerived();
36+
// CHECK-NEXT: CrossModuleDerivedClass.virtualMethodInDerived
37+
derivedDerived.virtualMethodInDerived();
38+
// CHECK-NEXT: CrossModuleDerivedDerivedClass.virtualMethodInDerived
39+
derivedDerivedAsDerived.virtualMethodInDerived();
40+
// CHECK-NEXT: CrossModuleDerivedDerivedClass.virtualMethodInDerived
41+
}
42+
43+
{
44+
derived.virtualMethod2InDerived();
45+
// CHECK-NEXT: CrossModuleDerivedClass.virtualMethod2InDerived
46+
derivedDerived.virtualMethod2InDerived();
47+
// CHECK-NEXT: CrossModuleDerivedDerivedClass.virtualMethod2InDerived
48+
derivedDerivedAsDerived.virtualMethod2InDerived();
49+
// CHECK-NEXT: CrossModuleDerivedDerivedClass.virtualMethod2InDerived
50+
}
51+
52+
{
53+
swift::Int x;
54+
x = derived.getDerivedComputedProp();
55+
assert(x == 23);
56+
x = derivedDerived.getDerivedComputedProp();
57+
assert(x == -95);
58+
x = derivedDerivedAsDerived.getDerivedComputedProp();
59+
assert(x == -95);
60+
}
61+
return 0;
62+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend %s -D RESILIENT_MODULE -module-name Class -emit-module -emit-module-path %t/Class.swiftmodule -enable-library-evolution -clang-header-expose-decls=all-public -emit-clang-header-path %t/class.h
3+
4+
// RUN: %target-swift-frontend %s -I %t -typecheck -module-name UseClass -clang-header-expose-decls=all-public -emit-clang-header-path %t/useclass.h
5+
6+
// RUN: %FileCheck %s < %t/useclass.h
7+
8+
// FIXME: add import automatically?
9+
// RUN: echo '#include "class.h"' > %t/fixed-useclass.h
10+
// RUN: cat %t/useclass.h >> %t/fixed-useclass.h
11+
12+
// RUN: %check-interop-cxx-header-in-clang(%t/fixed-useclass.h)
13+
14+
#if RESILIENT_MODULE
15+
16+
open class BaseClass {
17+
var field: Int64
18+
19+
public init() {
20+
field = 0
21+
}
22+
open func virtualMethod() {
23+
print("BaseClass.virtualMethod")
24+
}
25+
open var computedProp: Int {
26+
return 11
27+
}
28+
}
29+
30+
#else
31+
32+
import Class
33+
34+
public class CrossModuleDerivedClass: BaseClass {
35+
override public init() {}
36+
override public func virtualMethod() {
37+
print("CrossModuleDerivedClass.virtualMethod")
38+
}
39+
override public var computedProp: Int {
40+
return -56
41+
}
42+
43+
public func virtualMethodInDerived() {
44+
print("CrossModuleDerivedClass.virtualMethodInDerived")
45+
}
46+
public var derivedComputedProp: Int {
47+
return 23
48+
}
49+
public func virtualMethod2InDerived() {
50+
print("CrossModuleDerivedClass.virtualMethod2InDerived")
51+
}
52+
}
53+
54+
public class CrossModuleDerivedDerivedClass: CrossModuleDerivedClass {
55+
override public init() {}
56+
override public func virtualMethodInDerived() {
57+
print("CrossModuleDerivedDerivedClass.virtualMethodInDerived")
58+
}
59+
override public var derivedComputedProp: Int {
60+
return -95
61+
}
62+
override public final func virtualMethod2InDerived() {
63+
print("CrossModuleDerivedDerivedClass.virtualMethod2InDerived")
64+
}
65+
}
66+
67+
public func createCrossModuleDerivedClass() -> CrossModuleDerivedClass {
68+
return CrossModuleDerivedClass()
69+
}
70+
71+
public func createCrossModuleDerivedDerivedClass() -> CrossModuleDerivedDerivedClass {
72+
return CrossModuleDerivedDerivedClass()
73+
}
74+
75+
#endif
76+
77+
// CHECK: SWIFT_EXTERN void $s8UseClass018CrossModuleDerivedB0C015virtualMethodInE0yyF(SWIFT_CONTEXT void * _Nonnull _self) SWIFT_NOEXCEPT SWIFT_CALL; // virtualMethodInDerived()
78+
// CHECK-NEXT: SWIFT_EXTERN uint64_t $s8UseClass018CrossModuleDerivedB0CMo; // class metadata base offset
79+
// CHECK-NEXT: SWIFT_EXTERN ptrdiff_t $s8UseClass018CrossModuleDerivedB0C19derivedComputedPropSivg(SWIFT_CONTEXT void * _Nonnull _self) SWIFT_NOEXCEPT SWIFT_CALL;
80+
// CHECK-NEXT: SWIFT_EXTERN void $s8UseClass018CrossModuleDerivedB0C016virtualMethod2InE0yyF(SWIFT_CONTEXT void * _Nonnull _self) SWIFT_NOEXCEPT SWIFT_CALL; // virtualMethod2InDerived()
81+
82+
83+
// CHECK: void CrossModuleDerivedClass::virtualMethod() {
84+
// CHECK-NEXT: return _impl::$s5Class04BaseA0C13virtualMethodyyFTj(::swift::_impl::_impl_RefCountedClass::getOpaquePointer(*this));
85+
86+
// CHECK: void CrossModuleDerivedClass::virtualMethodInDerived() {
87+
// CHECK-NEXT: void ***selfPtr_ = reinterpret_cast<void ***>( ::swift::_impl::_impl_RefCountedClass::getOpaquePointer(*this));
88+
// CHECK-NEXT: #ifdef __arm64e__
89+
// CHECK-NEXT: void **vtable_ = ptrauth_auth_data(*selfPtr_, ptrauth_key_process_independent_data, ptrauth_blend_discriminator(selfPtr_,27361));
90+
// CHECK-NEXT: #else
91+
// CHECK-NEXT: void **vtable_ = *selfPtr_;
92+
// CHECK-NEXT: #endif
93+
// CHECK-NEXT: struct FTypeAddress {
94+
// CHECK-NEXT: decltype(_impl::$s8UseClass018CrossModuleDerivedB0C015virtualMethodInE0yyF) * func;
95+
// CHECK-NEXT: };
96+
// CHECK-NEXT: FTypeAddress *fptrptr_ = reinterpret_cast<FTypeAddress *>(vtable_ + (_impl::$s8UseClass018CrossModuleDerivedB0CMo / sizeof(void *)) + 0);
97+
// CHECK-NEXT: return (* fptrptr_->func)(::swift::_impl::_impl_RefCountedClass::getOpaquePointer(*this));
98+
// CHECK-NEXT: }
99+
100+
// CHECK: swift::Int CrossModuleDerivedClass::getDerivedComputedProp() {
101+
// CHECK: FTypeAddress *fptrptr_ = reinterpret_cast<FTypeAddress *>(vtable_ + (_impl::$s8UseClass018CrossModuleDerivedB0CMo / sizeof(void *)) + 1);
102+
103+
// CHECK: void CrossModuleDerivedClass::virtualMethod2InDerived() {
104+
// CHECK: FTypeAddress *fptrptr_ = reinterpret_cast<FTypeAddress *>(vtable_ + (_impl::$s8UseClass018CrossModuleDerivedB0CMo / sizeof(void *)) + 2);
105+
106+
// CHECK: void CrossModuleDerivedDerivedClass::virtualMethodInDerived() {
107+
// CHECK: FTypeAddress *fptrptr_ = reinterpret_cast<FTypeAddress *>(vtable_ + (_impl::$s8UseClass018CrossModuleDerivedB0CMo / sizeof(void *)) + 0);
108+
109+
// CHECK: swift::Int CrossModuleDerivedDerivedClass::getDerivedComputedProp() {
110+
// CHECK: FTypeAddress *fptrptr_ = reinterpret_cast<FTypeAddress *>(vtable_ + (_impl::$s8UseClass018CrossModuleDerivedB0CMo / sizeof(void *)) + 1);
111+
112+
// CHECK: void CrossModuleDerivedDerivedClass::virtualMethod2InDerived() {
113+
// CHECK-NEXT: return _impl::$s8UseClass018CrossModuleDerivedeB0C016virtualMethod2InE0yyF(::swift::_impl::_impl_RefCountedClass::getOpaquePointer(*this));

0 commit comments

Comments
 (0)