Skip to content

Commit 0851fa2

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 0851fa2

File tree

3 files changed

+276
-38
lines changed

3 files changed

+276
-38
lines changed

lib/ClangImporter/ClangImporter.cpp

Lines changed: 215 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4768,24 +4768,63 @@ MemberRefExpr *getSelfInteropStaticCast(FuncDecl *funcDecl,
47684768
// to the base class during the invocation of the method.
47694769
static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
47704770
ClangImporter &impl, const clang::CXXRecordDecl *derivedClass,
4771-
const clang::CXXRecordDecl *baseClass, const clang::CXXMethodDecl *method) {
4771+
const clang::CXXRecordDecl *baseClass, const clang::CXXMethodDecl *method,
4772+
bool promoteReferenceReturnToValue = false,
4773+
bool forceConstQualifier = false) {
47724774
auto &clangCtx = impl.getClangASTContext();
47734775
auto &clangSema = impl.getClangSema();
47744776

47754777
// Create a new method in the derived class that calls the base method.
4776-
auto name = method->getNameInfo().getName();
4778+
clang::DeclarationName name = method->getNameInfo().getName();
47774779
if (name.isIdentifier()) {
47784780
std::string newName;
47794781
llvm::raw_string_ostream os(newName);
47804782
os << "__synthesizedBaseCall_" << name.getAsIdentifierInfo()->getName();
47814783
name = clang::DeclarationName(
47824784
&impl.getClangPreprocessor().getIdentifierTable().get(os.str()));
4785+
} else if (name.getCXXOverloadedOperator() == clang::OO_Subscript) {
4786+
name = clang::DeclarationName(
4787+
&impl.getClangPreprocessor().getIdentifierTable().get(
4788+
"__synthesizedBaseCall_operatorSubscript"));
4789+
} else if (name.getCXXOverloadedOperator() == clang::OO_Star) {
4790+
name = clang::DeclarationName(
4791+
&impl.getClangPreprocessor().getIdentifierTable().get(
4792+
"__synthesizedBaseCall_operatorStar"));
4793+
}
4794+
auto methodType = method->getType();
4795+
// Check if we need to drop the reference from the return type
4796+
// of the new method. This is needed when a synthesized `operator []`
4797+
// derived-to-base call is invoked from Swift's subscript getter.
4798+
if (promoteReferenceReturnToValue) {
4799+
if (const auto *fpt = methodType->getAs<clang::FunctionProtoType>()) {
4800+
auto retType = fpt->getReturnType();
4801+
if (retType->isReferenceType()) {
4802+
methodType = clangCtx.getFunctionType(retType->getPointeeType(),
4803+
fpt->getParamTypes(),
4804+
fpt->getExtProtoInfo());
4805+
}
4806+
}
4807+
}
4808+
// Check if this method requires an additional `const` qualifier.
4809+
// This might needed when a non-const synthesized `operator []`
4810+
// derived-to-base call is invoked from Swift's subscript getter.
4811+
bool castThisToNonConstThis = false;
4812+
if (forceConstQualifier) {
4813+
if (const auto *fpt = methodType->getAs<clang::FunctionProtoType>()) {
4814+
auto info = fpt->getExtProtoInfo();
4815+
if (!info.TypeQuals.hasConst()) {
4816+
info.TypeQuals.addConst();
4817+
castThisToNonConstThis = true;
4818+
methodType = clangCtx.getFunctionType(fpt->getReturnType(),
4819+
fpt->getParamTypes(), info);
4820+
}
4821+
}
47834822
}
47844823
auto newMethod = clang::CXXMethodDecl::Create(
47854824
clangCtx, const_cast<clang::CXXRecordDecl *>(derivedClass),
47864825
method->getSourceRange().getBegin(),
4787-
clang::DeclarationNameInfo(name, clang::SourceLocation()),
4788-
method->getType(), method->getTypeSourceInfo(), method->getStorageClass(),
4826+
clang::DeclarationNameInfo(name, clang::SourceLocation()), methodType,
4827+
method->getTypeSourceInfo(), method->getStorageClass(),
47894828
method->UsesFPIntrin(), /*isInline=*/true, method->getConstexprKind(),
47904829
method->getSourceRange().getEnd());
47914830
newMethod->setImplicit();
@@ -4810,8 +4849,23 @@ static clang::CXXMethodDecl *synthesizeCxxBaseMethod(
48104849
auto diagState = clangSema.DelayedDiagnostics.push(diagPool);
48114850

48124851
// Construct the method's body.
4813-
auto *thisExpr = new (clangCtx) clang::CXXThisExpr(
4852+
clang::Expr *thisExpr = new (clangCtx) clang::CXXThisExpr(
48144853
clang::SourceLocation(), newMethod->getThisType(), /*IsImplicit=*/false);
4854+
if (castThisToNonConstThis) {
4855+
auto baseClassPtr =
4856+
clangCtx.getPointerType(clangCtx.getRecordType(derivedClass));
4857+
clang::CastKind Kind;
4858+
clang::CXXCastPath Path;
4859+
clangSema.CheckPointerConversion(thisExpr, baseClassPtr, Kind, Path,
4860+
/*IgnoreBaseAccess=*/false,
4861+
/*Diagnose=*/true);
4862+
auto conv = clangSema.ImpCastExprToType(thisExpr, baseClassPtr, Kind,
4863+
clang::VK_PRValue, &Path);
4864+
if (!conv.isUsable())
4865+
return nullptr;
4866+
thisExpr = conv.get();
4867+
}
4868+
48154869
auto memberExpr = clangSema.BuildMemberExpr(
48164870
thisExpr, /*isArrow=*/true, clang::SourceLocation(),
48174871
clang::NestedNameSpecifierLoc(), clang::SourceLocation(),
@@ -4865,7 +4919,13 @@ const clang::CXXMethodDecl *getCalledBaseCxxMethod(FuncDecl *baseMember) {
48654919
body->getElements().front().dyn_cast<Stmt *>());
48664920
if (!returnStmt)
48674921
return nullptr;
4868-
auto *callExpr = dyn_cast<CallExpr>(returnStmt->getResult());
4922+
Expr *returnExpr = returnStmt->getResult();
4923+
// A member ref expr for `.pointee` access can be wrapping a call
4924+
// when looking through the synthesized Swift body for `.pointee`
4925+
// accessor.
4926+
if (MemberRefExpr *mre = dyn_cast<MemberRefExpr>(returnExpr))
4927+
returnExpr = mre->getBase();
4928+
auto *callExpr = dyn_cast<CallExpr>(returnExpr);
48694929
if (!callExpr)
48704930
return nullptr;
48714931
auto *cv = callExpr->getCalledValue();
@@ -4966,9 +5026,94 @@ synthesizeBaseClassMethodBody(AbstractFunctionDecl *afd, void *context) {
49665026
return {body, /*isTypeChecked=*/true};
49675027
}
49685028

4969-
// Getters are relatively easy. Just cast and return the member:
4970-
// %0 = __swift_interopStaticCast<Base>(self)
4971-
// return %0.member
5029+
// Synthesize a C++ method that returns the field of interest from the base
5030+
// class. This lets Clang take care of the cast from the derived class
5031+
// to the base class while the field is accessed.
5032+
static clang::CXXMethodDecl *synthesizeCxxBaseGetterAccessorMethod(
5033+
ClangImporter &impl, const clang::CXXRecordDecl *derivedClass,
5034+
const clang::CXXRecordDecl *baseClass, const clang::FieldDecl *field) {
5035+
auto &clangCtx = impl.getClangASTContext();
5036+
auto &clangSema = impl.getClangSema();
5037+
5038+
// Create a new method in the derived class that calls the base method.
5039+
auto name = field->getDeclName();
5040+
if (name.isIdentifier()) {
5041+
std::string newName;
5042+
llvm::raw_string_ostream os(newName);
5043+
os << "__synthesizedBaseGetterAccessor_"
5044+
<< name.getAsIdentifierInfo()->getName();
5045+
name = clang::DeclarationName(
5046+
&impl.getClangPreprocessor().getIdentifierTable().get(os.str()));
5047+
}
5048+
auto returnType = field->getType();
5049+
if (returnType->isReferenceType())
5050+
returnType = returnType->getPointeeType();
5051+
clang::FunctionProtoType::ExtProtoInfo info;
5052+
info.TypeQuals.addConst();
5053+
info.ExceptionSpec.Type = clang::EST_NoThrow;
5054+
auto ftype = clangCtx.getFunctionType(returnType, {}, info);
5055+
auto newMethod = clang::CXXMethodDecl::Create(
5056+
clangCtx, const_cast<clang::CXXRecordDecl *>(derivedClass),
5057+
field->getSourceRange().getBegin(),
5058+
clang::DeclarationNameInfo(name, clang::SourceLocation()), ftype,
5059+
clangCtx.getTrivialTypeSourceInfo(ftype), clang::SC_None,
5060+
/*UsesFPIntrin=*/false, /*isInline=*/true,
5061+
clang::ConstexprSpecKind::Unspecified, field->getSourceRange().getEnd());
5062+
newMethod->setImplicit();
5063+
newMethod->setImplicitlyInline();
5064+
newMethod->setAccess(clang::AccessSpecifier::AS_public);
5065+
5066+
// Create a new Clang diagnostic pool to capture any diagnostics
5067+
// emitted during the construction of the method.
5068+
clang::sema::DelayedDiagnosticPool diagPool{
5069+
clangSema.DelayedDiagnostics.getCurrentPool()};
5070+
auto diagState = clangSema.DelayedDiagnostics.push(diagPool);
5071+
5072+
// Construct the method's body.
5073+
auto *thisExpr = new (clangCtx) clang::CXXThisExpr(
5074+
clang::SourceLocation(), newMethod->getThisType(), /*IsImplicit=*/false);
5075+
clang::QualType baseClassPtr = clangCtx.getRecordType(baseClass);
5076+
baseClassPtr.addConst();
5077+
baseClassPtr = clangCtx.getPointerType(baseClassPtr);
5078+
5079+
clang::CastKind Kind;
5080+
clang::CXXCastPath Path;
5081+
clangSema.CheckPointerConversion(thisExpr, baseClassPtr, Kind, Path,
5082+
/*IgnoreBaseAccess=*/false,
5083+
/*Diagnose=*/true);
5084+
auto conv = clangSema.ImpCastExprToType(thisExpr, baseClassPtr, Kind,
5085+
clang::VK_PRValue, &Path);
5086+
if (!conv.isUsable())
5087+
return nullptr;
5088+
auto memberExpr = clangSema.BuildMemberExpr(
5089+
conv.get(), /*isArrow=*/true, clang::SourceLocation(),
5090+
clang::NestedNameSpecifierLoc(), clang::SourceLocation(),
5091+
const_cast<clang::FieldDecl *>(field),
5092+
clang::DeclAccessPair::make(const_cast<clang::FieldDecl *>(field),
5093+
clang::AS_public),
5094+
/*HadMultipleCandidates=*/false,
5095+
clang::DeclarationNameInfo(field->getDeclName(), clang::SourceLocation()),
5096+
returnType, clang::VK_LValue, clang::OK_Ordinary);
5097+
auto returnCast = clangSema.ImpCastExprToType(
5098+
memberExpr, returnType, clang::CK_LValueToRValue, clang::VK_PRValue);
5099+
if (!returnCast.isUsable())
5100+
return nullptr;
5101+
auto returnStmt = clang::ReturnStmt::Create(clangCtx, clang::SourceLocation(),
5102+
returnCast.get(), nullptr);
5103+
5104+
// Check if there were any Clang errors during the construction
5105+
// of the method body.
5106+
clangSema.DelayedDiagnostics.popWithoutEmitting(diagState);
5107+
if (!diagPool.empty())
5108+
return nullptr;
5109+
newMethod->setBody(returnStmt);
5110+
return newMethod;
5111+
}
5112+
5113+
// Generates the body of a derived method, that invokes the base
5114+
// field getter or the base subscript.
5115+
// The method's body takes the following form:
5116+
// return self.__synthesizedBaseCall_fn(args...)
49725117
static std::pair<BraceStmt *, bool>
49735118
synthesizeBaseClassFieldGetterBody(AbstractFunctionDecl *afd, void *context) {
49745119
ASTContext &ctx = afd->getASTContext();
@@ -4980,48 +5125,80 @@ synthesizeBaseClassFieldGetterBody(AbstractFunctionDecl *afd, void *context) {
49805125
NominalTypeDecl *derivedStruct =
49815126
cast<NominalTypeDecl>(getterDecl->getDeclContext()->getAsDecl());
49825127

5128+
const clang::Decl *baseClangDecl;
5129+
if (baseClassVar->getClangDecl())
5130+
baseClangDecl = baseClassVar->getClangDecl();
5131+
else
5132+
baseClangDecl =
5133+
getCalledBaseCxxMethod(baseClassVar->getAccessor(AccessorKind::Get));
5134+
5135+
clang::CXXMethodDecl *baseGetterCxxMethod = nullptr;
5136+
if (auto *md = dyn_cast_or_null<clang::CXXMethodDecl>(baseClangDecl)) {
5137+
// Subscript operator, or `.pointee` wrapper is represented through a
5138+
// generated C++ method call that calls the base operator.
5139+
baseGetterCxxMethod = synthesizeCxxBaseMethod(
5140+
*static_cast<ClangImporter *>(ctx.getClangModuleLoader()),
5141+
cast<clang::CXXRecordDecl>(derivedStruct->getClangDecl()),
5142+
cast<clang::CXXRecordDecl>(baseStruct->getClangDecl()), md,
5143+
/*promoteReferenceReturnToValue=*/true,
5144+
/*forceConstQualifier=*/true);
5145+
} else if (auto *fd = dyn_cast_or_null<clang::FieldDecl>(baseClangDecl)) {
5146+
// Field getter is represented through a generated
5147+
// C++ method call that returns the value of the base field.
5148+
baseGetterCxxMethod = synthesizeCxxBaseGetterAccessorMethod(
5149+
*static_cast<ClangImporter *>(ctx.getClangModuleLoader()),
5150+
cast<clang::CXXRecordDecl>(derivedStruct->getClangDecl()),
5151+
cast<clang::CXXRecordDecl>(baseStruct->getClangDecl()), fd);
5152+
}
5153+
5154+
if (!baseGetterCxxMethod) {
5155+
ctx.Diags.diagnose(SourceLoc(), diag::failed_base_method_call_synthesis,
5156+
getterDecl, baseStruct);
5157+
auto body = BraceStmt::create(ctx, SourceLoc(), {}, SourceLoc(),
5158+
/*implicit=*/true);
5159+
return {body, true};
5160+
}
5161+
auto *baseGetterMethod = cast<FuncDecl>(
5162+
ctx.getClangModuleLoader()->importDeclDirectly(baseGetterCxxMethod));
5163+
49835164
auto selfDecl = getterDecl->getImplicitSelfDecl();
49845165
auto selfExpr = new (ctx) DeclRefExpr(selfDecl, DeclNameLoc(),
49855166
/*implicit*/ true);
49865167
selfExpr->setType(selfDecl->getTypeInContext());
49875168

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

4993-
auto *argList = ArgumentList::forImplicitUnlabeled(ctx, {selfExpr});
4994-
auto casted = CallExpr::createImplicit(ctx, staticCastRefExpr, argList);
4995-
casted->setType(baseStruct->getSelfInterfaceType());
4996-
casted->setThrows(false);
5171+
auto *baseMemberExpr =
5172+
new (ctx) DeclRefExpr(ConcreteDeclRef(baseGetterMethod), DeclNameLoc(),
5173+
/*Implicit=*/true);
5174+
baseMemberExpr->setType(baseGetterMethod->getInterfaceType());
5175+
5176+
auto baseMemberDotCallExpr =
5177+
DotSyntaxCallExpr::create(ctx, baseMemberExpr, SourceLoc(), selfArg);
5178+
baseMemberDotCallExpr->setType(baseGetterMethod->getMethodInterfaceType());
5179+
baseMemberDotCallExpr->setThrows(false);
49975180

4998-
Expr *baseMember = nullptr;
5181+
ArgumentList *argumentList;
49995182
if (auto subscript = dyn_cast<SubscriptDecl>(baseClassVar)) {
50005183
auto paramDecl = getterDecl->getParameters()->get(0);
5001-
auto paramRefExpr = new (ctx) DeclRefExpr(paramDecl,
5002-
DeclNameLoc(),
5003-
/*Implicit=*/ true);
5184+
auto paramRefExpr = new (ctx) DeclRefExpr(paramDecl, DeclNameLoc(),
5185+
/*Implicit=*/true);
50045186
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());
5187+
argumentList = ArgumentList::forImplicitUnlabeled(ctx, {paramRefExpr});
50095188
} 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());
5189+
argumentList = ArgumentList::forImplicitUnlabeled(ctx, {});
50205190
}
50215191

5022-
auto ret = new (ctx) ReturnStmt(SourceLoc(), baseMember);
5023-
auto body = BraceStmt::create(ctx, SourceLoc(), {ret}, SourceLoc(),
5024-
/*implicit*/ true);
5192+
auto *baseMemberCallExpr =
5193+
CallExpr::createImplicit(ctx, baseMemberDotCallExpr, argumentList);
5194+
baseMemberCallExpr->setType(baseGetterMethod->getResultInterfaceType());
5195+
baseMemberCallExpr->setThrows(false);
5196+
5197+
auto returnStmt = new (ctx) ReturnStmt(SourceLoc(), baseMemberCallExpr,
5198+
/*implicit=*/true);
5199+
5200+
auto body = BraceStmt::create(ctx, SourceLoc(), {returnStmt}, SourceLoc(),
5201+
/*implicit=*/true);
50255202
return {body, /*isTypeChecked=*/true};
50265203
}
50275204

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)