Skip to content

Commit

Permalink
Add dynamic loading support
Browse files Browse the repository at this point in the history
Co-authored-by: Michael-F-Bryan <michaelfbryan@gmail.com>
  • Loading branch information
Joe Ellis and Michael-F-Bryan committed Aug 26, 2020
1 parent 94bce16 commit a48a137
Show file tree
Hide file tree
Showing 14 changed files with 716 additions and 3 deletions.
264 changes: 264 additions & 0 deletions src/codegen/mod.rs
Expand Up @@ -182,6 +182,7 @@ impl From<DerivableTraits> for Vec<&'static str> {
}
}

#[derive(Clone)]
struct CodegenResult<'a> {
items: Vec<proc_macro2::TokenStream>,

Expand Down Expand Up @@ -435,6 +436,15 @@ impl CodeGenerator for Item {
return;
}

if self.is_dynamic(ctx) {
debug!(
"Item is to be dynamically generated; ignoring it for now. \
self = {:?}",
self
);
return;
}

if self.is_blacklisted(ctx) || result.seen(self.id()) {
debug!(
"<Item as CodeGenerator>::codegen: Ignoring hidden or seen: \
Expand Down Expand Up @@ -3913,6 +3923,126 @@ impl CodeGenerator for ObjCInterface {
}
}

trait DynamicBindingGenerator {
/// Extra information from the caller.
type Extra;

fn dyngen<'a>(
&self,
ctx: &BindgenContext,
def_result: &mut CodegenResult<'a>,
impl_result: &mut CodegenResult<'a>,
extra: &Self::Extra,
);
}

impl DynamicBindingGenerator for Item {
type Extra = ();

fn dyngen<'a>(
&self,
ctx: &BindgenContext,
def_result: &mut CodegenResult<'a>,
impl_result: &mut CodegenResult<'a>,
_extra: &(),
) {
assert!(self.is_dynamic(ctx));
if !self.is_enabled_for_codegen(ctx) {
return;
}

// If this item is blacklisted, or we've already seen it, nothing to do.
if self.is_blacklisted(ctx) ||
def_result.seen(self.id()) ||
impl_result.seen(self.id())
{
debug!(
"<Item as DynamicBindingGenerator>::codegen: Ignoring hidden or seen: \
self = {:?}",
self
);
return;
}

debug!(
"<Item as DynamicBindingGenerator>::dyngen: self = {:?}",
self
);

if !ctx.codegen_items().contains(&self.id()) {
warn!("Found non-whitelisted item in dynamic binding generation: {:?}", self);
}

def_result.set_seen(self.id());
impl_result.set_seen(self.id());

match *self.kind() {
ItemKind::Function(ref fun) => {
assert!(fun.kind() == FunctionKind::Function);
fun.dyngen(ctx, def_result, impl_result, self);
}
_ => panic!(
"Unexpected item type when doing dynamic bindings generation."
),
}
}
}

impl DynamicBindingGenerator for Function {
type Extra = Item;

fn dyngen<'a>(
&self,
ctx: &BindgenContext,
def_result: &mut CodegenResult<'a>,
impl_result: &mut CodegenResult<'a>,
item: &Item,
) {
let signature_item = ctx.resolve_item(self.signature());
let signature = signature_item.kind().expect_type().canonical_type(ctx);
let signature = match *signature.kind() {
TypeKind::Function(ref sig) => sig,
_ => panic!("Signature kind is not a Function: {:?}", signature),
};

let canonical_name = item.canonical_name(ctx);
let abi = match signature.abi() {
Abi::ThisCall if !ctx.options().rust_features().thiscall_abi => {
warn!("Skipping function with thiscall ABI that isn't supported by the configured Rust target");
return;
}
Abi::Win64 if signature.is_variadic() => {
warn!("Skipping variadic function with Win64 ABI that isn't supported");
return;
}
Abi::Unknown(unknown_abi) => {
panic!(
"Invalid or unknown abi {:?} for function {:?} ({:?})",
unknown_abi, canonical_name, self
);
}
abi => abi,
};

let args = utils::fnsig_arguments(ctx, signature);
let args_identifiers =
utils::fnsig_argument_identifiers(ctx, signature);
let ret = utils::fnsig_return_ty(ctx, signature);

let ident = ctx.rust_ident(&canonical_name);

def_result.push(quote! {
#ident: unsafe extern #abi fn ( #( #args ),* ) #ret,
});

impl_result.push(quote! {
pub unsafe extern #abi fn #ident ( &self, #( #args ),* ) #ret {
(self.#ident)(#( #args_identifiers ),*)
}
});
}
}

pub(crate) fn codegen(
context: BindgenContext,
) -> (Vec<proc_macro2::TokenStream>, BindgenOptions) {
Expand Down Expand Up @@ -3948,6 +4078,111 @@ pub(crate) fn codegen(
&(),
);

// If the set of items to generate dynamic bindings for is nonempty...
if !context.dyngen_items().is_empty() {
let _t = context.timer("dyngen");
debug!("dyngen: {:?}", context.options());

// `def_result` tracks the tokens that will appears inside the library struct -- these
// are the definitions of the symbols inside the library struct, e.g.:
// ```
// struct Lib {
// __library: ::libloading::Library,
// x: unsafe extern ..., // <- tracks these!
// ...
// }
// ```
//
// `impl_result` tracks the tokens that will appear inside the call implementation,
// e.g.:
//
// ```
// impl Lib {
// ...
// pub unsafe extern "C" fn foo(&self, ...) { // <- tracks these!
// (self.foo)(...)
// }
// }
// ```
//
// We clone the CodeGenerator object used for normal code generation, but set its items
// to the empty vector. We do this so that we can keep track of which items we have
// seen, etc.
let mut def_result = result.clone();
def_result.items = vec![];
let mut impl_result = result.clone();
impl_result.items = vec![];

// Run dynamic binding generation for each of the required items.
for item in context.dyngen_items() {
context.resolve_item(*item).dyngen(
context,
&mut def_result,
&mut impl_result,
&(),
);
}

let lib_ident = context.rust_ident(
context.options().dynamic_library_name.as_ref().unwrap(),
);

let struct_items = def_result.items;
result.push(quote! {
pub struct #lib_ident {
__library: ::libloading::Library,
#(#struct_items)*
}
});

let build_locals = context
.dyngen_items()
.iter()
.map(|&item| {
let canonical_name = item.canonical_name(context);
let ident = context.rust_ident(&canonical_name);

quote! {
let #ident = *__library.get(#canonical_name.as_bytes())?;
}
})
.collect::<Vec<_>>();

let init_fields = context
.dyngen_items()
.iter()
.map(|&item| {
let canonical_name = item.canonical_name(context);
let ident = context.rust_ident(&canonical_name);

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

let impl_items = impl_result.items;
result.push(quote! {
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)?;
#( #build_locals )*
Ok(
#lib_ident {
__library: __library,
#( #init_fields ),*
}
)
}

#( #impl_items )*
}
});
}

result.items
})
}
Expand Down Expand Up @@ -4356,6 +4591,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

0 comments on commit a48a137

Please sign in to comment.