| # 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. |