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
+124 -178
View File
@@ -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<clang::CXXMethodDecl>(decl);
bool is_ctor = isa<clang::CXXConstructorDecl>(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<clang::FunctionProtoType>()),
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<clang::CXXMethodDecl>(decl);
bool is_ctor = isa<clang::CXXConstructorDecl>(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::FunctionDecl>(clang_decl->decl());
if (signature.kind != SemIR::ClangDeclSignature::Normal ||
signature.num_params != static_cast<int>(decl->getNumNonObjectParams())) {
auto* decl = cast<clang::FunctionDecl>(callee_info.decl);
if (callee_info.signature->kind != SemIR::ClangDeclSignature::Normal ||
callee_info.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, &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<clang::QualType> {
llvm::SmallVector<clang::QualType> 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<clang::FunctionProtoType>();
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<clang::QualType> 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<clang::Expr*> {
llvm::SmallVector<clang::Expr*> 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<clang::CXXMethodDecl>(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<clang::CXXMethodDecl>(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<clang::CXXConstructorDecl>(callee_info.decl)) {
callee =
sema.BuildDeclRefExpr(callee_info.decl, callee_info.decl->getType(),
clang::VK_PRValue, clang_loc);
case CalleeFunctionInfo::SelfParamKind::None:
if (isa<clang::CXXConstructorDecl>(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()) {