Skip to content

Commit 3c9d267

Browse files
authored
Generate and use a C++ thunk to call non simple ABI C++ functions (#5850)
When the C++ function has a parameter that is not a pointer and not a signed integer of 32 or 64 bits, generate a thunk. Terminology: * Callee function: The C++ function we actually want to call. * Thunk function: The C++ function we generated that calls the callee function. * A simple ABI type, for now, is one of: * A pointer * signed integer with 32 bits * signed integer with 64 bits The thunk function is marked `always_inline` and uses the `asm` attribute to set its mangled to the callee function mangled name suffixed with `".carbon_thunk"`. When importing a C++ function, we decide whether calling it requires a thunk and if so we generate it and import it as well, which is currently a recursive call. When calling the thunk function, we initialize a temporary storage for each non simple ABI parameter type and take its address. This can be optimized when the variable is already in storage. Not supported yet: * Functions with non void return values. * Member methods. Moved unsigned int param test from `arithmetic_types_direct.carbon` to `arithmetic_types_bridged.carbon`, since only signed integers aren't bridged using a thunk. C++ Interop Demo: ```c++ // hello_world.h struct S { S() {} S(const S&) { x = 1; } int x; }; void hello_world(S s); ``` ```c++ // hello_world.cpp #include "hello_world.h" #include <cstdio> void hello_world2(S s) { printf("hello_world2: %d\n", s.x); } void hello_world(S s) { printf("hello_world: %d\n", s.x); hello_world2(s); } ``` ```carbon // main.carbon library "Main"; import Cpp library "hello_world.h"; fn Run() -> i32 { var s : Cpp.S; Cpp.hello_world(s); return 0; } ``` ```shell $ clang -c hello_world.cpp $ bazel-bin/toolchain/carbon compile main.carbon $ bazel-bin/toolchain/carbon link hello_world.o main.o --output=demo $ ./demo hello_world: 1 hello_world2: 1 ``` Before this change (no thunk - copy constructor not called when calling `hello_world()`): ```shell $ ./demo hello_world: -1219172304 hello_world2: 1 ```
1 parent 7cac771 commit 3c9d267

File tree

16 files changed

+2169
-400
lines changed

16 files changed

+2169
-400
lines changed

toolchain/check/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ cc_library(
2121
"context.cpp",
2222
"control_flow.cpp",
2323
"convert.cpp",
24+
"cpp_thunk.cpp",
2425
"decl_name_stack.cpp",
2526
"deduce.cpp",
2627
"deferred_definition_worklist.cpp",
@@ -65,6 +66,7 @@ cc_library(
6566
"context.h",
6667
"control_flow.h",
6768
"convert.h",
69+
"cpp_thunk.h",
6870
"decl_introducer_state.h",
6971
"decl_name_stack.h",
7072
"deduce.h",

toolchain/check/call.cpp

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "toolchain/check/context.h"
1111
#include "toolchain/check/control_flow.h"
1212
#include "toolchain/check/convert.h"
13+
#include "toolchain/check/cpp_thunk.h"
1314
#include "toolchain/check/deduce.h"
1415
#include "toolchain/check/facet_type.h"
1516
#include "toolchain/check/function.h"
@@ -255,27 +256,39 @@ static auto PerformCallToFunction(Context& context, SemIR::LocId loc_id,
255256
ConvertCallArgs(context, loc_id, callee_function.self_id, arg_ids,
256257
return_slot_arg_id, callee, *callee_specific_id);
257258

258-
// If we're about to form a direct call to a thunk, inline it.
259-
if (callee.special_function_kind ==
260-
SemIR::Function::SpecialFunctionKind::Thunk) {
261-
LoadImportRef(context, callee.thunk_decl_id());
259+
switch (callee.special_function_kind) {
260+
case SemIR::Function::SpecialFunctionKind::Thunk: {
261+
// If we're about to form a direct call to a thunk, inline it.
262+
LoadImportRef(context, callee.thunk_decl_id());
262263

263-
// Name the thunk target within the enclosing scope of the thunk.
264-
auto thunk_ref_id =
265-
BuildNameRef(context, loc_id, callee.name_id, callee.thunk_decl_id(),
266-
callee_function.enclosing_specific_id);
264+
// Name the thunk target within the enclosing scope of the thunk.
265+
auto thunk_ref_id =
266+
BuildNameRef(context, loc_id, callee.name_id, callee.thunk_decl_id(),
267+
callee_function.enclosing_specific_id);
267268

268-
// This recurses back into `PerformCall`. However, we never form a thunk to
269-
// a thunk, so we only recurse once.
270-
return PerformThunkCall(context, loc_id, callee_function.function_id,
271-
context.inst_blocks().Get(converted_args_id),
272-
thunk_ref_id);
273-
}
269+
// This recurses back into `PerformCall`. However, we never form a thunk
270+
// to a thunk, so we only recurse once.
271+
return PerformThunkCall(context, loc_id, callee_function.function_id,
272+
context.inst_blocks().Get(converted_args_id),
273+
thunk_ref_id);
274+
}
275+
276+
case SemIR::Function::SpecialFunctionKind::HasCppThunk: {
277+
// This recurses back into `PerformCall`. However, we never form a C++
278+
// thunk to a C++ thunk, so we only recurse once.
279+
return PerformCppThunkCall(context, loc_id, callee_function.function_id,
280+
context.inst_blocks().Get(converted_args_id),
281+
callee.cpp_thunk_decl_id());
282+
}
274283

275-
return GetOrAddInst<SemIR::Call>(context, loc_id,
276-
{.type_id = return_info.type_id,
277-
.callee_id = callee_id,
278-
.args_id = converted_args_id});
284+
case SemIR::Function::SpecialFunctionKind::None:
285+
case SemIR::Function::SpecialFunctionKind::Builtin: {
286+
return GetOrAddInst<SemIR::Call>(context, loc_id,
287+
{.type_id = return_info.type_id,
288+
.callee_id = callee_id,
289+
.args_id = converted_args_id});
290+
}
291+
}
279292
}
280293

281294
auto PerformCall(Context& context, SemIR::LocId loc_id, SemIR::InstId callee_id,

0 commit comments

Comments
 (0)