Given the C++ header below...
#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):
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:
ld: error: duplicate symbol: __rust_thunk___ZNK10MyTemplateIiE8GetValueEv
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:
src_code_gen.rs
).Cons:
Performance guarantees are unclear. Binary size depends on link time optimization (LTO) recognizing that all the thunks are identical and deduplicating them.
Requires escaping Bazel target names into valid C identifiers. See ConvertToCcIdentifier(const BazelLabel&)
in bazel_types.cc
.
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:
// 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:
Cons:
..._rs_api.rs
. Further research is needed to investigate the guarantees offered by other platforms (e.g., the MSVC ABI).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.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 was missing). Another risk is having to implement not just ItaniumMangleContext
, but also MicrosoftMangleContext
.__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.
linkonce_odr
attributeExample:
extern "C" int const& __rust_thunk___ZNK10MyTemplateIiE8GetValueEv( const class MyTemplate<int>* __this) __attribute__((linkonce_odr)) // <- THIS IS THE PROPOSAL { return __this->GetValue(); }
Pros:
Cons:
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.