I want to write a shared library for Linux which does not depend on libc. Since it contains purely computational (and panic-free) functions it depends only on memset and memcpy symbols.
As expected, readelf -Ws results in the following symbol table:
5: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND memcpy
6: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND memset
7: 00000000000015f0 15 FUNC GLOBAL DEFAULT 11 my_cpy
8: 0000000000001600 17 FUNC GLOBAL DEFAULT 11 my_set
Ideally, I would like to "inline" memcpy and memset into the library without exposing them as global symbols (I am fine with it being slightly less efficient than the system's implementation). I could use the compiler-builtins crate with enabled mem feature, but unfortunately it can be used only on Nightly.
But recently I encountered the #![no_builtins] attribute which looks exactly what I want. And indeed, adding it to the crate removes the memcpy symbol, but for some reason memset is still present:
5: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND memcpy
6: 00000000000015c0 15 FUNC GLOBAL DEFAULT 11 my_cpy
7: 00000000000015d0 156 FUNC GLOBAL DEFAULT 11 my_set
Your code using copy_nonoverlapping that is memcpy under the hood. Since you didn't provide your own memcpy in the library, the compiler expects it to come from an external source (See UND in Ndx).
A bunch of things are required even if you never call them -- LLVM will optimize certain code patterns into calls to those library methods that are expected to exist, for example.
The UND part is not relevant to the question. In practice I use #[link(name = "c")] unsafe extern "C" {} to implicitly link to memset/memcpy from libc.
Yes, but fill(0) compiles to memset by default as well and #![no_builtins] forces the compiler to generate appropriate code instead of calling the symbol.
I guess the difference is whether memset/memcpy is called explicitly or as a result of optimization passes. Replacing fill(0) with core::ptr::write_bytes results in memset regardless whether #![no_builtins] is used or not.
I wonder what prevents the compiler from replacing the symbols with "naive" code when the attribute is used. In the current form it looks quite useless...
Yeah, a no_mangle variant which does not export the symbol probably could've been useful here. Meanwhile, compiler-builtins looks like the only practical option for creating a libc-free library.
fill only compiles to memset due to llvm optimizations. The standard library implements it as
for item in self.iter_mut() {
*item = value;
}
for Copy types. The reference only talks about optimizations to functions that are assumed to exist. I am guessing that memcpy is directly called by the standard library.
Optimizing compiler-builtins intrinsics into self-recursive calls is the only thing #![no_builtins] prevents. It doesn't cause already memcpy that already exist in the input llvm ir to be turned into copy loops or anything like that. And rustc does emit memcpy for copy_nonoverlapping as well as moves.