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 <[email protected]>
  • Loading branch information
Joe Ellis and Michael-F-Bryan committed Sep 15, 2020
1 parent 94bce16 commit 51e3702
Show file tree
Hide file tree
Showing 15 changed files with 929 additions and 4 deletions.
306 changes: 306 additions & 0 deletions src/codegen/dyngen.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
use super::ToRustTyOrOpaque;
use crate::codegen::utils;
use crate::ir::context::{BindgenContext, ItemId};
use crate::ir::function::{Abi, Function, FunctionKind};
use crate::ir::item::{Item, ItemCanonicalName};
use crate::ir::item_kind::ItemKind;
use crate::ir::ty::TypeKind;
use crate::HashSet;
use proc_macro2::Ident;

/// This trait is similar to the CodeGenerator trait in src/codegen/mod.rs, but is instead focused
/// on the generation of dynamic bindings rather than static.
pub trait DynamicBindingGenerator {
/// Extra information from the caller.
type Extra;

/// Generate dynamic bindings for a particular item type.
fn dyngen<'a>(
&self,
ctx: &BindgenContext,
result: &mut DynamicBindingCodegenResult,
extra: &Self::Extra,
);
}

/// Used to build the output tokens for dynamic bindings.
pub struct DynamicBindingCodegenResult {
/// 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>,

/// Keeps track of the items that we have seen.
items_seen: HashSet<ItemId>,
}

impl DynamicBindingCodegenResult {
pub fn new() -> Self {
DynamicBindingCodegenResult {
struct_members: vec![],
struct_implementation: vec![],
runtime_checks: vec![],
constructor_inits: vec![],
init_fields: vec![],
items_seen: Default::default(),
}
}

pub fn seen<Id: Into<ItemId>>(&self, item: Id) -> bool {
self.items_seen.contains(&item.into())
}

pub fn set_seen<Id: Into<ItemId>>(&mut self, item: Id) {
self.items_seen.insert(item.into());
}

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,
) {
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
});
}
}

impl DynamicBindingGenerator for Item {
type Extra = ();

fn dyngen<'a>(
&self,
ctx: &BindgenContext,
result: &mut DynamicBindingCodegenResult,
_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) || result.seen(self.id()) {
debug!(
"<Item as DynamicBindingGenerator>::dyngen: 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);
}

result.set_seen(self.id());

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

impl DynamicBindingGenerator for Function {
type Extra = Item;

fn dyngen<'a>(
&self,
ctx: &BindgenContext,
result: &mut DynamicBindingCodegenResult,
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);

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.add_function(ident, abi, args, args_identifiers, ret, ret_ty);
}
}
Loading

0 comments on commit 51e3702

Please sign in to comment.