Skip to content

[WIP] reexport the static inline functions and TLS variables with C/CPP wrapper #1460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/clang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,18 @@ pub fn type_to_str(x: CXTypeKind) -> String {
unsafe { cxstring_into_string(clang_getTypeKindSpelling(x)) }
}

/// Convert a linkage kind to a static string.
pub fn linkage_to_str(x: CXLinkageKind) -> &'static str {
match x {
CXLinkage_Invalid => "invalid",
CXLinkage_NoLinkage => "non-extern",
CXLinkage_Internal => "internal",
CXLinkage_UniqueExternal => "unique-external",
CXLinkage_External =>"external",
_ => unreachable!(),
}
}

/// Dump the Clang AST to stdout for debugging purposes.
pub fn ast_dump(c: &Cursor, depth: isize) -> CXChildVisitResult {
fn print_indent<S: AsRef<str>>(depth: isize, s: S) {
Expand All @@ -1546,6 +1558,10 @@ pub fn ast_dump(c: &Cursor, depth: isize) -> CXChildVisitResult {
depth,
format!(" {}spelling = \"{}\"", prefix, c.spelling()),
);
print_indent(
depth,
format!(" {}linkage = {}", prefix, linkage_to_str(c.linkage())),
);
print_indent(depth, format!(" {}location = {}", prefix, c.location()));
print_indent(
depth,
Expand Down
93 changes: 85 additions & 8 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ struct CodegenResult<'a> {
/// Whether a bitfield allocation unit has been seen at least once.
saw_bitfield_unit: bool,

/// Whether a static inlined function have been seen at least once.
saw_static_inlined_function: bool,

items_seen: HashSet<ItemId>,
/// The set of generated function/var names, needed because in C/C++ is
/// legal to do something like:
Expand Down Expand Up @@ -142,6 +145,7 @@ impl<'a> CodegenResult<'a> {
saw_objc: false,
saw_block: false,
saw_bitfield_unit: false,
saw_static_inlined_function: false,
codegen_id: codegen_id,
items_seen: Default::default(),
functions_seen: Default::default(),
Expand Down Expand Up @@ -170,6 +174,10 @@ impl<'a> CodegenResult<'a> {
self.saw_bitfield_unit = true;
}

fn saw_static_inlined_function(&mut self) {
self.saw_static_inlined_function = true;
}

fn seen<Id: Into<ItemId>>(&self, item: Id) -> bool {
self.items_seen.contains(&item.into())
}
Expand Down Expand Up @@ -217,6 +225,7 @@ impl<'a> CodegenResult<'a> {
self.saw_block |= new.saw_block;
self.saw_bitfield_unit |= new.saw_bitfield_unit;
self.saw_bindgen_union |= new.saw_bindgen_union;
self.saw_static_inlined_function |= new.saw_static_inlined_function;

new.items
}
Expand Down Expand Up @@ -412,6 +421,9 @@ impl CodeGenerator for Module {
if result.saw_bitfield_unit {
utils::prepend_bitfield_unit_type(&mut *result);
}
if result.saw_static_inlined_function {
utils::prepend_c_include(ctx, &mut *result);
}
}
};

Expand Down Expand Up @@ -3315,12 +3327,16 @@ impl CodeGenerator for Function {
debug!("<Function as CodeGenerator>::codegen: item = {:?}", item);
debug_assert!(item.is_enabled_for_codegen(ctx));

// We can't currently do anything with Internal functions so just
// We can't currently do anything with Internal non-inlined functions so just
// avoid generating anything for them.
match self.linkage() {
Linkage::Internal => return,
Linkage::External => {}
}
let is_static_inlined_function = match self.linkage() {
Linkage::Internal if self.kind() == FunctionKind::Function && self.is_inlined() => {
result.saw_static_inlined_function();
true
},
Linkage::External => { false }
_ => { return }
};

// Pure virtual methods have no actual symbol, so we can't generate
// something meaningful for them.
Expand Down Expand Up @@ -3381,13 +3397,31 @@ impl CodeGenerator for Function {
write!(&mut canonical_name, "{}", times_seen).unwrap();
}

if let Some(mangled) = mangled_name {
let cfunc_name = if let Some(mangled) = mangled_name {
if canonical_name != mangled {
attributes.push(attributes::link_name(mangled));
Some(mangled.to_owned())
} else {
None
}
} else if name != canonical_name {
attributes.push(attributes::link_name(name));
}
Some(name.to_owned())
} else {
None
}.unwrap_or_else(|| {
if is_static_inlined_function {
let mut cfunc_name = format!("_{}", canonical_name);
let times_seen = result.overload_number(&cfunc_name);
if times_seen > 0 {
write!(&mut cfunc_name, "{}", times_seen).unwrap();
}
attributes.push(attributes::link_name(&cfunc_name));
cfunc_name
} else {
canonical_name.to_owned()
}
});

let abi = match signature.abi() {
Abi::ThisCall if !ctx.options().rust_features().thiscall_abi => {
Expand All @@ -3409,12 +3443,41 @@ impl CodeGenerator for Function {
abi => abi,
};

let ident = ctx.rust_ident(canonical_name);
let ident = ctx.rust_ident(&canonical_name);
let tokens = quote!( extern #abi {
#(#attributes)*
pub fn #ident ( #( #args ),* ) #ret;
});
result.push(tokens);
if is_static_inlined_function {
let cfunc_name = proc_macro2::TokenStream::from_str(&cfunc_name).unwrap();
let ret_ty = ctx.resolve_item(signature.return_type());
let ret_stmt = if let TypeKind::Void = *ret_ty.kind().expect_type().kind() {
None
} else {
Some(quote!{ return })
};
let ret_ty_name = proc_macro2::TokenStream::from_str(ret_ty.name(ctx).get().as_str()).unwrap();
let arg_decl = signature.argument_types().iter().map(|&(ref name, ty)| {
let arg_ty = ctx.resolve_item(ty).kind().expect_type().name().unwrap();
let arg_name = name.as_ref().map(|s| s.as_str()).unwrap();

proc_macro2::TokenStream::from_str(&format!("{} {}", arg_ty, arg_name)).unwrap()
});
let args = signature.argument_types().iter().map(|&(ref name, _)| {
let arg_name = name.as_ref().map(|s| s.as_str()).unwrap();

proc_macro2::TokenStream::from_str(arg_name).unwrap()
});
let macro_name = proc_macro2::TokenStream::from_str(if ctx.options().is_cpp() { "cpp" } else { "c" }).unwrap();

let tokens = quote!( #macro_name ! {
#ret_ty_name #cfunc_name ( #( #arg_decl ),* ) {
#ret_stmt #ident ( #( #args ),* );
}
});
result.push(tokens);
}
}
}

Expand Down Expand Up @@ -3576,6 +3639,7 @@ mod utils {
use ir::ty::TypeKind;
use proc_macro2;
use std::mem;
use std::path::Path;
use std::str::FromStr;

pub fn prepend_bitfield_unit_type(result: &mut Vec<proc_macro2::TokenStream>) {
Expand All @@ -3587,6 +3651,19 @@ mod utils {
result.extend(old_items);
}

pub fn prepend_c_include(
ctx: &BindgenContext,
result: &mut Vec<proc_macro2::TokenStream>,
) {
if let Some(ref header_file) = ctx.options().input_header() {
let macro_name = proc_macro2::TokenStream::from_str(if ctx.options().is_cpp() { "cpp" } else { "c" }).unwrap();
let include = proc_macro2::TokenStream::from_str("#include").unwrap();
let header_file = Path::new(header_file).file_name().unwrap().to_str().unwrap();

result.insert(0, quote!( #macro_name ! { #include #header_file }))
}
}

pub fn prepend_objc_header(
ctx: &BindgenContext,
result: &mut Vec<proc_macro2::TokenStream>,
Expand Down
14 changes: 12 additions & 2 deletions src/ir/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ pub struct Function {

/// The linkage of the function.
linkage: Linkage,

/// The function is inlined.
inlined: bool,
}

impl Function {
Expand All @@ -102,7 +105,8 @@ impl Function {
signature: TypeId,
comment: Option<String>,
kind: FunctionKind,
linkage: Linkage
linkage: Linkage,
inlined: bool,
) -> Self {
Function {
name,
Expand All @@ -111,6 +115,7 @@ impl Function {
comment,
kind,
linkage,
inlined,
}
}

Expand Down Expand Up @@ -139,6 +144,10 @@ impl Function {
self.linkage
}

/// Whether this function is inline.
pub fn is_inlined(&self) -> bool {
self.inlined
}
}

impl DotAttributes for Function {
Expand Down Expand Up @@ -546,6 +555,7 @@ impl ClangSubItemParser for Function {
{
return Err(ParseError::Continue);
}
let inlined = cursor.is_inlined_function();

let linkage = cursor.linkage();
let linkage = match linkage {
Expand Down Expand Up @@ -578,7 +588,7 @@ impl ClangSubItemParser for Function {
let mangled_name = cursor_mangling(context, &cursor);
let comment = cursor.raw_comment();

let function = Self::new(name, mangled_name, sig, comment, kind, linkage);
let function = Self::new(name, mangled_name, sig, comment, kind, linkage, inlined);
Ok(ParseResult::New(function, Some(cursor)))
}
}
Expand Down
27 changes: 19 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ fn args_are_cpp(clang_args: &[String]) -> bool {
.any(|w| w[0] == "-xc++" || w[1] == "-xc++" || w == &["-x", "c++"]);
}

fn header_is_cpp(name_file: &str) -> bool {
name_file.ends_with(".hpp") || name_file.ends_with(".hxx")
|| name_file.ends_with(".hh")
|| name_file.ends_with(".h++")
}

bitflags! {
/// A type used to indicate which kind of items we have to generate.
pub struct CodegenConfig: u32 {
Expand Down Expand Up @@ -1222,12 +1228,6 @@ impl Builder {
/// issues. The resulting file will be named something like `__bindgen.i` or
/// `__bindgen.ii`
pub fn dump_preprocessed_input(&self) -> io::Result<()> {
fn check_is_cpp(name_file: &str) -> bool {
name_file.ends_with(".hpp") || name_file.ends_with(".hxx")
|| name_file.ends_with(".hh")
|| name_file.ends_with(".h++")
}

let clang = clang_sys::support::Clang::find(None, &[]).ok_or_else(|| {
io::Error::new(io::ErrorKind::Other, "Cannot find clang executable")
})?;
Expand All @@ -1241,7 +1241,7 @@ impl Builder {

// For each input header, add `#include "$header"`.
for header in &self.input_headers {
is_cpp |= check_is_cpp(header);
is_cpp |= header_is_cpp(header);

wrapper_contents.push_str("#include \"");
wrapper_contents.push_str(header);
Expand All @@ -1251,7 +1251,7 @@ impl Builder {
// For each input header content, add a prefix line of `#line 0 "$name"`
// followed by the contents.
for &(ref name, ref contents) in &self.input_header_contents {
is_cpp |= check_is_cpp(name);
is_cpp |= header_is_cpp(name);

wrapper_contents.push_str("#line 0 \"");
wrapper_contents.push_str(name);
Expand Down Expand Up @@ -1580,6 +1580,17 @@ impl BindgenOptions {
pub fn rust_features(&self) -> RustFeatures {
self.rust_features
}

/// Get the input header file.
pub fn input_header(&self) -> Option<&str> {
self.input_header.as_ref().map(|s| s.as_str())
}

/// Whether the header is c++ mode.
pub fn is_cpp(&self) -> bool {
args_are_cpp(&self.clang_args) ||
self.input_header.as_ref().map(|filename| header_is_cpp(filename)).unwrap_or(false)
}
}

impl Default for BindgenOptions {
Expand Down
15 changes: 12 additions & 3 deletions tests/expectations/tests/generate-inline.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
/* automatically generated by rust-bindgen */

#![allow(
dead_code,
non_snake_case,
non_camel_case_types,
non_upper_case_globals
)]

#![allow(dead_code, non_snake_case, non_camel_case_types, non_upper_case_globals)]


cpp! { # include "generate-inline.hpp" }
#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct Foo {
Expand Down Expand Up @@ -36,3 +40,8 @@ extern "C" {
#[link_name = "\u{1}_Z3foov"]
pub fn foo() -> ::std::os::raw::c_int;
}
extern "C" {
#[link_name = "\u{1}_ZL3barii"]
pub fn bar(x: ::std::os::raw::c_int, y: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
}
cpp! { int _ZL3barii ( const int x , const int y ) { return bar ( x , y ) ; } }
20 changes: 20 additions & 0 deletions tests/expectations/tests/libclang-5/generate-c-inline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* automatically generated by rust-bindgen */

#![allow(
dead_code,
non_snake_case,
non_camel_case_types,
non_upper_case_globals
)]

c! { # include "generate-c-inline.h" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do these macros come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rust-c has a simple c! {} implementation for C, and rust-cpp also provides cpp! {}macro for C++

if you think the way is right, I could add a special implementation to bindgen, it need add some code to build.rs, generate some .c/.cpp file on building.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it may be ok to depend on external crates, but then it should be documented and obviously not on-by-default.

Copy link
Contributor Author

@flier flier Dec 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After read the rust_c and rust-cpp crates, I believe we only need a very simple implementation like

macro_rules! c {
    () => {};

    (#include $filename:tt $($rest:tt)*) => {
        c!{ $($rest)* }
    };

    ({ $body:tt } $($rest:tt)*) => {
        c!{ $($rest)* }
    };
}

and a bindgen-build crate be added to support extract and build C/C++ code in the build script.

You can check a real example that includes hundreds of static inline functions with some build script, like

    bindgen_build::build(binding_file, "rte", |build| {
        build
            .flag("-march=native")
            .include("src")
            .include(rte_sdk_dir.join("include"));

        for (name, value) in &cpu_features {
            build.define(name, value.as_ref().map(|s| s.as_str()));
        }
    }).unwrap();

besides, I added some code to handle the deprecated functions, like

extern "C" {
    #[deprecated]
    #[link_name = "\u{1}_rte_bsf64"]
    pub fn rte_bsf64(slab: u64, pos: *mut u32) -> ::std::os::raw::c_int;
}
c ! { { int _rte_bsf64 ( uint64_t slab , uint32_t * pos ) { return rte_bsf64 ( slab , pos ) ; } } }

and the generated C/C++ file like

// generated by bindgen; DO NOT EDIT

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
# include "rte.h"
...
int _rte_bsf64_safe ( uint64_t v , uint32_t * pos ) { return rte_bsf64_safe ( v , pos ) ; }
...
#pragma GCC diagnostic pop

What's your opinion?

Copy link
Contributor Author

@flier flier Dec 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

besides, with the C/C++ wrapper, we could reexport the thread local variable to solve #1187

__thread int i;
extern __thread struct state s;
static __thread char *p;

could generate the getter/setter functions

extern "C" {
    pub fn i() -> ::std::os::raw::c_int;
    pub fn set_i(v: ::std::os::raw::c_int);
}
c! { { int i ( ) { return i ; } } }
c! { { void set_i ( int v ) { i = v ; } } }
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct state {
    _unused: [u8; 0],
}
extern "C" {
    pub fn s() -> state;
    pub fn set_s(v: state);
}
c! { { struct state s ( ) { return s ; } } }
c! { { void set_s ( struct state v ) { s = v ; } } }
extern "C" {
    pub fn p() -> *mut ::std::os::raw::c_char;
    pub fn set_p(v: *mut ::std::os::raw::c_char);
}
c! { { char * p ( ) { return p ; } } }
c! { { void set_p ( char * v ) { p = v ; } } }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach works, but there's a lot of unanswered questions related to mangling and such...

I'm sorry for having missed this, btw :(

Maybe a better approach would be to instead add a ParseCallback when we see a thread-local variable, in order to let the caller to do whatever they want with it, since this seems like a pretty opinionated solution to the problem.

That would allow us not to depend on c! / cpp! in any way.

extern "C" {
#[link_name = "\u{1}_foo"]
pub fn foo(x: ::std::os::raw::c_int, y: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
}
c! { int _foo ( const int x , const int y ) { return foo ( x , y ) ; } }
extern "C" {
#[link_name = "\u{1}_nop"]
pub fn nop();
}
c! { void _nop ( ) { nop ( ) ; } }
5 changes: 5 additions & 0 deletions tests/headers/generate-c-inline.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// bindgen-flags: --generate-inline-functions

inline static int foo(const int x, const int y) { return x + y; }
static int bar(const int x, const int y) { return x - y; }
inline static void nop() { return; }
1 change: 1 addition & 0 deletions tests/headers/generate-inline.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ class Foo {
inline int foo() {
return 42;
}
inline static int bar(const int x, const int y) { return x + y; }