Files
carbon-lang/toolchain/check/cpp/thunk.cpp
T
Richard Smith 71ba07239f 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
2026-05-13 01:44:07 +00:00

814 lines
33 KiB
C++

// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include "toolchain/check/cpp/thunk.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/GlobalDecl.h"
#include "clang/AST/Mangle.h"
#include "clang/Sema/Lookup.h"
#include "clang/Sema/Overload.h"
#include "clang/Sema/Sema.h"
#include "toolchain/check/call.h"
#include "toolchain/check/context.h"
#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"
#include "toolchain/sem_ir/function.h"
#include "toolchain/sem_ir/ids.h"
#include "toolchain/sem_ir/typed_insts.h"
namespace Carbon::Check {
// Generate and return a function:
// `void* operator new(__SIZE_TYPE__, void*) noexcept`.
static auto GeneratePlacementNewFunctionDecl(clang::ASTContext& context)
-> clang::FunctionDecl* {
clang::QualType size_type = context.getSizeType();
clang::QualType void_ptr_type = context.VoidPtrTy;
auto ext_info = clang::FunctionProtoType::ExtProtoInfo();
ext_info.ExceptionSpec.Type = clang::EST_BasicNoexcept;
clang::QualType function_type = context.getFunctionType(
void_ptr_type, {size_type, void_ptr_type}, ext_info);
clang::DeclarationName name =
context.DeclarationNames.getCXXOperatorName(clang::OO_New);
clang::FunctionDecl* function_decl = clang::FunctionDecl::Create(
context, context.getTranslationUnitDecl(), clang::SourceLocation(),
clang::SourceLocation(), name, function_type,
/*TInfo=*/nullptr, clang::SC_None);
clang::ParmVarDecl* size_param = clang::ParmVarDecl::Create(
context, function_decl, clang::SourceLocation(), clang::SourceLocation(),
nullptr, size_type, nullptr, clang::SC_None, nullptr);
clang::ParmVarDecl* ptr_param = clang::ParmVarDecl::Create(
context, function_decl, clang::SourceLocation(), clang::SourceLocation(),
nullptr, void_ptr_type, nullptr, clang::SC_None, nullptr);
function_decl->setParams({size_param, ptr_param});
CARBON_CHECK(function_decl->isReservedGlobalPlacementOperator());
return function_decl;
}
// Returns the GlobalDecl to use to represent the given function declaration.
// TODO: Refactor with `Lower::CreateGlobalDecl`.
static auto GetGlobalDecl(const clang::FunctionDecl* decl)
-> clang::GlobalDecl {
if (const auto* ctor = dyn_cast<clang::CXXConstructorDecl>(decl)) {
return clang::GlobalDecl(ctor, clang::CXXCtorType::Ctor_Complete);
}
if (const auto* dtor = dyn_cast<clang::CXXDestructorDecl>(decl)) {
return clang::GlobalDecl(dtor, clang::CXXDtorType::Dtor_Complete);
}
return clang::GlobalDecl(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 SemIR::ClangDeclSignature& signature) -> std::string {
RawStringOstream mangled_name_stream;
mangle_context.mangleName(GetGlobalDecl(&callee_function_decl),
mangled_name_stream);
switch (signature.kind) {
case SemIR::ClangDeclSignature::Normal:
mangled_name_stream << ".carbon_thunk";
break;
case SemIR::ClangDeclSignature::TuplePattern:
mangled_name_stream << ".carbon_thunk_tuple";
break;
}
// 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();
}
// Returns whether the Carbon lowering for a parameter or return of this type is
// known to match the C++ lowering.
static auto IsSimpleAbiType(clang::ASTContext& ast_context,
clang::QualType type, bool for_parameter) -> bool {
if (type->isVoidType() || type->isPointerType()) {
return true;
}
if (type->isReferenceType()) {
if (for_parameter) {
// A reference parameter has a simple ABI if it's a non-const lvalue
// reference. Otherwise, we map it to pass-by-value, and it's only simple
// if the type uses a pointer value representation.
//
// TODO: Check whether the pointee type maps to a Carbon type that uses a
// pointer value representation, and treat it as simple if so.
return type->isLValueReferenceType() &&
!type->getPointeeType().isConstQualified();
}
// A reference return type is always mapped to a Carbon pointer, which uses
// the same ABI rule as a C++ reference.
return true;
}
if (const auto* enum_decl = type->getAsEnumDecl()) {
// An enum type has a simple ABI if its underlying type does.
type = enum_decl->getIntegerType();
if (type.isNull()) {
return false;
}
}
if (const auto* builtin_type = type->getAs<clang::BuiltinType>()) {
if (builtin_type->isIntegerType()) {
uint64_t type_size = ast_context.getIntWidth(type);
return type_size == 32 || type_size == 64;
}
}
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();
}
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();
}
// Returns whether this callee has an explicit `this` parameter.
auto has_explicit_object_parameter() const -> bool {
return has_object_parameter && !has_implicit_object_parameter();
}
// 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)
-> bool {
if (!function.clang_decl_id.has_value()) {
return false;
}
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 (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, &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) ||
signature.self_passing_mode ==
SemIR::ClangDeclSignature::PassingMode::ByVar)) {
return true;
}
const auto* function_type =
decl->getType()->castAs<clang::FunctionProtoType>();
for (int i : llvm::seq(decl->getNumParams())) {
if (!IsSimpleAbiType(ast_context, function_type->getParamType(i),
/*for_parameter=*/true) ||
signature.GetPassingMode(i) ==
SemIR::ClangDeclSignature::PassingMode::ByVar) {
return true;
}
}
return false;
}
// Given a pointer type, returns the corresponding _Nonnull-qualified pointer
// type.
static auto GetNonnullType(clang::ASTContext& ast_context,
clang::QualType pointer_type) -> clang::QualType {
return ast_context.getAttributedType(clang::NullabilityKind::NonNull,
pointer_type, pointer_type);
}
// Given a type, returns the corresponding _Nonnull-qualified pointer type,
// ignoring references.
static auto GetNonNullablePointerType(clang::ASTContext& ast_context,
clang::QualType type) {
return GetNonnullType(ast_context,
ast_context.getPointerType(type.getNonReferenceType()));
}
// Given the type of a callee parameter, returns the type to use for the
// corresponding thunk parameter.
static auto GetThunkParameterType(clang::ASTContext& ast_context,
clang::QualType callee_type)
-> clang::QualType {
if (IsSimpleAbiType(ast_context, callee_type, /*for_parameter=*/true)) {
return callee_type;
}
return GetNonNullablePointerType(ast_context, callee_type);
}
// Creates the thunk parameter types given the callee function.
static auto BuildThunkParameterTypes(clang::ASTContext& ast_context,
CalleeFunctionInfo callee_info)
-> 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);
}
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)));
}
if (!callee_info.has_simple_return_type) {
thunk_param_types.push_back(GetNonNullablePointerType(
ast_context, callee_info.effective_return_type));
}
CARBON_CHECK(thunk_param_types.size() == callee_info.num_thunk_params());
return thunk_param_types;
}
// Returns the thunk parameters using the callee function parameter identifiers.
static auto BuildThunkParameters(clang::ASTContext& ast_context,
CalleeFunctionInfo callee_info,
clang::FunctionDecl* thunk_function_decl)
-> llvm::SmallVector<clang::ParmVarDecl*> {
clang::SourceLocation clang_loc = callee_info.decl->getLocation();
const auto* thunk_function_proto_type =
thunk_function_decl->getType()->castAs<clang::FunctionProtoType>();
llvm::SmallVector<clang::ParmVarDecl*> thunk_params;
unsigned num_thunk_params = thunk_function_decl->getNumParams();
thunk_params.reserve(num_thunk_params);
if (callee_info.has_implicit_object_parameter()) {
clang::ParmVarDecl* thunk_param =
clang::ParmVarDecl::Create(ast_context, thunk_function_decl, clang_loc,
clang_loc, &ast_context.Idents.get("this"),
thunk_function_proto_type->getParamType(0),
nullptr, clang::SC_None, nullptr);
thunk_params.push_back(thunk_param);
}
for (int i : llvm::seq(callee_info.num_params)) {
clang::ParmVarDecl* thunk_param = clang::ParmVarDecl::Create(
ast_context, thunk_function_decl, clang_loc, clang_loc,
callee_info.decl->getParamDecl(i)->getIdentifier(),
thunk_function_proto_type->getParamType(
callee_info.GetThunkParamIndex(i)),
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);
thunk_params.push_back(thunk_param);
}
CARBON_CHECK(thunk_params.size() == num_thunk_params);
return thunk_params;
}
// Computes a name to use for a thunk, based on the name of the thunk's target.
// The actual name used isn't critical, since it doesn't show up much except in
// AST dumps and SemIR output, but we try to produce a valid C++ identifier.
static auto GetDeclNameForThunk(clang::ASTContext& ast_context,
clang::DeclarationName name)
-> clang::DeclarationName {
llvm::SmallString<64> thunk_name;
switch (name.getNameKind()) {
case clang::DeclarationName::NameKind::Identifier: {
thunk_name = name.getAsIdentifierInfo()->getName();
break;
}
case clang::DeclarationName::NameKind::CXXOperatorName: {
thunk_name = "operator_";
switch (name.getCXXOverloadedOperator()) {
case clang::OO_None:
case clang::NUM_OVERLOADED_OPERATORS:
break;
#define OVERLOADED_OPERATOR(Name, Spelling, Token, Unary, Binary, MemberOnly) \
case clang::OO_##Name: \
thunk_name += #Name; \
break;
#include "clang/Basic/OperatorKinds.def"
}
break;
}
default: {
break;
}
}
if (auto type = name.getCXXNameType(); !type.isNull()) {
if (auto* class_decl = type->getAsCXXRecordDecl()) {
thunk_name += class_decl->getName();
}
}
thunk_name += "__carbon_thunk";
return &ast_context.Idents.get(thunk_name);
}
// Returns the thunk function declaration given the callee function and the
// thunk parameter types.
static auto CreateThunkFunctionDecl(
Context& context, CalleeFunctionInfo callee_info,
llvm::ArrayRef<clang::QualType> thunk_param_types) -> clang::FunctionDecl* {
clang::ASTContext& ast_context = context.ast_context();
clang::SourceLocation clang_loc = callee_info.decl->getLocation();
clang::DeclarationName name =
GetDeclNameForThunk(ast_context, callee_info.decl->getDeclName());
auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
clang::QualType thunk_function_type = ast_context.getFunctionType(
callee_info.has_simple_return_type ? callee_info.effective_return_type
: ast_context.VoidTy,
thunk_param_types, ext_proto_info);
clang::DeclContext* decl_context = ast_context.getTranslationUnitDecl();
clang::FunctionDecl* thunk_function_decl = clang::FunctionDecl::Create(
ast_context, decl_context, clang_loc, clang_loc, name,
thunk_function_type, /*TInfo=*/nullptr, clang::SC_None,
/*UsesFPIntrin=*/false, /*isInlineSpecified=*/true);
decl_context->addDecl(thunk_function_decl);
thunk_function_decl->setParams(
BuildThunkParameters(ast_context, callee_info, thunk_function_decl));
// Force the thunk to be inlined and discarded.
thunk_function_decl->addAttr(
clang::AlwaysInlineAttr::CreateImplicit(ast_context));
thunk_function_decl->addAttr(
clang::InternalLinkageAttr::CreateImplicit(ast_context));
// 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),
clang_loc));
// Set function declaration type source info.
thunk_function_decl->setTypeSourceInfo(ast_context.getTrivialTypeSourceInfo(
thunk_function_decl->getType(), clang_loc));
return thunk_function_decl;
}
// 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, 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();
clang::Expr* call_arg = sema.BuildDeclRefExpr(
thunk_param, thunk_param->getType().getNonReferenceType(),
clang::VK_LValue, clang_loc);
if (!type.isNull() && thunk_param->getType() != type) {
clang::ExprResult deref_result =
sema.BuildUnaryOp(nullptr, clang_loc, clang::UO_Deref, call_arg);
CARBON_CHECK(deref_result.isUsable());
call_arg = deref_result.get();
}
// 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());
}
return call_arg;
}
// Builds a reference to the parameter thunk parameter corresponding to the
// given callee parameter index.
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);
return BuildThunkParamRef(
sema, thunk_function_decl, thunk_index,
callee_info.signature->GetPassingMode(callee_index),
callee_info.decl->getParamDecl(callee_index)->getType());
}
// Builds an argument list for the callee function by creating suitable uses of
// the corresponding thunk parameters.
static auto BuildCalleeArgs(clang::Sema& sema,
clang::FunctionDecl* thunk_function_decl,
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.push_back(BuildParamRefForCalleeArg(sema, thunk_function_decl,
callee_info, callee_index));
}
return call_args;
}
// Builds the thunk function body which calls the callee function using the call
// args and returns the callee function return value. Returns nullptr on
// failure.
static auto BuildThunkBody(CppContext& cpp_context, clang::Sema& sema,
clang::FunctionDecl* thunk_function_decl,
CalleeFunctionInfo callee_info)
-> clang::StmtResult {
// TODO: Consider building a CompoundStmt holding our created statement to
// make our result more closely resemble a real C++ function.
clang::SourceLocation clang_loc = callee_info.decl->getLocation();
// 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();
}
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(),
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);
}
if (callee.isInvalid()) {
return clang::StmtError();
}
// Build the argument list.
llvm::SmallVector<clang::Expr*> call_args =
BuildCalleeArgs(sema, thunk_function_decl, callee_info);
clang::ExprResult call;
if (auto info = clang::getConstructorInfo(callee_info.decl);
info.Constructor) {
// In C++, there are no direct calls to constructors, only initialization,
// so we need to type-check and build the call ourselves.
auto type = sema.Context.getCanonicalTagType(
cast<clang::CXXRecordDecl>(callee_info.decl->getParent()));
llvm::SmallVector<clang::Expr*> converted_args;
converted_args.reserve(call_args.size());
if (sema.CompleteConstructorCall(info.Constructor, type, call_args,
clang_loc, converted_args)) {
return clang::StmtError();
}
call = sema.BuildCXXConstructExpr(
clang_loc, type, callee_info.decl, info.Constructor, converted_args,
false, false, false, false, clang::CXXConstructionKind::Complete,
clang_loc);
} else {
call = sema.BuildCallExpr(nullptr, callee.get(), clang_loc, call_args,
clang_loc);
}
if (!call.isUsable()) {
return clang::StmtError();
}
if (callee_info.has_simple_return_type) {
return sema.BuildReturnStmt(clang_loc, call.get());
}
auto* return_object_addr = BuildThunkParamRef(
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);
auto* placement_new_decl = cpp_context.placement_new_decl();
if (!placement_new_decl) {
placement_new_decl = GeneratePlacementNewFunctionDecl(sema.getASTContext());
cpp_context.set_placement_new_decl(placement_new_decl);
}
sema.MarkFunctionReferenced(clang_loc, placement_new_decl);
clang::ImplicitAllocationParameters params(return_type,
clang::TypeAwareAllocationMode::No,
clang::AlignedAllocationMode::No);
clang::SourceRange range(clang_loc, clang_loc);
auto* placement_new = clang::CXXNewExpr::Create(
sema.getASTContext(), /*IsGlobalNew*/ true, placement_new_decl,
/*OperatorDelete*/ nullptr, params, /*UsualArrayDeleteWantsSize*/ false,
{return_object_addr},
/*TypeIdParens=*/clang::SourceRange(), /*ArraySize=*/std::nullopt,
clang::CXXNewInitializationStyle::Parens, call.get(),
sema.getASTContext().getPointerType(return_type), return_type_info, range,
range);
return sema.ActOnExprStmt(placement_new, /*DiscardedValue=*/true);
}
auto BuildCppThunk(Context& context, const SemIR::Function& callee_function)
-> clang::FunctionDecl* {
auto clang_decl_key =
context.clang_decls().Get(callee_function.clang_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);
// Build the thunk function declaration.
auto thunk_param_types =
BuildThunkParameterTypes(context.ast_context(), callee_info);
clang::FunctionDecl* thunk_function_decl =
CreateThunkFunctionDecl(context, callee_info, 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,
thunk_function_decl, callee_info);
sema.ActOnFinishFunctionBody(thunk_function_decl, body.get());
if (body.isInvalid()) {
return nullptr;
}
context.clang_sema().getASTConsumer().HandleTopLevelDecl(
clang::DeclGroupRef(thunk_function_decl));
return thunk_function_decl;
}
auto PerformCppThunkCall(Context& context, SemIR::LocId loc_id,
SemIR::FunctionId callee_function_id,
llvm::ArrayRef<SemIR::InstId> callee_arg_ids,
SemIR::InstId thunk_callee_id) -> SemIR::InstId {
auto& callee_function = context.functions().Get(callee_function_id);
auto callee_function_params =
context.inst_blocks().Get(callee_function.call_params_id);
auto num_callee_return_params =
callee_function.call_param_ranges.return_size();
auto thunk_callee = GetCalleeAsFunction(context.sem_ir(), thunk_callee_id);
auto& thunk_function = context.functions().Get(thunk_callee.function_id);
auto thunk_function_params =
context.inst_blocks().Get(thunk_function.call_params_id);
auto num_thunk_return_params = thunk_function.call_param_ranges.return_size();
CARBON_CHECK(
num_callee_return_params <= 1 && num_thunk_return_params <= 1,
"TODO: generalize this logic to support multiple return patterns.");
// Whether we need to pass a return address to the thunk as a final argument.
bool thunk_takes_return_address =
num_callee_return_params > 0 && num_thunk_return_params == 0;
// The number of arguments we should be acquiring in order to call the thunk.
// This includes the return address parameters, if any.
unsigned num_thunk_args =
context.inst_blocks().Get(thunk_function.param_patterns_id).size();
// The corresponding number of arguments that would be provided in a syntactic
// call to the callee. This excludes the return slot.
unsigned num_callee_args = num_thunk_args - thunk_takes_return_address;
// Grab the return slot argument, if we were given one.
auto return_slot_id = SemIR::InstId::None;
if (callee_arg_ids.size() == num_callee_args + 1) {
return_slot_id = callee_arg_ids.consume_back();
}
// If there are return slot patterns, drop the corresponding parameters.
// TODO: The parameter should probably only be created if the return pattern
// actually needs a return address to be passed in.
thunk_function_params =
thunk_function_params.drop_back(num_thunk_return_params);
callee_function_params =
callee_function_params.drop_back(num_callee_return_params);
// We assume that the call parameters exactly match the parameter patterns for
// both the thunk and the callee. This is guaranteed even when we generate a
// tuple pattern wrapping the function parameters.
CARBON_CHECK(num_callee_args == callee_function_params.size(), "{0} != {1}",
num_callee_args, callee_function_params.size());
CARBON_CHECK(num_callee_args == callee_arg_ids.size());
CARBON_CHECK(num_thunk_args == thunk_function_params.size());
// Build the thunk arguments by converting the callee arguments as needed.
llvm::SmallVector<SemIR::InstId> thunk_arg_ids;
thunk_arg_ids.reserve(num_thunk_args);
for (auto [callee_param_inst_id, thunk_param_inst_id, callee_arg_id] :
llvm::zip(callee_function_params, thunk_function_params,
callee_arg_ids)) {
SemIR::TypeId callee_param_type_id =
context.insts().GetAs<SemIR::AnyParam>(callee_param_inst_id).type_id;
SemIR::TypeId thunk_param_type_id =
context.insts().GetAs<SemIR::AnyParam>(thunk_param_inst_id).type_id;
SemIR::InstId arg_id = callee_arg_id;
if (callee_param_type_id != thunk_param_type_id) {
arg_id = Convert(context, loc_id, arg_id,
{.kind = ConversionTarget::CppThunkRef,
.type_id = callee_param_type_id});
arg_id = AddInst<SemIR::AddrOf>(
context, loc_id,
{.type_id = GetPointerType(
context, context.types().GetTypeInstId(callee_param_type_id)),
.lvalue_id = arg_id});
arg_id =
ConvertToValueOfType(context, loc_id, arg_id, thunk_param_type_id);
}
thunk_arg_ids.push_back(arg_id);
}
// Add an argument to hold the result of the call, if necessary.
auto return_type_id = callee_function.GetDeclaredReturnType(context.sem_ir());
if (thunk_takes_return_address) {
// Create a temporary if the caller didn't provide a return slot.
if (!return_slot_id.has_value()) {
return_slot_id = AddInst<SemIR::TemporaryStorage>(
context, loc_id, {.type_id = return_type_id});
}
auto arg_id = AddInst<SemIR::AddrOf>(
context, loc_id,
{.type_id = GetPointerType(
context, context.types().GetTypeInstId(
context.insts().Get(return_slot_id).type_id())),
.lvalue_id = return_slot_id});
thunk_arg_ids.push_back(arg_id);
} else if (return_slot_id.has_value()) {
thunk_arg_ids.push_back(return_slot_id);
}
// Compute the return type of the call to the thunk.
auto thunk_return_type_id =
thunk_function.GetDeclaredReturnType(context.sem_ir());
if (!thunk_return_type_id.has_value()) {
CARBON_CHECK(thunk_takes_return_address || !return_type_id.has_value());
thunk_return_type_id = GetTupleType(context, {});
} else {
CARBON_CHECK(thunk_return_type_id == return_type_id);
}
auto result_id = GetOrAddInst<SemIR::Call>(
context, loc_id,
{.type_id = thunk_return_type_id,
.callee_id = thunk_callee_id,
.args_id = context.inst_blocks().Add(thunk_arg_ids)});
// Produce the result of the call, taking the value from the return storage.
if (thunk_takes_return_address) {
result_id = AddInst<SemIR::MarkInPlaceInit>(context, loc_id,
{.type_id = return_type_id,
.src_id = result_id,
.dest_id = return_slot_id});
}
return result_id;
}
} // namespace Carbon::Check