Skip to content

Commit

Permalink
Add dynamic loading support
Browse files Browse the repository at this point in the history
  • Loading branch information
Joe Ellis committed Aug 11, 2020
1 parent 94bce16 commit 8269532
Show file tree
Hide file tree
Showing 14 changed files with 599 additions and 3 deletions.
195 changes: 195 additions & 0 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
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,122 @@ impl CodeGenerator for ObjCInterface {
}
}

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

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

impl DynamicBindingGenerator for Item {
type Extra = ();

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

struct_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, struct_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,
struct_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 ret = utils::fnsig_return_ty(ctx, signature);

let ident = ctx.rust_ident(&canonical_name);

struct_result.push(quote! {
pub #ident: libloading::Symbol<'a, unsafe extern #abi fn ( #( #args ),* ) #ret>,
});

impl_result.push(quote! {
#ident: lib.get(#canonical_name.as_bytes()).unwrap(),
});
}
}

pub(crate) fn codegen(
context: BindgenContext,
) -> (Vec<proc_macro2::TokenStream>, BindgenOptions) {
Expand Down Expand Up @@ -3948,6 +4074,75 @@ 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());

// `struct_result` tracks the tokens that will appears inside the library struct, e.g.:
// ```
// struct Lib {
// x: libloading::Symbol<'a, ...>, // <- tracks these!
// }
// ```
//
// `impl_result` tracks the tokens that will appear inside the library struct's
// implementation, e.g.:
//
// ```
// impl<'a> Lib<'a> {
// pub fn new(lib: &libloading::Library) -> Lib {
// unsafe {
// x: lib.get(...), // <- tracks these!
// }
// }
// }
// ```
//
// 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 struct_result = result.clone();
struct_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 struct_result,
&mut impl_result,
&(),
);
}

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

let struct_items = struct_result.items;
result.push(quote! {
extern crate libloading;
pub struct #lib_ident<'a> {
#(#struct_items)*
}
});

let impl_items = impl_result.items;
result.push(quote! {
impl<'a> #lib_ident<'a> {
pub fn new(lib: &libloading::Library) -> #lib_ident {
unsafe {
#lib_ident {
#(#impl_items)*
}
}
}
}
});
}

result.items
})
}
Expand Down
82 changes: 81 additions & 1 deletion src/ir/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use super::derive::{
CanDerive, CanDeriveCopy, CanDeriveDebug, CanDeriveDefault, CanDeriveEq,
CanDeriveHash, CanDeriveOrd, CanDerivePartialEq, CanDerivePartialOrd,
};
use super::function::Function;
use super::function::{Function, FunctionKind};
use super::int::IntKind;
use super::item::{IsOpaque, Item, ItemAncestors, ItemSet};
use super::item_kind::ItemKind;
Expand Down Expand Up @@ -386,6 +386,11 @@ pub struct BindgenContext {
/// It's computed right after computing the whitelisted items.
codegen_items: Option<ItemSet>,

/// The set of `ItemId`s that we will perform dynamic binding generation for.
///
/// It's computed following the codegen/whitelisted items.
dyngen_items: Option<ItemSet>,

/// Map from an item's id to the set of template parameter items that it
/// uses. See `ir::named` for more details. Always `Some` during the codegen
/// phase.
Expand Down Expand Up @@ -619,6 +624,7 @@ If you encounter an error missing from this list, please file an issue or a PR!"
generated_bindgen_complex: Cell::new(false),
whitelisted: None,
codegen_items: None,
dyngen_items: None,
used_template_parameters: None,
need_bitfield_allocation: Default::default(),
cannot_derive_debug: None,
Expand Down Expand Up @@ -1189,6 +1195,9 @@ If you encounter an error missing from this list, please file an issue or a PR!"
// graph, and their completion means that the IR graph is now frozen.
self.compute_whitelisted_and_codegen_items();

// Compute the items for dynamic generation.
self.compute_dynamic_generation_items();

// Make sure to do this after processing replacements, since that messes
// with the parentage and module children, and we want to assert that it
// messes with them correctly.
Expand Down Expand Up @@ -2262,6 +2271,13 @@ If you encounter an error missing from this list, please file an issue or a PR!"
self.codegen_items.as_ref().unwrap()
}

/// Get a reference to the set of dynamic bindings we should generate.
pub fn dyngen_items(&self) -> &ItemSet {
assert!(self.in_codegen_phase());
assert!(self.current_module == self.root_module);
self.dyngen_items.as_ref().unwrap()
}

/// Compute the whitelisted items set and populate `self.whitelisted`.
fn compute_whitelisted_and_codegen_items(&mut self) {
assert!(self.in_codegen_phase());
Expand Down Expand Up @@ -2412,6 +2428,70 @@ If you encounter an error missing from this list, please file an issue or a PR!"
}
}

/// Compute the items for dynamic generation.
fn compute_dynamic_generation_items(&mut self) {
assert!(self.in_codegen_phase());
assert!(self.current_module == self.root_module);
let _t = self.timer("compute_dynamic_generation_items");

let dyngen_items = self
.items()
// Only consider items that are enabled for codegen.
.filter(|&(_, item)| item.is_enabled_for_codegen(self))
.filter(|&(_, item)| {
// If the user has not chosen to do dynamic loading, then we have nothing to
// do.
if !self.options().dynamic_loading {
return false;
}

let name = item.path_for_whitelisting(self)[1..].join("::");

// If there is a whitelist and this function is not on it, don't add it.
if !self.options().whitelisted_functions.is_empty() &&
!self.options().whitelisted_functions.matches(&name)
{
return false;
}

// If there is a blacklist and this function is on it, don't add it.
if !self.options().blacklisted_functions.is_empty() &&
self.options.blacklisted_functions.matches(&name)
{
return false;
}

// We don't want to include the root module in the list of items to generate
// dynamic bindings for.
if item.id() == self.root_module {
return false;
}

// We only want to generate bindings for the stuff that we have at the toplevel.
if item.parent_id() != self.root_module {
return false;
}

// If we get to here:
// - The user wants to generate dynamic bindings.
// - The item being considered is at the toplevel (but is not the root itself).
// - The item is permitted by the {white,black}list.

// Finally, only generate dynamic bindings for functions (for now! this could
// change in the future to support variables).
match item.kind() {
ItemKind::Function(ref f) => {
f.kind() == FunctionKind::Function
}
_ => false,
}
})
.map(|(id, _)| id)
.collect::<ItemSet>();

self.dyngen_items = Some(dyngen_items);
}

/// Convenient method for getting the prefix to use for most traits in
/// codegen depending on the `use_core` option.
pub fn trait_prefix(&self) -> Ident {
Expand Down
9 changes: 9 additions & 0 deletions src/ir/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,15 @@ impl Item {
&self.annotations
}

/// Whether this item should have dynamic bindings generated, rather than normal bindings.
pub fn is_dynamic(&self, ctx: &BindgenContext) -> bool {
debug_assert!(
ctx.in_codegen_phase(),
"You're not supposed to call this yet"
);
ctx.dyngen_items().contains(&self.id)
}

/// Whether this item should be blacklisted.
///
/// This may be due to either annotations or to other kind of configuration.
Expand Down
Loading

0 comments on commit 8269532

Please sign in to comment.