Thunks for class template member functions
Problem definition
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
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&)inbazel_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:
// 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 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::FunctionDeclcorresponding to the function-template-based thunk, and therefore Crubit can’t useclang::MangleContext::mangleNameto calculate the linkable/mangled name of the thunk. - Reimplementing
clang::MangleContext::mangleNamein 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 justItaniumMangleContext, but alsoMicrosoftMangleContext. - 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).
- Crubit doesn’t have a
An abandoned prototype of this approach can be found in a (Google-internal) cl/450495903.
Explicit linkonce_odr attribute
Example:
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
-
selectanydoesn't work with functions, only data members. Furthermore, we need something that maps tolinkonce_odr, and selectany maps only tolinkonce. -
__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.