mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Support pass-by-move when calling a C++ function taking by value. (#7135)
Previously, we picked a single Carbon parameter pattern for each C++ parameter pattern. This doesn't work well in cases where the Carbon semantics and the C++ semantics are not perfectly aligned. In particular, when a parameter is passed by value in C++, that might mean either pass-by-move (which in Carbon would best be modeled by a `var` pattern, as no other form of parameter would perform a move) or pass-by-copy (which in Carbon would best be modeled by a value parameter, as a `var` parameter would force an extra copy). After this change, we compute a passing mode for each parameter based on the implicit conversion sequence from the argument to the parameter as determined by C++ overload resolution, and use that to determine the Carbon pattern corresponding to each C++ parameter. This results in potentially generating multiple different thunks for the same C++ function if it's called in different ways, but we already did that to handle default arguments and list-initialization. The passing modes are included in the thunk mangling. Add a new value store for clang decl signatures, which capture the information about parameter passing mode as well as the other existing information about different ways that a C++ function might be imported to Carbon. Most of the rules for computing passing modes are the same as before: const references use pass by value, non-const lvalue references use pass-by-ref, non-const rvalue references use pass-by-var. But for C++ non-reference parameters, pick between pass-by-value and pass-by-var based on whether the implicit conversion sequence was effectively performing a copy. Prefer pass-by-value if either would work and they'd do the same thing. We still use pass-by-value for const references, even when the argument is an lvalue and we could pass a reference; we may want to change this in future. For virtual functions, we try to pick a worst-case passing mode, as we can only pick a single signature for what goes in the vtable. Calls to virtual functions will still use a thunk to C++, allowing variance in the calling convention at call sites. We don't allow variance in the overriders as we don't implement support for thunks for virtual functions yet. We currently use pass-by-value for const reference parameters here, but that should probably change at some point. Assisted-by: Gemini via Antigravity
This commit is contained in:
@@ -76,7 +76,7 @@ namespace {
|
||||
struct DeclInfo {
|
||||
// If null, no C++ decl was found and no witness can be created.
|
||||
clang::NamedDecl* decl = nullptr;
|
||||
SemIR::ClangDeclKey::Signature signature;
|
||||
SemIR::ClangDeclSignatureId signature_id;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
@@ -98,7 +98,7 @@ static auto GetFunctionId(Context& context, SemIR::LocId loc_id,
|
||||
}
|
||||
|
||||
auto fn_id =
|
||||
ImportCppFunctionDecl(context, loc_id, cpp_fn, decl_info.signature);
|
||||
ImportCppFunctionDecl(context, loc_id, cpp_fn, decl_info.signature_id);
|
||||
if (fn_id == SemIR::ErrorInst::InstId) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
@@ -109,6 +109,18 @@ static auto GetFunctionId(Context& context, SemIR::LocId loc_id,
|
||||
return fn_id;
|
||||
}
|
||||
|
||||
// Creates a signature with `Normal` kind and the given parameter passing
|
||||
// modes, and adds it to the value store.
|
||||
static auto MakeSignature(
|
||||
Context& context,
|
||||
std::initializer_list<SemIR::ClangDeclSignature::PassingMode> modes,
|
||||
SemIR::ClangDeclSignature::PassingMode self_passing_mode =
|
||||
SemIR::ClangDeclSignature::PassingMode::ByRef)
|
||||
-> SemIR::ClangDeclSignatureId {
|
||||
return context.clang_decl_signatures().Add(SemIR::ClangDeclSignature::Make(
|
||||
modes, SemIR::ClangDeclSignature::Normal, self_passing_mode));
|
||||
}
|
||||
|
||||
static auto BuildCopyWitness(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ConstantId query_self_const_id,
|
||||
@@ -131,9 +143,12 @@ static auto BuildCopyWitness(
|
||||
})) {
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
|
||||
SemIR::ClangDeclSignatureId signature_id = MakeSignature(
|
||||
context, {SemIR::ClangDeclSignature::PassingMode::ByValue});
|
||||
auto decl_info = DeclInfo{.decl = clang_sema.LookupCopyingConstructor(
|
||||
class_decl, clang::Qualifiers::Const),
|
||||
.signature = {.num_params = 1}};
|
||||
.signature_id = signature_id};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
@@ -168,8 +183,14 @@ static auto BuildCppUnsafeDerefWitness(
|
||||
context.TODO(loc_id, "operator* overload sets not implemented yet");
|
||||
return SemIR::ErrorInst::InstId;
|
||||
}
|
||||
|
||||
// TODO: Parameterize the interface by the form of the operand and compute the
|
||||
// appropriate passing mode here.
|
||||
SemIR::ClangDeclSignatureId signature_id =
|
||||
MakeSignature(context, {}, SemIR::ClangDeclSignature::PassingMode::ByRef);
|
||||
|
||||
auto decl_info =
|
||||
DeclInfo{.decl = *candidates.begin(), .signature = {.num_params = 0}};
|
||||
DeclInfo{.decl = *candidates.begin(), .signature_id = signature_id};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
@@ -204,9 +225,11 @@ static auto BuildDefaultWitness(
|
||||
// That happens if class_decl->hasUninitializedExplicitInitFields() is true.
|
||||
//
|
||||
// TODO: Consider treating such types as not implementing `Default`.
|
||||
SemIR::ClangDeclSignatureId signature_id = MakeSignature(context, {});
|
||||
|
||||
auto decl_info =
|
||||
DeclInfo{.decl = clang_sema.LookupDefaultConstructor(class_decl),
|
||||
.signature = {.num_params = 0}};
|
||||
.signature_id = signature_id};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
@@ -227,8 +250,10 @@ static auto BuildDestroyWitness(
|
||||
if (!class_decl) {
|
||||
return SemIR::InstId::None;
|
||||
}
|
||||
SemIR::ClangDeclSignatureId signature_id = MakeSignature(context, {});
|
||||
|
||||
auto decl_info = DeclInfo{.decl = clang_sema.LookupDestructor(class_decl),
|
||||
.signature = {.num_params = 0}};
|
||||
.signature_id = signature_id};
|
||||
auto fn_id = GetFunctionId(context, loc_id, decl_info);
|
||||
if (fn_id == SemIR::ErrorInst::InstId || fn_id == SemIR::InstId::None) {
|
||||
return fn_id;
|
||||
@@ -375,8 +400,8 @@ static auto LookupCppMethod(
|
||||
|
||||
auto decl_info = DeclInfo{
|
||||
.decl = *lookup_info.begin(),
|
||||
.signature = {.num_params = 0},
|
||||
};
|
||||
.signature_id = MakeSignature(
|
||||
context, {}, SemIR::ClangDeclSignature::PassingMode::ByValue)};
|
||||
return GetFunctionId(context, loc_id, decl_info);
|
||||
}
|
||||
|
||||
|
||||
+145
-63
@@ -70,6 +70,12 @@
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
auto IsObjectMemberFunction(const clang::FunctionDecl& decl) -> bool {
|
||||
const auto* method = dyn_cast<clang::CXXMethodDecl>(&decl);
|
||||
return method && !method->isStatic() &&
|
||||
!isa<clang::CXXConstructorDecl>(&decl);
|
||||
}
|
||||
|
||||
// Adds the name to the scope with the given `access_kind` and `inst_id`.
|
||||
// `inst_id` must have a value.
|
||||
static auto AddNameToScope(Context& context, SemIR::NameScopeId scope_id,
|
||||
@@ -504,6 +510,7 @@ static auto ImportNamespaceDecl(Context& context,
|
||||
clang::NamespaceDecl* clang_decl)
|
||||
-> SemIR::InstId {
|
||||
auto key = SemIR::ClangDeclKey(clang_decl);
|
||||
|
||||
// Check if the declaration is already mapped.
|
||||
if (SemIR::InstId existing_inst_id = LookupClangDeclInstId(context, key);
|
||||
existing_inst_id.has_value()) {
|
||||
@@ -833,6 +840,75 @@ static auto ImportClassObjectRepr(Context& context, SemIR::ClassId class_id,
|
||||
.layout_id = context.custom_layouts().Add(layout)}));
|
||||
}
|
||||
|
||||
// Returns the passing mode to use for a given virtual function's object
|
||||
// parameter.
|
||||
static auto GetVirtualFunctionSelfPassingMode(
|
||||
const clang::CXXMethodDecl* method_decl)
|
||||
-> SemIR::ClangDeclSignature::PassingMode {
|
||||
if (method_decl->getMethodQualifiers().hasConst()) {
|
||||
// Map these signatures to pass-by-value:
|
||||
//
|
||||
// virtual void f() const;
|
||||
// virtual void f() const&;
|
||||
// virtual void f() const&&;
|
||||
//
|
||||
// In each case, we expect `self` to not be modified.
|
||||
return SemIR::ClangDeclSignature::PassingMode::ByValue;
|
||||
}
|
||||
|
||||
// Map anything else to pass-by-reference. This includes `&&`-qualified
|
||||
// functions, which we can't map to pass-by-var since that would perform a
|
||||
// slicing copy at the call site, which would be disastrous for a virtual
|
||||
// function call.
|
||||
// TODO: Find a better way to handle such cases, perhaps with a library type
|
||||
// representing a `&&` parameter.
|
||||
return SemIR::ClangDeclSignature::PassingMode::ByRef;
|
||||
}
|
||||
|
||||
// Returns the passing mode to use for a virtual function parameter of the given
|
||||
// type.
|
||||
static auto GetVirtualFunctionParamPassingMode(clang::QualType type)
|
||||
-> SemIR::ClangDeclSignature::PassingMode {
|
||||
if (type->isReferenceType() &&
|
||||
type.getNonReferenceType().isConstQualified()) {
|
||||
// For `const &`, `const &&`, use pass by value.
|
||||
return SemIR::ClangDeclSignature::PassingMode::ByValue;
|
||||
}
|
||||
|
||||
if (type->isLValueReferenceType()) {
|
||||
// For non-const `&`, use pass by reference.
|
||||
return SemIR::ClangDeclSignature::PassingMode::ByRef;
|
||||
}
|
||||
|
||||
// Map everything else to pass by var. That's the closest match we have to C++
|
||||
// parameter semantics, and is necessary to support parameters that are passed
|
||||
// by move.
|
||||
return SemIR::ClangDeclSignature::PassingMode::ByVar;
|
||||
}
|
||||
|
||||
// Computes the signature to use for the given imported virtual function. Unlike
|
||||
// with regular imported functions, we can only use a single signature here, so
|
||||
// we pick one conservatively.
|
||||
static auto MakeVirtualFunctionSignature(
|
||||
Context& context, const clang::CXXMethodDecl* method_decl)
|
||||
-> SemIR::ClangDeclSignatureId {
|
||||
SemIR::ClangDeclSignature signature = {
|
||||
.kind = SemIR::ClangDeclSignature::Normal,
|
||||
// Include all parameters. Virtual calls do not support using default
|
||||
// arguments.
|
||||
.num_params = static_cast<int32_t>(method_decl->getNumNonObjectParams()),
|
||||
.self_passing_mode = GetVirtualFunctionSelfPassingMode(method_decl),
|
||||
};
|
||||
signature.passing_modes.reserve(signature.num_params);
|
||||
for (auto i : llvm::seq(signature.num_params)) {
|
||||
const auto* param = method_decl->getNonObjectParameter(i);
|
||||
signature.passing_modes.push_back(
|
||||
GetVirtualFunctionParamPassingMode(param->getType()));
|
||||
}
|
||||
|
||||
return context.clang_decl_signatures().Add(signature);
|
||||
}
|
||||
|
||||
// Creates a Carbon class definition based on the information in the given Clang
|
||||
// class declaration, which is assumed to be for a class definition.
|
||||
static auto BuildClassDefinition(Context& context,
|
||||
@@ -879,7 +955,7 @@ static auto BuildClassDefinition(Context& context,
|
||||
vtable.push_back(ImportCppFunctionDecl(
|
||||
context, SemIR::LocId(import_ir_inst_id),
|
||||
const_cast<clang::CXXMethodDecl*>(method_decl),
|
||||
{.num_params = static_cast<int32_t>(method_decl->getNumParams())}));
|
||||
MakeVirtualFunctionSignature(context, method_decl)));
|
||||
}
|
||||
vtable.truncate(num_components);
|
||||
auto vtable_id = context.vtables().Add(
|
||||
@@ -1304,47 +1380,38 @@ struct ParameterTypeInfo {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// Maps a C++ parameter passing mode to a Carbon pattern kind.
|
||||
static auto GetParamPatternKindForPassingMode(
|
||||
SemIR::ClangDeclSignature::PassingMode mode) -> ParamPatternKind {
|
||||
switch (mode) {
|
||||
case SemIR::ClangDeclSignature::PassingMode::ByValue:
|
||||
return ParamPatternKind::Value;
|
||||
case SemIR::ClangDeclSignature::PassingMode::ByVar:
|
||||
return ParamPatternKind::Var;
|
||||
case SemIR::ClangDeclSignature::PassingMode::ByRef:
|
||||
return ParamPatternKind::Ref;
|
||||
}
|
||||
}
|
||||
|
||||
// Given the type of a C++ function parameter, returns information about the
|
||||
// type to use for the corresponding Carbon parameter.
|
||||
//
|
||||
// Note that if the parameter has a type for which `IsSimpleAbiType` returns
|
||||
// true, we must produce a parameter type that has the same calling convention
|
||||
// as the C++ type.
|
||||
static auto MapParameterType(Context& context, SemIR::LocId loc_id,
|
||||
clang::QualType param_type) -> ParameterTypeInfo {
|
||||
ParameterTypeInfo info = {.type = TypeExpr::None,
|
||||
.kind = ParamPatternKind::Value};
|
||||
|
||||
// Perform some custom mapping for parameters of reference type:
|
||||
//
|
||||
// * `T& x` -> `ref x: T`.
|
||||
// * `T&& x` -> `var x: T`.
|
||||
// * `const T& x` -> `x: T`.
|
||||
// * `const T&& x` -> `x: T`.
|
||||
static auto MapParameterType(
|
||||
Context& context, SemIR::LocId loc_id, clang::QualType param_type,
|
||||
SemIR::ClangDeclSignature::PassingMode passing_mode) -> ParameterTypeInfo {
|
||||
if (param_type->isReferenceType()) {
|
||||
clang::QualType pointee_type = param_type->getPointeeType();
|
||||
if (pointee_type.isConstQualified()) {
|
||||
// TODO: Consider only doing this if `const` is the only qualifier. For
|
||||
// now, any other qualifier will fail when mapping the type.
|
||||
auto split_type = pointee_type.getSplitUnqualifiedType();
|
||||
split_type.Quals.removeConst();
|
||||
pointee_type = context.ast_context().getQualifiedType(split_type);
|
||||
} else if (param_type->isLValueReferenceType()) {
|
||||
// Lvalue references map to a `ref` pattern.
|
||||
info.kind = ParamPatternKind::Ref;
|
||||
} else {
|
||||
// Rvalue references map to a `var` pattern. When given a value expression
|
||||
// as an argument, this will result in a copy. However, if the argument is
|
||||
// of class type, we will map its type to `const T`, which means overload
|
||||
// resolution won't allow the call anyway, so this only permits passing
|
||||
// value expressions of non-class type to a `T&&` parameter.
|
||||
info.kind = ParamPatternKind::Var;
|
||||
}
|
||||
param_type = pointee_type;
|
||||
// TODO: For now, we only remove `const`; any other qualifier will fail when
|
||||
// mapping the type.
|
||||
auto split_type =
|
||||
param_type.getNonReferenceType().getSplitUnqualifiedType();
|
||||
split_type.Quals.removeConst();
|
||||
param_type = context.ast_context().getQualifiedType(split_type);
|
||||
}
|
||||
|
||||
info.type = MapType(context, loc_id, param_type);
|
||||
return info;
|
||||
return {.type = MapType(context, loc_id, param_type),
|
||||
.kind = GetParamPatternKindForPassingMode(passing_mode)};
|
||||
}
|
||||
|
||||
// Returns a block for the implicit parameters of the given function
|
||||
@@ -1354,19 +1421,22 @@ static auto MapParameterType(Context& context, SemIR::LocId loc_id,
|
||||
static auto MakeImplicitParamPatternsBlockId(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ImportIRInstId import_ir_inst_id,
|
||||
const clang::FunctionDecl& clang_decl) -> SemIR::InstBlockId {
|
||||
const auto* method_decl = dyn_cast<clang::CXXMethodDecl>(&clang_decl);
|
||||
if (!method_decl || method_decl->isStatic() ||
|
||||
isa<clang::CXXConstructorDecl>(clang_decl)) {
|
||||
const clang::FunctionDecl& clang_decl,
|
||||
SemIR::ClangDeclSignatureId signature_id) -> SemIR::InstBlockId {
|
||||
if (!IsObjectMemberFunction(clang_decl)) {
|
||||
return SemIR::InstBlockId::Empty;
|
||||
}
|
||||
const auto* method_decl = cast<clang::CXXMethodDecl>(&clang_decl);
|
||||
|
||||
// Build a `self` parameter from the object parameter.
|
||||
BeginSubpattern(context);
|
||||
|
||||
clang::QualType param_type =
|
||||
method_decl->getFunctionObjectParameterReferenceType();
|
||||
auto param_info = MapParameterType(context, loc_id, param_type);
|
||||
const auto& signature = context.clang_decl_signatures().Get(signature_id);
|
||||
SemIR::ClangDeclSignature::PassingMode passing_mode =
|
||||
signature.self_passing_mode;
|
||||
auto param_info = MapParameterType(context, loc_id, param_type, passing_mode);
|
||||
auto [type_inst_id, type_id] = param_info.type;
|
||||
SemIR::ExprRegionId type_expr_region_id =
|
||||
ConsumeSubpatternExpr(context, type_inst_id);
|
||||
@@ -1399,8 +1469,9 @@ static auto MakeImplicitParamPatternsBlockId(
|
||||
static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ImportIRInstId import_ir_inst_id,
|
||||
const clang::FunctionDecl& clang_decl,
|
||||
SemIR::ClangDeclKey::Signature signature)
|
||||
SemIR::ClangDeclSignatureId signature_id)
|
||||
-> SemIR::InstBlockId {
|
||||
const auto& signature = context.clang_decl_signatures().Get(signature_id);
|
||||
llvm::SmallVector<SemIR::InstId> param_ids;
|
||||
llvm::SmallVector<SemIR::InstId> param_type_ids;
|
||||
param_ids.reserve(signature.num_params);
|
||||
@@ -1411,7 +1482,7 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
|
||||
clang_decl.getNumNonObjectParams(), signature.num_params);
|
||||
const auto* function_type =
|
||||
clang_decl.getType()->castAs<clang::FunctionProtoType>();
|
||||
for (int i : llvm::seq(signature.num_params)) {
|
||||
for (auto i : llvm::seq(signature.num_params)) {
|
||||
const auto* param = clang_decl.getNonObjectParameter(i);
|
||||
clang::QualType orig_param_type = function_type->getParamType(
|
||||
clang_decl.hasCXXExplicitFunctionObjectParameter() + i);
|
||||
@@ -1425,7 +1496,8 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
|
||||
// Mark the start of a region of insts, needed for the type expression
|
||||
// created later with the call of `ConsumeSubpatternExpr()`.
|
||||
BeginSubpattern(context);
|
||||
auto param_info = MapParameterType(context, loc_id, param_type);
|
||||
auto param_info = MapParameterType(context, loc_id, param_type,
|
||||
signature.GetPassingMode(i));
|
||||
auto [type_inst_id, type_id] = param_info.type;
|
||||
// Type expression of the binding pattern - a single-entry/single-exit
|
||||
// region that allows control flow in the type expression e.g. fn F(x: if C
|
||||
@@ -1460,12 +1532,12 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id,
|
||||
}
|
||||
|
||||
switch (signature.kind) {
|
||||
case SemIR::ClangDeclKey::Signature::Normal: {
|
||||
case SemIR::ClangDeclSignature::Normal: {
|
||||
// Use the converted parameter list as-is.
|
||||
break;
|
||||
}
|
||||
|
||||
case SemIR::ClangDeclKey::Signature::TuplePattern: {
|
||||
case SemIR::ClangDeclSignature::TuplePattern: {
|
||||
// Replace the parameters with a single tuple pattern containing the
|
||||
// converted parameter list.
|
||||
auto param_block_id = context.inst_blocks().Add(param_ids);
|
||||
@@ -1629,18 +1701,18 @@ struct FunctionSignatureInsts {
|
||||
static auto CreateFunctionSignatureInsts(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ImportIRInstId import_ir_inst_id, clang::FunctionDecl* clang_decl,
|
||||
SemIR::ClangDeclKey::Signature signature)
|
||||
SemIR::ClangDeclSignatureId signature_id)
|
||||
-> std::optional<FunctionSignatureInsts> {
|
||||
context.full_pattern_stack().StartImplicitParamList();
|
||||
auto implicit_param_patterns_id = MakeImplicitParamPatternsBlockId(
|
||||
context, loc_id, import_ir_inst_id, *clang_decl);
|
||||
context, loc_id, import_ir_inst_id, *clang_decl, signature_id);
|
||||
if (!implicit_param_patterns_id.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
context.full_pattern_stack().EndImplicitParamList();
|
||||
context.full_pattern_stack().StartExplicitParamList();
|
||||
auto param_patterns_id = MakeParamPatternsBlockId(
|
||||
context, loc_id, import_ir_inst_id, *clang_decl, signature);
|
||||
context, loc_id, import_ir_inst_id, *clang_decl, signature_id);
|
||||
if (!param_patterns_id.has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -1705,12 +1777,12 @@ static auto GetFunctionName(Context& context, clang::FunctionDecl* clang_decl)
|
||||
static auto ImportFunction(Context& context, SemIR::LocId loc_id,
|
||||
SemIR::ImportIRInstId import_ir_inst_id,
|
||||
clang::FunctionDecl* clang_decl,
|
||||
SemIR::ClangDeclKey::Signature signature)
|
||||
SemIR::ClangDeclSignatureId signature_id)
|
||||
-> std::optional<SemIR::FunctionId> {
|
||||
StartFunctionSignature(context);
|
||||
|
||||
auto function_params_insts = CreateFunctionSignatureInsts(
|
||||
context, loc_id, import_ir_inst_id, clang_decl, signature);
|
||||
context, loc_id, import_ir_inst_id, clang_decl, signature_id);
|
||||
|
||||
auto [pattern_block_id, decl_block_id] =
|
||||
FinishFunctionSignature(context, /*check_unused=*/false);
|
||||
@@ -1780,9 +1852,9 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
|
||||
context.imports().push_back(decl_id);
|
||||
|
||||
context.functions().Get(function_id).clang_decl_id =
|
||||
context.clang_decls().Add(
|
||||
{.key = SemIR::ClangDeclKey::ForFunctionDecl(clang_decl, signature),
|
||||
.inst_id = decl_id});
|
||||
context.clang_decls().Add({.key = SemIR::ClangDeclKey::ForFunctionDecl(
|
||||
clang_decl, signature_id),
|
||||
.inst_id = decl_id});
|
||||
|
||||
return function_id;
|
||||
}
|
||||
@@ -1794,9 +1866,9 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id,
|
||||
// the trailing parameters.
|
||||
static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
|
||||
clang::FunctionDecl* clang_decl,
|
||||
SemIR::ClangDeclKey::Signature signature)
|
||||
SemIR::ClangDeclSignatureId signature_id)
|
||||
-> SemIR::InstId {
|
||||
auto key = SemIR::ClangDeclKey::ForFunctionDecl(clang_decl, signature);
|
||||
auto key = SemIR::ClangDeclKey::ForFunctionDecl(clang_decl, signature_id);
|
||||
|
||||
// Check if the declaration is already mapped.
|
||||
if (SemIR::InstId existing_inst_id = LookupClangDeclInstId(context, key);
|
||||
@@ -1822,8 +1894,8 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
CARBON_CHECK(clang_decl->getFunctionType()->isFunctionProtoType(),
|
||||
"Not Prototype function (non-C++ code)");
|
||||
auto function_id =
|
||||
ImportFunction(context, loc_id, import_ir_inst_id, clang_decl, signature);
|
||||
auto function_id = ImportFunction(context, loc_id, import_ir_inst_id,
|
||||
clang_decl, signature_id);
|
||||
if (!function_id) {
|
||||
MarkFailedDecl(context, key);
|
||||
return SemIR::ErrorInst::InstId;
|
||||
@@ -1840,10 +1912,19 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
if (clang::FunctionDecl* thunk_clang_decl =
|
||||
BuildCppThunk(context, function_info)) {
|
||||
if (auto thunk_function_id = ImportFunction(
|
||||
context, loc_id, import_ir_inst_id, thunk_clang_decl,
|
||||
{.num_params =
|
||||
static_cast<int32_t>(thunk_clang_decl->getNumParams())})) {
|
||||
SemIR::ClangDeclSignature thunk_signature;
|
||||
thunk_signature.kind = SemIR::ClangDeclSignature::Normal;
|
||||
thunk_signature.num_params =
|
||||
static_cast<int32_t>(thunk_clang_decl->getNumParams());
|
||||
thunk_signature.passing_modes.assign(
|
||||
thunk_signature.num_params,
|
||||
SemIR::ClangDeclSignature::PassingMode::ByValue);
|
||||
SemIR::ClangDeclSignatureId thunk_signature_id =
|
||||
context.clang_decl_signatures().Add(std::move(thunk_signature));
|
||||
|
||||
if (auto thunk_function_id =
|
||||
ImportFunction(context, loc_id, import_ir_inst_id,
|
||||
thunk_clang_decl, thunk_signature_id)) {
|
||||
auto& thunk_function = context.functions().Get(*thunk_function_id);
|
||||
thunk_function.SetCppThunk(function_info.first_owning_decl_id);
|
||||
SemIR::InstId thunk_function_decl_id =
|
||||
@@ -1922,8 +2003,9 @@ static auto AddDependentUnimportedTypeDecls(Context& context,
|
||||
// and adds them to the given set.
|
||||
static auto AddDependentUnimportedFunctionDecls(
|
||||
Context& context, const clang::FunctionDecl& clang_decl,
|
||||
SemIR::ClangDeclKey::Signature signature, ImportWorklist& worklist)
|
||||
SemIR::ClangDeclSignatureId signature_id, ImportWorklist& worklist)
|
||||
-> void {
|
||||
const auto& signature = context.clang_decl_signatures().Get(signature_id);
|
||||
const auto* function_type =
|
||||
clang_decl.getType()->castAs<clang::FunctionProtoType>();
|
||||
for (int i : llvm::seq(clang_decl.hasCXXExplicitFunctionObjectParameter() +
|
||||
@@ -1943,7 +2025,7 @@ static auto AddDependentUnimportedDecls(Context& context,
|
||||
clang::Decl* clang_decl = key.decl;
|
||||
if (auto* clang_function_decl = clang_decl->getAsFunction()) {
|
||||
AddDependentUnimportedFunctionDecls(context, *clang_function_decl,
|
||||
key.signature, worklist);
|
||||
key.signature_id, worklist);
|
||||
} else if (auto* type_decl = dyn_cast<clang::TypeDecl>(clang_decl)) {
|
||||
if (!isa<clang::TagDecl>(clang_decl)) {
|
||||
AddDependentUnimportedTypeDecls(
|
||||
@@ -2063,7 +2145,7 @@ static auto ImportDeclAfterDependencies(Context& context, SemIR::LocId loc_id,
|
||||
clang::Decl* clang_decl = key.decl;
|
||||
if (auto* clang_function_decl = clang_decl->getAsFunction()) {
|
||||
return ImportFunctionDecl(context, loc_id, clang_function_decl,
|
||||
key.signature);
|
||||
key.signature_id);
|
||||
}
|
||||
if (auto* clang_namespace_decl = dyn_cast<clang::NamespaceDecl>(clang_decl)) {
|
||||
return ImportNamespaceDecl(context, clang_namespace_decl);
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
|
||||
namespace Carbon::Check {
|
||||
|
||||
// Returns whether the given function is an object member function. This is true
|
||||
// if it's a non-static member function and not a constructor. Object member
|
||||
// functions correspond to Carbon functions with a `self` parameter.
|
||||
// TODO: Find a better home for this function.
|
||||
auto IsObjectMemberFunction(const clang::FunctionDecl& decl) -> bool;
|
||||
|
||||
// Generates a C++ header that includes the imported cpp files, parses it,
|
||||
// generates the AST from it and links `SemIR::File` to it. Reports C++ errors
|
||||
// and warnings. If successful, adds a `Cpp` namespace.
|
||||
@@ -59,11 +65,11 @@ auto ImportCppDecl(Context& context, SemIR::LocId loc_id,
|
||||
// imported, returns the mapped instruction.
|
||||
inline auto ImportCppFunctionDecl(Context& context, SemIR::LocId loc_id,
|
||||
clang::FunctionDecl* clang_decl,
|
||||
SemIR::ClangDeclKey::Signature signature)
|
||||
SemIR::ClangDeclSignatureId signature_id)
|
||||
-> SemIR::InstId {
|
||||
return ImportCppDecl(
|
||||
context, loc_id,
|
||||
SemIR::ClangDeclKey::ForFunctionDecl(clang_decl, signature));
|
||||
SemIR::ClangDeclKey::ForFunctionDecl(clang_decl, signature_id));
|
||||
}
|
||||
|
||||
// Imports a function declaration from Clang to Carbon. If successful, returns
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "toolchain/check/type.h"
|
||||
#include "toolchain/check/type_completion.h"
|
||||
#include "toolchain/sem_ir/builtin_function_kind.h"
|
||||
#include "toolchain/sem_ir/clang_decl.h"
|
||||
#include "toolchain/sem_ir/cpp_initializer_list.h"
|
||||
#include "toolchain/sem_ir/ids.h"
|
||||
#include "toolchain/sem_ir/inst.h"
|
||||
@@ -285,44 +286,67 @@ static auto MakeCppStdInitializerListMake(Context& context, SemIR::LocId loc_id,
|
||||
static auto GetConversionSignatureToImport(
|
||||
Context& context, SemIR::InstId source_id,
|
||||
clang::InitializationSequence::StepKind step_kind,
|
||||
clang::FunctionDecl* function_decl) -> SemIR::ClangDeclKey::Signature {
|
||||
clang::FunctionDecl* function_decl, clang::DeclAccessPair found_decl,
|
||||
clang::Expr* arg_expr) -> SemIR::ClangDeclSignatureId {
|
||||
auto signature_kind = SemIR::ClangDeclSignature::Normal;
|
||||
clang::Expr* self_expr = nullptr;
|
||||
llvm::ArrayRef<clang::Expr*> arg_exprs(arg_expr);
|
||||
|
||||
// If we're performing a constructor initialization from a list, form a
|
||||
// function signature that takes a single tuple or struct pattern
|
||||
// instead of a function signature with one parameter per C++ parameter.
|
||||
if (step_kind ==
|
||||
clang::InitializationSequence::SK_ConstructorInitializationFromList) {
|
||||
// Initialization from a tuple `(a, b, c)` results in a constructor
|
||||
// function that takes a tuple pattern:
|
||||
//
|
||||
// fn Class.Class((a: A, b: B, c: C)) -> Class;
|
||||
//
|
||||
// The source type should always be a tuple type, because we don't support
|
||||
// C++ initialization from struct types.
|
||||
auto tuple_type = context.types().TryGetAs<SemIR::TupleType>(
|
||||
context.insts().Get(source_id).type_id());
|
||||
CARBON_CHECK(tuple_type, "List initialization from non-tuple type");
|
||||
|
||||
// Initialization from a tuple `(a, b, c)` results in a constructor
|
||||
// function that takes a tuple pattern:
|
||||
//
|
||||
// fn Class.Class((a: A, b: B, c: C)) -> Class;
|
||||
return {
|
||||
.kind = SemIR::ClangDeclKey::Signature::Kind::TuplePattern,
|
||||
.num_params = static_cast<int32_t>(
|
||||
context.inst_blocks().Get(tuple_type->type_elements_id).size())};
|
||||
arg_exprs = cast<clang::InitListExpr>(arg_expr)->inits();
|
||||
signature_kind = SemIR::ClangDeclSignature::TuplePattern;
|
||||
}
|
||||
|
||||
// Any other initialization using a constructor is calling a converting
|
||||
// constructor:
|
||||
//
|
||||
// fn Class.Class(a: A) -> Class;
|
||||
// In order to determine how to map the parameters, we need to build the
|
||||
// conversion sequence(s) again. Clang already threw them away. The only way
|
||||
// to do this is to "redo" overload resolution with our single candidate.
|
||||
clang::OverloadCandidateSet candidates(
|
||||
function_decl->getLocation(),
|
||||
clang::OverloadCandidateSet::CSK_InitByUserDefinedConversion);
|
||||
|
||||
if (isa<clang::CXXConstructorDecl>(function_decl)) {
|
||||
return {.kind = SemIR::ClangDeclKey::Signature::Kind::Normal,
|
||||
.num_params = 1};
|
||||
// This is either tuple list initialization as described above or a
|
||||
// constructor call:
|
||||
//
|
||||
// fn Class.Class(a: A) -> Class;
|
||||
context.clang_sema().AddOverloadCandidate(function_decl, found_decl,
|
||||
arg_exprs, candidates);
|
||||
} else {
|
||||
// Otherwise, the initialization is calling a conversion function
|
||||
// `Source::operator Dest`:
|
||||
//
|
||||
// fn Source.<conversion function>[self: Source]() -> Dest;
|
||||
auto* conversion_decl = cast<clang::CXXConversionDecl>(function_decl);
|
||||
self_expr = arg_expr;
|
||||
arg_exprs = {};
|
||||
context.clang_sema().AddMethodCandidate(
|
||||
conversion_decl, found_decl, conversion_decl->getParent(),
|
||||
self_expr->getType(), self_expr->Classify(context.ast_context()),
|
||||
arg_exprs, candidates);
|
||||
}
|
||||
|
||||
// Otherwise, the initialization is calling a conversion function
|
||||
// `Source::operator Dest`:
|
||||
//
|
||||
// fn Source.<conversion function>[self: Source]() -> Dest;
|
||||
CARBON_CHECK(isa<clang::CXXConversionDecl>(function_decl));
|
||||
return {.kind = SemIR::ClangDeclKey::Signature::Kind::Normal,
|
||||
.num_params = 0};
|
||||
clang::OverloadCandidateSet::iterator best;
|
||||
auto result = candidates.BestViableFunction(
|
||||
context.clang_sema(), function_decl->getLocation(), best);
|
||||
CARBON_CHECK(result == clang::OverloadingResult::OR_Success ||
|
||||
result == clang::OverloadingResult::OR_Deleted);
|
||||
|
||||
return ComputeClangDeclSignatureFromBestViableFunction(
|
||||
context, best, self_expr, arg_exprs, signature_kind);
|
||||
}
|
||||
|
||||
static auto LookupCppConversion(Context& context, SemIR::LocId loc_id,
|
||||
@@ -402,10 +426,12 @@ static auto LookupCppConversion(Context& context, SemIR::LocId loc_id,
|
||||
|
||||
sema.MarkFunctionReferenced(loc, step.Function.Function);
|
||||
|
||||
auto signature = GetConversionSignatureToImport(
|
||||
context, source_id, step.Kind, step.Function.Function);
|
||||
SemIR::ClangDeclSignatureId signature_id =
|
||||
GetConversionSignatureToImport(context, source_id, step.Kind,
|
||||
step.Function.Function,
|
||||
step.Function.FoundDecl, arg_expr);
|
||||
auto result_id = ImportCppFunctionDecl(
|
||||
context, loc_id, step.Function.Function, signature);
|
||||
context, loc_id, step.Function.Function, signature_id);
|
||||
if (auto fn_decl = context.insts().TryGetAsWithId<SemIR::FunctionDecl>(
|
||||
result_id)) {
|
||||
CheckCppOverloadAccess(context, loc_id, step.Function.FoundDecl,
|
||||
@@ -637,14 +663,18 @@ static auto FindClangOperator(Context& context, SemIR::LocId loc_id,
|
||||
sema.MarkFunctionReferenced(loc, best_viable_fn->Function);
|
||||
|
||||
// If this is an operator method, the first arg will be used as self.
|
||||
int32_t num_params = arg_exprs.size();
|
||||
if (isa<clang::CXXMethodDecl>(best_viable_fn->Function)) {
|
||||
--num_params;
|
||||
clang::Expr* self_expr = nullptr;
|
||||
auto arg_exprs_for_signature = arg_exprs;
|
||||
if (IsObjectMemberFunction(*best_viable_fn->Function)) {
|
||||
self_expr = arg_exprs_for_signature.consume_front();
|
||||
}
|
||||
|
||||
auto result_id =
|
||||
ImportCppFunctionDecl(context, loc_id, best_viable_fn->Function,
|
||||
{.num_params = num_params});
|
||||
SemIR::ClangDeclSignatureId signature_id =
|
||||
ComputeClangDeclSignatureFromBestViableFunction(
|
||||
context, best_viable_fn, self_expr, arg_exprs_for_signature);
|
||||
|
||||
auto result_id = ImportCppFunctionDecl(
|
||||
context, loc_id, best_viable_fn->Function, signature_id);
|
||||
if (result_id != SemIR::ErrorInst::InstId) {
|
||||
CheckCppOverloadAccess(
|
||||
context, loc_id, best_viable_fn->FoundDecl,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "toolchain/check/cpp/overload_resolution.h"
|
||||
|
||||
#include "clang/AST/DeclCXX.h"
|
||||
#include "clang/Basic/DiagnosticSema.h"
|
||||
#include "clang/Sema/Overload.h"
|
||||
#include "clang/Sema/Sema.h"
|
||||
@@ -68,9 +69,8 @@ static auto AddOverloadCandidates(
|
||||
|
||||
auto* fn_decl = template_decl ? template_decl->getTemplatedDecl()
|
||||
: cast<clang::FunctionDecl>(decl);
|
||||
auto* method_decl = dyn_cast<clang::CXXMethodDecl>(fn_decl);
|
||||
if (method_decl && !method_decl->isStatic() &&
|
||||
!isa<clang::CXXConstructorDecl>(fn_decl)) {
|
||||
if (IsObjectMemberFunction(*fn_decl)) {
|
||||
auto* method_decl = cast<clang::CXXMethodDecl>(fn_decl);
|
||||
clang::QualType self_type;
|
||||
clang::Expr::Classification self_classification;
|
||||
if (self_arg) {
|
||||
@@ -130,6 +130,139 @@ auto CheckCppOverloadAccess(
|
||||
.highest_allowed_access = allowed_access_kind});
|
||||
}
|
||||
|
||||
// Computes the passing mode for a C++ function parameter that is a reference.
|
||||
static auto ComputePassingModeForReferenceBinding(
|
||||
const clang::StandardConversionSequence& scs)
|
||||
-> SemIR::ClangDeclSignature::PassingMode {
|
||||
CARBON_CHECK(scs.ReferenceBinding);
|
||||
auto pointee_type = scs.getToType(2);
|
||||
if (pointee_type.isConstQualified() ||
|
||||
(scs.IsLvalueReference && scs.BindsToRvalue)) {
|
||||
// Reference to const is always mapped to Carbon pass by value. A non-const
|
||||
// lvalue reference bound to an rvalue only happens when initializing an
|
||||
// object parameter with no ref-qualifier from an rvalue, which we also
|
||||
// model as pass-by-value.
|
||||
return SemIR::ClangDeclSignature::PassingMode::ByValue;
|
||||
}
|
||||
// Rvalue reference to non-const is passed as a `var` to force a copy or move
|
||||
// in the caller. Lvalue reference to non-const is passed by reference.
|
||||
return scs.IsLvalueReference ? SemIR::ClangDeclSignature::PassingMode::ByRef
|
||||
: SemIR::ClangDeclSignature::PassingMode::ByVar;
|
||||
}
|
||||
|
||||
// Returns whether move-construction of type `type` is known to be equivalent to
|
||||
// a copy. If so, it's safe to map C++ pass-by-value into Carbon pass-by-value
|
||||
// instead of pass-by-var.
|
||||
static auto IsMoveEquivalentToCopy(clang::QualType type) {
|
||||
// We can pass by copy instead of by move if:
|
||||
// - The type is not a class type.
|
||||
auto* record_decl = type->getAsCXXRecordDecl();
|
||||
if (!record_decl) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// - The move constructor is defaulted and deleted or non-existent, in
|
||||
// which case overload resolution for a move will call the copy
|
||||
// constructor.
|
||||
if (!record_decl->hasMoveConstructor() ||
|
||||
(!record_decl->hasUserDeclaredMoveConstructor() &&
|
||||
record_decl->defaultedMoveConstructorIsDeleted())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// - Both move and copy are trivial and not deleted, in which case they
|
||||
// are equivalent.
|
||||
if (record_decl->hasTrivialMoveConstructor() &&
|
||||
!record_decl->defaultedMoveConstructorIsDeleted() &&
|
||||
record_decl->hasTrivialCopyConstructor() &&
|
||||
!record_decl->defaultedCopyConstructorIsDeleted()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise we need a move, so we pass by var.
|
||||
return false;
|
||||
}
|
||||
|
||||
auto GetPassingModeForCppParameter(const clang::ImplicitConversionSequence& ics,
|
||||
const clang::Expr* arg_expr)
|
||||
-> SemIR::ClangDeclSignature::PassingMode {
|
||||
if (ics.isStandard()) {
|
||||
const auto& scs = ics.Standard;
|
||||
if (scs.ReferenceBinding) {
|
||||
return ComputePassingModeForReferenceBinding(scs);
|
||||
}
|
||||
|
||||
// Most standard conversions can be mapped to Carbon pass by value. The
|
||||
// exception is where the source is an initializing expression of record
|
||||
// type, which we map to pass by var, unless a copy would do the same thing.
|
||||
if (arg_expr->isXValue() && !IsMoveEquivalentToCopy(arg_expr->getType())) {
|
||||
return SemIR::ClangDeclSignature::PassingMode::ByVar;
|
||||
}
|
||||
|
||||
return SemIR::ClangDeclSignature::PassingMode::ByValue;
|
||||
}
|
||||
|
||||
if (ics.isUserDefined()) {
|
||||
const auto& ucs = ics.UserDefined;
|
||||
if (ucs.After.ReferenceBinding) {
|
||||
return ComputePassingModeForReferenceBinding(ucs.After);
|
||||
}
|
||||
|
||||
const auto* ctor =
|
||||
dyn_cast_or_null<clang::CXXConstructorDecl>(ucs.ConversionFunction);
|
||||
if (ctor && ctor->isCopyConstructor()) {
|
||||
// Overload resolution wanted to call a copy constructor to initialize
|
||||
// this parameter. Pass by value instead; we'll copy in the thunk.
|
||||
return SemIR::ClangDeclSignature::PassingMode::ByValue;
|
||||
}
|
||||
|
||||
// We're calling a user-defined conversion, so we're performing
|
||||
// initialization. Pass by move unless the type being initialized doesn't
|
||||
// distinguish moves and copies.
|
||||
return IsMoveEquivalentToCopy(ucs.After.getToType(2))
|
||||
? SemIR::ClangDeclSignature::PassingMode::ByValue
|
||||
: SemIR::ClangDeclSignature::PassingMode::ByVar;
|
||||
}
|
||||
|
||||
// TODO: Support ellipsis conversion sequences.
|
||||
CARBON_FATAL("Unexpected kind of implicit conversion sequence");
|
||||
}
|
||||
|
||||
// Computes the signature for a C++ function candidate based on the conversions
|
||||
// performed on the arguments.
|
||||
auto ComputeClangDeclSignatureFromBestViableFunction(
|
||||
Context& context, clang::OverloadCandidateSet::iterator candidate,
|
||||
clang::Expr* self_expr, llvm::ArrayRef<clang::Expr*> arg_exprs,
|
||||
SemIR::ClangDeclSignature::Kind kind) -> SemIR::ClangDeclSignatureId {
|
||||
SemIR::ClangDeclSignature signature;
|
||||
signature.kind = kind;
|
||||
signature.num_params = static_cast<int32_t>(arg_exprs.size());
|
||||
signature.passing_modes.reserve(signature.num_params);
|
||||
|
||||
for (auto [i, arg_expr] : llvm::enumerate(arg_exprs)) {
|
||||
// Compute which conversion sequence corresponds to this argument.
|
||||
// TODO: Clang should expose a way to compute this.
|
||||
int conversion_index = i;
|
||||
if (auto* method = dyn_cast<clang::CXXMethodDecl>(candidate->Function)) {
|
||||
if (method->isStatic()) {
|
||||
// Static methods get an object parameter conversion at index 0, even
|
||||
// though there's no argument.
|
||||
++conversion_index;
|
||||
}
|
||||
}
|
||||
|
||||
signature.passing_modes.push_back(GetPassingModeForCppParameter(
|
||||
candidate->Conversions[conversion_index], arg_expr));
|
||||
}
|
||||
|
||||
if (IsObjectMemberFunction(*candidate->Function)) {
|
||||
signature.self_passing_mode =
|
||||
GetPassingModeForCppParameter(candidate->Conversions[0], self_expr);
|
||||
}
|
||||
|
||||
return context.clang_decl_signatures().Add(std::move(signature));
|
||||
}
|
||||
|
||||
auto PerformCppOverloadResolution(
|
||||
Context& context, SemIR::LocId loc_id,
|
||||
const SemIR::CppOverloadSet& overload_set,
|
||||
@@ -179,9 +312,12 @@ auto PerformCppOverloadResolution(
|
||||
case clang::OverloadingResult::OR_Success: {
|
||||
CARBON_CHECK(best_viable_fn->Function);
|
||||
CARBON_CHECK(!best_viable_fn->RewriteKind);
|
||||
SemIR::ClangDeclSignatureId signature_id =
|
||||
ComputeClangDeclSignatureFromBestViableFunction(
|
||||
context, best_viable_fn, self_expr, arg_exprs);
|
||||
|
||||
SemIR::InstId result_id = ImportCppFunctionDecl(
|
||||
context, loc_id, best_viable_fn->Function,
|
||||
{.num_params = static_cast<int32_t>(arg_exprs.size())});
|
||||
context, loc_id, best_viable_fn->Function, signature_id);
|
||||
if (result_id != SemIR::ErrorInst::InstId) {
|
||||
CheckCppOverloadAccess(
|
||||
context, loc_id, best_viable_fn->FoundDecl,
|
||||
|
||||
@@ -20,6 +20,18 @@ auto CheckCppOverloadAccess(
|
||||
SemIR::KnownInstId<SemIR::FunctionDecl> overload_inst_id,
|
||||
SemIR::NameScopeId parent_scope_id = SemIR::NameScopeId::None) -> void;
|
||||
|
||||
// Returns the passing mode to use for a parameter given the implicit
|
||||
// conversion sequence and the argument expression.
|
||||
auto GetPassingModeForCppParameter(const clang::ImplicitConversionSequence& ics,
|
||||
const clang::Expr* arg_expr)
|
||||
-> SemIR::ClangDeclSignature::PassingMode;
|
||||
|
||||
auto ComputeClangDeclSignatureFromBestViableFunction(
|
||||
Context& context, clang::OverloadCandidateSet::iterator candidate,
|
||||
clang::Expr* self_expr, llvm::ArrayRef<clang::Expr*> arg_exprs,
|
||||
SemIR::ClangDeclSignature::Kind kind = SemIR::ClangDeclSignature::Normal)
|
||||
-> SemIR::ClangDeclSignatureId;
|
||||
|
||||
// Resolves which function to call using Clang overload resolution. Returns an
|
||||
// instruction referring to that function, or an error instruction if overload
|
||||
// resolution failed.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "toolchain/check/control_flow.h"
|
||||
#include "toolchain/check/convert.h"
|
||||
#include "toolchain/check/cpp/context.h"
|
||||
#include "toolchain/check/cpp/import.h"
|
||||
#include "toolchain/check/literal.h"
|
||||
#include "toolchain/check/type.h"
|
||||
#include "toolchain/check/type_completion.h"
|
||||
@@ -74,23 +75,42 @@ static auto GetGlobalDecl(const clang::FunctionDecl* decl)
|
||||
static auto GenerateThunkMangledName(
|
||||
clang::MangleContext& mangle_context,
|
||||
const clang::FunctionDecl& callee_function_decl,
|
||||
SemIR::ClangDeclKey::Signature::Kind signature_kind, int num_params)
|
||||
-> std::string {
|
||||
const SemIR::ClangDeclSignature& signature) -> std::string {
|
||||
RawStringOstream mangled_name_stream;
|
||||
mangle_context.mangleName(GetGlobalDecl(&callee_function_decl),
|
||||
mangled_name_stream);
|
||||
switch (signature_kind) {
|
||||
case SemIR::ClangDeclKey::Signature::Normal:
|
||||
switch (signature.kind) {
|
||||
case SemIR::ClangDeclSignature::Normal:
|
||||
mangled_name_stream << ".carbon_thunk";
|
||||
break;
|
||||
case SemIR::ClangDeclKey::Signature::TuplePattern:
|
||||
case SemIR::ClangDeclSignature::TuplePattern:
|
||||
mangled_name_stream << ".carbon_thunk_tuple";
|
||||
break;
|
||||
}
|
||||
|
||||
if (num_params !=
|
||||
static_cast<int>(callee_function_decl.getNumNonObjectParams())) {
|
||||
mangled_name_stream << num_params;
|
||||
// Append passing modes.
|
||||
// TODO: Pick one "likely" set of passing modes for the function and omit the
|
||||
// suffix for that signature.
|
||||
mangled_name_stream << ".";
|
||||
auto append_mode = [&](SemIR::ClangDeclSignature::PassingMode mode) {
|
||||
switch (mode) {
|
||||
case SemIR::ClangDeclSignature::PassingMode::ByValue:
|
||||
mangled_name_stream << "_";
|
||||
break;
|
||||
case SemIR::ClangDeclSignature::PassingMode::ByVar:
|
||||
mangled_name_stream << "v";
|
||||
break;
|
||||
case SemIR::ClangDeclSignature::PassingMode::ByRef:
|
||||
mangled_name_stream << "r";
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (IsObjectMemberFunction(callee_function_decl)) {
|
||||
append_mode(signature.self_passing_mode);
|
||||
}
|
||||
for (auto mode : signature.passing_modes) {
|
||||
append_mode(mode);
|
||||
}
|
||||
|
||||
return mangled_name_stream.TakeStr();
|
||||
@@ -143,15 +163,15 @@ namespace {
|
||||
// Information about the callee of a thunk.
|
||||
struct CalleeFunctionInfo {
|
||||
explicit CalleeFunctionInfo(clang::FunctionDecl* decl,
|
||||
SemIR::ClangDeclKey::Signature signature)
|
||||
const SemIR::ClangDeclSignature* signature)
|
||||
: decl(decl),
|
||||
signature_kind(signature.kind),
|
||||
num_params(signature.num_params +
|
||||
signature(signature),
|
||||
num_params(signature->num_params +
|
||||
decl->hasCXXExplicitFunctionObjectParameter()) {
|
||||
auto& ast_context = decl->getASTContext();
|
||||
const auto* method_decl = dyn_cast<clang::CXXMethodDecl>(decl);
|
||||
bool is_ctor = isa<clang::CXXConstructorDecl>(decl);
|
||||
has_object_parameter = method_decl && !method_decl->isStatic() && !is_ctor;
|
||||
has_object_parameter = IsObjectMemberFunction(*decl);
|
||||
if (has_object_parameter && method_decl->isImplicitObjectMemberFunction()) {
|
||||
implicit_object_parameter_type =
|
||||
method_decl->getFunctionObjectParameterReferenceType();
|
||||
@@ -195,8 +215,8 @@ struct CalleeFunctionInfo {
|
||||
// The callee function.
|
||||
clang::FunctionDecl* decl;
|
||||
|
||||
// The kind of function signature being imported.
|
||||
SemIR::ClangDeclKey::Signature::Kind signature_kind;
|
||||
// The signature of the function being imported.
|
||||
const SemIR::ClangDeclSignature* signature;
|
||||
|
||||
// The number of explicit parameters to import. This may be less than the
|
||||
// number of parameters that the function has if default arguments are being
|
||||
@@ -229,25 +249,28 @@ auto IsCppThunkRequired(Context& context, const SemIR::Function& function)
|
||||
}
|
||||
|
||||
const auto& decl_info = context.clang_decls().Get(function.clang_decl_id);
|
||||
const auto& signature =
|
||||
context.clang_decl_signatures().Get(decl_info.key.signature_id);
|
||||
auto* decl = cast<clang::FunctionDecl>(decl_info.key.decl);
|
||||
if (decl_info.key.signature.kind != SemIR::ClangDeclKey::Signature::Normal ||
|
||||
decl_info.key.signature.num_params !=
|
||||
static_cast<int>(decl->getNumNonObjectParams())) {
|
||||
if (signature.kind != SemIR::ClangDeclSignature::Normal ||
|
||||
signature.num_params != static_cast<int>(decl->getNumNonObjectParams())) {
|
||||
// We require a thunk if the number of parameters we want isn't all of them.
|
||||
// This happens if default arguments are in use, or (eventually) when
|
||||
// calling a varargs function.
|
||||
return true;
|
||||
}
|
||||
|
||||
CalleeFunctionInfo callee_info(decl, decl_info.key.signature);
|
||||
CalleeFunctionInfo callee_info(decl, &signature);
|
||||
if (!callee_info.has_simple_return_type) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto& ast_context = context.ast_context();
|
||||
if (callee_info.has_implicit_object_parameter() &&
|
||||
!IsSimpleAbiType(ast_context, callee_info.implicit_object_parameter_type,
|
||||
/*for_parameter=*/true)) {
|
||||
(!IsSimpleAbiType(ast_context, callee_info.implicit_object_parameter_type,
|
||||
/*for_parameter=*/true) ||
|
||||
signature.self_passing_mode ==
|
||||
SemIR::ClangDeclSignature::PassingMode::ByVar)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -255,7 +278,9 @@ auto IsCppThunkRequired(Context& context, const SemIR::Function& function)
|
||||
decl->getType()->castAs<clang::FunctionProtoType>();
|
||||
for (int i : llvm::seq(decl->getNumParams())) {
|
||||
if (!IsSimpleAbiType(ast_context, function_type->getParamType(i),
|
||||
/*for_parameter=*/true)) {
|
||||
/*for_parameter=*/true) ||
|
||||
signature.GetPassingMode(i) ==
|
||||
SemIR::ClangDeclSignature::PassingMode::ByVar) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -437,10 +462,8 @@ static auto CreateThunkFunctionDecl(
|
||||
// Set asm("<callee function mangled name>.carbon_thunk").
|
||||
thunk_function_decl->addAttr(clang::AsmLabelAttr::CreateImplicit(
|
||||
ast_context,
|
||||
GenerateThunkMangledName(
|
||||
context.cpp_context()->clang_mangle_context(), *callee_info.decl,
|
||||
callee_info.signature_kind,
|
||||
callee_info.num_params - callee_info.has_explicit_object_parameter()),
|
||||
GenerateThunkMangledName(context.cpp_context()->clang_mangle_context(),
|
||||
*callee_info.decl, *callee_info.signature),
|
||||
clang_loc));
|
||||
|
||||
// Set function declaration type source info.
|
||||
@@ -453,11 +476,10 @@ static auto CreateThunkFunctionDecl(
|
||||
// Builds a reference to the given parameter thunk. If `type` is specified, that
|
||||
// is the callee parameter type that's being held by the parameter, and
|
||||
// conversions will be performed as necessary to recover a value of that type.
|
||||
static auto BuildThunkParamRef(clang::Sema& sema,
|
||||
clang::FunctionDecl* thunk_function_decl,
|
||||
unsigned thunk_index,
|
||||
clang::QualType type = clang::QualType())
|
||||
-> clang::Expr* {
|
||||
static auto BuildThunkParamRef(
|
||||
clang::Sema& sema, clang::FunctionDecl* thunk_function_decl,
|
||||
unsigned thunk_index, SemIR::ClangDeclSignature::PassingMode passing_mode,
|
||||
clang::QualType type = clang::QualType()) -> clang::Expr* {
|
||||
clang::ParmVarDecl* thunk_param =
|
||||
thunk_function_decl->getParamDecl(thunk_index);
|
||||
clang::SourceLocation clang_loc = thunk_param->getLocation();
|
||||
@@ -472,15 +494,10 @@ static auto BuildThunkParamRef(clang::Sema& sema,
|
||||
call_arg = deref_result.get();
|
||||
}
|
||||
|
||||
// Cast to an rvalue when initializing an rvalue reference. The validity of
|
||||
// the initialization of the reference should be validated by the caller of
|
||||
// the thunk.
|
||||
//
|
||||
// TODO: Consider inserting a cast to an rvalue in more cases. Note that we
|
||||
// currently pass pointers to non-temporary objects as the argument when
|
||||
// calling a thunk, so we'll need to either change that or generate
|
||||
// different thunks depending on whether we're moving from each parameter.
|
||||
if (!type.isNull() && type->isRValueReferenceType()) {
|
||||
// Cast to an xvalue when using pass-by-`var` or when initializing an rvalue
|
||||
// reference (which might be passed by value if it's const-qualified).
|
||||
if (passing_mode == SemIR::ClangDeclSignature::PassingMode::ByVar ||
|
||||
thunk_param->getType()->isRValueReferenceType()) {
|
||||
call_arg = clang::ImplicitCastExpr::Create(
|
||||
sema.getASTContext(), call_arg->getType(), clang::CK_NoOp, call_arg,
|
||||
nullptr, clang::ExprValueKind::VK_XValue, clang::FPOptionsOverride());
|
||||
@@ -497,6 +514,7 @@ static auto BuildParamRefForCalleeArg(clang::Sema& sema,
|
||||
unsigned thunk_index = callee_info.GetThunkParamIndex(callee_index);
|
||||
return BuildThunkParamRef(
|
||||
sema, thunk_function_decl, thunk_index,
|
||||
callee_info.signature->GetPassingMode(callee_index),
|
||||
callee_info.decl->getParamDecl(callee_index)->getType());
|
||||
}
|
||||
|
||||
@@ -538,8 +556,9 @@ static auto BuildThunkBody(CppContext& cpp_context, clang::Sema& sema,
|
||||
clang::QualType object_param_type =
|
||||
cast<clang::CXXMethodDecl>(callee_info.decl)
|
||||
->getFunctionObjectParameterReferenceType();
|
||||
auto* object_param_ref =
|
||||
BuildThunkParamRef(sema, thunk_function_decl, 0, object_param_type);
|
||||
auto* object_param_ref = BuildThunkParamRef(
|
||||
sema, thunk_function_decl, 0, callee_info.signature->self_passing_mode,
|
||||
object_param_type);
|
||||
constexpr bool IsArrow = false;
|
||||
auto object =
|
||||
sema.PerformMemberExprBaseConversion(object_param_ref, IsArrow);
|
||||
@@ -597,7 +616,8 @@ static auto BuildThunkBody(CppContext& cpp_context, clang::Sema& sema,
|
||||
}
|
||||
|
||||
auto* return_object_addr = BuildThunkParamRef(
|
||||
sema, thunk_function_decl, callee_info.GetThunkReturnParamIndex());
|
||||
sema, thunk_function_decl, callee_info.GetThunkReturnParamIndex(),
|
||||
SemIR::ClangDeclSignature::PassingMode::ByValue);
|
||||
auto return_type = callee_info.effective_return_type.getNonReferenceType();
|
||||
auto* return_type_info =
|
||||
sema.Context.getTrivialTypeSourceInfo(return_type, clang_loc);
|
||||
@@ -635,8 +655,9 @@ auto BuildCppThunk(Context& context, const SemIR::Function& callee_function)
|
||||
// shouldn't consider it here. However, to do that, we would need to cache the
|
||||
// thunks we build so that we don't build the same thunk multiple times if
|
||||
// it's used with multiple different signature kinds.
|
||||
CalleeFunctionInfo callee_info(callee_function_decl,
|
||||
clang_decl_key.signature);
|
||||
const auto& signature =
|
||||
context.clang_decl_signatures().Get(clang_decl_key.signature_id);
|
||||
CalleeFunctionInfo callee_info(callee_function_decl, &signature);
|
||||
|
||||
// Build the thunk function declaration.
|
||||
auto thunk_param_types =
|
||||
|
||||
@@ -518,7 +518,10 @@ static auto InventPrimitiveClangArg(Context& context, FormInfo form)
|
||||
|
||||
case SemIR::ExprCategory::ReprInitializing:
|
||||
case SemIR::ExprCategory::InPlaceInitializing:
|
||||
value_kind = clang::ExprValueKind::VK_PRValue;
|
||||
// A Carbon initializing expression is much more similar to a C++ prvalue
|
||||
// than a C++ xvalue, but we encode it as an xvalue expression to request
|
||||
// that it be passed through the thunk by move rather than by copy.
|
||||
value_kind = clang::ExprValueKind::VK_XValue;
|
||||
break;
|
||||
|
||||
case SemIR::ExprCategory::Mixed:
|
||||
|
||||
Reference in New Issue
Block a user