Skip to content

Commit b7007cb

Browse files
committed
[interop][SwiftToCxx] dispatch Swift class methods correctly using the vtable
1 parent b70ba09 commit b7007cb

File tree

8 files changed

+351
-23
lines changed

8 files changed

+351
-23
lines changed

include/swift/IRGen/IRABIDetailsProvider.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,40 @@ class IRABIDetailsProvider {
248248
llvm::MapVector<EnumElementDecl *, EnumElementInfo>
249249
getEnumTagMapping(const EnumDecl *ED);
250250

251+
/// Details how a specific method should be dispatched.
252+
struct MethodDispatchInfo {
253+
enum class Kind {
254+
/// A direct call can be made to the underlying function.
255+
Direct,
256+
/// An indirect call that can be made via a static offset in a vtable.
257+
IndirectVTableStaticOffset
258+
};
259+
260+
static MethodDispatchInfo direct() {
261+
return MethodDispatchInfo(Kind::Direct, 0);
262+
}
263+
264+
static MethodDispatchInfo indirectVTableStaticOffset(size_t bitOffset) {
265+
return MethodDispatchInfo(Kind::IndirectVTableStaticOffset, bitOffset);
266+
}
267+
268+
Kind getKind() const { return kind; }
269+
size_t getStaticBitOffset() const {
270+
assert(kind == Kind::IndirectVTableStaticOffset);
271+
return bitOffset;
272+
}
273+
274+
private:
275+
constexpr MethodDispatchInfo(Kind kind, size_t bitOffset)
276+
: kind(kind), bitOffset(bitOffset) {}
277+
278+
Kind kind;
279+
size_t bitOffset;
280+
};
281+
282+
Optional<MethodDispatchInfo>
283+
getMethodDispatchInfo(const AbstractFunctionDecl *funcDecl);
284+
251285
private:
252286
std::unique_ptr<IRABIDetailsProviderImpl> impl;
253287
};

lib/IRGen/IRABIDetailsProvider.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "GenericRequirement.h"
1919
#include "IRGen.h"
2020
#include "IRGenModule.h"
21+
#include "MetadataLayout.h"
2122
#include "NativeConventionSchema.h"
2223

2324
// FIXME: This include should removed once getFunctionLoweredSignature() is
@@ -202,6 +203,40 @@ class IRABIDetailsProviderImpl {
202203
return result;
203204
}
204205

206+
using MethodDispatchInfo = IRABIDetailsProvider::MethodDispatchInfo;
207+
208+
Optional<MethodDispatchInfo>
209+
getMethodDispatchInfo(const AbstractFunctionDecl *funcDecl) {
210+
if (funcDecl->isSemanticallyFinal())
211+
return MethodDispatchInfo::direct();
212+
// If this is an override of an existing method, then lookup
213+
// its base method in its base class.
214+
if (auto *overridenDecl = funcDecl->getOverriddenDecl())
215+
funcDecl = overridenDecl;
216+
auto *parentClass = dyn_cast<ClassDecl>(funcDecl->getDeclContext());
217+
if (!parentClass)
218+
return MethodDispatchInfo::direct();
219+
auto &layout = IGM.getMetadataLayout(parentClass);
220+
if (!isa<ClassMetadataLayout>(layout))
221+
return {};
222+
auto &classLayout = cast<ClassMetadataLayout>(layout);
223+
auto *mi = classLayout.getStoredMethodInfoIfPresent(
224+
SILDeclRef(const_cast<AbstractFunctionDecl *>(funcDecl)));
225+
if (!mi)
226+
return {};
227+
switch (mi->TheKind) {
228+
case ClassMetadataLayout::MethodInfo::Kind::DirectImpl:
229+
return MethodDispatchInfo::direct();
230+
case ClassMetadataLayout::MethodInfo::Kind::Offset:
231+
if (mi->TheOffset.isStatic()) {
232+
return MethodDispatchInfo::indirectVTableStaticOffset(
233+
/*bitOffset=*/mi->TheOffset.getStaticOffset().getValue());
234+
}
235+
return {};
236+
}
237+
llvm_unreachable("invalid kind");
238+
}
239+
205240
Lowering::TypeConverter typeConverter;
206241
// Default silOptions are sufficient, as we don't need to generated SIL.
207242
SILOptions silOpts;
@@ -405,3 +440,9 @@ llvm::MapVector<EnumElementDecl *, IRABIDetailsProvider::EnumElementInfo>
405440
IRABIDetailsProvider::getEnumTagMapping(const EnumDecl *ED) {
406441
return impl->getEnumTagMapping(ED);
407442
}
443+
444+
Optional<IRABIDetailsProvider::MethodDispatchInfo>
445+
IRABIDetailsProvider::getMethodDispatchInfo(
446+
const AbstractFunctionDecl *funcDecl) {
447+
return impl->getMethodDispatchInfo(funcDecl);
448+
}

lib/IRGen/MetadataLayout.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,14 @@ class ClassMetadataLayout : public NominalMetadataLayout {
273273

274274
MethodInfo getMethodInfo(IRGenFunction &IGF, SILDeclRef method) const;
275275

276+
const StoredMethodInfo *
277+
getStoredMethodInfoIfPresent(SILDeclRef method) const {
278+
auto it = MethodInfos.find(method);
279+
if (it != MethodInfos.end())
280+
return &it->second;
281+
return nullptr;
282+
}
283+
276284
Offset getFieldOffset(IRGenFunction &IGF, VarDecl *field) const;
277285

278286
/// Assuming that the given field offset is at a static offset in

lib/PrintAsClang/DeclAndTypePrinter.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,13 @@ class DeclAndTypePrinter::Implementation
995995
/*selfTypeDeclContext=*/typeDeclContext);
996996
if (!funcABI)
997997
return;
998+
Optional<IRABIDetailsProvider::MethodDispatchInfo> dispatchInfo;
999+
if (!isa<ConstructorDecl>(AFD)) {
1000+
dispatchInfo = owningPrinter.interopContext.getIrABIDetails()
1001+
.getMethodDispatchInfo(AFD);
1002+
if (!dispatchInfo)
1003+
return;
1004+
}
9981005
owningPrinter.prologueOS << cFuncPrologueOS.str();
9991006

10001007
printDocumentationComment(AFD);
@@ -1018,7 +1025,7 @@ class DeclAndTypePrinter::Implementation
10181025
funcABI->getSignature(),
10191026
funcABI->getSymbolName(), resultTy,
10201027
/*isStatic=*/isClassMethod,
1021-
/*isDefinition=*/false);
1028+
/*isDefinition=*/false, dispatchInfo);
10221029
}
10231030

10241031
DeclAndTypeClangFunctionPrinter defPrinter(
@@ -1041,7 +1048,7 @@ class DeclAndTypePrinter::Implementation
10411048
defPrinter.printCxxMethod(typeDeclContext, AFD, funcABI->getSignature(),
10421049
funcABI->getSymbolName(), resultTy,
10431050
/*isStatic=*/isClassMethod,
1044-
/*isDefinition=*/true);
1051+
/*isDefinition=*/true, dispatchInfo);
10451052
}
10461053

10471054
// FIXME: SWIFT_WARN_UNUSED_RESULT

lib/PrintAsClang/PrintClangFunction.cpp

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1084,7 +1084,8 @@ void DeclAndTypeClangFunctionPrinter::printCxxThunkBody(
10841084
const AbstractFunctionDecl *FD, const LoweredFunctionSignature &signature,
10851085
StringRef swiftSymbolName, const NominalTypeDecl *typeDeclContext,
10861086
const ModuleDecl *moduleContext, Type resultTy, const ParameterList *params,
1087-
bool hasThrows, const AnyFunctionType *funcType, bool isStaticMethod) {
1087+
bool hasThrows, const AnyFunctionType *funcType, bool isStaticMethod,
1088+
Optional<IRABIDetailsProvider::MethodDispatchInfo> dispatchInfo) {
10881089
if (typeDeclContext)
10891090
ClangSyntaxPrinter(os).printNominalTypeOutsideMemberDeclInnerStaticAssert(
10901091
typeDeclContext);
@@ -1096,9 +1097,29 @@ void DeclAndTypeClangFunctionPrinter::printCxxThunkBody(
10961097
os << " void* opaqueError = nullptr;\n";
10971098
os << " void* _ctx = nullptr;\n";
10981099
}
1100+
Optional<StringRef> indirectFunctionVar;
1101+
if (dispatchInfo &&
1102+
dispatchInfo->getKind() !=
1103+
IRABIDetailsProvider::MethodDispatchInfo::Kind::Direct) {
1104+
assert(dispatchInfo->getKind() == IRABIDetailsProvider::MethodDispatchInfo::
1105+
Kind::IndirectVTableStaticOffset);
1106+
auto vtableBitOffset = dispatchInfo->getStaticBitOffset();
1107+
1108+
os << "void ***selfPtr_ = reinterpret_cast<void ***>( "
1109+
"::swift::_impl::_impl_RefCountedClass::getOpaquePointer(*this));\n";
1110+
os << "void **vtable_ = *selfPtr_;\n";
1111+
os << "using FType = decltype(" << cxx_synthesis::getCxxImplNamespaceName()
1112+
<< "::" << swiftSymbolName << ");\n";
1113+
os << "FType *fptr_ = reinterpret_cast<FType *>(*(vtable_ + "
1114+
<< (vtableBitOffset / 8) << "));\n"; // FIXME: not 8
1115+
indirectFunctionVar = StringRef("fptr_");
1116+
}
10991117
auto printCallToCFunc = [&](Optional<StringRef> additionalParam) {
1100-
os << cxx_synthesis::getCxxImplNamespaceName() << "::" << swiftSymbolName
1101-
<< '(';
1118+
if (indirectFunctionVar)
1119+
os << "(* " << *indirectFunctionVar << ')';
1120+
else
1121+
os << cxx_synthesis::getCxxImplNamespaceName() << "::" << swiftSymbolName;
1122+
os << '(';
11021123

11031124
bool needsComma = false;
11041125
size_t paramIndex = 1;
@@ -1329,7 +1350,8 @@ static StringRef getConstructorName(const AbstractFunctionDecl *FD) {
13291350
void DeclAndTypeClangFunctionPrinter::printCxxMethod(
13301351
const NominalTypeDecl *typeDeclContext, const AbstractFunctionDecl *FD,
13311352
const LoweredFunctionSignature &signature, StringRef swiftSymbolName,
1332-
Type resultTy, bool isStatic, bool isDefinition) {
1353+
Type resultTy, bool isStatic, bool isDefinition,
1354+
Optional<IRABIDetailsProvider::MethodDispatchInfo> dispatchInfo) {
13331355
bool isConstructor = isa<ConstructorDecl>(FD);
13341356
os << " ";
13351357

@@ -1357,10 +1379,11 @@ void DeclAndTypeClangFunctionPrinter::printCxxMethod(
13571379

13581380
os << " {\n";
13591381
// FIXME: should it be objTy for resultTy?
1360-
printCxxThunkBody(
1361-
FD, signature, swiftSymbolName, typeDeclContext, FD->getModuleContext(),
1362-
resultTy, FD->getParameters(), FD->hasThrows(),
1363-
FD->getInterfaceType()->castTo<AnyFunctionType>(), isStatic);
1382+
printCxxThunkBody(FD, signature, swiftSymbolName, typeDeclContext,
1383+
FD->getModuleContext(), resultTy, FD->getParameters(),
1384+
FD->hasThrows(),
1385+
FD->getInterfaceType()->castTo<AnyFunctionType>(), isStatic,
1386+
dispatchInfo);
13641387
os << " }\n";
13651388
}
13661389

lib/PrintAsClang/PrintClangFunction.h

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "swift/Basic/LLVM.h"
1919
#include "swift/ClangImporter/ClangImporter.h"
2020
#include "swift/IRGen/GenericRequirement.h"
21+
#include "swift/IRGen/IRABIDetailsProvider.h"
2122
#include "llvm/ADT/ArrayRef.h"
2223
#include "llvm/ADT/MapVector.h"
2324
#include "llvm/ADT/Optional.h"
@@ -107,22 +108,21 @@ class DeclAndTypeClangFunctionPrinter {
107108

108109
/// Print the body of the inline C++ function thunk that calls the underlying
109110
/// Swift function.
110-
void printCxxThunkBody(const AbstractFunctionDecl *FD,
111-
const LoweredFunctionSignature &signature,
112-
StringRef swiftSymbolName,
113-
const NominalTypeDecl *typeDeclContext,
114-
const ModuleDecl *moduleContext, Type resultTy,
115-
const ParameterList *params, bool hasThrows = false,
116-
const AnyFunctionType *funcType = nullptr,
117-
bool isStaticMethod = false);
111+
void printCxxThunkBody(
112+
const AbstractFunctionDecl *FD, const LoweredFunctionSignature &signature,
113+
StringRef swiftSymbolName, const NominalTypeDecl *typeDeclContext,
114+
const ModuleDecl *moduleContext, Type resultTy,
115+
const ParameterList *params, bool hasThrows = false,
116+
const AnyFunctionType *funcType = nullptr, bool isStaticMethod = false,
117+
Optional<IRABIDetailsProvider::MethodDispatchInfo> dispatchInfo = None);
118118

119119
/// Print the Swift method as C++ method declaration/definition, including
120120
/// constructors.
121-
void printCxxMethod(const NominalTypeDecl *typeDeclContext,
122-
const AbstractFunctionDecl *FD,
123-
const LoweredFunctionSignature &signature,
124-
StringRef swiftSymbolName, Type resultTy, bool isStatic,
125-
bool isDefinition);
121+
void printCxxMethod(
122+
const NominalTypeDecl *typeDeclContext, const AbstractFunctionDecl *FD,
123+
const LoweredFunctionSignature &signature, StringRef swiftSymbolName,
124+
Type resultTy, bool isStatic, bool isDefinition,
125+
Optional<IRABIDetailsProvider::MethodDispatchInfo> dispatchInfo);
126126

127127
/// Print the C++ getter/setter method signature.
128128
void printCxxPropertyAccessorMethod(const NominalTypeDecl *typeDeclContext,
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// RUN: %empty-directory(%t)
2+
3+
// RUN: %target-swift-frontend %S/swift-class-virtual-method-dispatch.swift -typecheck -module-name Class -clang-header-expose-decls=all-public -emit-clang-header-path %t/class.h
4+
5+
// RUN: %target-interop-build-clangxx -c %s -I %t -o %t/swift-class-execution.o
6+
// RUN: %target-interop-build-swift %S/swift-class-virtual-method-dispatch.swift -o %t/swift-class-execution -Xlinker %t/swift-class-execution.o -module-name Class -Xfrontend -entry-point-function-name -Xfrontend swiftMain
7+
8+
// RUN: %target-codesign %t/swift-class-execution
9+
// RUN: %target-run %t/swift-class-execution | %FileCheck %s
10+
11+
// FIXME: pointer signing support.
12+
// UNSUPPORTED: CPU=arm64e
13+
14+
#include "class.h"
15+
#include <assert.h>
16+
#include <cstdio>
17+
18+
using namespace Class;
19+
20+
int main() {
21+
auto base = returnBaseClass();
22+
auto derived = returnDerivedClass();
23+
BaseClass derivedAsBase = derived;
24+
auto derivedDerived = returnDerivedDerivedClass();
25+
BaseClass derivedDerivedAsBase = derivedDerived;
26+
DerivedClass derivedDerivedAsDerived = derivedDerived;
27+
28+
{
29+
base.virtualMethod();
30+
// CHECK: BaseClass.virtualMethod
31+
puts("after base invoke");
32+
derived.virtualMethod();
33+
derivedAsBase.virtualMethod();
34+
// CHECK-NEXT: after base invoke
35+
// CHECK-NEXT: DerivedClass.virtualMethod
36+
// CHECK-NEXT: DerivedClass.virtualMethod
37+
puts("after derived invoke");
38+
derivedDerived.virtualMethod();
39+
derivedDerivedAsBase.virtualMethod();
40+
// CHECK-NEXT: after derived invoke
41+
// CHECK-NEXT: DerivedDerivedClass.virtualMethod
42+
// CHECK-NEXT: DerivedDerivedClass.virtualMethod
43+
}
44+
45+
{
46+
swift::Int x;
47+
x = base.virtualMethodIntInt(5);
48+
assert(x == 5);
49+
50+
x = derived.virtualMethodIntInt(5);
51+
assert(x == -5);
52+
x = derivedAsBase.virtualMethodIntInt(-13);
53+
assert(x == 13);
54+
55+
x = derivedDerived.virtualMethodIntInt(789);
56+
assert(x == -789);
57+
x = derivedDerivedAsBase.virtualMethodIntInt(76);
58+
assert(x == -76);
59+
}
60+
61+
{
62+
swift::Int x;
63+
x = base.finalMethodInBase(5);
64+
assert(x == 10);
65+
66+
x = derived.finalMethodInBase(10);
67+
assert(x == 20);
68+
x = derivedAsBase.finalMethodInBase(30);
69+
assert(x == 60);
70+
71+
x = derivedDerived.finalMethodInBase(11);
72+
assert(x == 22);
73+
x = derivedDerivedAsBase.finalMethodInBase(-22);
74+
assert(x == -44);
75+
}
76+
77+
{
78+
auto obj = derived.virtualMethodInDerived(base);
79+
obj.virtualMethod();
80+
// CHECK-NEXT: BaseClass.virtualMethod
81+
obj = derivedDerived.virtualMethodInDerived(base);
82+
obj.virtualMethod();
83+
// CHECK-NEXT: DerivedDerivedClass.virtualMethod
84+
obj = derivedDerivedAsDerived.virtualMethodInDerived(base);
85+
obj.virtualMethod();
86+
// CHECK-NEXT: DerivedDerivedClass.virtualMethod
87+
}
88+
89+
{
90+
derivedDerived.methodInDerivedDerived();
91+
// CHECK-NEXT: DerivedDerivedClass.methodInDerivedDerived
92+
}
93+
return 0;
94+
}

0 commit comments

Comments
 (0)