blob: 466349413b020e9dda579edb22bf953ee9a895a6 [file] [log] [blame] [view]
# Thunks for class template member functions
## Problem definition
Given the C++ header below...
```cpp
#pragma clang lifetime_elision
template <typename T>
class MyTemplate {
public:
MyTemplate(T value) : value_(value) {}
const T& GetValue() const;
private:
T value_;
};
using MyIntTemplate = MyTemplate<int>;
```
... Crubit will generate Rust bindings that can call into the
`MyTemplate<int>::GetValue()` member function. To support such calls, Crubit has
to generate a C++ *thunk* (to *instantiate* the class template and to provide a
symbol for a C-ABI-compatible function that Rust can call into):
```cpp
extern "C" // <- C ABI
int const& __rust_thunk___ZNK10MyTemplateIiE8GetValueEv(
const class MyTemplate<int>* __this) {
return __this->GetValue();
}
```
There are other (non-`template`-related) scenarios that require generating
thunks (e.g. `inline` functions, or functions that use a custom calling
convention), but templates bring one extra requirement: a class template can
be defined in one header (say `my_template.h`) and used in *multiple* other
headers (e.g. `library_foo/template_user1.h` and
`library_bar/template_user2.h`). Because of this, the same thunk might need to
be present in *multiple* generated `..._rs_api_impl.cc` files (e.g. in
`library_foo_rs_api_impl.cc` and `library_bar_rs_api_impl.cc`). This may lead to
duplicate symbol errors from the linker:
```stderr
ld: error: duplicate symbol: __rust_thunk___ZNK10MyTemplateIiE8GetValueEv
```
## Implemented solution: Encoding target name in the thunk name
One solution is to give each of the generated thunks a unique,
target/library-specific name, e.g.:
`__rust_thunk___ZNK10MyTemplateIiE8GetValueEv__library_foo` (note the
`library_foo` suffix).
Pros:
- **Minimal extra code complexity** (e.g. no need for templates-specific code
in thunk-related code in `src_code_gen.rs`).
- **Obviously correct behavior-wise** (e.g. since it is just like other thunks
which we assume are implemented correctly).
Cons:
- **Performance guarantees are unclear**. Binary size depends on link time
optimization (LTO) recognizing that all the thunks are identical and
deduplicating them.
- This seems to work in practice (at least for production binaries).
- Future work: add tests + consider asking LLVM to provide LTO guarantees
- **Requires escaping Bazel target names** into valid C identifiers. See
`ConvertToCcIdentifier(const BazelLabel&)` in `bazel_types.cc`.
## Alternative solutions
### Function template
An alternative solution would be to use a function template that we immediately
explicitly instantiate. These still generate the code we need, but their
duplicated symbol definitions (across multiple binding crates) won't cause an
ODR violation. It is expected that a single function template is instantiated
multiple times in multiple translation units, therefore the linker silently
merges these equivalent definitions.
Example:
```cpp
// Thunk is expressed as a function template:
template <typename = void>
__attribute__((__always_inline__)) int const&
__rust_thunk___ZNK10MyTemplateIiE8GetValueEv(
const class MyTemplate<int>* __this) {
return __this->GetValue();
}
// Explicit instantiation of the function template:
// (to generate a symbol that `..._rs_api.rs` can call into)
template int const& __rust_thunk___ZNK10MyTemplateIiE8GetValueEv(
const class MyTemplate<int>* __this);
```
Pros:
- **Naturally deduplicated** (just depending on what C++ already does for
function templates).
Cons:
- **Assumes a particular ABI** - a function template specialization uses the
calling convention prescribed by the platform C++ ABI. We know that
[the Itanium ABI maps C++ sigatures to the C
ABI](https://itanium-cxx-abi.github.io/cxx-abi/abi.html#functions) and
therefore will be compatible with the calling convention expected by the
generated `..._rs_api.rs`. Further research is needed to investigate the
guarantees offered by other platforms (e.g., the MSVC ABI).
- **Requires extra complexity** to calculate the mangled name of the function
template specialization.
- Crubit doesn’t have a `clang::FunctionDecl` corresponding to the
function-template-based thunk, and therefore Crubit can’t use
`clang::MangleContext::mangleName` to calculate the linkable/mangled
name of the thunk.
- Reimplementing `clang::MangleContext::mangleName` in Crubit seems
fragile. One risk is bugs in Crubit's code that would make it behave
differently from Clang (e.g. code review of the initial prototype
identified that
[mangling compression](https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling-compression)
was missing). Another risk is having to implement not just
`ItaniumMangleContext`, but also `MicrosoftMangleContext`.
- One idea to avoid reimpliementing mangling is to explicitly specify
the name for the function template instantiation using
`__asm__("abc")` (sadly this doesn't seem to work - it may be a
Clang bug).
An abandoned prototype of this approach can be found in a (Google-internal)
cl/450495903.
### Explicit `linkonce_odr` attribute
Example:
```cpp
extern "C"
int const& __rust_thunk___ZNK10MyTemplateIiE8GetValueEv(
const class MyTemplate<int>* __this)
__attribute__((linkonce_odr)) // <- THIS IS THE PROPOSAL
{
return __this->GetValue();
}
```
Pros:
- All the "pros" of the "Encoding target name in the thunk name" approach
(simplicity + correctness of behavior)
- All the "pros" of the "Function template" template approach (deduplication)
Cons:
- Requires changing Clang to support the new attribute (e.g. requires
convincing the Clang community that this is a language extension that is
worth supporting).
**TODO(b/234889162)**: Send out a short RFC to gauge interest?
## Rejected solutions
- [`selectany`](https://clang.llvm.org/docs/AttributeReference.html#selectany)
doesn't work with functions, only data members. Furthermore, we need
something that maps to `linkonce_odr`, and selectany maps only to
`linkonce`.
- `__attribute__((weak))` has the disadvantage that a weak definition can be
overridden by a strong one. This rule makes weak definitions non-inlineable
except in full-program LTO. C++ function template instead follows the ODR
rule that says that all definitions must be equivalent, making them
inlineable.