Files
carbon-lang/toolchain/check/cpp_thunk.h
T
Boaz Brickner 3c9d267388 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
```
2025-08-06 10:27:33 +00:00

39 lines
1.7 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
#ifndef CARBON_TOOLCHAIN_CHECK_CPP_THUNK_H_
#define CARBON_TOOLCHAIN_CHECK_CPP_THUNK_H_
#include "toolchain/check/context.h"
#include "toolchain/sem_ir/ids.h"
namespace Carbon::Check {
// Returns whether the given C++ imported function requires a C++ thunk to be
// used to call it. A C++ thunk is required for functions that use any type
// except void, pointer types and signed 32-bit and 64-bit integers.
auto IsCppThunkRequired(Context& context, const SemIR::Function& function)
-> bool;
// Given a function signature and a callee function, builds a C++ thunk with
// simple ABI (pointers, i32 and i64 types) that calls the specified callee.
// Assumes `IsCppThunkRequired()` return true for `callee_function`. Returns
// `nullptr` on failure.
auto BuildCppThunk(Context& context, const SemIR::Function& callee_function)
-> clang::FunctionDecl*;
// Builds a call to a thunk function that forwards a call argument list built
// for `callee_function_id` to a call to `thunk_callee_id`, for use when
// building a call from a C++ thunk to its target. This is like `PerformCall`,
// except that it takes a list of call arguments for `callee_function_id`, not a
// syntactic argument list.
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;
} // namespace Carbon::Check
#endif // CARBON_TOOLCHAIN_CHECK_CPP_THUNK_H_