How to handle static inline functions
#2405
Replies: 9 comments 22 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.
-
|
It's much easier to use the bindgen::Builder::default()
.header(format!("{}/src/wrapper.h", env!("CARGO_MANIFEST_DIR")))
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.wrap_static_fns(true)
.wrap_static_fns_path(out_dir.join("wrap_static_fns"))
.clang_arg(format!("-I{dlss_sdk}/include"))
.clang_arg(format!("-I{vulkan_sdk}/{vulkan_sdk_include}"))
.allowlist_item(".*NGX.*")
.generate()
.unwrap()
.write_to_file(out_dir.join("bindings.rs"))
.unwrap();
cc::Build::new()
.file(out_dir.join("wrap_static_fns.c"))
.includes([
format!("{}/src/wrapper.h", env!("CARGO_MANIFEST_DIR")),
format!("{dlss_sdk}/include"),
format!("{vulkan_sdk}/{vulkan_sdk_include}"),
])
.compile("wrap_static_fns"); |
Beta Was this translation helpful? Give feedback.
-
|
I am working on a MacOS. After running Does anyone know how can I fix this? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Before
v0.64.0was released, the only way to handlestatic inlinefunctions onbindgenwas using the--generate-inline-functionsoption 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
bindgenversion there is another alternative, the--wrap-static-fns-*flags which generate external wrapper functions for thesestatic inlinefunctions, 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.hheader file:If we passed this file to bindgen without any flags we would get an empty output:
However, if we pass the
--wrap-static-fnsflag we get the following:We need to pass this
--experimentalflag because this feature is not complete and prone to change. However, the good news is that now we got rust bindings for bothincanddec. Additionally a new c source file should be created under thebindgendirectory inside your temporal folder (/tmp/bindgen/if you're on unix-like systems):These
__externfunctions are wrappers for the static functions we defined in our input. Now the only thing we need to do is to compile this newextern.cfile into a library and includeinput.h:As we can see, the
extern.oobject file includes two symbols:inc__externanddec__extern. These symbols are the ones that will replaceincanddecin 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.libAnd now we can link our bindings against this
libexternstatic library with rust. This same procedure could be done in a build script:In either case, you should be able to call
incanddecfrom rust without issue now!Using LTO optimizations
If you made it up to this point you might have noticed that using the wrappers for
staticfunction 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.rsfile so it has the following contents:and we will add a
src/main.rsfile with the following contents:where
playground_bindgenis the name of our crate.If we compile this crate using
cargo build --releaseand then disassemble the resulting binary usingobjdumpwe will find thisBasically
increaseanddecreaseare just jumping to someplace else instead of doingleaasinc__externanddec__externdo.In order to solve this, we can enable LTO optimizations for our crate. First we need to change the
clanginvocation so it uses "thin" LTO:We must also change the
arinvocation (if someone knows the windows equivalent of this, please let me know):Then we must change the
Cargo.tomlmanifest 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 --releaseNow if we check the generated machine code using
objdumpwe will find thisWhere
increaseanddecreasejust doleaand 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.__externsuffix 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