Skip to content
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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dynamic loading support #1846

Closed
wants to merge 2 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
181 changes: 181 additions & 0 deletions src/codegen/dyngen.rs
@@ -0,0 +1,181 @@
use crate::ir::function::Abi;
use proc_macro2::Ident;

/// Used to build the output tokens for dynamic bindings.
pub struct DynamicItems {
/// Tracks the tokens that will appears inside the library struct -- e.g.:
/// ```ignore
/// struct Lib {
/// __library: ::libloading::Library,
/// x: Result<unsafe extern ..., ::libloading::Error>, // <- tracks these
/// ...
/// }
/// ```
struct_members: Vec<proc_macro2::TokenStream>,

/// Tracks the tokens that will appear inside the library struct's implementation, e.g.:
///
/// ```ignore
/// impl Lib {
/// ...
/// pub unsafe fn foo(&self, ...) { // <- tracks these
/// ...
/// }
/// }
/// ```
struct_implementation: Vec<proc_macro2::TokenStream>,

/// Tracks the tokens that will appear inside the struct used for checking if a symbol is
/// usable, e.g.:
/// ```ignore
/// pub fn f(&self) -> Result<(), &'a ::libloading::Error> { // <- tracks these
/// self.__library.f.as_ref().map(|_| ())
/// }
/// ```
runtime_checks: Vec<proc_macro2::TokenStream>,

/// Tracks the initialization of the fields inside the `::new` constructor of the library
/// struct, e.g.:
/// ```ignore
/// impl Lib {
///
/// pub unsafe fn new<P>(path: P) -> Result<Self, ::libloading::Error>
/// where
/// P: AsRef<::std::ffi::OsStr>,
/// {
/// ...
/// let foo = __library.get(...) ...; // <- tracks these
/// ...
/// }
///
/// ...
/// }
/// ```
constructor_inits: Vec<proc_macro2::TokenStream>,

/// Tracks the information that is passed to the library struct at the end of the `::new`
/// constructor, e.g.:
/// ```ignore
/// impl LibFoo {
/// pub unsafe fn new<P>(path: P) -> Result<Self, ::libloading::Error>
/// where
/// P: AsRef<::std::ffi::OsStr>,
/// {
/// ...
/// Ok(LibFoo {
/// __library: __library,
/// foo,
/// bar, // <- tracks these
/// ...
/// })
/// }
/// }
/// ```
init_fields: Vec<proc_macro2::TokenStream>,
}

impl Default for DynamicItems {
Copy link
Contributor

Choose a reason for hiding this comment

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

Derive this?

fn default() -> Self {
DynamicItems {
struct_members: vec![],
struct_implementation: vec![],
runtime_checks: vec![],
constructor_inits: vec![],
init_fields: vec![],
}
}
}

impl DynamicItems {
pub fn new() -> Self {
Self::default()
}

pub fn get_tokens(
&self,
lib_ident: Ident,
check_struct_ident: Ident,
) -> proc_macro2::TokenStream {
let struct_members = &self.struct_members;
let constructor_inits = &self.constructor_inits;
let init_fields = &self.init_fields;
let struct_implementation = &self.struct_implementation;
let runtime_checks = &self.runtime_checks;
quote! {
extern crate libloading;

pub struct #lib_ident {
__library: ::libloading::Library,
#(#struct_members)*
}

impl #lib_ident {
pub unsafe fn new<P>(
path: P
) -> Result<Self, ::libloading::Error>
where P: AsRef<::std::ffi::OsStr> {
let __library = ::libloading::Library::new(path)?;
#( #constructor_inits )*
Ok(
#lib_ident {
__library: __library,
#( #init_fields ),*
}
)
}

pub fn can_call(&self) -> #check_struct_ident {
#check_struct_ident { __library: self }
}

#( #struct_implementation )*
}

pub struct #check_struct_ident<'a> {
__library: &'a #lib_ident,
}

impl<'a> #check_struct_ident<'a> {
#( #runtime_checks )*
}
}
}

pub fn add_function(
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe just push in order to match the result function name?

&mut self,
ident: Ident,
abi: Abi,
args: Vec<proc_macro2::TokenStream>,
args_identifiers: Vec<proc_macro2::TokenStream>,
ret: proc_macro2::TokenStream,
ret_ty: proc_macro2::TokenStream,
) {
assert_eq!(args.len(), args_identifiers.len());

Choose a reason for hiding this comment

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

Just a note, this seems to assert on varargs parameters.

Not sure if those are not supported widely and this is expected? At least things seem to work when I remove the assert. The following snippet will trigger the assert.

int thing(int, ...);


self.struct_members.push(quote!{
#ident: Result<unsafe extern #abi fn ( #( #args ),* ) #ret, ::libloading::Error>,
});

self.struct_implementation.push(quote! {
pub unsafe fn #ident ( &self, #( #args ),* ) -> #ret_ty {
let sym = self.#ident.as_ref().expect("Expected function, got error.");
(sym)(#( #args_identifiers ),*)
}
});

self.runtime_checks.push(quote! {
pub fn #ident (&self) -> Result<(), &'a::libloading::Error> {
self.__library.#ident.as_ref().map(|_| ())
}
});

let ident_str = ident.to_string();
self.constructor_inits.push(quote! {
let #ident = __library.get(#ident_str.as_bytes()).map(|sym| *sym);
});

self.init_fields.push(quote! {
#ident
});
}
}
80 changes: 78 additions & 2 deletions src/codegen/mod.rs
@@ -1,3 +1,4 @@
mod dyngen;
mod error;
mod helpers;
mod impl_debug;
Expand All @@ -10,6 +11,7 @@ pub(crate) mod bitfield_unit;
#[cfg(all(test, target_endian = "little"))]
mod bitfield_unit_tests;

use self::dyngen::DynamicItems;
use self::helpers::attributes;
use self::struct_layout::StructLayoutTracker;

Expand Down Expand Up @@ -184,6 +186,7 @@ impl From<DerivableTraits> for Vec<&'static str> {

struct CodegenResult<'a> {
items: Vec<proc_macro2::TokenStream>,
dynamic_items: DynamicItems,

/// A monotonic counter used to add stable unique id's to stuff that doesn't
/// need to be referenced by anything.
Expand Down Expand Up @@ -234,6 +237,7 @@ impl<'a> CodegenResult<'a> {
fn new(codegen_id: &'a Cell<usize>) -> Self {
CodegenResult {
items: vec![],
dynamic_items: DynamicItems::new(),
saw_bindgen_union: false,
saw_incomplete_array: false,
saw_objc: false,
Expand All @@ -247,6 +251,10 @@ impl<'a> CodegenResult<'a> {
}
}

fn dynamic_items(&mut self) -> &mut DynamicItems {
&mut self.dynamic_items
}

fn saw_bindgen_union(&mut self) {
self.saw_bindgen_union = true;
}
Expand Down Expand Up @@ -3704,7 +3712,29 @@ impl CodeGenerator for Function {
pub fn #ident ( #( #args ),* ) #ret;
}
};
result.push(tokens);

// If we're doing dynamic binding generation, add to the dynamic items.
if ctx.options().dynamic_library_name.is_some() &&
self.kind() == FunctionKind::Function
{
let args_identifiers =
utils::fnsig_argument_identifiers(ctx, signature);
let return_item = ctx.resolve_item(signature.return_type());
let ret_ty = match *return_item.kind().expect_type().kind() {
TypeKind::Void => quote! {()},
_ => return_item.to_rust_ty_or_opaque(ctx, &()),
};
result.dynamic_items().add_function(
ident,
abi,
args,
args_identifiers,
ret,
ret_ty,
);
} else {
result.push(tokens);
}
}
}

Expand Down Expand Up @@ -3948,11 +3978,28 @@ pub(crate) fn codegen(
&(),
);

if context.options().dynamic_library_name.is_some() {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: if let Some(ref lib_name) = context.options().dynamic_library_name {.

Then use that instead of unwrapping around.

let lib_ident = context.rust_ident(
context.options().dynamic_library_name.as_ref().unwrap(),
);
let check_struct_ident = context.rust_ident(
[
"Check",
context.options().dynamic_library_name.as_ref().unwrap(),
]
.join(""),
Copy link
Contributor

Choose a reason for hiding this comment

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

just format!("Check{}", lib_name)?

);
let dynamic_items_tokens = result
.dynamic_items()
.get_tokens(lib_ident, check_struct_ident);
result.push(dynamic_items_tokens);
}

result.items
})
}

mod utils {
pub mod utils {
use super::{error, ToRustTyOrOpaque};
use crate::ir::context::BindgenContext;
use crate::ir::function::{Abi, FunctionSig};
Expand Down Expand Up @@ -4356,6 +4403,35 @@ mod utils {
args
}

pub fn fnsig_argument_identifiers(
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you document what this returns? The signature is not super-self-descriptive.

ctx: &BindgenContext,
sig: &FunctionSig,
) -> Vec<proc_macro2::TokenStream> {
let mut unnamed_arguments = 0;
let args = sig
.argument_types()
.iter()
.map(|&(ref name, _ty)| {
let arg_name = match *name {
Some(ref name) => ctx.rust_mangle(name).into_owned(),
None => {
unnamed_arguments += 1;
format!("arg{}", unnamed_arguments)
}
};

assert!(!arg_name.is_empty());
let arg_name = ctx.rust_ident(arg_name);

quote! {
#arg_name
}
})
.collect::<Vec<_>>();

args
}

pub fn fnsig_block(
ctx: &BindgenContext,
sig: &FunctionSig,
Expand Down
20 changes: 20 additions & 0 deletions src/lib.rs
Expand Up @@ -503,6 +503,12 @@ impl Builder {
output_vector.push(path.into());
}

if self.options.dynamic_library_name.is_some() {
let libname = self.options.dynamic_library_name.as_ref().unwrap();
output_vector.push("--dynamic-loading".into());
output_vector.push(libname.clone());
}

// Add clang arguments

output_vector.push("--".into());
Expand Down Expand Up @@ -1431,6 +1437,15 @@ impl Builder {
self.options.wasm_import_module_name = Some(import_name.into());
self
}

/// Specify the dynamic library name if we are generating bindings for a shared library.
pub fn dynamic_library_name<T: Into<String>>(
mut self,
dynamic_library_name: T,
) -> Self {
self.options.dynamic_library_name = Some(dynamic_library_name.into());
self
}
}

/// Configuration options for generated bindings.
Expand Down Expand Up @@ -1699,6 +1714,10 @@ struct BindgenOptions {

/// Wasm import module name.
wasm_import_module_name: Option<String>,

/// The name of the dynamic library (if we are generating bindings for a shared library). If
/// this is None, no dynamic bindings are created.
dynamic_library_name: Option<String>,
}

/// TODO(emilio): This is sort of a lie (see the error message that results from
Expand Down Expand Up @@ -1827,6 +1846,7 @@ impl Default for BindgenOptions {
no_hash_types: Default::default(),
array_pointers_in_arguments: false,
wasm_import_module_name: None,
dynamic_library_name: None,
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/options.rs
Expand Up @@ -450,7 +450,11 @@ where
.long("wasm-import-module-name")
.value_name("name")
.takes_value(true)
.help("The name to be used in a #[link(wasm_import_module = ...)] statement")
.help("The name to be used in a #[link(wasm_import_module = ...)] statement"),
Arg::with_name("dynamic-loading")
.long("dynamic-loading")
.takes_value(true)
.help("Use dynamic loading mode with the given library name."),
]) // .args()
.get_matches_from(args);

Expand Down Expand Up @@ -837,6 +841,10 @@ where
}
}

if let Some(dynamic_library_name) = matches.value_of("dynamic-loading") {
builder = builder.dynamic_library_name(dynamic_library_name);
}

let verbose = matches.is_present("verbose");

Ok((builder, output, verbose))
Expand Down