Skip to content

Commit 14814d7

Browse files
committed
[cxx-interop] Use a synthesized C++ method when accessing a base field or subscript from a derived class synthesized method
The use of a synthesized C++ method allows us to avoid making a copy of self when accessing the base field or subscript from Swift
1 parent 82f8d5d commit 14814d7

File tree

3 files changed

+213
-33
lines changed

3 files changed

+213
-33
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 152 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4773,13 +4773,17 @@ static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
47734773
auto &clangSema = impl.getClangSema();
47744774

47754775
// Create a new method in the derived class that calls the base method.
4776-
auto name = method->getNameInfo().getName();
4776+
clang::DeclarationName name = method->getNameInfo().getName();
47774777
if (name.isIdentifier()) {
47784778
std::string newName;
47794779
llvm::raw_string_ostream os(newName);
47804780
os << "__synthesizedBaseCall_" << name.getAsIdentifierInfo()->getName();
47814781
name = clang::DeclarationName(
47824782
&impl.getClangPreprocessor().getIdentifierTable().get(os.str()));
4783+
} else if (name.getCXXOverloadedOperator() == clang::OO_Subscript) {
4784+
name = clang::DeclarationName(
4785+
&impl.getClangPreprocessor().getIdentifierTable().get(
4786+
"__synthesizedBaseCall_operatorSubscript"));
47834787
}
47844788
auto newMethod = clang::CXXMethodDecl::Create(
47854789
clangCtx, const_cast<clang::CXXRecordDecl *>(derivedClass),
@@ -4966,9 +4970,94 @@ synthesizeBaseClassMethodBody(AbstractFunctionDecl *afd, void *context) {
49664970
return {body, /*isTypeChecked=*/true};
49674971
}
49684972

4969-
// Getters are relatively easy. Just cast and return the member:
4970-
// %0 = __swift_interopStaticCast<Base>(self)
4971-
// return %0.member
4973+
// Synthesize a C++ method that returns the field of interest from the base
4974+
// class. This lets Clang take care of the cast from the derived class
4975+
// to the base class while the field is accessed.
4976+
static clang::CXXMethodDecl *synthesizeCxxBaseGetterAccessorMethod(
4977+
ClangImporter &impl, const clang::CXXRecordDecl *derivedClass,
4978+
const clang::CXXRecordDecl *baseClass, const clang::FieldDecl *field) {
4979+
auto &clangCtx = impl.getClangASTContext();
4980+
auto &clangSema = impl.getClangSema();
4981+
4982+
// Create a new method in the derived class that calls the base method.
4983+
auto name = field->getDeclName();
4984+
if (name.isIdentifier()) {
4985+
std::string newName;
4986+
llvm::raw_string_ostream os(newName);
4987+
os << "__synthesizedBaseGetterAccessor_"
4988+
<< name.getAsIdentifierInfo()->getName();
4989+
name = clang::DeclarationName(
4990+
&impl.getClangPreprocessor().getIdentifierTable().get(os.str()));
4991+
}
4992+
auto returnType = field->getType();
4993+
if (returnType->isReferenceType())
4994+
returnType = returnType->getPointeeType();
4995+
clang::FunctionProtoType::ExtProtoInfo info;
4996+
info.TypeQuals.addConst();
4997+
info.ExceptionSpec.Type = clang::EST_NoThrow;
4998+
auto ftype = clangCtx.getFunctionType(returnType, {}, info);
4999+
auto newMethod = clang::CXXMethodDecl::Create(
5000+
clangCtx, const_cast<clang::CXXRecordDecl *>(derivedClass),
5001+
field->getSourceRange().getBegin(),
5002+
clang::DeclarationNameInfo(name, clang::SourceLocation()), ftype,
5003+
clangCtx.getTrivialTypeSourceInfo(ftype), clang::SC_None,
5004+
/*UsesFPIntrin=*/false, /*isInline=*/true,
5005+
clang::ConstexprSpecKind::Unspecified, field->getSourceRange().getEnd());
5006+
newMethod->setImplicit();
5007+
newMethod->setImplicitlyInline();
5008+
newMethod->setAccess(clang::AccessSpecifier::AS_public);
5009+
5010+
// Create a new Clang diagnostic pool to capture any diagnostics
5011+
// emitted during the construction of the method.
5012+
clang::sema::DelayedDiagnosticPool diagPool{
5013+
clangSema.DelayedDiagnostics.getCurrentPool()};
5014+
auto diagState = clangSema.DelayedDiagnostics.push(diagPool);
5015+
5016+
// Construct the method's body.
5017+
auto *thisExpr = new (clangCtx) clang::CXXThisExpr(
5018+
clang::SourceLocation(), newMethod->getThisType(), /*IsImplicit=*/false);
5019+
clang::QualType baseClassPtr = clangCtx.getRecordType(baseClass);
5020+
baseClassPtr.addConst();
5021+
baseClassPtr = clangCtx.getPointerType(baseClassPtr);
5022+
5023+
clang::CastKind Kind;
5024+
clang::CXXCastPath Path;
5025+
clangSema.CheckPointerConversion(thisExpr, baseClassPtr, Kind, Path,
5026+
/*IgnoreBaseAccess=*/false,
5027+
/*Diagnose=*/true);
5028+
auto conv = clangSema.ImpCastExprToType(thisExpr, baseClassPtr, Kind,
5029+
clang::VK_PRValue, &Path);
5030+
if (!conv.isUsable())
5031+
return nullptr;
5032+
auto memberExpr = clangSema.BuildMemberExpr(
5033+
conv.get(), /*isArrow=*/true, clang::SourceLocation(),
5034+
clang::NestedNameSpecifierLoc(), clang::SourceLocation(),
5035+
const_cast<clang::FieldDecl *>(field),
5036+
clang::DeclAccessPair::make(const_cast<clang::FieldDecl *>(field),
5037+
clang::AS_public),
5038+
/*HadMultipleCandidates=*/false,
5039+
clang::DeclarationNameInfo(field->getDeclName(), clang::SourceLocation()),
5040+
returnType, clang::VK_LValue, clang::OK_Ordinary);
5041+
auto returnCast = clangSema.ImpCastExprToType(
5042+
memberExpr, returnType, clang::CK_LValueToRValue, clang::VK_PRValue);
5043+
if (!returnCast.isUsable())
5044+
return nullptr;
5045+
auto returnStmt = clang::ReturnStmt::Create(clangCtx, clang::SourceLocation(),
5046+
returnCast.get(), nullptr);
5047+
5048+
// Check if there were any Clang errors during the construction
5049+
// of the method body.
5050+
clangSema.DelayedDiagnostics.popWithoutEmitting(diagState);
5051+
if (!diagPool.empty())
5052+
return nullptr;
5053+
newMethod->setBody(returnStmt);
5054+
return newMethod;
5055+
}
5056+
5057+
// Generates the body of a derived method, that invokes the base
5058+
// field getter or the base subscript.
5059+
// The method's body takes the following form:
5060+
// return self.__synthesizedBaseCall_fn(args...)
49725061
static std::pair<BraceStmt *, bool>
49735062
synthesizeBaseClassFieldGetterBody(AbstractFunctionDecl *afd, void *context) {
49745063
ASTContext &ctx = afd->getASTContext();
@@ -4980,48 +5069,78 @@ synthesizeBaseClassFieldGetterBody(AbstractFunctionDecl *afd, void *context) {
49805069
NominalTypeDecl *derivedStruct =
49815070
cast<NominalTypeDecl>(getterDecl->getDeclContext()->getAsDecl());
49825071

5072+
const clang::Decl *baseClangDecl;
5073+
if (baseClassVar->getClangDecl())
5074+
baseClangDecl = baseClassVar->getClangDecl();
5075+
else
5076+
baseClangDecl =
5077+
getCalledBaseCxxMethod(baseClassVar->getAccessor(AccessorKind::Get));
5078+
5079+
clang::CXXMethodDecl *baseGetterCxxMethod = nullptr;
5080+
if (auto *md = dyn_cast_or_null<clang::CXXMethodDecl>(baseClangDecl)) {
5081+
// Subscript operator is represented through a generated
5082+
// C++ method call that calls the base operator.
5083+
baseGetterCxxMethod = synthesizeCxxBaseMethod(
5084+
*static_cast<ClangImporter *>(ctx.getClangModuleLoader()),
5085+
cast<clang::CXXRecordDecl>(derivedStruct->getClangDecl()),
5086+
cast<clang::CXXRecordDecl>(baseStruct->getClangDecl()), md);
5087+
} else if (auto *fd = dyn_cast_or_null<clang::FieldDecl>(baseClangDecl)) {
5088+
// Field getter is represented through a generated
5089+
// C++ method call that returns the value of the base field.
5090+
baseGetterCxxMethod = synthesizeCxxBaseGetterAccessorMethod(
5091+
*static_cast<ClangImporter *>(ctx.getClangModuleLoader()),
5092+
cast<clang::CXXRecordDecl>(derivedStruct->getClangDecl()),
5093+
cast<clang::CXXRecordDecl>(baseStruct->getClangDecl()), fd);
5094+
}
5095+
5096+
if (!baseGetterCxxMethod) {
5097+
ctx.Diags.diagnose(SourceLoc(), diag::failed_base_method_call_synthesis,
5098+
getterDecl, baseStruct);
5099+
auto body = BraceStmt::create(ctx, SourceLoc(), {}, SourceLoc(),
5100+
/*implicit=*/true);
5101+
return {body, true};
5102+
}
5103+
auto *baseGetterMethod = cast<FuncDecl>(
5104+
ctx.getClangModuleLoader()->importDeclDirectly(baseGetterCxxMethod));
5105+
49835106
auto selfDecl = getterDecl->getImplicitSelfDecl();
49845107
auto selfExpr = new (ctx) DeclRefExpr(selfDecl, DeclNameLoc(),
49855108
/*implicit*/ true);
49865109
selfExpr->setType(selfDecl->getTypeInContext());
49875110

4988-
auto staticCastRefExpr = getInteropStaticCastDeclRefExpr(
4989-
ctx, baseStruct->getClangDecl()->getOwningModule(),
4990-
baseStruct->getSelfInterfaceType(),
4991-
derivedStruct->getSelfInterfaceType());
5111+
Argument selfArg = Argument::unlabeled(selfExpr);
49925112

4993-
auto *argList = ArgumentList::forImplicitUnlabeled(ctx, {selfExpr});
4994-
auto casted = CallExpr::createImplicit(ctx, staticCastRefExpr, argList);
4995-
casted->setType(baseStruct->getSelfInterfaceType());
4996-
casted->setThrows(false);
5113+
auto *baseMemberExpr =
5114+
new (ctx) DeclRefExpr(ConcreteDeclRef(baseGetterMethod), DeclNameLoc(),
5115+
/*Implicit=*/true);
5116+
baseMemberExpr->setType(baseGetterMethod->getInterfaceType());
5117+
5118+
auto baseMemberDotCallExpr =
5119+
DotSyntaxCallExpr::create(ctx, baseMemberExpr, SourceLoc(), selfArg);
5120+
baseMemberDotCallExpr->setType(baseGetterMethod->getMethodInterfaceType());
5121+
baseMemberDotCallExpr->setThrows(false);
49975122

4998-
Expr *baseMember = nullptr;
5123+
ArgumentList *argumentList;
49995124
if (auto subscript = dyn_cast<SubscriptDecl>(baseClassVar)) {
50005125
auto paramDecl = getterDecl->getParameters()->get(0);
5001-
auto paramRefExpr = new (ctx) DeclRefExpr(paramDecl,
5002-
DeclNameLoc(),
5003-
/*Implicit=*/ true);
5126+
auto paramRefExpr = new (ctx) DeclRefExpr(paramDecl, DeclNameLoc(),
5127+
/*Implicit=*/true);
50045128
paramRefExpr->setType(paramDecl->getTypeInContext());
5005-
5006-
auto *argList = ArgumentList::forImplicitUnlabeled(ctx, {paramRefExpr});
5007-
baseMember = SubscriptExpr::create(ctx, casted, argList, subscript);
5008-
baseMember->setType(subscript->getElementInterfaceType());
5129+
argumentList = ArgumentList::forImplicitUnlabeled(ctx, {paramRefExpr});
50095130
} else {
5010-
// If the base class var has a clang decl, that means it's an access into a
5011-
// stored field. Otherwise, we're looking into another base class, so it's a
5012-
// another synthesized accessor.
5013-
AccessSemantics accessKind = baseClassVar->getClangDecl()
5014-
? AccessSemantics::DirectToStorage
5015-
: AccessSemantics::DirectToImplementation;
5016-
baseMember =
5017-
new (ctx) MemberRefExpr(casted, SourceLoc(), baseClassVar, DeclNameLoc(),
5018-
/*Implicit=*/true, accessKind);
5019-
baseMember->setType(cast<VarDecl>(baseClassVar)->getTypeInContext());
5131+
argumentList = ArgumentList::forImplicitUnlabeled(ctx, {});
50205132
}
50215133

5022-
auto ret = new (ctx) ReturnStmt(SourceLoc(), baseMember);
5023-
auto body = BraceStmt::create(ctx, SourceLoc(), {ret}, SourceLoc(),
5024-
/*implicit*/ true);
5134+
auto *baseMemberCallExpr =
5135+
CallExpr::createImplicit(ctx, baseMemberDotCallExpr, argumentList);
5136+
baseMemberCallExpr->setType(baseGetterMethod->getResultInterfaceType());
5137+
baseMemberCallExpr->setThrows(false);
5138+
5139+
auto returnStmt = new (ctx) ReturnStmt(SourceLoc(), baseMemberCallExpr,
5140+
/*implicit=*/true);
5141+
5142+
auto body = BraceStmt::create(ctx, SourceLoc(), {returnStmt}, SourceLoc(),
5143+
/*implicit=*/true);
50255144
return {body, /*isTypeChecked=*/true};
50265145
}
50275146

test/Interop/Cxx/class/inheritance/Inputs/fields.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,37 @@ struct ClassTemplate {
6666
};
6767

6868
struct DerivedFromClassTemplate : ClassTemplate<int> {};
69+
70+
int &getCopyCounter() {
71+
static int copyCounter = 0;
72+
return copyCounter;
73+
}
74+
75+
class CopyTrackedBaseClass {
76+
public:
77+
CopyTrackedBaseClass(int x) : x(x) {}
78+
CopyTrackedBaseClass(const CopyTrackedBaseClass &other) : x(other.x) {
79+
++getCopyCounter();
80+
}
81+
82+
int x;
83+
};
84+
85+
class CopyTrackedDerivedClass: public CopyTrackedBaseClass {
86+
public:
87+
CopyTrackedDerivedClass(int x) : CopyTrackedBaseClass(x) {}
88+
};
89+
90+
class NonEmptyBase {
91+
public:
92+
int getY() const {
93+
return y;
94+
}
95+
private:
96+
int y = 11;
97+
};
98+
99+
class CopyTrackedDerivedDerivedClass: public NonEmptyBase, public CopyTrackedDerivedClass {
100+
public:
101+
CopyTrackedDerivedDerivedClass(int x) : CopyTrackedDerivedClass(x) {}
102+
};

test/Interop/Cxx/class/inheritance/fields.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,31 @@ FieldsTestSuite.test("Same field from derived") {
8585
expectEqual(derived.a, 42)
8686
}
8787

88+
extension CopyTrackedBaseClass {
89+
var swiftProp: CInt {
90+
return x
91+
}
92+
}
93+
94+
FieldsTestSuite.test("Get field without copying base in the getter accessor") {
95+
let base = CopyTrackedBaseClass(0)
96+
var copyCounter = getCopyCounter().pointee
97+
expectEqual(base.swiftProp, 0)
98+
// Measure copy counter of a regular
99+
// property access for a C++ type to compare
100+
// it to see if any additional copies are
101+
// needed to access the property from the base class.
102+
let expectedCopyCountDiff = getCopyCounter().pointee - copyCounter
103+
104+
let derived = CopyTrackedDerivedClass(234)
105+
copyCounter = getCopyCounter().pointee
106+
expectEqual(derived.x, 234)
107+
expectEqual(copyCounter, getCopyCounter().pointee - expectedCopyCountDiff)
108+
109+
let derivedDerived = CopyTrackedDerivedDerivedClass(-11)
110+
copyCounter = getCopyCounter().pointee
111+
expectEqual(derivedDerived.x, -11)
112+
expectEqual(copyCounter, getCopyCounter().pointee - expectedCopyCountDiff)
113+
}
114+
88115
runAllTests()

0 commit comments

Comments
 (0)