Skip to content

Commit

Permalink
Generate extern wrappers for inlined functions (#2335)
Browse files Browse the repository at this point in the history
* 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 <aj@amanjeev.com>
  • Loading branch information
pvdrz and amanjeev authored Feb 7, 2023
1 parent 62b48c5 commit 2be14a3
Show file tree
Hide file tree
Showing 17 changed files with 876 additions and 96 deletions.
2 changes: 1 addition & 1 deletion bindgen-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ path = "main.rs"
name = "bindgen"

[dependencies]
bindgen = { path = "../bindgen", version = "=0.63.0", features = ["cli"] }
bindgen = { path = "../bindgen", version = "=0.63.0", features = ["cli", "experimental"] }
shlex = "1"
clap = { version = "4", features = ["derive"] }
env_logger = { version = "0.9.0", optional = true }
Expand Down
30 changes: 30 additions & 0 deletions bindgen-cli/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,20 @@ struct BindgenCommand {
/// 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.
#[arg(long, value_name = "CUSTOM")]
with_derive_custom_union: Vec<String>,
/// Generate wrappers for `static` and `static inline` functions.
#[arg(long, requires = "experimental")]
wrap_static_fns: bool,
/// Sets the path for the source file that must be created due to the presence of `static` and
/// `static inline` functions.
#[arg(long, requires = "experimental", value_name = "PATH")]
wrap_static_fns_path: Option<PathBuf>,
/// Sets the suffix added to the extern wrapper functions generated for `static` and `static
/// inline` functions.
#[arg(long, requires = "experimental", value_name = "SUFFIX")]
wrap_static_fns_suffix: Option<String>,
/// Enables experimental features.
#[arg(long)]
experimental: bool,
/// Prints the version, and exits
#[arg(short = 'V', long)]
version: bool,
Expand Down Expand Up @@ -473,6 +487,10 @@ where
with_derive_custom_struct,
with_derive_custom_enum,
with_derive_custom_union,
wrap_static_fns,
wrap_static_fns_path,
wrap_static_fns_suffix,
experimental: _,
version,
clang_args,
} = command;
Expand Down Expand Up @@ -978,5 +996,17 @@ where
}
}

if wrap_static_fns {
builder = builder.wrap_static_fns(true);
}

if let Some(path) = wrap_static_fns_path {
builder = builder.wrap_static_fns_path(path);
}

if let Some(suffix) = wrap_static_fns_suffix {
builder = builder.wrap_static_fns_suffix(suffix);
}

Ok((builder, output, verbose))
}
2 changes: 1 addition & 1 deletion bindgen-integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ publish = false
build = "build.rs"

[build-dependencies]
bindgen = { path = "../bindgen" }
bindgen = { path = "../bindgen", features = ["experimental"] }
cc = "1.0"

[features]
Expand Down
128 changes: 100 additions & 28 deletions bindgen-integration/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ extern crate cc;
use bindgen::callbacks::{
DeriveInfo, IntKind, MacroParsingBehavior, ParseCallbacks,
};
use bindgen::{Builder, EnumVariation};
use bindgen::{Builder, CargoCallbacks, EnumVariation};
use std::collections::HashSet;
use std::env;
use std::path::PathBuf;
Expand All @@ -28,21 +28,14 @@ impl ParseCallbacks for MacroCallback {
MacroParsingBehavior::Default
}

fn item_name(&self, original_item_name: &str) -> Option<String> {
if original_item_name.starts_with("my_prefixed_") {
Some(
original_item_name
.trim_start_matches("my_prefixed_")
.to_string(),
)
} else if original_item_name.starts_with("MY_PREFIXED_") {
Some(
original_item_name
.trim_start_matches("MY_PREFIXED_")
.to_string(),
)
} else {
None
fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
match name {
"TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom {
name: "crate::MacroInteger",
is_signed: true,
}),

_ => None,
}
}

Expand All @@ -67,17 +60,6 @@ impl ParseCallbacks for MacroCallback {
}
}

fn int_macro(&self, name: &str, _value: i64) -> Option<IntKind> {
match name {
"TESTMACRO_CUSTOMINTKIND_PATH" => Some(IntKind::Custom {
name: "crate::MacroInteger",
is_signed: true,
}),

_ => None,
}
}

fn func_macro(&self, name: &str, value: &[&[u8]]) {
match name {
"TESTMACRO_NONFUNCTIONAL" => {
Expand Down Expand Up @@ -122,6 +104,24 @@ impl ParseCallbacks for MacroCallback {
}
}

fn item_name(&self, original_item_name: &str) -> Option<String> {
if original_item_name.starts_with("my_prefixed_") {
Some(
original_item_name
.trim_start_matches("my_prefixed_")
.to_string(),
)
} else if original_item_name.starts_with("MY_PREFIXED_") {
Some(
original_item_name
.trim_start_matches("MY_PREFIXED_")
.to_string(),
)
} else {
None
}
}

// Test the "custom derives" capability by adding `PartialEq` to the `Test` struct.
fn add_derives(&self, info: &DeriveInfo<'_>) -> Vec<String> {
if info.name == "Test" {
Expand Down Expand Up @@ -149,7 +149,7 @@ impl Drop for MacroCallback {
}
}

fn main() {
fn setup_macro_test() {
cc::Build::new()
.cpp(true)
.file("cpp/Test.cc")
Expand Down Expand Up @@ -204,3 +204,75 @@ fn main() {
"including stub via include dir must produce correct dep path",
);
}

fn setup_wrap_static_fns_test() {
// GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090
// set output directory under /target so it is easy to clean generated files
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
let out_rust_file = out_path.join("extern.rs");

let input_header_dir = PathBuf::from("../bindgen-tests/tests/headers/")
.canonicalize()
.expect("Cannot canonicalize libdir path");
let input_header_file_path = input_header_dir.join("wrap-static-fns.h");
let input_header_file_path_str = input_header_file_path
.to_str()
.expect("Path could not be converted to a str");

// generate external bindings with the external .c and .h files
let bindings = Builder::default()
.header(input_header_file_path_str)
.parse_callbacks(Box::new(CargoCallbacks))
.wrap_static_fns(true)
.wrap_static_fns_path(
out_path.join("wrap_static_fns").display().to_string(),
)
.generate()
.expect("Unable to generate bindings");

println!("cargo:rustc-link-lib=static=wrap_static_fns"); // tell cargo to link libextern
println!("bindings generated: {}", bindings);

let obj_path = out_path.join("wrap_static_fns.o");
let lib_path = out_path.join("libwrap_static_fns.a");

// build the external files to check if they work
let clang_output = std::process::Command::new("clang")
.arg("-c")
.arg("-o")
.arg(&obj_path)
.arg(out_path.join("wrap_static_fns.c"))
.arg("-include")
.arg(input_header_file_path)
.output()
.expect("`clang` command error");
if !clang_output.status.success() {
panic!(
"Could not compile object file:\n{}",
String::from_utf8_lossy(&clang_output.stderr)
);
}

let ar_output = std::process::Command::new("ar")
.arg("rcs")
.arg(lib_path)
.arg(obj_path)
.output()
.expect("`ar` command error");

if !ar_output.status.success() {
panic!(
"Could not emit library file:\n{}",
String::from_utf8_lossy(&ar_output.stderr)
);
}

bindings
.write_to_file(out_rust_file)
.expect("Cound not write bindings to the Rust file");
}

fn main() {
setup_macro_test();
setup_wrap_static_fns_test();
}
36 changes: 36 additions & 0 deletions bindgen-integration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ mod bindings {
include!(concat!(env!("OUT_DIR"), "/test.rs"));
}

mod extern_bindings {
include!(concat!(env!("OUT_DIR"), "/extern.rs"));
}

use std::ffi::CStr;
use std::mem;
use std::os::raw::c_int;
Expand Down Expand Up @@ -286,3 +290,35 @@ fn test_custom_derive() {
assert!(meter < lightyear);
assert!(meter > micron);
}

#[test]
fn test_wrap_static_fns() {
// GH-1090: https://github.com/rust-lang/rust-bindgen/issues/1090
unsafe {
let f = extern_bindings::foo();
assert_eq!(11, f);

let b = extern_bindings::bar();
assert_eq!(1, b);

let t = extern_bindings::takes_ptr(&mut 1);
assert_eq!(2, t);

extern "C" fn function(x: i32) -> i32 {
x + 1
}

let tp = extern_bindings::takes_fn_ptr(Some(function));
assert_eq!(2, tp);

let tf = extern_bindings::takes_fn(Some(function));
assert_eq!(3, tf);

let ta = extern_bindings::takes_alias(Some(function));
assert_eq!(4, ta);

let tq =
extern_bindings::takes_qualified(&(&5 as *const _) as *const _);
assert_eq!(5, tq);
}
}
2 changes: 1 addition & 1 deletion bindgen-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ version = "0.1.0"
publish = false

[dev-dependencies]
bindgen = { path = "../bindgen", features = ["cli"] }
bindgen = { path = "../bindgen", features = ["cli", "experimental"] }
diff = "0.1"
shlex = "1"
clap = { version = "4", features = ["derive"] }
Expand Down
4 changes: 4 additions & 0 deletions bindgen-tests/tests/expectations/tests/generated/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Generated C, C++, Header files

This directory contains files for features where extra files are generated
as a part of the feature. For example, `--wrap-static-fns`.
14 changes: 14 additions & 0 deletions bindgen-tests/tests/expectations/tests/generated/wrap_static_fns.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
int foo__extern(void) asm("foo__extern");
int foo__extern() { return foo(); }
int bar__extern(void) asm("bar__extern");
int bar__extern() { return bar(); }
int takes_ptr__extern(int *arg) asm("takes_ptr__extern");
int takes_ptr__extern(int *arg) { return takes_ptr(arg); }
int takes_fn_ptr__extern(int (*f) (int)) asm("takes_fn_ptr__extern");
int takes_fn_ptr__extern(int (*f) (int)) { return takes_fn_ptr(f); }
int takes_fn__extern(int (f) (int)) asm("takes_fn__extern");
int takes_fn__extern(int (f) (int)) { return takes_fn(f); }
int takes_alias__extern(func f) asm("takes_alias__extern");
int takes_alias__extern(func f) { return takes_alias(f); }
int takes_qualified__extern(const int *const *arg) asm("takes_qualified__extern");
int takes_qualified__extern(const int *const *arg) { return takes_qualified(arg); }
52 changes: 52 additions & 0 deletions bindgen-tests/tests/expectations/tests/wrap-static-fns.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions bindgen-tests/tests/headers/wrap-static-fns.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// bindgen-flags: --experimental --wrap-static-fns

static inline int foo() {
return 11;
}
static int bar() {
return 1;
}
inline int baz() {
return 2;
}

static inline int takes_ptr(int* arg) {
return *arg + 1;
}

static inline int takes_fn_ptr(int (*f)(int)) {
return f(1);
}

static inline int takes_fn(int (f)(int)) {
return f(2);
}

typedef int (func)(int);

static inline int takes_alias(func f) {
return f(3);
}

static inline int takes_qualified(const int *const *arg) {
return **arg;
}
Loading

0 comments on commit 2be14a3

Please sign in to comment.