Skip to content

Commit

Permalink
Add dynamic loading support
Browse files Browse the repository at this point in the history
Closes #1541.
Closes #1846.

Co-authored-by: Michael-F-Bryan <michaelfbryan@gmail.com>
  • Loading branch information
2 people authored and emilio committed Nov 25, 2020
1 parent db3d170 commit fc5fa9a
Show file tree
Hide file tree
Showing 4 changed files with 288 additions and 3 deletions.
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 {
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(
&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());

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 @@ -3785,7 +3793,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 @@ -4075,11 +4105,28 @@ pub(crate) fn codegen(
&(),
);

if context.options().dynamic_library_name.is_some() {
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(""),
);
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 @@ -4484,6 +4531,35 @@ mod utils {
args
}

pub fn fnsig_argument_identifiers(
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 @@ -518,6 +518,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 @@ -1468,6 +1474,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 @@ -1745,6 +1760,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 @@ -1877,6 +1896,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 @@ -471,7 +471,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 @@ -873,6 +877,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

0 comments on commit fc5fa9a

Please sign in to comment.