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.
This commit is contained in:
Geoff Romer
2026-09-29 23:03:45 +00:00
committed by GitHub
parent 7659215050
commit f65572a1e6
4 changed files with 381 additions and 299 deletions
+116 -115
View File
@@ -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<clang::TagDecl>(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<SemIR::InstId, SemIR::TypeInstId> {
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<SemIR::InstId, SemIR::TypeInstId> {
// 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<SemIR::InstId> param_ids;
llvm::SmallVector<SemIR::InstId> 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::CXXMethodDecl>(&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<int>(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<clang::FunctionProtoType>();
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::CXXConstructorDecl>(clang_decl);
auto* ctor =
dyn_cast_if_present<clang::CXXConstructorDecl>(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<FunctionSignatureInsts> {
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::CXXConstructorDecl>(clang_decl)->getParent());
cast<clang::CXXConstructorDecl>(function_info.decl)->getParent());
return context.classes()
.Get(context.insts()
.GetAs<SemIR::ClassDecl>(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<SemIR::FunctionId> {
const CalleeFunctionInfo& function_info)
-> std::optional<SemIR::InstId> {
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::CXXMethodDecl>(clang_decl)) {
if (auto* method_decl =
dyn_cast_if_present<clang::CXXMethodDecl>(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<SemIR::FunctionDecl>(*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<SemIR::FunctionDecl>(*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::CXXDestructorDecl>(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 {