Skip to content

Commit 6011040

Browse files
authored
Rework handling of C++ references. (#6268)
For now, map C++ reference types to const-qualified Carbon pointer types rather than picking between a (non-const) pointer or a value type. This fixes misbehavior in lowering for reference members in classes and reference return types. Update the special-case handling for references as function parameters so that it continues to map const reference parameters to Carbon pass-by-value, and unify the code paths for `self` parameters and other parameters, which were mostly doing the same thing but had some subtle differences. Add references to the list of types that we can pass to and from C++ directly, without needing an additional layer of thunks.
1 parent 33166ff commit 6011040

File tree

17 files changed

+842
-434
lines changed

17 files changed

+842
-434
lines changed

toolchain/check/cpp/import.cpp

Lines changed: 94 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,8 +1212,7 @@ static auto MapQualifiedType(Context& context, clang::QualType type,
12121212

12131213
if (quals.hasConst()) {
12141214
auto type_id = GetConstType(context, type_expr.inst_id);
1215-
type_expr = {.inst_id = context.types().GetInstId(type_id),
1216-
.type_id = type_id};
1215+
type_expr = TypeExpr::ForUnsugared(context, type_id);
12171216
quals.removeConst();
12181217
}
12191218

@@ -1260,19 +1259,18 @@ static auto MapPointerType(Context& context, SemIR::LocId loc_id,
12601259
return pointer_type_expr;
12611260
}
12621261

1263-
// Maps a C++ reference type to a Carbon type.
1264-
// We map `T&` to `T*`, and `T&&` to `T`.
1262+
// Maps a C++ reference type to a Carbon type. We map all references to
1263+
// pointers for now. Note that when mapping function parameters and return
1264+
// types, a different rule is used; see MapParameterType for details.
12651265
// TODO: Revisit this and decide what we really want to do here.
12661266
static auto MapReferenceType(Context& context, clang::QualType type,
12671267
TypeExpr referenced_type_expr) -> TypeExpr {
12681268
CARBON_CHECK(type->isReferenceType());
1269-
1270-
if (!type->isLValueReferenceType()) {
1271-
return referenced_type_expr;
1272-
}
1273-
1274-
return TypeExpr::ForUnsugared(
1275-
context, GetPointerType(context, referenced_type_expr.inst_id));
1269+
SemIR::TypeId pointer_type_id =
1270+
GetPointerType(context, referenced_type_expr.inst_id);
1271+
pointer_type_id =
1272+
GetConstType(context, context.types().GetInstId(pointer_type_id));
1273+
return TypeExpr::ForUnsugared(context, pointer_type_id);
12761274
}
12771275

12781276
// Maps a C++ type to a Carbon type. `type` should not be canonicalized because
@@ -1318,6 +1316,81 @@ static auto MapType(Context& context, SemIR::LocId loc_id, clang::QualType type)
13181316
return mapped;
13191317
}
13201318

1319+
namespace {
1320+
// Information about how to map a C++ parameter type into Carbon.
1321+
struct ParameterTypeInfo {
1322+
// The type to use for the Carbon parameter.
1323+
TypeExpr type;
1324+
// Whether to build an `addr` pattern.
1325+
bool want_addr_pattern;
1326+
// If building an `addr` pattern, the type matched by that pattern.
1327+
TypeExpr pointee_type;
1328+
};
1329+
} // namespace
1330+
1331+
// Given the type of a C++ function parameter, returns information about the
1332+
// type to use for the corresponding Carbon parameter.
1333+
//
1334+
// Note that if the parameter has a type for which `IsSimpleAbiType` returns
1335+
// true, we must produce a parameter type that has the same calling convention
1336+
// as the C++ type.
1337+
//
1338+
// TODO: Use `ref` instead of `addr`.
1339+
static auto MapParameterType(Context& context, SemIR::LocId loc_id,
1340+
clang::QualType param_type) -> ParameterTypeInfo {
1341+
ParameterTypeInfo info = {.type = TypeExpr::None,
1342+
.want_addr_pattern = false,
1343+
.pointee_type = TypeExpr::None};
1344+
1345+
// Perform some custom mapping for parameters of reference type:
1346+
//
1347+
// * `T& x` -> `addr x: T*`.
1348+
// * `const T& x` -> `x: T`.
1349+
// * `T&& x` -> `x: T`.
1350+
//
1351+
// TODO: For the `&&` mapping, we allow an rvalue reference to bind to a
1352+
// durable reference expression. This should not be allowed.
1353+
if (param_type->isReferenceType()) {
1354+
clang::QualType pointee_type = param_type->getPointeeType();
1355+
if (param_type->isLValueReferenceType()) {
1356+
if (pointee_type.isConstQualified()) {
1357+
// TODO: Consider only doing this if `const` is the only qualifier. For
1358+
// now, any other qualifier will fail when mapping the type.
1359+
auto split_type = pointee_type.getSplitUnqualifiedType();
1360+
split_type.Quals.removeConst();
1361+
pointee_type = context.ast_context().getQualifiedType(split_type);
1362+
} else {
1363+
// The reference will map to a pointer. Request an `addr` pattern.
1364+
info.want_addr_pattern = true;
1365+
}
1366+
}
1367+
param_type = pointee_type;
1368+
}
1369+
1370+
info.type = MapType(context, loc_id, param_type);
1371+
if (info.want_addr_pattern && info.type.inst_id.has_value()) {
1372+
info.pointee_type = info.type;
1373+
info.type = TypeExpr::ForUnsugared(
1374+
context, GetPointerType(context, info.pointee_type.inst_id));
1375+
}
1376+
return info;
1377+
}
1378+
1379+
// Finishes building the pattern to use for a function parameter, given the
1380+
// binding pattern and information about how the parameter is being mapped into
1381+
// Carbon.
1382+
static auto FinishParameterPattern(Context& context, SemIR::InstId pattern_id,
1383+
ParameterTypeInfo info) -> SemIR::InstId {
1384+
if (!info.want_addr_pattern || pattern_id == SemIR::ErrorInst::InstId) {
1385+
return pattern_id;
1386+
}
1387+
return AddPatternInst(
1388+
context, {SemIR::LocId(pattern_id),
1389+
SemIR::AddrPattern({.type_id = GetPatternType(
1390+
context, info.pointee_type.type_id),
1391+
.inner_id = pattern_id})});
1392+
}
1393+
13211394
// Returns a block for the implicit parameters of the given function
13221395
// declaration. Because function templates are not yet supported, this currently
13231396
// only contains the `self` parameter. On error, produces a diagnostic and
@@ -1334,32 +1407,10 @@ static auto MakeImplicitParamPatternsBlockId(
13341407
// Build a `self` parameter from the object parameter.
13351408
BeginSubpattern(context);
13361409

1337-
// Perform some special-case mapping for the object parameter:
1338-
//
1339-
// - If it's a const reference to T, produce a by-value `self: T` parameter.
1340-
// - If it's a non-const reference to T, produce an `addr self: T*`
1341-
// parameter.
1342-
// - Otherwise, map it directly, which will currently fail for `&&`-qualified
1343-
// methods.
1344-
//
1345-
// TODO: Some of this mapping should be performed for all parameters.
13461410
clang::QualType param_type =
13471411
method_decl->getFunctionObjectParameterReferenceType();
1348-
bool addr_self = false;
1349-
if (param_type->isLValueReferenceType()) {
1350-
param_type = param_type.getNonReferenceType();
1351-
if (param_type.isConstQualified()) {
1352-
// TODO: Consider only doing this if `const` is the only qualifier. For
1353-
// now, any other qualifier will fail when mapping the type.
1354-
auto split_type = param_type.getSplitUnqualifiedType();
1355-
split_type.Quals.removeConst();
1356-
param_type = method_decl->getASTContext().getQualifiedType(split_type);
1357-
} else {
1358-
addr_self = true;
1359-
}
1360-
}
1361-
1362-
auto [type_inst_id, type_id] = MapType(context, loc_id, param_type);
1412+
auto param_info = MapParameterType(context, loc_id, param_type);
1413+
auto [type_inst_id, type_id] = param_info.type;
13631414
SemIR::ExprRegionId type_expr_region_id =
13641415
EndSubpatternAsExpr(context, type_inst_id);
13651416

@@ -1372,10 +1423,8 @@ static auto MakeImplicitParamPatternsBlockId(
13721423

13731424
// TODO: Fill in a location once available.
13741425
auto pattern_id =
1375-
addr_self ? AddAddrSelfParamPattern(context, SemIR::LocId::None,
1376-
type_expr_region_id, type_inst_id)
1377-
: AddSelfParamPattern(context, SemIR::LocId::None,
1378-
type_expr_region_id, type_id);
1426+
AddSelfParamPattern(context, loc_id, type_expr_region_id, type_id);
1427+
pattern_id = FinishParameterPattern(context, pattern_id, param_info);
13791428

13801429
return context.inst_blocks().Add({pattern_id});
13811430
}
@@ -1409,12 +1458,11 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
14091458
// TODO: The presence of qualifiers here is probably a Clang bug.
14101459
clang::QualType param_type = orig_param_type.getUnqualifiedType();
14111460

1412-
bool is_ref_param = param_type->isLValueReferenceType();
1413-
14141461
// Mark the start of a region of insts, needed for the type expression
14151462
// created later with the call of `EndSubpatternAsExpr()`.
14161463
BeginSubpattern(context);
1417-
auto [orig_type_inst_id, type_id] = MapType(context, loc_id, param_type);
1464+
auto param_info = MapParameterType(context, loc_id, param_type);
1465+
auto [orig_type_inst_id, type_id] = param_info.type;
14181466
// Type expression of the binding pattern - a single-entry/single-exit
14191467
// region that allows control flow in the type expression e.g. fn F(x: if C
14201468
// then i32 else i64).
@@ -1452,17 +1500,7 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
14521500
{.type_id = context.insts().Get(pattern_id).type_id(),
14531501
.subpattern_id = pattern_id,
14541502
.index = SemIR::CallParamIndex::None})});
1455-
if (is_ref_param) {
1456-
// We map `T&` parameters to `addr param: T*`.
1457-
// TODO: Revisit this and decide what we really want to do here.
1458-
pattern_id = AddPatternInst(
1459-
context, {param_loc_id,
1460-
SemIR::AddrPattern(
1461-
{.type_id = GetPatternType(
1462-
context, context.types().GetTypeIdForTypeInstId(
1463-
orig_type_inst_id)),
1464-
.inner_id = pattern_id})});
1465-
}
1503+
pattern_id = FinishParameterPattern(context, pattern_id, param_info);
14661504
params.push_back(pattern_id);
14671505
}
14681506
return context.inst_blocks().Add(params);
@@ -1476,6 +1514,9 @@ static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id,
14761514
clang::FunctionDecl* clang_decl) -> TypeExpr {
14771515
clang::QualType orig_ret_type = clang_decl->getReturnType();
14781516
if (!orig_ret_type->isVoidType()) {
1517+
// TODO: We should eventually map reference returns to non-pointer types
1518+
// here. We should return by `ref` for `T&` return types once `ref` return
1519+
// is implemented.
14791520
auto [orig_type_inst_id, type_id] = MapType(context, loc_id, orig_ret_type);
14801521
if (!orig_type_inst_id.has_value()) {
14811522
context.TODO(loc_id, llvm::formatv("Unsupported: return type: {0}",

toolchain/check/cpp/thunk.cpp

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,20 @@ static auto IsSimpleAbiType(clang::ASTContext& ast_context,
5858
return true;
5959
}
6060

61-
if (!for_parameter && type->isLValueReferenceType()) {
62-
// An lvalue reference return type maps to a pointer, which uses the same
63-
// lowering rule.
61+
if (type->isReferenceType()) {
62+
if (for_parameter) {
63+
// A reference parameter has a simple ABI if it's a non-const lvalue
64+
// reference. Otherwise, we map it to pass-by-value, and it's only simple
65+
// if the type uses a pointer value representation.
66+
//
67+
// TODO: Check whether the pointee type maps to a Carbon type that uses a
68+
// pointer value representation, and treat it as simple if so.
69+
return type->isLValueReferenceType() &&
70+
!type->getPointeeType().isConstQualified();
71+
}
72+
73+
// A reference return type is always mapped to a Carbon pointer, which uses
74+
// the same ABI rule as a C++ reference.
6475
return true;
6576
}
6677

@@ -93,7 +104,8 @@ struct CalleeFunctionInfo {
93104
bool is_ctor = isa<clang::CXXConstructorDecl>(decl);
94105
has_object_parameter = method_decl && !method_decl->isStatic() && !is_ctor;
95106
if (has_object_parameter && method_decl->isImplicitObjectMemberFunction()) {
96-
implicit_this_type = method_decl->getThisType();
107+
implicit_object_parameter_type =
108+
method_decl->getFunctionObjectParameterReferenceType();
97109
}
98110
effective_return_type =
99111
is_ctor ? ast_context.getCanonicalTagType(method_decl->getParent())
@@ -104,7 +116,7 @@ struct CalleeFunctionInfo {
104116

105117
// Returns whether this callee has an implicit `this` parameter.
106118
auto has_implicit_object_parameter() const -> bool {
107-
return !implicit_this_type.isNull();
119+
return !implicit_object_parameter_type.isNull();
108120
}
109121

110122
// Returns whether this callee has an explicit `this` parameter.
@@ -143,9 +155,9 @@ struct CalleeFunctionInfo {
143155
// implicit.
144156
bool has_object_parameter;
145157

146-
// If the callee has an implicit object parameter, the corresponding `this`
147-
// type. Otherwise a null type.
148-
clang::QualType implicit_this_type;
158+
// If the callee has an implicit object parameter, the type of that parameter,
159+
// which will always be a reference type. Otherwise a null type.
160+
clang::QualType implicit_object_parameter_type;
149161

150162
// The return type that the callee has when viewed from Carbon. This is the
151163
// C++ return type, except that constructors return the class type in Carbon
@@ -180,16 +192,10 @@ auto IsCppThunkRequired(Context& context, const SemIR::Function& function)
180192
}
181193

182194
auto& ast_context = context.ast_context();
183-
if (callee_info.has_implicit_object_parameter()) {
184-
// TODO: The object parameter is a reference parameter, but we don't force a
185-
// thunk here like we do for explicit reference parameters in the case where
186-
// we would map the parameter to an `addr` parameter. We should make this
187-
// behavior consistent.
188-
auto* method_decl = cast<clang::CXXMethodDecl>(decl);
189-
if (method_decl->getRefQualifier() == clang::RQ_RValue ||
190-
method_decl->getMethodQualifiers().hasConst()) {
191-
return true;
192-
}
195+
if (callee_info.has_implicit_object_parameter() &&
196+
!IsSimpleAbiType(ast_context, callee_info.implicit_object_parameter_type,
197+
/*for_parameter=*/true)) {
198+
return true;
193199
}
194200

195201
const auto* function_type =
@@ -238,8 +244,7 @@ static auto BuildThunkParameterTypes(clang::ASTContext& ast_context,
238244
llvm::SmallVector<clang::QualType> thunk_param_types;
239245
thunk_param_types.reserve(callee_info.num_thunk_params());
240246
if (callee_info.has_implicit_object_parameter()) {
241-
thunk_param_types.push_back(
242-
GetNonnullType(ast_context, callee_info.implicit_this_type));
247+
thunk_param_types.push_back(callee_info.implicit_object_parameter_type);
243248
}
244249

245250
const auto* function_type =
@@ -371,23 +376,22 @@ static auto BuildThunkParamRef(clang::Sema& sema,
371376
clang::ExprResult deref_result =
372377
sema.BuildUnaryOp(nullptr, clang_loc, clang::UO_Deref, call_arg);
373378
CARBON_CHECK(deref_result.isUsable());
374-
375-
// Cast to an rvalue when initializing an rvalue reference. The validity of
376-
// the initialization of the reference should be validated by the caller of
377-
// the thunk.
378-
//
379-
// TODO: Consider inserting a cast to an rvalue in more cases. Note that we
380-
// currently pass pointers to non-temporary objects as the argument when
381-
// calling a thunk, so we'll need to either change that or generate
382-
// different thunks depending on whether we're moving from each parameter.
383-
if (type->isRValueReferenceType()) {
384-
deref_result = clang::ImplicitCastExpr::Create(
385-
sema.getASTContext(), deref_result.get()->getType(), clang::CK_NoOp,
386-
deref_result.get(), nullptr, clang::ExprValueKind::VK_XValue,
387-
clang::FPOptionsOverride());
388-
}
389379
call_arg = deref_result.get();
390380
}
381+
382+
// Cast to an rvalue when initializing an rvalue reference. The validity of
383+
// the initialization of the reference should be validated by the caller of
384+
// the thunk.
385+
//
386+
// TODO: Consider inserting a cast to an rvalue in more cases. Note that we
387+
// currently pass pointers to non-temporary objects as the argument when
388+
// calling a thunk, so we'll need to either change that or generate
389+
// different thunks depending on whether we're moving from each parameter.
390+
if (!type.isNull() && type->isRValueReferenceType()) {
391+
call_arg = clang::ImplicitCastExpr::Create(
392+
sema.getASTContext(), call_arg->getType(), clang::CK_NoOp, call_arg,
393+
nullptr, clang::ExprValueKind::VK_XValue, clang::FPOptionsOverride());
394+
}
391395
return call_arg;
392396
}
393397

@@ -443,14 +447,14 @@ static auto BuildThunkBody(clang::Sema& sema,
443447
callee_info.has_explicit_object_parameter()
444448
? callee_info.decl->getParamDecl(0)->getType()
445449
: clang::QualType());
446-
bool is_arrow = callee_info.has_implicit_object_parameter();
450+
constexpr bool IsArrow = false;
447451
auto object =
448-
sema.PerformMemberExprBaseConversion(object_param_ref, is_arrow);
452+
sema.PerformMemberExprBaseConversion(object_param_ref, IsArrow);
449453
if (object.isInvalid()) {
450454
return clang::StmtError();
451455
}
452456
callee = sema.BuildMemberExpr(
453-
object.get(), is_arrow, clang_loc, clang::NestedNameSpecifierLoc(),
457+
object.get(), IsArrow, clang_loc, clang::NestedNameSpecifierLoc(),
454458
clang::SourceLocation(), callee_info.decl,
455459
clang::DeclAccessPair::make(callee_info.decl, clang::AS_public),
456460
/*HadMultipleCandidates=*/false, clang::DeclarationNameInfo(),

toolchain/check/pattern.cpp

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -157,15 +157,4 @@ auto AddSelfParamPattern(Context& context, SemIR::LocId loc_id,
157157
return pattern_id;
158158
}
159159

160-
auto AddAddrSelfParamPattern(Context& context, SemIR::LocId loc_id,
161-
SemIR::ExprRegionId type_expr_region_id,
162-
SemIR::TypeInstId type_inst_id) -> SemIR::InstId {
163-
auto pattern_id = AddSelfParamPattern(context, loc_id, type_expr_region_id,
164-
GetPointerType(context, type_inst_id));
165-
return AddPatternInst<SemIR::AddrPattern>(
166-
context, loc_id,
167-
{.type_id = GetPatternType(context, SemIR::AutoType::TypeId),
168-
.inner_id = pattern_id});
169-
}
170-
171160
} // namespace Carbon::Check

toolchain/check/pattern.h

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,6 @@ auto AddSelfParamPattern(Context& context, SemIR::LocId loc_id,
5959
SemIR::ExprRegionId type_expr_region_id,
6060
SemIR::TypeId type_id) -> SemIR::InstId;
6161

62-
// As the above, but for `addr self: Self*`.
63-
auto AddAddrSelfParamPattern(Context& context, SemIR::LocId loc_id,
64-
SemIR::ExprRegionId type_expr_region_id,
65-
SemIR::TypeInstId type_inst_id) -> SemIR::InstId;
66-
6762
} // namespace Carbon::Check
6863

6964
#endif // CARBON_TOOLCHAIN_CHECK_PATTERN_H_

0 commit comments

Comments
 (0)