From f65572a1e6cfb15d00d19a7e42ca71befbcb40de Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Tue, 29 Sep 2026 23:03:45 +0000 Subject: [PATCH] Generalize `CalleeFunctionInfo` and reuse it in C++ import (#7787) This is a step toward supporting C++ function pointer types, which need to be imported and thunked in much the same way as C++ functions, but have a different underlying representation. `CalleeFunctionInfo` gives us a way to abstract away the representation differences, so expressing import and thunking in terms of `CalleeFunctionInfo` lets us reuse that code for function pointers. Actual support for function pointers will come in a follow-up PR, but the API design choices I've made here are driven by that use case. --- toolchain/check/cpp/import.cpp | 231 +++++++------- toolchain/check/cpp/thunk.cpp | 302 +++++++----------- toolchain/check/cpp/thunk.h | 145 ++++++++- .../basics/raw_sem_ir/cpp_interop.carbon | 2 + 4 files changed, 381 insertions(+), 299 deletions(-) diff --git a/toolchain/check/cpp/import.cpp b/toolchain/check/cpp/import.cpp index 3c8c896158c7..71203c31bd90 100644 --- a/toolchain/check/cpp/import.cpp +++ b/toolchain/check/cpp/import.cpp @@ -477,6 +477,9 @@ static auto GetParentDecl(clang::Decl* clang_decl) -> clang::Decl* { // was already imported. static auto GetParentNameScopeId(Context& context, clang::Decl* clang_decl) -> SemIR::NameScopeId { + if (clang_decl == nullptr) { + return SemIR::NameScopeId::Package; + } auto* parent_decl = GetParentDecl(clang_decl); if (auto* tag_decl = dyn_cast(parent_decl)) { @@ -1524,10 +1527,9 @@ static auto MapParameterType( // passing mode, and parameter name. Returns the IDs of the pattern inst and // the inst representing the parameter type. static auto MakeParamPattern( - Context& context, SemIR::LocId loc_id, - SemIR::ImportIRInstId import_ir_inst_id, clang::QualType type, - SemIR::ClangDeclSignature::PassingMode passing_mode, SemIR::NameId name_id) - -> std::pair { + Context& context, SemIR::LocId loc_id, SemIR::LocId param_loc_id, + clang::QualType type, SemIR::ClangDeclSignature::PassingMode passing_mode, + SemIR::NameId name_id) -> std::pair { // Mark the start of a region of insts, needed for the type expression // created later with the call of `ConsumeExprRegionForPattern()`. BeginExprRegionForPattern(context); @@ -1545,8 +1547,8 @@ static auto MakeParamPattern( return {SemIR::ErrorInst::InstId, SemIR::ErrorInst::TypeInstId}; } - return {AddParamPattern(context, import_ir_inst_id, name_id, - type_expr_region_id, type_id, param_info.kind), + return {AddParamPattern(context, param_loc_id, name_id, type_expr_region_id, + type_id, param_info.kind), type_inst_id}; } @@ -1558,30 +1560,22 @@ static auto MakeParamPattern( // TODO: Consider refactoring to extract and reuse more logic from // `HandleAnyBindingPattern()`. static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id, - SemIR::ImportIRInstId import_ir_inst_id, - const clang::FunctionDecl& clang_decl, - SemIR::ClangDeclSignatureId signature_id) + const CalleeFunctionInfo& function_info) -> SemIR::InstBlockId { // The `self` parameter of a method is the first entry in the explicit // parameter list. Build it (if any) first, then the remaining explicit // parameters. - bool has_self_param = IsObjectMemberFunction(clang_decl); - const auto& signature = context.clang_decl_signatures().Get(signature_id); llvm::SmallVector param_ids; llvm::SmallVector param_type_ids; - param_ids.reserve(signature.num_params + has_self_param); - param_type_ids.reserve(signature.num_params); - if (has_self_param) { - const auto* method_decl = cast(&clang_decl); - clang::QualType param_type = - method_decl->getFunctionObjectParameterReferenceType(); - const auto& signature = context.clang_decl_signatures().Get(signature_id); - + param_ids.reserve(function_info.num_carbon_params()); + param_type_ids.reserve(function_info.num_carbon_params()); + if (function_info.carbon_has_self_param()) { // TODO: Use a location associated with the object parameter instead of the // location of the function as a whole. - auto [self_param_pattern_id, _] = - MakeParamPattern(context, loc_id, import_ir_inst_id, param_type, - signature.self_passing_mode, SemIR::NameId::SelfValue); + auto [self_param_pattern_id, _] = MakeParamPattern( + context, loc_id, function_info.sem_ir_loc, + function_info.self_param_type, + function_info.signature->self_passing_mode, SemIR::NameId::SelfValue); if (self_param_pattern_id == SemIR::ErrorInst::InstId) { return SemIR::InstBlockId::None; } @@ -1589,16 +1583,16 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id, // We don't push to param_type_ids because the self parameter can't be part // of a parameter tuple, which is the only case where we use param_type_ids. } - CARBON_CHECK(static_cast(clang_decl.getNumNonObjectParams()) >= - signature.num_params, - "Function has fewer parameters than requested: {0} < {1}", - clang_decl.getNumNonObjectParams(), signature.num_params); - const auto* function_type = - clang_decl.getType()->castAs(); - 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); + // The signature parameters always exclude the self parameter, like the callee + // args, so we can use the callee args offset to index into the signature + // params. + for (auto signature_index : + llvm::seq(function_info.num_callee_params - + function_info.callee_arg_to_callee_param_offset())) { + int callee_index = + signature_index + function_info.callee_arg_to_callee_param_offset(); + clang::QualType orig_param_type = + function_info.function_type->getParamType(callee_index); // The parameter type is decayed but hasn't necessarily had its qualifiers // removed. @@ -1606,21 +1600,19 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id, clang::QualType param_type = ClangGetUnqualifiedTypePreserveNonNull(context, orig_param_type); - llvm::StringRef param_name = param->getName(); - SemIR::NameId name_id = - param_name.empty() - // Translate an unnamed parameter to an underscore to - // match Carbon's naming of unnamed/unused function params. - ? SemIR::NameId::Underscore - : AddIdentifierName(context, param_name); + SemIR::NameId name_id = SemIR::NameId::Underscore; + if (auto* identifier_info = + function_info.GetCalleeParamIdentifier(callee_index)) { + name_id = AddIdentifierName(context, identifier_info->getName()); + } - SemIR::ImportIRInstId param_loc_id = - AddImportIRInst(context.sem_ir(), param->getLocation()); + SemIR::ImportIRInstId param_loc_id = AddImportIRInst( + context.sem_ir(), function_info.GetCalleeParamLocation(callee_index)); // TODO: Add template support. - auto [pattern_id, type_inst_id] = - MakeParamPattern(context, loc_id, param_loc_id, param_type, - signature.GetPassingMode(i), name_id); + auto [pattern_id, type_inst_id] = MakeParamPattern( + context, loc_id, param_loc_id, param_type, + function_info.signature->GetPassingMode(signature_index), name_id); if (pattern_id == SemIR::ErrorInst::InstId) { return SemIR::InstBlockId::None; } @@ -1628,7 +1620,7 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id, param_type_ids.push_back(type_inst_id); } - switch (signature.kind) { + switch (function_info.signature->kind) { case SemIR::ClangDeclSignature::Normal: { // Use the converted parameter list as-is. break; @@ -1637,13 +1629,13 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id, case SemIR::ClangDeclSignature::TuplePattern: { // Replace the parameters with a single tuple pattern containing the // converted parameter list. - CARBON_CHECK(!has_self_param); + CARBON_CHECK(!function_info.carbon_has_self_param()); auto param_block_id = context.inst_blocks().Add(param_ids); auto tuple_pattern_type_id = GetPatternType(context, GetTupleType(context, param_type_ids)); SemIR::InstId pattern_id = AddInst( context, SemIR::LocIdAndInst::RuntimeVerified( - context.sem_ir(), import_ir_inst_id, + context.sem_ir(), function_info.sem_ir_loc, SemIR::TuplePattern{.type_id = tuple_pattern_type_id, .elements_id = param_block_id})); param_ids = {pattern_id}; @@ -1659,7 +1651,7 @@ static auto MakeParamPatternsBlockId(Context& context, SemIR::LocId loc_id, // are treated as returning a class instance. // TODO: Support more return types. static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id, - clang::FunctionDecl* clang_decl) + const CalleeFunctionInfo& function_info) -> Context::FormExpr { auto make_init_form = [&](SemIR::TypeInstId type_component_inst_id) { SemIR::InitForm inst = {.type_id = SemIR::FormType::TypeId, @@ -1671,7 +1663,7 @@ static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id, .type_component_inst_id = type_component_inst_id}; return context.constant_values().GetInstId(TryEvalInst(context, inst)); }; - clang::QualType orig_ret_type = clang_decl->getReturnType(); + clang::QualType orig_ret_type = function_info.function_type->getReturnType(); if (!orig_ret_type->isVoidType()) { bool is_reference = orig_ret_type->isReferenceType(); if (is_reference) { @@ -1695,7 +1687,8 @@ static auto GetReturnTypeExpr(Context& context, SemIR::LocId loc_id, return result; } - auto* ctor = dyn_cast(clang_decl); + auto* ctor = + dyn_cast_if_present(function_info.decl); if (!ctor) { // void. return {.form_inst_id = SemIR::InstId::None, @@ -1725,9 +1718,10 @@ struct ReturnInfo { // and the returned return_type_inst_id will be `SemIR::ErrorInst::InstId`. // Constructors are treated as returning a class instance. static auto GetReturnInfo(Context& context, SemIR::LocId loc_id, - clang::FunctionDecl* clang_decl) -> ReturnInfo { + const CalleeFunctionInfo& function_info) + -> ReturnInfo { auto [form_inst_id, type_inst_id, type_id] = - GetReturnTypeExpr(context, loc_id, clang_decl); + GetReturnTypeExpr(context, loc_id, function_info); if (!form_inst_id.has_value()) { // void. return {.return_type_inst_id = SemIR::TypeInstId::None, @@ -1740,14 +1734,16 @@ static auto GetReturnInfo(Context& context, SemIR::LocId loc_id, .return_pattern_id = SemIR::InstId::None}; } auto pattern_type_id = GetPatternType(context, type_id); - clang::SourceLocation return_type_loc = - clang_decl->getReturnTypeSourceRange().getBegin(); - if (return_type_loc.isInvalid()) { - // TODO: While `getReturnTypeSourceRange()` should work, it seems broken for - // trailing return type. See - // https://github.com/llvm/llvm-project/issues/162649. Until this is fixed, - // we fallback to `getTypeSpecStartLoc()`. - return_type_loc = clang_decl->getTypeSpecStartLoc(); + clang::SourceLocation return_type_loc; + if (function_info.decl != nullptr) { + return_type_loc = function_info.decl->getReturnTypeSourceRange().getBegin(); + if (return_type_loc.isInvalid()) { + // TODO: While `getReturnTypeSourceRange()` should work, it seems broken + // for trailing return type. See + // https://github.com/llvm/llvm-project/issues/162649. Until this is + // fixed, we fallback to `getTypeSpecStartLoc()`. + return_type_loc = function_info.decl->getTypeSpecStartLoc(); + } } SemIR::ImportIRInstId return_type_import_ir_inst_id = AddImportIRInst(context.sem_ir(), return_type_loc); @@ -1798,19 +1794,18 @@ struct FunctionSignatureInsts { // signature to the Carbon function signature. static auto CreateFunctionSignatureInsts( Context& context, SemIR::LocId loc_id, - SemIR::ImportIRInstId import_ir_inst_id, clang::FunctionDecl* clang_decl, - SemIR::ClangDeclSignatureId signature_id) + const CalleeFunctionInfo& function_info) -> std::optional { context.full_pattern_stack().StartExplicitParamList(); - auto param_patterns_id = MakeParamPatternsBlockId( - context, loc_id, import_ir_inst_id, *clang_decl, signature_id); + auto param_patterns_id = + MakeParamPatternsBlockId(context, loc_id, function_info); if (!param_patterns_id.has_value()) { return std::nullopt; } context.full_pattern_stack().EndExplicitParamList(); auto [return_type_inst_id, return_form_inst_id, return_pattern_id] = - GetReturnInfo(context, loc_id, clang_decl); + GetReturnInfo(context, loc_id, function_info); if (return_type_inst_id == SemIR::ErrorInst::TypeInstId) { return std::nullopt; } @@ -1832,12 +1827,14 @@ static auto CreateFunctionSignatureInsts( } // Returns the Carbon function name for the given function. -static auto GetFunctionName(Context& context, clang::FunctionDecl* clang_decl) +static auto GetFunctionName(Context& context, + const CalleeFunctionInfo& function_info) -> SemIR::NameId { - switch (clang_decl->getDeclName().getNameKind()) { + clang::DeclarationName decl_name = function_info.decl_name; + switch (decl_name.getNameKind()) { case clang::DeclarationName::CXXConstructorName: { auto key = SemIR::ClangDeclKey( - cast(clang_decl)->getParent()); + cast(function_info.decl)->getParent()); return context.classes() .Get(context.insts() .GetAs(LookupClangDeclInstId(context, key)) @@ -1855,29 +1852,28 @@ static auto GetFunctionName(Context& context, clang::FunctionDecl* clang_decl) } default: { - return AddIdentifierName(context, clang_decl->getName()); + CARBON_CHECK(decl_name.isIdentifier()); + return AddIdentifierName(context, + decl_name.getAsIdentifierInfo()->getName()); } } } -// Creates a `FunctionDecl` and a `Function` without C++ thunk information. -// Returns std::nullopt on failure. +// Creates a `FunctionDecl` and a `Function` that represents the given C++ +// callee in Carbon, but does not create an ABI thunk for it, or add it to +// clang_decls. Returns the ID of the `FunctionDecl`, or `std::nullopt` on +// failure. // -// The given Clang declaration is assumed to: +// The given callee is assumed to: // * Have not been imported before. // * Be of supported type (ignoring parameters). -// -// `signature` specifies how to convert the C++ function signature to the Carbon -// function signature. static auto ImportFunction(Context& context, SemIR::LocId loc_id, - SemIR::ImportIRInstId import_ir_inst_id, - clang::FunctionDecl* clang_decl, - SemIR::ClangDeclSignatureId signature_id) - -> std::optional { + const CalleeFunctionInfo& function_info) + -> std::optional { StartFunctionSignature(context); - auto function_params_insts = CreateFunctionSignatureInsts( - context, loc_id, import_ir_inst_id, clang_decl, signature_id); + auto function_params_insts = + CreateFunctionSignatureInsts(context, loc_id, function_info); auto [pattern_block_id, decl_block_id] = FinishFunctionSignature(context, /*check_unused=*/false); @@ -1888,7 +1884,8 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id, auto virtual_modifier = SemIR::Function::VirtualModifier::None; int32_t virtual_index = -1; - if (auto* method_decl = dyn_cast(clang_decl)) { + if (auto* method_decl = + dyn_cast_if_present(function_info.decl)) { if (method_decl->size_overridden_methods()) { virtual_modifier = SemIR::Function::VirtualModifier::Override; } else if (method_decl->isVirtual()) { @@ -1911,19 +1908,22 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id, SemIR::FunctionFields::EvaluationMode evaluation_mode = SemIR::FunctionFields::EvaluationMode::None; - if (clang_decl->isConsteval()) { - evaluation_mode = SemIR::FunctionFields::EvaluationMode::MustEval; - } else if (clang_decl->isConstexpr()) { - evaluation_mode = SemIR::FunctionFields::EvaluationMode::Eval; + if (function_info.decl != nullptr) { + if (function_info.decl->isConsteval()) { + evaluation_mode = SemIR::FunctionFields::EvaluationMode::MustEval; + } else if (function_info.decl->isConstexpr()) { + evaluation_mode = SemIR::FunctionFields::EvaluationMode::Eval; + } } auto [decl_id, function_id] = MakeFunctionDecl( - context, import_ir_inst_id, decl_block_id, /*build_generic=*/false, + context, function_info.sem_ir_loc, decl_block_id, /*build_generic=*/false, /*is_definition=*/false, SemIR::Function{ { - .name_id = GetFunctionName(context, clang_decl), - .parent_scope_id = GetParentNameScopeId(context, clang_decl), + .name_id = GetFunctionName(context, function_info), + .parent_scope_id = + GetParentNameScopeId(context, function_info.decl), .generic_id = SemIR::GenericId::None, .first_param_node_id = Parse::NodeId::None, .last_param_node_id = Parse::NodeId::None, @@ -1953,13 +1953,7 @@ static auto ImportFunction(Context& context, SemIR::LocId loc_id, function_params_insts->param_patterns_id), }}); context.imports().push_back(decl_id); - - context.clang_decls().Add( - {.key = SemIR::ClangDeclKey::ForFunctionDecl(clang_decl, signature_id), - .inst_id = decl_id, - .is_imported = true}); - - return function_id; + return decl_id; } // Imports a C++ function, returning a corresponding Carbon function. @@ -1992,20 +1986,21 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id, return SemIR::ErrorInst::InstId; } - auto import_ir_inst_id = - AddImportIRInst(context.sem_ir(), clang_decl->getLocation()); - 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_id); - if (!function_id) { + CalleeFunctionInfo callee_info(context, clang_decl, key.signature_id); + auto function_decl_id = ImportFunction(context, loc_id, callee_info); + if (!function_decl_id) { MarkFailedDecl(context, key); return SemIR::ErrorInst::InstId; } + context.clang_decls().Add( + {.key = key, .inst_id = *function_decl_id, .is_imported = true}); - SemIR::Function& function_info = context.functions().Get(*function_id); - if (IsCppThunkRequired(context, function_info)) { + auto function_id = + context.insts().GetAs(*function_decl_id).function_id; + SemIR::Function& imported_function = context.functions().Get(function_id); + if (IsCppThunkRequired(context, callee_info)) { Diagnostics::AnnotationScope annotate_diagnostics( &context.emitter(), [&](auto& builder) { CARBON_DIAGNOSTIC(InCppThunk, Note, @@ -2014,7 +2009,7 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id, }); if (clang::FunctionDecl* thunk_clang_decl = - BuildCppThunk(context, function_info)) { + BuildCppThunk(context, callee_info)) { SemIR::ClangDeclSignature thunk_signature; thunk_signature.kind = SemIR::ClangDeclSignature::Normal; thunk_signature.num_params = @@ -2025,14 +2020,20 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id, 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 = - thunk_function.first_owning_decl_id; - function_info.SetHasCppThunk(thunk_function_decl_id); + CalleeFunctionInfo thunk_callee_info(context, thunk_clang_decl, + thunk_signature_id); + if (auto thunk_decl_id = + ImportFunction(context, loc_id, thunk_callee_info)) { + context.clang_decls().Add({.key = SemIR::ClangDeclKey::ForFunctionDecl( + thunk_clang_decl, thunk_signature_id), + .inst_id = *thunk_decl_id, + .is_imported = true}); + auto thunk_function_id = context.insts() + .GetAs(*thunk_decl_id) + .function_id; + auto& thunk_function = context.functions().Get(thunk_function_id); + thunk_function.SetCppThunk(imported_function.first_owning_decl_id); + imported_function.SetHasCppThunk(*thunk_decl_id); } } } else { @@ -2045,7 +2046,7 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id, if (clang_decl->isTrivial()) { // Trivial destructors map to a "no_op" builtin. if (isa(clang_decl)) { - function_info.SetBuiltinFunction(SemIR::BuiltinFunctionKind::NoOp); + imported_function.SetBuiltinFunction(SemIR::BuiltinFunctionKind::NoOp); } // TODO: Should we model a trivial default constructor as performing // value-initialization (zero-initializing all fields) or @@ -2055,7 +2056,7 @@ static auto ImportFunctionDecl(Context& context, SemIR::LocId loc_id, } } - return function_info.first_owning_decl_id; + return *function_decl_id; } namespace { diff --git a/toolchain/check/cpp/thunk.cpp b/toolchain/check/cpp/thunk.cpp index e11f3465c72e..457705d7fd62 100644 --- a/toolchain/check/cpp/thunk.cpp +++ b/toolchain/check/cpp/thunk.cpp @@ -75,11 +75,13 @@ static auto GetGlobalDecl(const clang::FunctionDecl* decl) // Returns the C++ thunk mangled name given the callee function. static auto GenerateThunkMangledName( clang::MangleContext& mangle_context, - const clang::FunctionDecl& callee_function_decl, + const clang::FunctionDecl* callee_function_decl, const SemIR::ClangDeclSignature& signature) -> std::string { RawStringOstream mangled_name_stream; - mangle_context.mangleName(GetGlobalDecl(&callee_function_decl), - mangled_name_stream); + if (callee_function_decl != nullptr) { + mangle_context.mangleName(GetGlobalDecl(callee_function_decl), + mangled_name_stream); + } switch (signature.kind) { case SemIR::ClangDeclSignature::Normal: mangled_name_stream << ".carbon_thunk"; @@ -107,7 +109,10 @@ static auto GenerateThunkMangledName( } }; - if (IsObjectMemberFunction(callee_function_decl)) { + // If there is no decl, the callee is a function pointer, which we treat as + // the thunk's `self` parameter. + if (callee_function_decl == nullptr || + IsObjectMemberFunction(*callee_function_decl)) { append_mode(signature.self_passing_mode); } for (auto mode : signature.passing_modes) { @@ -160,122 +165,69 @@ static auto IsSimpleAbiType(clang::ASTContext& ast_context, return false; } -namespace { -// Information about the callee of a thunk. -struct CalleeFunctionInfo { - explicit CalleeFunctionInfo(clang::FunctionDecl* decl, - const SemIR::ClangDeclSignature* signature) - : decl(decl), - signature(signature), - num_params(signature->num_params + - decl->hasCXXExplicitFunctionObjectParameter()) { - auto& ast_context = decl->getASTContext(); - const auto* method_decl = dyn_cast(decl); - bool is_ctor = isa(decl); - has_object_parameter = IsObjectMemberFunction(*decl); - if (has_object_parameter && method_decl->isImplicitObjectMemberFunction()) { - implicit_object_parameter_type = - method_decl->getFunctionObjectParameterReferenceType(); +CalleeFunctionInfo::CalleeFunctionInfo(Context& context, + clang::FunctionDecl* decl, + SemIR::ClangDeclSignatureId signature_id) + : decl(decl), + decl_name(decl->getDeclName()), + clang_loc(decl->getLocation()), + sem_ir_loc(AddImportIRInst(context.sem_ir(), clang_loc)), + function_type(decl->getType()->getAs()), + signature_id(signature_id), + signature(&context.clang_decl_signatures().Get(signature_id)), + num_callee_params(signature->num_params + + decl->hasCXXExplicitFunctionObjectParameter()) { + auto& ast_context = decl->getASTContext(); + const auto* method_decl = dyn_cast(decl); + bool is_ctor = isa(decl); + if (IsObjectMemberFunction(*decl)) { + self_param_type = method_decl->getFunctionObjectParameterReferenceType(); + if (method_decl->isImplicitObjectMemberFunction()) { + self_param_kind = SelfParamKind::ImplicitObjectParam; + } else { + self_param_kind = SelfParamKind::ExplicitObjectParam; } - effective_return_type = - is_ctor ? ast_context.getCanonicalTagType(method_decl->getParent()) - : decl->getReturnType(); - has_simple_return_type = IsSimpleAbiType(ast_context, effective_return_type, - /*for_parameter=*/false); + } else { + self_param_kind = SelfParamKind::None; } + effective_return_type = + is_ctor ? ast_context.getCanonicalTagType(method_decl->getParent()) + : decl->getReturnType(); + has_simple_return_type = IsSimpleAbiType(ast_context, effective_return_type, + /*for_parameter=*/false); +} - // Returns whether this callee has an implicit `this` parameter. - auto has_implicit_object_parameter() const -> bool { - return !implicit_object_parameter_type.isNull(); - } +auto CalleeFunctionInfo::GetCalleeParamIdentifier(int i) const + -> clang::IdentifierInfo* { + return decl->getParamDecl(i)->getIdentifier(); +} - // Returns whether this callee has an explicit `this` parameter. - auto has_explicit_object_parameter() const -> bool { - return has_object_parameter && !has_implicit_object_parameter(); - } +auto CalleeFunctionInfo::GetCalleeParamLocation(int i) const + -> clang::SourceLocation { + return decl->getParamDecl(i)->getLocation(); +} - // Returns the number of parameters the thunk should have. - auto num_thunk_params() const -> unsigned { - return has_implicit_object_parameter() + num_params + - !has_simple_return_type; - } - - // Returns the thunk parameter index corresponding to a given callee parameter - // index. - auto GetThunkParamIndex(unsigned callee_param_index) const -> unsigned { - return has_implicit_object_parameter() + callee_param_index; - } - - // Returns the thunk parameter index corresponding to the parameter that holds - // the address of the return value. - auto GetThunkReturnParamIndex() const -> unsigned { - CARBON_CHECK(!has_simple_return_type); - return has_implicit_object_parameter() + num_params; - } - - // The callee function. - clang::FunctionDecl* decl; - - // 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 - // used. - int num_params; - - // Whether the callee has an object parameter, which might be explicit or - // implicit. - bool has_object_parameter; - - // If the callee has an implicit object parameter, the type of that parameter, - // which will always be a reference type. Otherwise a null type. - clang::QualType implicit_object_parameter_type; - - // The return type that the callee has when viewed from Carbon. This is the - // C++ return type, except that constructors return the class type in Carbon - // and return void in Clang's AST. - clang::QualType effective_return_type; - - // Whether the callee has a simple return type, that we can return directly. - // If not, we'll return through an out parameter instead. - bool has_simple_return_type; -}; -} // namespace - -auto IsCppThunkRequired(Context& context, const SemIR::Function& function) +auto IsCppThunkRequired(Context& context, const CalleeFunctionInfo& callee_info) -> bool { - const auto* clang_decl = - context.clang_decls().Lookup(function.first_decl_id()); - if (!clang_decl) { - return false; - } - - if (!clang_decl->is_imported) { - return false; - } - - const auto& signature = - context.clang_decl_signatures().Get(clang_decl->key.signature_id); - auto* decl = cast(clang_decl->decl()); - if (signature.kind != SemIR::ClangDeclSignature::Normal || - signature.num_params != static_cast(decl->getNumNonObjectParams())) { + auto* decl = cast(callee_info.decl); + if (callee_info.signature->kind != SemIR::ClangDeclSignature::Normal || + callee_info.signature->num_params != + static_cast(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, &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, + if (!callee_info.self_param_type.isNull() && + (!IsSimpleAbiType(ast_context, callee_info.self_param_type, /*for_parameter=*/true) || - signature.self_passing_mode == + callee_info.signature->self_passing_mode == SemIR::ClangDeclSignature::PassingMode::ByVar)) { return true; } @@ -285,7 +237,7 @@ auto IsCppThunkRequired(Context& context, const SemIR::Function& function) for (int i : llvm::seq(decl->getNumParams())) { if (!IsSimpleAbiType(ast_context, function_type->getParamType(i), /*for_parameter=*/true) || - signature.GetPassingMode(i) == + callee_info.signature->GetPassingMode(i) == SemIR::ClangDeclSignature::PassingMode::ByVar) { return true; } @@ -327,15 +279,13 @@ static auto BuildThunkParameterTypes(clang::ASTContext& ast_context, -> llvm::SmallVector { llvm::SmallVector thunk_param_types; thunk_param_types.reserve(callee_info.num_thunk_params()); - if (callee_info.has_implicit_object_parameter()) { - thunk_param_types.push_back(callee_info.implicit_object_parameter_type); + if (callee_info.callee_param_to_carbon_param_offset() > 0) { + thunk_param_types.push_back(callee_info.self_param_type); } - const auto* function_type = - callee_info.decl->getType()->castAs(); - for (int i : llvm::seq(callee_info.num_params)) { - thunk_param_types.push_back( - GetThunkParameterType(ast_context, function_type->getParamType(i))); + for (int i : llvm::seq(callee_info.num_callee_params)) { + thunk_param_types.push_back(GetThunkParameterType( + ast_context, callee_info.function_type->getParamType(i))); } if (!callee_info.has_simple_return_type) { @@ -360,7 +310,7 @@ static auto BuildThunkParameters(clang::ASTContext& ast_context, unsigned num_thunk_params = thunk_function_decl->getNumParams(); thunk_params.reserve(num_thunk_params); - if (callee_info.has_implicit_object_parameter()) { + if (callee_info.callee_param_to_carbon_param_offset() > 0) { clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create(ast_context, thunk_function_decl, clang_loc, clang_loc, &ast_context.Idents.get("this"), @@ -369,23 +319,24 @@ static auto BuildThunkParameters(clang::ASTContext& ast_context, thunk_params.push_back(thunk_param); } - for (int i : llvm::seq(callee_info.num_params)) { + for (int i : llvm::seq(callee_info.num_callee_params)) { clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create( ast_context, thunk_function_decl, clang_loc, clang_loc, - callee_info.decl->getParamDecl(i)->getIdentifier(), + callee_info.GetCalleeParamIdentifier(i), thunk_function_proto_type->getParamType( - callee_info.GetThunkParamIndex(i)), + i + callee_info.callee_param_to_carbon_param_offset()), nullptr, clang::SC_None, nullptr); thunk_params.push_back(thunk_param); } if (!callee_info.has_simple_return_type) { - clang::ParmVarDecl* thunk_param = - clang::ParmVarDecl::Create(ast_context, thunk_function_decl, clang_loc, - clang_loc, &ast_context.Idents.get("return"), - thunk_function_proto_type->getParamType( - callee_info.GetThunkReturnParamIndex()), - nullptr, clang::SC_None, nullptr); + int thunk_return_index = callee_info.num_callee_params + + callee_info.callee_param_to_carbon_param_offset(); + clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create( + ast_context, thunk_function_decl, clang_loc, clang_loc, + &ast_context.Idents.get("return"), + thunk_function_proto_type->getParamType(thunk_return_index), nullptr, + clang::SC_None, nullptr); thunk_params.push_back(thunk_param); } @@ -440,7 +391,7 @@ static auto CreateThunkFunctionDecl( llvm::ArrayRef thunk_param_types) -> clang::FunctionDecl* { clang::ASTContext& ast_context = context.ast_context(); clang::DeclarationName name = - GetDeclNameForThunk(ast_context, callee_info.decl->getDeclName()); + GetDeclNameForThunk(ast_context, callee_info.decl_name); auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo(); clang::QualType thunk_function_type = ast_context.getFunctionType( @@ -468,7 +419,7 @@ static auto CreateThunkFunctionDecl( thunk_function_decl->addAttr(clang::AsmLabelAttr::CreateImplicit( ast_context, GenerateThunkMangledName(context.cpp_context()->clang_mangle_context(), - *callee_info.decl, *callee_info.signature), + callee_info.decl, *callee_info.signature), clang_loc)); // Set function declaration type source info. @@ -516,11 +467,12 @@ static auto BuildParamRefForCalleeArg(clang::Sema& sema, clang::FunctionDecl* thunk_function_decl, CalleeFunctionInfo callee_info, unsigned callee_index) -> clang::Expr* { - unsigned thunk_index = callee_info.GetThunkParamIndex(callee_index); + unsigned thunk_index = + callee_index + callee_info.callee_param_to_carbon_param_offset(); return BuildThunkParamRef( sema, thunk_function_decl, thunk_index, callee_info.signature->GetPassingMode(callee_index), - callee_info.decl->getParamDecl(callee_index)->getType()); + callee_info.function_type->getParamType(callee_index)); } // Builds an argument list for the callee function by creating suitable uses of @@ -530,12 +482,11 @@ static auto BuildCalleeArgs(clang::Sema& sema, CalleeFunctionInfo callee_info) -> llvm::SmallVector { llvm::SmallVector call_args; - // The object parameter is always passed as `self`, not in the callee argument - // list, so the first argument corresponds to the second parameter if there is - // an explicit object parameter and the first parameter otherwise. - int first_param = callee_info.has_explicit_object_parameter(); - call_args.reserve(callee_info.num_params - first_param); - for (unsigned callee_index : llvm::seq(first_param, callee_info.num_params)) { + call_args.reserve(callee_info.num_callee_params - + callee_info.callee_arg_to_callee_param_offset()); + for (unsigned callee_index : + llvm::seq(callee_info.callee_arg_to_callee_param_offset(), + callee_info.num_callee_params)) { call_args.push_back(BuildParamRefForCalleeArg(sema, thunk_function_decl, callee_info, callee_index)); } @@ -556,31 +507,40 @@ static auto BuildThunkBody(CppContext& cpp_context, clang::Sema& sema, // If the callee has an object parameter, build a member access expression as // the callee. Otherwise, build a regular reference to the function. clang::ExprResult callee; - if (callee_info.has_object_parameter) { - clang::QualType object_param_type = - cast(callee_info.decl) - ->getFunctionObjectParameterReferenceType(); - 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); - if (object.isInvalid()) { - return clang::StmtError(); + switch (callee_info.self_param_kind) { + case CalleeFunctionInfo::SelfParamKind::ExplicitObjectParam: + case CalleeFunctionInfo::SelfParamKind::ImplicitObjectParam: { + clang::QualType object_param_type = + cast(callee_info.decl) + ->getFunctionObjectParameterReferenceType(); + 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); + if (object.isInvalid()) { + return clang::StmtError(); + } + callee = sema.BuildMemberExpr( + object.get(), IsArrow, clang_loc, clang::NestedNameSpecifierLoc(), + clang::SourceLocation(), callee_info.decl, + clang::DeclAccessPair::make(callee_info.decl, clang::AS_public), + /*HadMultipleCandidates=*/false, + clang::DeclarationNameInfo(callee_info.decl->getDeclName(), + clang_loc), + sema.getASTContext().BoundMemberTy, clang::VK_PRValue, + clang::OK_Ordinary); + break; } - callee = sema.BuildMemberExpr( - object.get(), IsArrow, clang_loc, clang::NestedNameSpecifierLoc(), - clang::SourceLocation(), callee_info.decl, - clang::DeclAccessPair::make(callee_info.decl, clang::AS_public), - /*HadMultipleCandidates=*/false, - clang::DeclarationNameInfo(callee_info.decl->getDeclName(), clang_loc), - sema.getASTContext().BoundMemberTy, clang::VK_PRValue, - clang::OK_Ordinary); - } else if (!isa(callee_info.decl)) { - callee = - sema.BuildDeclRefExpr(callee_info.decl, callee_info.decl->getType(), - clang::VK_PRValue, clang_loc); + case CalleeFunctionInfo::SelfParamKind::None: + if (isa(callee_info.decl)) { + break; + } + callee = + sema.BuildDeclRefExpr(callee_info.decl, callee_info.decl->getType(), + clang::VK_PRValue, clang_loc); + break; } if (callee.isInvalid()) { @@ -592,7 +552,8 @@ static auto BuildThunkBody(CppContext& cpp_context, clang::Sema& sema, BuildCalleeArgs(sema, thunk_function_decl, callee_info); clang::ExprResult call; - if (auto info = clang::getConstructorInfo(callee_info.decl); + if (auto info = callee_info.decl ? clang::getConstructorInfo(callee_info.decl) + : clang::ConstructorInfo{}; info.Constructor) { // In C++, there are no direct calls to constructors, only initialization, // so we need to type-check and build the call ourselves. @@ -620,9 +581,11 @@ static auto BuildThunkBody(CppContext& cpp_context, clang::Sema& sema, return sema.BuildReturnStmt(clang_loc, call.get()); } - auto* return_object_addr = BuildThunkParamRef( - sema, thunk_function_decl, callee_info.GetThunkReturnParamIndex(), - SemIR::ClangDeclSignature::PassingMode::ByValue); + int return_thunk_index = callee_info.num_callee_params + + callee_info.callee_param_to_carbon_param_offset(); + auto* return_object_addr = + BuildThunkParamRef(sema, thunk_function_decl, return_thunk_index, + SemIR::ClangDeclSignature::PassingMode::ByValue); auto return_type = callee_info.effective_return_type.getNonReferenceType(); auto* return_type_info = sema.Context.getTrivialTypeSourceInfo(return_type, clang_loc); @@ -648,37 +611,20 @@ static auto BuildThunkBody(CppContext& cpp_context, clang::Sema& sema, return sema.ActOnExprStmt(placement_new, /*DiscardedValue=*/true); } -auto BuildCppThunk(Context& context, const SemIR::Function& callee_function) +auto BuildCppThunk(Context& context, const CalleeFunctionInfo& callee_info) -> clang::FunctionDecl* { - auto clang_decl_key = - context.clang_decls().Lookup(callee_function.first_decl_id())->key; - clang::FunctionDecl* callee_function_decl = - clang_decl_key.decl->getAsFunction(); - CARBON_CHECK(callee_function_decl); - - // TODO: The signature kind doesn't affect the thunk that we build, so we - // 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. - const auto& signature = - context.clang_decl_signatures().Get(clang_decl_key.signature_id); - CalleeFunctionInfo callee_info(callee_function_decl, &signature); - - clang::SourceLocation clang_loc = callee_function_decl->getLocation(); - CARBON_CHECK(clang_loc.isValid(), "Missing location for function"); - // Build the thunk function declaration. auto thunk_param_types = BuildThunkParameterTypes(context.ast_context(), callee_info); clang::FunctionDecl* thunk_function_decl = CreateThunkFunctionDecl( - context, callee_info, clang_loc, thunk_param_types); + context, callee_info, callee_info.clang_loc, thunk_param_types); // Build the thunk function body. clang::Sema& sema = context.clang_sema(); clang::Sema::ContextRAII context_raii(sema, thunk_function_decl); sema.ActOnStartOfFunctionDef(nullptr, thunk_function_decl); clang::StmtResult body = - BuildThunkBody(*context.cpp_context(), sema, clang_loc, + BuildThunkBody(*context.cpp_context(), sema, callee_info.clang_loc, thunk_function_decl, callee_info); sema.ActOnFinishFunctionBody(thunk_function_decl, body.get()); if (body.isInvalid()) { diff --git a/toolchain/check/cpp/thunk.h b/toolchain/check/cpp/thunk.h index 80c4ee03637b..19ba53541a5d 100644 --- a/toolchain/check/cpp/thunk.h +++ b/toolchain/check/cpp/thunk.h @@ -5,23 +5,156 @@ #ifndef CARBON_TOOLCHAIN_CHECK_CPP_THUNK_H_ #define CARBON_TOOLCHAIN_CHECK_CPP_THUNK_H_ +#include "clang/AST/DeclarationName.h" +#include "clang/AST/TypeBase.h" #include "toolchain/check/context.h" #include "toolchain/sem_ir/ids.h" namespace Carbon::Check { +// Information about a C++ callee in the process of being imported. One of +// this class's key responsibilities is to track the mapping between several +// different calling conventions: +// - The native C++ function, which may or may not have an object parameter, +// and that object parameter may or may not be present in the parameter list +// (but is never present in the corresponding argument list). +// - The thunk that wraps it with a simple ABI, so that it can be called from +// Carbon. +// - The Carbon function that is actually called by user code, and maps its +// parameters to the simple ABI. +struct CalleeFunctionInfo { + // Constructs a CalleeFunctionInfo that represents the given C++ function with + // the given signature. + explicit CalleeFunctionInfo(Context& context, clang::FunctionDecl* decl, + SemIR::ClangDeclSignatureId signature_id); + + // Returns the offset I such that callee parameter N corresponds to + // parameter N+I of the imported Carbon function. + auto callee_param_to_carbon_param_offset() const -> int { + switch (self_param_kind) { + case SelfParamKind::ImplicitObjectParam: + return 1; + case SelfParamKind::None: + case SelfParamKind::ExplicitObjectParam: + return 0; + } + } + + // Returns the offset I such that callee argument N corresponds to callee + // parameter N+I. + auto callee_arg_to_callee_param_offset() const -> int { + switch (self_param_kind) { + case SelfParamKind::ExplicitObjectParam: + return 1; + case SelfParamKind::ImplicitObjectParam: + case SelfParamKind::None: + return 0; + } + } + + // Returns the number of parameters that the imported Carbon function should + // have. + auto num_carbon_params() const -> int { + return num_callee_params + callee_param_to_carbon_param_offset(); + } + + // Returns the number of parameters the simple-ABI thunk should have. + auto num_thunk_params() const -> unsigned { + return num_carbon_params() + !has_simple_return_type; + } + + // Returns true if the imported Carbon function should have a self parameter. + auto carbon_has_self_param() const -> bool { + return self_param_kind != SelfParamKind::None; + } + + // Returns true if the callee has an object parameter (i.e. `this`). + auto callee_has_object_param() const -> bool { + return self_param_kind == SelfParamKind::ImplicitObjectParam || + self_param_kind == SelfParamKind::ExplicitObjectParam; + } + + // Returns the identifier for the i-th callee parameter, or null if it doesn't + // have one. + auto GetCalleeParamIdentifier(int i) const -> clang::IdentifierInfo*; + + // Returns the location of the i-th callee parameter declaration. + auto GetCalleeParamLocation(int i) const -> clang::SourceLocation; + + // Information about the C++ parameter that corresponds to the `self` + // parameter in the Carbon function. + enum class SelfParamKind { + // The C++ callee is an ordinary function, so it has no `self` parameter in + // Carbon. The Nth Carbon parameter corresponds to the Nth callee argument, + // and to the Nth callee parameter. + None, + // The callee is a traditional C++ method, so the Carbon `self` parameter + // corresponds to the C++ object parameter, which is not present in + // the callee parameter or argument list, but is passed at the callsite + // using member access syntax. Consequently, the Nth Carbon parameter + // corresponds to the N-1th callee argument, and the N-1th callee parameter. + ImplicitObjectParam, + // The callee is a C++ method with an explicit object parameter, such as + // `F(this Foo& self)`, so the first Carbon parameter corresponds to the + // object parameter, which is present in the callee parameter list but + // passed at the callsite using member access syntax rather than the + // argument list. Consequently, the Nth Carbon parameter corresponds to the + // N-1th callee argument, and the Nth callee parameter. + ExplicitObjectParam, + }; + SelfParamKind self_param_kind; + + // The callee function's declaration, or null if it doesn't have one. + clang::FunctionDecl* decl; + + // The name of the callee function. + clang::DeclarationName decl_name; + + // The location of the callee function declaration. + clang::SourceLocation clang_loc; + + // The SemIR representation of `clang_loc`. + SemIR::LocId sem_ir_loc; + + // The function type of the callee. This is never null. + const clang::FunctionProtoType* function_type; + + // The ID of `signature`. + SemIR::ClangDeclSignatureId signature_id; + + // 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 + // used. + int num_callee_params; + + // The type of the callee parameter that is treated as `self` in the Carbon + // function, or null if there isn't one. + clang::QualType self_param_type; + + // The return type that the callee has when viewed from Carbon. This is the + // C++ return type, except that constructors return the class type in Carbon + // and return void in Clang's AST. + clang::QualType effective_return_type; + + // Whether the callee has a simple return type, that we can return directly. + // If not, we'll return through an out parameter instead. + bool has_simple_return_type; +}; + // Returns whether the given C++ imported function requires a C++ thunk to be // used to call it. A C++ thunk is required for functions whose ABI uses any // type except void, pointer and reference types, and signed 32-bit and 64-bit // integers. -auto IsCppThunkRequired(Context& context, const SemIR::Function& function) +auto IsCppThunkRequired(Context& context, const CalleeFunctionInfo& callee_info) -> bool; -// Given a function signature and a callee function, builds a C++ thunk with -// simple ABI (pointers, i32 and i64 types) that calls the specified callee. -// Assumes `IsCppThunkRequired()` return true for `callee_function`. Returns -// `nullptr` on failure. -auto BuildCppThunk(Context& context, const SemIR::Function& callee_function) +// Builds a C++ thunk with simple ABI (pointers, i32 and i64 types) that calls +// the specified callee. Assumes `IsCppThunkRequired()` return true for +// `callee_info`. Returns `nullptr` on failure. +auto BuildCppThunk(Context& context, const CalleeFunctionInfo& callee_info) -> clang::FunctionDecl*; // Builds a call to a thunk function that forwards a call argument list built diff --git a/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon b/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon index 51f0641d1869..1fa1ca36aac6 100644 --- a/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon +++ b/toolchain/check/testdata/basics/raw_sem_ir/cpp_interop.carbon @@ -52,6 +52,8 @@ fn G(x: Cpp.X) { // CHECK:STDOUT: import_ir_inst2: {ir_id: import_ir(Cpp), clang_source_loc_id: clang_source_loc50000002} // CHECK:STDOUT: import_ir_inst3: {ir_id: import_ir(Cpp), clang_source_loc_id: clang_source_loc50000003} // CHECK:STDOUT: import_ir_inst4: {ir_id: import_ir(Cpp), clang_source_loc_id: clang_source_loc50000004} +// CHECK:STDOUT: import_ir_inst5: {ir_id: import_ir(Cpp), clang_source_loc_id: clang_source_loc50000005} +// CHECK:STDOUT: import_ir_inst6: {ir_id: import_ir(Cpp), clang_source_loc_id: clang_source_loc50000006} // CHECK:STDOUT: clang_decls: // CHECK:STDOUT: clang_decl_id50000000: {key: {decl: "namespace Carbon {\n}"}, inst_id: inst(Package)} // CHECK:STDOUT: clang_decl_id50000001: {key: {decl: ""}, inst_id: inst50000011}