[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.
This commit is contained in:
Puyan Lotfi
2023-11-16 23:40:13 -08:00
parent 520e12430e
commit 128064f31d
7 changed files with 84 additions and 12 deletions

View File

@@ -4785,7 +4785,8 @@ static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
ReferenceReturnTypeBehaviorForBaseMethodSynthesis
referenceReturnTypeBehavior =
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::KeepReference,
bool forceConstQualifier = false) {
bool forceConstQualifier = false,
bool isVirtualCall = false) {
auto &clangCtx = impl.getClangASTContext();
auto &clangSema = impl.getClangSema();
@@ -4794,17 +4795,21 @@ static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
if (name.isIdentifier()) {
std::string newName;
llvm::raw_string_ostream os(newName);
os << "__synthesizedBaseCall_" << name.getAsIdentifierInfo()->getName();
os << (isVirtualCall ? "__synthesizedVirtualCall_" :
"__synthesizedBaseCall_")
<< name.getAsIdentifierInfo()->getName();
name = clang::DeclarationName(
&impl.getClangPreprocessor().getIdentifierTable().get(os.str()));
} else if (name.getCXXOverloadedOperator() == clang::OO_Subscript) {
name = clang::DeclarationName(
&impl.getClangPreprocessor().getIdentifierTable().get(
"__synthesizedBaseCall_operatorSubscript"));
(isVirtualCall ? "__synthesizedVirtualCall_operatorSubscript" :
"__synthesizedBaseCall_operatorSubscript")));
} else if (name.getCXXOverloadedOperator() == clang::OO_Star) {
name = clang::DeclarationName(
&impl.getClangPreprocessor().getIdentifierTable().get(
"__synthesizedBaseCall_operatorStar"));
(isVirtualCall ? "__synthesizedVirtualCall_operatorStar" :
"__synthesizedBaseCall_operatorStar")));
}
auto methodType = method->getType();
// Check if we need to drop the reference from the return type
@@ -4930,6 +4935,16 @@ static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
return newMethod;
}
// Synthesize a C++ virtual method
clang::CXXMethodDecl *synthesizeCxxVirtualMethod(
swift::ClangImporter &Impl, const clang::CXXRecordDecl *derivedClass,
const clang::CXXRecordDecl *baseClass, const clang::CXXMethodDecl *method) {
return synthesizeCxxBaseMethod(
Impl, derivedClass, baseClass, method,
ReferenceReturnTypeBehaviorForBaseMethodSynthesis::KeepReference,
false /* forceConstQualifier */, true /* isVirtualCall */);
}
// Find the base C++ method called by the base function we want to synthesize
// the derived thunk for.
// The base C++ method is either the original C++ method that corresponds
@@ -6555,7 +6570,7 @@ static ValueDecl *addThunkForDependentTypes(FuncDecl *oldDecl,
// are not used in the function signature. We supply the type params as explicit
// metatype arguments to aid in typechecking, but they shouldn't be forwarded to
// the corresponding C++ function.
static std::pair<BraceStmt *, bool>
std::pair<BraceStmt *, bool>
synthesizeForwardingThunkBody(AbstractFunctionDecl *afd, void *context) {
ASTContext &ctx = afd->getASTContext();

View File

@@ -3711,9 +3711,32 @@ namespace {
Decl *VisitCXXMethodDecl(const clang::CXXMethodDecl *decl) {
auto method = VisitFunctionDecl(decl);
if (decl->isVirtual() && isa_and_nonnull<ValueDecl>(method)) {
if (auto dc = method->getDeclContext();
!decl->isPure() &&
isa_and_nonnull<NominalTypeDecl>(dc->getAsDecl())) {
// generates the __synthesizedVirtualCall_ C++ thunk
clang::CXXMethodDecl *cxxThunk = synthesizeCxxVirtualMethod(
*static_cast<ClangImporter *>(
dc->getASTContext().getClangModuleLoader()),
decl->getParent(), decl->getParent(), decl);
// call the __synthesizedVirtualCall_ C++ thunk from a Swift thunk
if (Decl *swiftThunk = VisitCXXMethodDecl(cxxThunk);
isa_and_nonnull<FuncDecl>(swiftThunk)) {
// synthesize the body of the Swift method to call the swiftThunk
synthesizeForwardingThunkBody(cast<FuncDecl>(method),
cast<FuncDecl>(swiftThunk));
return method;
}
}
Impl.markUnavailable(
cast<ValueDecl>(method),
"virtual functions are not yet available in Swift");
decl->isPure() ?
"virtual function is not available in Swift because it is pure" :
"virtual function is not available in Swift");
}
if (Impl.SwiftContext.LangOpts.CxxInteropGettersSettersAsProperties ||

View File

@@ -1988,4 +1988,14 @@ inline std::string getPrivateOperatorName(const std::string &OperatorToken) {
}
}
// Forwards to synthesizeCxxBasicMethod(), producing a thunk that calls a
// virtual function.
clang::CXXMethodDecl *synthesizeCxxVirtualMethod(
swift::ClangImporter &Impl, const clang::CXXRecordDecl *derivedClass,
const clang::CXXRecordDecl *baseClass, const clang::CXXMethodDecl *method);
// Exposed to produce a Swift method body for calling a Swift thunk.
std::pair<swift::BraceStmt *, bool>
synthesizeForwardingThunkBody(swift::AbstractFunctionDecl *afd, void *context);
#endif

View File

@@ -8,6 +8,12 @@ struct Base {
virtual void foo() = 0;
};
struct Base2 { virtual int f() = 0; };
struct Base3 { virtual int f() { return 24; } };
struct Derived2 : public Base2 { virtual int f() { return 42; } };
struct Derived3 : public Base3 { virtual int f() { return 42; } };
struct Derived4 : public Base3 { };
template <class T>
struct Derived : Base {
inline void foo() override {

View File

@@ -8,6 +8,24 @@ import VirtualMethods
var x = DerivedInt()
x.callMe()
var b3 = Base3()
var d2 = Derived2()
var d3 = Derived3()
var d4 = Derived4()
b3.f()
d2.f()
d3.f()
d4.f()
// CHECK: invoke {{.*}} @_ZN5Base31fEv
// CHECK: invoke {{.*}} @_ZN8Derived21fEv
// CHECK: invoke {{.*}} @_ZN8Derived31fEv
// CHECK: call swiftcc {{.*}} @"$sSo8Derived4V1fs5Int32VyF"
// CHECK: define {{.*}} @"$sSo8Derived4V1fs5Int32VyF"(ptr swiftself dereferenceable
// CHECK: invoke {{.*}} @_ZN8Derived423__synthesizedBaseCall_fEv
// CHECK: define {{.*}}void @{{_ZN7DerivedIiE3fooEv|"\?foo@\?$Derived@H@@UEAAXXZ"}}
// CHECK: call void @{{_Z21testFunctionCollectedv|"\?testFunctionCollected@@YAXXZ"}}

View File

@@ -2,14 +2,14 @@
// CHECK: struct Base {
// CHECK-NEXT: init()
// CHECK-NEXT: @available(*, unavailable, message: "virtual functions are not yet available in Swift")
// CHECK-NEXT: @available(*, unavailable, message: "virtual function is not available in Swift because it is pure")
// CHECK-NEXT: mutating func foo()
// CHECK: struct Derived<CInt> {
// CHECK: @available(*, unavailable, message: "virtual functions are not yet available in Swift")
// CHECK: mutating func foo()
// CHECK-NEXT: init()
// CHECK-NEXT: mutating func foo()
// CHECK: }
// CHECK: struct VirtualNonAbstractBase {
// CHECK: @available(*, unavailable, message: "virtual functions are not yet available in Swift")
// CHECK: func nonAbstractMethod()
// CHECK-NEXT: init()
// CHECK-NEXT: func nonAbstractMethod()

View File

@@ -2,4 +2,4 @@
import VirtualMethods
VirtualNonAbstractBase().nonAbstractMethod() // expected-error {{'nonAbstractMethod()' is unavailable: virtual functions are not yet available in Swift}}
VirtualNonAbstractBase().nonAbstractMethod()