mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-10-05 22:02:55 +01:00
Add return value support to C++ thunks. (#5976)
Based on #5948. A couple of tricky parts: * When generating the C++ side of the thunk, we are given a pointer to the location to emplace the return value. The only mechanism C++ provides to perform this emplacement is using placement `operator new`, which requires a library function in the `<new>` header. We handle this by declaring that library function ourselves, and rely on Clang not actually needing a definition for it (which the standard library owns). * On the Carbon side of the thunk, we want to form an initializing expression as the result of the call. We don't have a way of expressing in SemIR that an initializing expression performs its initialization by storing through a pointer, so this PR adds a new initializing instruction, `InPlaceInit`, to model an initialization that's performed opaquely in-place.
This commit is contained in:
+184
-47
@@ -74,34 +74,46 @@ auto IsCppThunkRequired(Context& context, const SemIR::Function& function)
|
||||
return false;
|
||||
}
|
||||
|
||||
SemIR::TypeId return_type_id =
|
||||
function.GetDeclaredReturnType(context.sem_ir());
|
||||
if (return_type_id.has_value()) {
|
||||
// TODO: Support non-void return values.
|
||||
if (isa<clang::CXXConstructorDecl>(
|
||||
context.sem_ir().clang_decls().Get(function.clang_decl_id).decl)) {
|
||||
// TODO: Support generating thunks for constructors.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool thunk_required_for_param = false;
|
||||
// A thunk is required if any parameter or return type requires it. However,
|
||||
// we don't generate a thunk if any relevant type is erroneous.
|
||||
bool thunk_required = false;
|
||||
|
||||
SemIR::TypeId return_type_id =
|
||||
function.GetDeclaredReturnType(context.sem_ir());
|
||||
if (return_type_id.has_value()) {
|
||||
if (return_type_id == SemIR::ErrorInst::TypeId) {
|
||||
return false;
|
||||
}
|
||||
thunk_required = IsThunkRequiredForType(context, return_type_id);
|
||||
}
|
||||
|
||||
for (auto param_id :
|
||||
context.inst_blocks().GetOrEmpty(function.call_params_id)) {
|
||||
if (param_id == SemIR::ErrorInst::InstId) {
|
||||
return false;
|
||||
}
|
||||
if (!thunk_required_for_param &&
|
||||
if (!thunk_required &&
|
||||
IsThunkRequiredForType(
|
||||
context,
|
||||
context.insts().GetAs<SemIR::AnyParam>(param_id).type_id)) {
|
||||
thunk_required_for_param = true;
|
||||
thunk_required = true;
|
||||
}
|
||||
}
|
||||
|
||||
return thunk_required_for_param;
|
||||
return thunk_required;
|
||||
}
|
||||
|
||||
// Returns whether the type is a pointer or a signed int of 32 or 64 bits.
|
||||
// Returns whether the type is void, a pointer, or a signed int of 32 or 64
|
||||
// bits.
|
||||
static auto IsSimpleAbiType(clang::ASTContext& ast_context,
|
||||
clang::QualType type) -> bool {
|
||||
if (type->isPointerType()) {
|
||||
if (type->isVoidType() || type->isPointerType()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -125,6 +137,8 @@ struct CalleeFunctionInfo {
|
||||
if (has_object_parameter && method_decl->isImplicitObjectMemberFunction()) {
|
||||
implicit_this_type = method_decl->getThisType();
|
||||
}
|
||||
has_simple_return_type =
|
||||
IsSimpleAbiType(decl->getASTContext(), decl->getReturnType());
|
||||
}
|
||||
|
||||
// Returns whether this callee has an implicit `this` parameter.
|
||||
@@ -139,13 +153,23 @@ struct CalleeFunctionInfo {
|
||||
|
||||
// Returns the number of parameters the thunk should have.
|
||||
auto num_thunk_params() const -> unsigned {
|
||||
return has_implicit_object_parameter() + decl->getNumParams();
|
||||
return has_implicit_object_parameter() + decl->getNumParams() +
|
||||
!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() + decl->getNumParams();
|
||||
}
|
||||
|
||||
// The callee function.
|
||||
clang::FunctionDecl* decl;
|
||||
|
||||
@@ -156,9 +180,32 @@ struct CalleeFunctionInfo {
|
||||
// If the callee has an implicit object parameter, the corresponding `this`
|
||||
// type. Otherwise a null type.
|
||||
clang::QualType implicit_this_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
|
||||
|
||||
// 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 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)) {
|
||||
return callee_type;
|
||||
}
|
||||
return GetNonnullType(ast_context, ast_context.getPointerType(callee_type));
|
||||
}
|
||||
|
||||
// Creates the thunk parameter types given the callee function.
|
||||
static auto BuildThunkParameterTypes(clang::ASTContext& ast_context,
|
||||
CalleeFunctionInfo callee_info)
|
||||
@@ -166,21 +213,22 @@ static auto BuildThunkParameterTypes(clang::ASTContext& ast_context,
|
||||
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(ast_context.getAttributedType(
|
||||
clang::NullabilityKind::NonNull, callee_info.implicit_this_type,
|
||||
callee_info.implicit_this_type));
|
||||
thunk_param_types.push_back(
|
||||
GetNonnullType(ast_context, callee_info.implicit_this_type));
|
||||
}
|
||||
|
||||
for (const clang::ParmVarDecl* callee_param :
|
||||
callee_info.decl->parameters()) {
|
||||
clang::QualType param_type = callee_param->getType();
|
||||
bool is_simple_abi_type = IsSimpleAbiType(ast_context, param_type);
|
||||
if (!is_simple_abi_type) {
|
||||
clang::QualType pointer_type = ast_context.getPointerType(param_type);
|
||||
param_type = ast_context.getAttributedType(
|
||||
clang::NullabilityKind::NonNull, pointer_type, pointer_type);
|
||||
}
|
||||
thunk_param_types.push_back(param_type);
|
||||
// TODO: We should use the type from the function signature, not the type of
|
||||
// the parameter here.
|
||||
thunk_param_types.push_back(
|
||||
GetThunkParameterType(ast_context, callee_param->getType()));
|
||||
}
|
||||
|
||||
if (!callee_info.has_simple_return_type) {
|
||||
thunk_param_types.push_back(GetNonnullType(
|
||||
ast_context,
|
||||
ast_context.getPointerType(callee_info.decl->getReturnType())));
|
||||
}
|
||||
|
||||
CARBON_CHECK(thunk_param_types.size() == callee_info.num_thunk_params());
|
||||
@@ -220,6 +268,16 @@ static auto BuildThunkParameters(clang::ASTContext& ast_context,
|
||||
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;
|
||||
}
|
||||
@@ -237,7 +295,9 @@ static auto CreateThunkFunctionDecl(
|
||||
|
||||
auto ext_proto_info = clang::FunctionProtoType::ExtProtoInfo();
|
||||
clang::QualType thunk_function_type = ast_context.getFunctionType(
|
||||
callee_info.decl->getReturnType(), thunk_param_types, ext_proto_info);
|
||||
callee_info.has_simple_return_type ? callee_info.decl->getReturnType()
|
||||
: ast_context.VoidTy,
|
||||
thunk_param_types, ext_proto_info);
|
||||
|
||||
clang::DeclContext* decl_context = ast_context.getTranslationUnitDecl();
|
||||
// TODO: Thunks should not have external linkage, consider using `SC_Static`.
|
||||
@@ -264,11 +324,13 @@ static auto CreateThunkFunctionDecl(
|
||||
return thunk_function_decl;
|
||||
}
|
||||
|
||||
// Builds a reference to the given parameter thunk, which is carrying a value of
|
||||
// the given type.
|
||||
// Builds a reference to the given parameter thunk. If `type` is specified, that
|
||||
// is the callee parameter type that's being held by the parameter, and
|
||||
// conversions will be performed as necessary to recover a value of that type.
|
||||
static auto BuildThunkParamRef(clang::Sema& sema,
|
||||
clang::FunctionDecl* thunk_function_decl,
|
||||
unsigned thunk_index, clang::QualType type)
|
||||
unsigned thunk_index,
|
||||
clang::QualType type = clang::QualType())
|
||||
-> clang::Expr* {
|
||||
clang::ParmVarDecl* thunk_param =
|
||||
thunk_function_decl->getParamDecl(thunk_index);
|
||||
@@ -277,7 +339,7 @@ static auto BuildThunkParamRef(clang::Sema& sema,
|
||||
clang::Expr* call_arg = sema.BuildDeclRefExpr(
|
||||
thunk_param, thunk_param->getType().getNonReferenceType(),
|
||||
clang::VK_LValue, clang_loc);
|
||||
if (thunk_param->getType() != type) {
|
||||
if (!type.isNull() && thunk_param->getType() != type) {
|
||||
// TODO: Consider inserting a cast to an rvalue. Note that we currently
|
||||
// pass pointers to non-temporary objects as the argument when calling a
|
||||
// thunk, so we'll need to either change that or generate different thunks
|
||||
@@ -329,6 +391,9 @@ static auto BuildThunkBody(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
|
||||
@@ -337,9 +402,9 @@ static auto BuildThunkBody(clang::Sema& sema,
|
||||
if (callee_info.has_object_parameter) {
|
||||
auto* object_param_ref =
|
||||
BuildThunkParamRef(sema, thunk_function_decl, 0,
|
||||
callee_info.has_implicit_object_parameter()
|
||||
? thunk_function_decl->getParamDecl(0)->getType()
|
||||
: callee_info.decl->getParamDecl(0)->getType());
|
||||
callee_info.has_explicit_object_parameter()
|
||||
? callee_info.decl->getParamDecl(0)->getType()
|
||||
: clang::QualType());
|
||||
bool is_arrow = callee_info.has_implicit_object_parameter();
|
||||
auto object =
|
||||
sema.PerformMemberExprBaseConversion(object_param_ref, is_arrow);
|
||||
@@ -373,9 +438,20 @@ static auto BuildThunkBody(clang::Sema& sema,
|
||||
return clang::StmtError();
|
||||
}
|
||||
|
||||
// TODO: Consider building a CompoundStmt holding this to make our result more
|
||||
// closely resemble a real C++ function.
|
||||
return sema.BuildReturnStmt(clang_loc, call.get());
|
||||
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());
|
||||
auto return_type = callee_info.decl->getReturnType();
|
||||
auto* return_type_info =
|
||||
sema.Context.getTrivialTypeSourceInfo(return_type, clang_loc);
|
||||
auto placement_new = sema.BuildCXXNew(
|
||||
clang_loc, /*UseGlobal=*/true, clang_loc, {return_object_addr}, clang_loc,
|
||||
/*TypeIdParens=*/clang::SourceRange(), return_type, return_type_info,
|
||||
/*ArraySize=*/std::nullopt, clang_loc, call.get());
|
||||
return sema.ActOnExprStmt(placement_new, /*DiscardedValue=*/true);
|
||||
}
|
||||
|
||||
auto BuildCppThunk(Context& context, const SemIR::Function& callee_function)
|
||||
@@ -413,22 +489,55 @@ 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 {
|
||||
llvm::ArrayRef<SemIR::InstId> callee_function_params =
|
||||
context.inst_blocks().GetOrEmpty(
|
||||
context.functions().Get(callee_function_id).call_params_id);
|
||||
auto& callee_function = context.functions().Get(callee_function_id);
|
||||
auto callee_function_params =
|
||||
context.inst_blocks().Get(callee_function.call_params_id);
|
||||
|
||||
llvm::ArrayRef<SemIR::InstId> thunk_function_params =
|
||||
context.inst_blocks().GetOrEmpty(
|
||||
context.functions()
|
||||
.Get(GetCalleeFunction(context.sem_ir(), thunk_callee_id)
|
||||
.function_id)
|
||||
.call_params_id);
|
||||
auto thunk_callee = GetCalleeFunction(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);
|
||||
|
||||
size_t num_params = callee_function_params.size();
|
||||
CARBON_CHECK(thunk_function_params.size() == num_params);
|
||||
CARBON_CHECK(callee_arg_ids.size() == num_params);
|
||||
// Whether we need to pass a return address to the thunk as a final argument.
|
||||
bool thunk_takes_return_address =
|
||||
callee_function.return_slot_pattern_id.has_value() &&
|
||||
!thunk_function.return_slot_pattern_id.has_value();
|
||||
|
||||
// The number of arguments we should be acquiring in order to call the thunk.
|
||||
// This includes the return address parameter, 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's a return slot pattern, drop the corresponding parameter.
|
||||
// TODO: The parameter should probably only be created if the return pattern
|
||||
// actually needs a return address to be passed in.
|
||||
if (thunk_function.return_slot_pattern_id.has_value()) {
|
||||
thunk_function_params.consume_back();
|
||||
}
|
||||
if (callee_function.return_slot_pattern_id.has_value()) {
|
||||
callee_function_params.consume_back();
|
||||
}
|
||||
|
||||
// We assume that the call parameters exactly match the parameter patterns for
|
||||
// both the thunk and the callee. This is currently guaranteed because we only
|
||||
// create trivial *ParamPatterns when importing a C++ function.
|
||||
CARBON_CHECK(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_params);
|
||||
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)) {
|
||||
@@ -453,7 +562,35 @@ auto PerformCppThunkCall(Context& context, SemIR::LocId loc_id,
|
||||
thunk_arg_ids.push_back(arg_id);
|
||||
}
|
||||
|
||||
return PerformCall(context, loc_id, thunk_callee_id, thunk_arg_ids);
|
||||
// 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 = AddInstWithCleanup<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().GetInstId(
|
||||
context.insts().Get(return_slot_id).type_id())),
|
||||
.lvalue_id = return_slot_id});
|
||||
thunk_arg_ids.push_back(arg_id);
|
||||
}
|
||||
|
||||
auto result_id = PerformCall(context, loc_id, thunk_callee_id, 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::InPlaceInit>(context, loc_id,
|
||||
{.type_id = return_type_id,
|
||||
.src_id = result_id,
|
||||
.dest_id = return_slot_id});
|
||||
}
|
||||
|
||||
return result_id;
|
||||
}
|
||||
|
||||
} // namespace Carbon::Check
|
||||
|
||||
Reference in New Issue
Block a user