Skip to content

Commit 128064f

Browse files
committed
[cxx-interop] Enable virtual function calling from Swift to C++
This is a forward-interop feature that wires up existing functionality for synthesizing base class function calling to enable virtual function calling. The general idea is to sythesize the pattern: ``` // C++ class: struct S { virtual auto f() -> int { return 42; } }; // Swift User: var s = S() print("42: \(s.f())") // Synthetized Swift Code: extension S { func f() -> CInt { __synthesizedVirtualCall_f() } } // Synthetized C/C++ Code: auto __cxxVirtualCall_f(S *s) -> int { return s->f(); } ``` The idea here is to allow for the synthetized C++ bits from the Clang side to handle the complexity of virtual function calling.
1 parent 520e124 commit 128064f

File tree

7 files changed

+84
-12
lines changed

7 files changed

+84
-12
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4785,7 +4785,8 @@ static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
47854785
ReferenceReturnTypeBehaviorForBaseMethodSynthesis
47864786
referenceReturnTypeBehavior =
47874787
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::KeepReference,
4788-
bool forceConstQualifier = false) {
4788+
bool forceConstQualifier = false,
4789+
bool isVirtualCall = false) {
47894790
auto &clangCtx = impl.getClangASTContext();
47904791
auto &clangSema = impl.getClangSema();
47914792

@@ -4794,17 +4795,21 @@ static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
47944795
if (name.isIdentifier()) {
47954796
std::string newName;
47964797
llvm::raw_string_ostream os(newName);
4797-
os << "__synthesizedBaseCall_" << name.getAsIdentifierInfo()->getName();
4798+
os << (isVirtualCall ? "__synthesizedVirtualCall_" :
4799+
"__synthesizedBaseCall_")
4800+
<< name.getAsIdentifierInfo()->getName();
47984801
name = clang::DeclarationName(
47994802
&impl.getClangPreprocessor().getIdentifierTable().get(os.str()));
48004803
} else if (name.getCXXOverloadedOperator() == clang::OO_Subscript) {
48014804
name = clang::DeclarationName(
48024805
&impl.getClangPreprocessor().getIdentifierTable().get(
4803-
"__synthesizedBaseCall_operatorSubscript"));
4806+
(isVirtualCall ? "__synthesizedVirtualCall_operatorSubscript" :
4807+
"__synthesizedBaseCall_operatorSubscript")));
48044808
} else if (name.getCXXOverloadedOperator() == clang::OO_Star) {
48054809
name = clang::DeclarationName(
48064810
&impl.getClangPreprocessor().getIdentifierTable().get(
4807-
"__synthesizedBaseCall_operatorStar"));
4811+
(isVirtualCall ? "__synthesizedVirtualCall_operatorStar" :
4812+
"__synthesizedBaseCall_operatorStar")));
48084813
}
48094814
auto methodType = method->getType();
48104815
// Check if we need to drop the reference from the return type
@@ -4930,6 +4935,16 @@ static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
49304935
return newMethod;
49314936
}
49324937

4938+
// Synthesize a C++ virtual method
4939+
clang::CXXMethodDecl *synthesizeCxxVirtualMethod(
4940+
swift::ClangImporter &Impl, const clang::CXXRecordDecl *derivedClass,
4941+
const clang::CXXRecordDecl *baseClass, const clang::CXXMethodDecl *method) {
4942+
return synthesizeCxxBaseMethod(
4943+
Impl, derivedClass, baseClass, method,
4944+
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::KeepReference,
4945+
false /* forceConstQualifier */, true /* isVirtualCall */);
4946+
}
4947+
49334948
// Find the base C++ method called by the base function we want to synthesize
49344949
// the derived thunk for.
49354950
// The base C++ method is either the original C++ method that corresponds
@@ -6555,7 +6570,7 @@ static ValueDecl *addThunkForDependentTypes(FuncDecl *oldDecl,
65556570
// are not used in the function signature. We supply the type params as explicit
65566571
// metatype arguments to aid in typechecking, but they shouldn't be forwarded to
65576572
// the corresponding C++ function.
6558-
static std::pair<BraceStmt *, bool>
6573+
std::pair<BraceStmt *, bool>
65596574
synthesizeForwardingThunkBody(AbstractFunctionDecl *afd, void *context) {
65606575
ASTContext &ctx = afd->getASTContext();
65616576

lib/ClangImporter/ImportDecl.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3711,9 +3711,32 @@ namespace {
37113711
Decl *VisitCXXMethodDecl(const clang::CXXMethodDecl *decl) {
37123712
auto method = VisitFunctionDecl(decl);
37133713
if (decl->isVirtual() && isa_and_nonnull<ValueDecl>(method)) {
3714+
3715+
if (auto dc = method->getDeclContext();
3716+
!decl->isPure() &&
3717+
isa_and_nonnull<NominalTypeDecl>(dc->getAsDecl())) {
3718+
3719+
// generates the __synthesizedVirtualCall_ C++ thunk
3720+
clang::CXXMethodDecl *cxxThunk = synthesizeCxxVirtualMethod(
3721+
*static_cast<ClangImporter *>(
3722+
dc->getASTContext().getClangModuleLoader()),
3723+
decl->getParent(), decl->getParent(), decl);
3724+
3725+
// call the __synthesizedVirtualCall_ C++ thunk from a Swift thunk
3726+
if (Decl *swiftThunk = VisitCXXMethodDecl(cxxThunk);
3727+
isa_and_nonnull<FuncDecl>(swiftThunk)) {
3728+
// synthesize the body of the Swift method to call the swiftThunk
3729+
synthesizeForwardingThunkBody(cast<FuncDecl>(method),
3730+
cast<FuncDecl>(swiftThunk));
3731+
return method;
3732+
}
3733+
}
3734+
37143735
Impl.markUnavailable(
37153736
cast<ValueDecl>(method),
3716-
"virtual functions are not yet available in Swift");
3737+
decl->isPure() ?
3738+
"virtual function is not available in Swift because it is pure" :
3739+
"virtual function is not available in Swift");
37173740
}
37183741

37193742
if (Impl.SwiftContext.LangOpts.CxxInteropGettersSettersAsProperties ||

lib/ClangImporter/ImporterImpl.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,4 +1988,14 @@ inline std::string getPrivateOperatorName(const std::string &OperatorToken) {
19881988
}
19891989
}
19901990

1991+
// Forwards to synthesizeCxxBasicMethod(), producing a thunk that calls a
1992+
// virtual function.
1993+
clang::CXXMethodDecl *synthesizeCxxVirtualMethod(
1994+
swift::ClangImporter &Impl, const clang::CXXRecordDecl *derivedClass,
1995+
const clang::CXXRecordDecl *baseClass, const clang::CXXMethodDecl *method);
1996+
1997+
// Exposed to produce a Swift method body for calling a Swift thunk.
1998+
std::pair<swift::BraceStmt *, bool>
1999+
synthesizeForwardingThunkBody(swift::AbstractFunctionDecl *afd, void *context);
2000+
19912001
#endif

test/Interop/Cxx/class/inheritance/Inputs/virtual-methods.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ struct Base {
88
virtual void foo() = 0;
99
};
1010

11+
struct Base2 { virtual int f() = 0; };
12+
struct Base3 { virtual int f() { return 24; } };
13+
struct Derived2 : public Base2 { virtual int f() { return 42; } };
14+
struct Derived3 : public Base3 { virtual int f() { return 42; } };
15+
struct Derived4 : public Base3 { };
16+
1117
template <class T>
1218
struct Derived : Base {
1319
inline void foo() override {

test/Interop/Cxx/class/inheritance/virtual-methods-irgen.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,24 @@ import VirtualMethods
88
var x = DerivedInt()
99
x.callMe()
1010

11+
var b3 = Base3()
12+
var d2 = Derived2()
13+
var d3 = Derived3()
14+
var d4 = Derived4()
15+
16+
b3.f()
17+
d2.f()
18+
d3.f()
19+
d4.f()
20+
21+
// CHECK: invoke {{.*}} @_ZN5Base31fEv
22+
// CHECK: invoke {{.*}} @_ZN8Derived21fEv
23+
// CHECK: invoke {{.*}} @_ZN8Derived31fEv
24+
// CHECK: call swiftcc {{.*}} @"$sSo8Derived4V1fs5Int32VyF"
25+
26+
// CHECK: define {{.*}} @"$sSo8Derived4V1fs5Int32VyF"(ptr swiftself dereferenceable
27+
// CHECK: invoke {{.*}} @_ZN8Derived423__synthesizedBaseCall_fEv
28+
1129
// CHECK: define {{.*}}void @{{_ZN7DerivedIiE3fooEv|"\?foo@\?$Derived@H@@UEAAXXZ"}}
1230
// CHECK: call void @{{_Z21testFunctionCollectedv|"\?testFunctionCollected@@YAXXZ"}}
1331

test/Interop/Cxx/class/inheritance/virtual-methods-module-interface.swift

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
// CHECK: struct Base {
44
// CHECK-NEXT: init()
5-
// CHECK-NEXT: @available(*, unavailable, message: "virtual functions are not yet available in Swift")
5+
// CHECK-NEXT: @available(*, unavailable, message: "virtual function is not available in Swift because it is pure")
66
// CHECK-NEXT: mutating func foo()
77

88
// CHECK: struct Derived<CInt> {
9-
// CHECK: @available(*, unavailable, message: "virtual functions are not yet available in Swift")
10-
// CHECK: mutating func foo()
9+
// CHECK-NEXT: init()
10+
// CHECK-NEXT: mutating func foo()
1111
// CHECK: }
1212

1313
// CHECK: struct VirtualNonAbstractBase {
14-
// CHECK: @available(*, unavailable, message: "virtual functions are not yet available in Swift")
15-
// CHECK: func nonAbstractMethod()
14+
// CHECK-NEXT: init()
15+
// CHECK-NEXT: func nonAbstractMethod()

test/Interop/Cxx/class/inheritance/virtual-methods-typechecker.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
import VirtualMethods
44

5-
VirtualNonAbstractBase().nonAbstractMethod() // expected-error {{'nonAbstractMethod()' is unavailable: virtual functions are not yet available in Swift}}
5+
VirtualNonAbstractBase().nonAbstractMethod()

0 commit comments

Comments
 (0)