How to handle static inline
functions
#2405
Replies: 7 comments 19 replies
-
Some fixes I discovered:
fn link_static_fns(out_dir_path: PathBuf) {
let obj_path = out_dir_path.join("extern.o");
let clang_output = Command::new("clang")
.arg("-O")
.arg("-c")
.arg("-o")
.arg(&obj_path)
.arg(env::temp_dir().join("bindgen").join("extern.c"))
.arg("-include")
.arg("src/wrapper.h")
.output()
.unwrap();
if !clang_output.status.success() {
panic!(
"Could not compile object file:\n{}",
String::from_utf8_lossy(&clang_output.stderr)
);
}
#[cfg(not(target_os = "windows"))]
let lib_output = Command::new("ar")
.arg("rcs")
.arg(out_dir_path.join("libextern.a"))
.arg(obj_path)
.output()
.unwrap();
#[cfg(target_os = "windows")]
let lib_output = Command::new("lib").arg(&obj_path).output().unwrap();
if !lib_output.status.success() {
panic!(
"Could not emit library file:\n{}",
String::from_utf8_lossy(&lib_output.stderr)
);
}
println!(
"cargo:rustc-link-search=native={}",
out_dir_path.to_string_lossy()
);
println!("cargo:rustc-link-lib=static=extern");
} |
Beta Was this translation helpful? Give feedback.
-
Would it be possible to generate Rust code instead, perhaps via c2rust? That would allow the code to be inlined, which could be a big performance win. |
Beta Was this translation helpful? Give feedback.
-
First of all thanks for this guide. Where is |
Beta Was this translation helpful? Give feedback.
-
What needs to be considered when cross-compiling? // Compile the generated wrappers into an object file.
let clang_output = std::process::Command::new("clang")
.arg("-O")
.arg("-c")
.arg("-o")
.arg(&obj_path)
.args(clang_args.clone())
.arg(std::env::temp_dir().join("bindgen").join("extern.c"))
.arg("-include")
.arg(input)
.output()
.unwrap(); But i still get this error:
any advices? |
Beta Was this translation helpful? Give feedback.
-
This discussion is very great. Could it be summarized as an example project ? In order to help others to use inline functions easily with bindgen. |
Beta Was this translation helpful? Give feedback.
-
What is the purpose of the
|
Beta Was this translation helpful? Give feedback.
-
Thanks for adding this feature! The functionality seems to have been stable for a while -- are there any plans to move it out of |
Beta Was this translation helpful? Give feedback.
-
Before
v0.64.0
was released, the only way to handlestatic inline
functions onbindgen
was using the--generate-inline-functions
option which generated rust bindings for these functions. However, that meant that the input C library should still expose those function symbols somehow, most likely by compiling the library without inlining enabled which could be a serious performance issue.With the new
bindgen
version there is another alternative, the--wrap-static-fns-*
flags which generate external wrapper functions for thesestatic inline
functions, requiring the user to only compile these generated wrappers against the headers file being used as an input. For me, the clearest way to explain how this works is by doing an example.Let's say we have the following
input.h
header file:If we passed this file to bindgen without any flags we would get an empty output:
However, if we pass the
--wrap-static-fns
flag we get the following:We need to pass this
--experimental
flag because this feature is not complete and prone to change. However, the good news is that now we got rust bindings for bothinc
anddec
. Additionally a new c source file should be created under thebindgen
directory inside your temporal folder (/tmp/bindgen/
if you're on unix-like systems):These
__extern
functions are wrappers for the static functions we defined in our input. Now the only thing we need to do is to compile this newextern.c
file into a library and includeinput.h
:As we can see, the
extern.o
object file includes two symbols:inc__extern
anddec__extern
. These symbols are the ones that will replaceinc
anddec
in our Rust bindings, and that's why both function declarations in the bindings have the#[link_name]
attribute overriding the linking name.We could take different approaches from here, one of them would be turning this object file into a static library:
or if you're on windows:
$ LIB extern.o /OUT:extern.lib
And now we can link our bindings against this
libextern
static library with rust. This same procedure could be done in a build script:In either case, you should be able to call
inc
anddec
from rust without issue now!Using LTO optimizations
If you made it up to this point you might have noticed that using the wrappers for
static
function is going to be less performant just because those functions are not being inlined by the Rust compiler. To illustrate this. We will edit thesrc/lib.rs
file so it has the following contents:and we will add a
src/main.rs
file with the following contents:where
playground_bindgen
is the name of our crate.If we compile this crate using
cargo build --release
and then disassemble the resulting binary usingobjdump
we will find thisBasically
increase
anddecrease
are just jumping to someplace else instead of doinglea
asinc__extern
anddec__extern
do.In order to solve this, we can enable LTO optimizations for our crate. First we need to change the
clang
invocation so it uses "thin" LTO:We must also change the
ar
invocation (if someone knows the windows equivalent of this, please let me know):Then we must change the
Cargo.toml
manifest to enable "thin" LTO from the rust side by adding the following:Finally we can compile our project with the following
RUSTFLAGS
:$ env RUSTFLAGS="-Clinker-plugin-lto -Clinker=clang -Clink-arg=-fuse-ld=lld" cargo build --release
Now if we check the generated machine code using
objdump
we will find thisWhere
increase
anddecrease
just dolea
and then return!Customizing the wrappers
There are additional flags/methods to customize the behavior of this feature:
--wrap-static-fns-path
. You should not try to set the extension of this file as bindgen will infer automatically if this should be a C or C++ source code file.__extern
suffix used for wrapper functions by using the--wrap-static-fns-suffix
. This is useful if for some reason there are name collisions with the default suffix.Where's the catch?
The weakest point of this feature is the C/C++ code generation. As of today, we can only generate a subset of C code and we know that this subset is good enough to compile some real-life libraries. However, C++ support is lacking (PRs are welcome!).
If you have any issues with this feature you can open a new issue or discussion and tag me.
Thanks to @JMS55 for the windows instructions and to @DemiMarie for the LTO suggestion!
Beta Was this translation helpful? Give feedback.
All reactions