Skip to content

Commit 2be14a3

Browse files
pvdrzamanjeev
andauthored
Generate extern wrappers for inlined functions (#2335)
* Generate extern wrappers for inlined functions If bindgen finds an inlined function and the `--generate-extern-functions` options is enabled, then: - It will generate two new source and header files with external functions that wrap the inlined functions. - Rerun `Bindings::generate` using the new header file to include these wrappers in the generated bindings. The following additional options were added: - `--extern-function-suffix=<suffix>`: Adds <suffix> to the name of each external wrapper function (`__extern` is used by default). - `--extern-functions-file-name=<name>`: Uses <name> as the file name for the header and source files (`extern` is used by default). - `--extern-function-directory=<dir>`: Creates the source and header files inside <dir> (`/tmp/bindgen` is used by default). The C code serialization is experimental and only supports a very limited set of C functions. Fixes #1090. --------- Co-authored-by: Amanjeev Sethi <[email protected]>
1 parent 62b48c5 commit 2be14a3

File tree

17 files changed

+876
-96
lines changed

17 files changed

+876
-96
lines changed

bindgen-cli/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ path = "main.rs"
2121
name = "bindgen"
2222

2323
[dependencies]
24-
bindgen = { path = "../bindgen", version = "=0.63.0", features = ["cli"] }
24+
bindgen = { path = "../bindgen", version = "=0.63.0", features = ["cli", "experimental"] }
2525
shlex = "1"
2626
clap = { version = "4", features = ["derive"] }
2727
env_logger = { version = "0.9.0", optional = true }

bindgen-cli/options.rs

+30
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,20 @@ struct BindgenCommand {
353353
/// Derive custom traits on a `union`. The <CUSTOM> value must be of the shape <REGEX>=<DERIVE> where <DERIVE> is a coma-separated list of derive macros.
354354
#[arg(long, value_name = "CUSTOM")]
355355
with_derive_custom_union: Vec<String>,
356+
/// Generate wrappers for `static` and `static inline` functions.
357+
#[arg(long, requires = "experimental")]
358+
wrap_static_fns: bool,
359+
/// Sets the path for the source file that must be created due to the presence of `static` and
360+
/// `static inline` functions.
361+
#[arg(long, requires = "experimental", value_name = "PATH")]
362+
wrap_static_fns_path: Option<PathBuf>,
363+
/// Sets the suffix added to the extern wrapper functions generated for `static` and `static
364+
/// inline` functions.
365+
#[arg(long, requires = "experimental", value_name = "SUFFIX")]
366+
wrap_static_fns_suffix: Option<String>,
367+
/// Enables experimental features.
368+
#[arg(long)]
369+
experimental: bool,
356370
/// Prints the version, and exits
357371
#[arg(short = 'V', long)]
358372
version: bool,
@@ -473,6 +487,10 @@ where
473487
with_derive_custom_struct,
474488
with_derive_custom_enum,
475489
with_derive_custom_union,
490+
wrap_static_fns,
491+
wrap_static_fns_path,
492+
wrap_static_fns_suffix,
493+
experimental: _,
476494
version,
477495
clang_args,
478496
} = command;
@@ -978,5 +996,17 @@ where
978996
}
979997
}
980998

999+
if wrap_static_fns {
1000+
builder = builder.wrap_static_fns(true);
1001+
}
1002+
1003+
if let Some(path) = wrap_static_fns_path {
1004+
builder = builder.wrap_static_fns_path(path);
1005+
}
1006+
1007+
if let Some(suffix) = wrap_static_fns_suffix {
1008+
builder = builder.wrap_static_fns_suffix(suffix);
1009+
}
1010+
9811011
Ok((builder, output, verbose))
9821012
}

bindgen-integration/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ publish = false
77
build = "build.rs"
88

99
[build-dependencies]
10-
bindgen = { path = "../bindgen" }
10+
bindgen = { path = "../bindgen", features = ["experimental"] }
1111
cc = "1.0"
1212

1313
[features]

bindgen-integration/build.rs

+100-28
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ extern crate cc;
44
use bindgen::callbacks::{
55
DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks,
66
};
7-
use bindgen::{Builder, EnumVariation};
7+
use bindgen::{Builder, CargoCallbacks, EnumVariation};
88
use std::collections::HashSet;
99
use std::env;
1010
use std::path::PathBuf;
@@ -28,21 +28,14 @@ impl ParseCallbacks for MacroCallback {
2828
MacroParsingBehavior::Default
2929
}
3030

31-
fn item_name(&self, original_item_name: &str) -> Option<String> {
32-
if original_item_name.starts_with("my_prefixed_") {
33-
Some(
34-
original_item_name
35-
.trim_start_matches("my_prefixed_")
36-
.to_string(),
37-
)
38-
} else if original_item_name.starts_with("MY_PREFIXED_") {
39-
Some(
40-
original_item_name
41-
.trim_start_matches("MY_PREFIXED_")
42-
.to_string(),
43-
)
44-
} else {
45-
None
31+
fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
32+
match name {
33+
"TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom {
34+
name: "crate::MacroInteger",
35+
is_signed: true,
36+
}),
37+
38+
_ => None,
4639
}
4740
}
4841

@@ -67,17 +60,6 @@ impl ParseCallbacks for MacroCallback {
6760
}
6861
}
6962

70-
fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
71-
match name {
72-
"TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom {
73-
name: "crate::MacroInteger",
74-
is_signed: true,
75-
}),
76-
77-
_ => None,
78-
}
79-
}
80-
8163
fn func_macro(&self, name: &str, value: &[&[u8]]) {
8264
match name {
8365
"TESTMACRO_NONFUNCTIONAL" => {
@@ -122,6 +104,24 @@ impl ParseCallbacks for MacroCallback {
122104
}
123105
}
124106

107+
fn item_name(&self, original_item_name: &str) -> Option<String> {
108+
if original_item_name.starts_with("my_prefixed_") {
109+
Some(
110+
original_item_name
111+
.trim_start_matches("my_prefixed_")
112+
.to_string(),
113+
)
114+
} else if original_item_name.starts_with("MY_PREFIXED_") {
115+
Some(
116+
original_item_name
117+
.trim_start_matches("MY_PREFIXED_")
118+
.to_string(),
119+
)
120+
} else {
121+
None
122+
}
123+
}
124+
125125
// Test the "custom derives" capability by adding `PartialEq` to the `Test` struct.
126126
fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> {
127127
if info.name == "Test" {
@@ -149,7 +149,7 @@ impl Drop for MacroCallback {
149149
}
150150
}
151151

152-
fn main() {
152+
fn setup_macro_test() {
153153
cc::Build::new()
154154
.cpp(true)
155155
.file("cpp/Test.cc")
@@ -204,3 +204,75 @@ fn main() {
204204
"including stub via include dir must produce correct dep path",
205205
);
206206
}
207+
208+
fn setup_wrap_static_fns_test() {
209+
// GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090
210+
// set output directory under /target so it is easy to clean generated files
211+
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
212+
let out_rust_file = out_path.join("extern.rs");
213+
214+
let input_header_dir = PathBuf::from("../bindgen-tests/tests/headers/")
215+
.canonicalize()
216+
.expect("Cannot canonicalize libdir path");
217+
let input_header_file_path = input_header_dir.join("wrap-static-fns.h");
218+
let input_header_file_path_str = input_header_file_path
219+
.to_str()
220+
.expect("Path could not be converted to a str");
221+
222+
// generate external bindings with the external .c and .h files
223+
let bindings = Builder::default()
224+
.header(input_header_file_path_str)
225+
.parse_callbacks(Box::new(CargoCallbacks))
226+
.wrap_static_fns(true)
227+
.wrap_static_fns_path(
228+
out_path.join("wrap_static_fns").display().to_string(),
229+
)
230+
.generate()
231+
.expect("Unable to generate bindings");
232+
233+
println!("cargo:rustc-link-lib=static=wrap_static_fns"); // tell cargo to link libextern
234+
println!("bindings generated: {}", bindings);
235+
236+
let obj_path = out_path.join("wrap_static_fns.o");
237+
let lib_path = out_path.join("libwrap_static_fns.a");
238+
239+
// build the external files to check if they work
240+
let clang_output = std::process::Command::new("clang")
241+
.arg("-c")
242+
.arg("-o")
243+
.arg(&obj_path)
244+
.arg(out_path.join("wrap_static_fns.c"))
245+
.arg("-include")
246+
.arg(input_header_file_path)
247+
.output()
248+
.expect("`clang` command error");
249+
if !clang_output.status.success() {
250+
panic!(
251+
"Could not compile object file:\n{}",
252+
String::from_utf8_lossy(&clang_output.stderr)
253+
);
254+
}
255+
256+
let ar_output = std::process::Command::new("ar")
257+
.arg("rcs")
258+
.arg(lib_path)
259+
.arg(obj_path)
260+
.output()
261+
.expect("`ar` command error");
262+
263+
if !ar_output.status.success() {
264+
panic!(
265+
"Could not emit library file:\n{}",
266+
String::from_utf8_lossy(&ar_output.stderr)
267+
);
268+
}
269+
270+
bindings
271+
.write_to_file(out_rust_file)
272+
.expect("Cound not write bindings to the Rust file");
273+
}
274+
275+
fn main() {
276+
setup_macro_test();
277+
setup_wrap_static_fns_test();
278+
}

bindgen-integration/src/lib.rs

+36
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ mod bindings {
44
include!(concat!(env!("OUT_DIR"), "/test.rs"));
55
}
66

7+
mod extern_bindings {
8+
include!(concat!(env!("OUT_DIR"), "/extern.rs"));
9+
}
10+
711
use std::ffi::CStr;
812
use std::mem;
913
use std::os::raw::c_int;
@@ -286,3 +290,35 @@ fn test_custom_derive() {
286290
assert!(meter < lightyear);
287291
assert!(meter > micron);
288292
}
293+
294+
#[test]
295+
fn test_wrap_static_fns() {
296+
// GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090
297+
unsafe {
298+
let f = extern_bindings::foo();
299+
assert_eq!(11, f);
300+
301+
let b = extern_bindings::bar();
302+
assert_eq!(1, b);
303+
304+
let t = extern_bindings::takes_ptr(&mut 1);
305+
assert_eq!(2, t);
306+
307+
extern "C" fn function(x: i32) -> i32 {
308+
x + 1
309+
}
310+
311+
let tp = extern_bindings::takes_fn_ptr(Some(function));
312+
assert_eq!(2, tp);
313+
314+
let tf = extern_bindings::takes_fn(Some(function));
315+
assert_eq!(3, tf);
316+
317+
let ta = extern_bindings::takes_alias(Some(function));
318+
assert_eq!(4, ta);
319+
320+
let tq =
321+
extern_bindings::takes_qualified(&(&5 as *const _) as *const _);
322+
assert_eq!(5, tq);
323+
}
324+
}

bindgen-tests/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ version = "0.1.0"
55
publish = false
66

77
[dev-dependencies]
8-
bindgen = { path = "../bindgen", features = ["cli"] }
8+
bindgen = { path = "../bindgen", features = ["cli", "experimental"] }
99
diff = "0.1"
1010
shlex = "1"
1111
clap = { version = "4", features = ["derive"] }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Generated C, C++, Header files
2+
3+
This directory contains files for features where extra files are generated
4+
as a part of the feature. For example, `--wrap-static-fns`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
int foo__extern(void) asm("foo__extern");
2+
int foo__extern() { return foo(); }
3+
int bar__extern(void) asm("bar__extern");
4+
int bar__extern() { return bar(); }
5+
int takes_ptr__extern(int *arg) asm("takes_ptr__extern");
6+
int takes_ptr__extern(int *arg) { return takes_ptr(arg); }
7+
int takes_fn_ptr__extern(int (*f) (int)) asm("takes_fn_ptr__extern");
8+
int takes_fn_ptr__extern(int (*f) (int)) { return takes_fn_ptr(f); }
9+
int takes_fn__extern(int (f) (int)) asm("takes_fn__extern");
10+
int takes_fn__extern(int (f) (int)) { return takes_fn(f); }
11+
int takes_alias__extern(func f) asm("takes_alias__extern");
12+
int takes_alias__extern(func f) { return takes_alias(f); }
13+
int takes_qualified__extern(const int *const *arg) asm("takes_qualified__extern");
14+
int takes_qualified__extern(const int *const *arg) { return takes_qualified(arg); }

bindgen-tests/tests/expectations/tests/wrap-static-fns.rs

+52
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// bindgen-flags: --experimental --wrap-static-fns
2+
3+
static inline int foo() {
4+
return 11;
5+
}
6+
static int bar() {
7+
return 1;
8+
}
9+
inline int baz() {
10+
return 2;
11+
}
12+
13+
static inline int takes_ptr(int* arg) {
14+
return *arg + 1;
15+
}
16+
17+
static inline int takes_fn_ptr(int (*f)(int)) {
18+
return f(1);
19+
}
20+
21+
static inline int takes_fn(int (f)(int)) {
22+
return f(2);
23+
}
24+
25+
typedef int (func)(int);
26+
27+
static inline int takes_alias(func f) {
28+
return f(3);
29+
}
30+
31+
static inline int takes_qualified(const int *const *arg) {
32+
return **arg;
33+
}

0 commit comments

Comments
 (0)