Skip to content

Commit

Permalink
link: Improvements over the runtime API.
Browse files Browse the repository at this point in the history
This allows the runtime API:

 * To be queried.
 * To be reused across threads.

I need the first to use it on bindgen. The second is not needed right now, but
it's nice to have.

The querying API is really similar to what the `gl` bindings crate uses.

I wonder why a mutex to guard the setting of a thread-local variable, is there
something I missed?
  • Loading branch information
emilio committed Dec 14, 2016
1 parent 655ca81 commit b207940
Showing 1 changed file with 86 additions and 54 deletions.
140 changes: 86 additions & 54 deletions src/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,29 @@

#[cfg(feature="runtime")]
macro_rules! link {
(@IMPL: #[cfg($cfg:meta)] fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*) => (
(@LOAD: #[cfg($cfg:meta)] fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*) => (
#[cfg($cfg)]
pub fn $name(library: &mut super::SharedLibrary) -> Result<(), String> {
let symbol = unsafe { library.library.get(stringify!($name).as_bytes()) }.map_err(|_| {
format!("could not load `{}`", stringify!($name))
});
library.functions.$name = *try!(symbol);
Ok(())
pub fn $name(library: &mut super::SharedLibrary) {
let symbol = unsafe { library.library.get(stringify!($name).as_bytes()) }.ok();
library.functions.$name = symbol.map(|s| *s);
}

#[cfg(not($cfg))]
pub fn $name(_: &mut super::SharedLibrary) -> Result<(), String> {
Ok(())
}
pub fn $name(_: &mut super::SharedLibrary) {}
);

(@IMPL: fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*) => (
pub fn $name(library: &mut super::SharedLibrary) -> Result<(), String> {
let symbol = unsafe { library.library.get(stringify!($name).as_bytes()) }.map_err(|_| {
format!("could not load `{}`", stringify!($name))
});
library.functions.$name = *try!(symbol);
Ok(())
}
(@LOAD: fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*) => (
link!(@LOAD: #[cfg(feature="runtime")] fn $name($($pname: $pty), *) $(-> $ret)*);
);

($($(#[cfg($cfg:meta)])* pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*;)+) => (
use std::cell::{RefCell};
use std::sync::{Mutex};
use std::sync::{Arc};

#[doc(hidden)]
/// The set of functions loaded dynamically.
#[derive(Debug)]
pub struct Functions {
$($(#[cfg($cfg)])* $name: extern fn($($pname: $pty), *) $(-> $ret)*), +
$($(#[cfg($cfg)])* pub $name: Option<extern fn($($pname: $pty), *) $(-> $ret)*>,)+
}

impl Default for Functions {
Expand All @@ -61,10 +51,11 @@ macro_rules! link {
}
}

#[doc(hidden)]
/// A dynamically loaded instance of the libclang library.
#[derive(Debug)]
pub struct SharedLibrary {
library: libloading::Library,
functions: Functions,
pub functions: Functions,
}

impl SharedLibrary {
Expand All @@ -75,44 +66,87 @@ macro_rules! link {
}
}

lazy_static!(static ref LOADED: Mutex<bool> = Mutex::new(false););
thread_local!(static LIBRARY: RefCell<Option<SharedLibrary>> = RefCell::new(None));
thread_local!(static LIBRARY: RefCell<Option<Arc<SharedLibrary>>> = RefCell::new(None));

$($(#[cfg($cfg)])* pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* {
let f = LIBRARY.with(|l| l.borrow().as_ref().map(|l| l.functions.$name));
(f.expect("a `libclang` shared library was not loaded on this thread"))($($pname), *)
})+
/// Whether `libclang` is loaded on this thread.
pub fn is_loaded() -> bool {
LIBRARY.with(|l| l.borrow().is_some())
}

$(
$(#[cfg($cfg)])* pub unsafe fn $name($($pname: $pty), *) $(-> $ret)* {
let f = LIBRARY.with(|l| l.borrow().as_ref().map(|l| {
match l.functions.$name {
Some(f) => f,
None => panic!("Function not loaded: {}!", stringify!($name)),
}
}));
(f.expect("a `libclang` shared library was not loaded on this thread"))($($pname), *)
}

$(#[cfg($cfg)])* pub mod $name {
use super::LIBRARY;
pub fn is_loaded() -> bool {
LIBRARY.with(|l| l.borrow().as_ref().map_or(false, |l| {
l.functions.$name.is_some()
}))
}
}
)+

#[path="../build.rs"]
mod build;

mod load {
$(link!(@LOAD: $(#[cfg($cfg)])* fn $name($($pname: $pty), *) $(-> $ret)*);)+
}

/// Loads a `libclang` shared library for use in the current thread.
///
/// # Failures
///
/// * a `libclang` shared library has already been loaded
/// * a `libclang` shared library could not be found
/// * a `libclang` shared library symbol could not be loaded
///
/// Note that this tries to find all the symbols. To check if a symbol
/// has been found or not, you can use `clang_Foo::is_loaded()`.
#[allow(dead_code)]
pub fn load() -> Result<(), String> {
#[path="../build.rs"]
mod build;
let lib = Arc::new(try!(load_manually()));
LIBRARY.with(|l| *l.borrow_mut() = Some(lib));
Ok(())
}

mod load {
$(link!(@IMPL: $(#[cfg($cfg)])* fn $name($($pname: $pty), *) $(-> $ret)*);)+
}
/// Gets the library from tls. This, along with `set_library`, allows
/// reusing the same library across threads.
pub fn get_library() -> Option<Arc<SharedLibrary>> {
LIBRARY.with(|l| {
l.borrow_mut().clone()
})
}

let mut loaded = LOADED.lock().unwrap();
if *loaded {
return Err("a `libclang` shared library has already been loaded".into());
}
/// Sets the current library from tls, and returns the previous one.
pub fn set_library(lib: Option<Arc<SharedLibrary>>) -> Option<Arc<SharedLibrary>> {
LIBRARY.with(|l| {
let mut l = l.borrow_mut();
mem::replace(&mut *l, lib)
})
}

/// Tries to load a libclang library manually, returning the
/// corresponding `SharedLibrary`.
///
/// Only returns an error when the library couldn't be found or opened,
/// and the caller is responsible handle the functions manually.
pub fn load_manually() -> Result<SharedLibrary, String> {
let file = try!(build::find_shared_library());
let library = libloading::Library::new(&file).map_err(|_| {
format!("'{}' could not be opened", file.display())
});
let mut library = SharedLibrary::new(try!(library));
$(try!(load::$name(&mut library));)+
LIBRARY.with(|l| *l.borrow_mut() = Some(library));
*loaded = true;
Ok(())
$(load::$name(&mut library);)+

Ok(library)
}

/// Unloads the `libclang` shared library in use in the current thread.
Expand All @@ -121,21 +155,19 @@ macro_rules! link {
///
/// * a `libclang` shared library is not in use in the current thread
pub fn unload() -> Result<(), String> {
let mut loaded = LOADED.lock().unwrap();
LIBRARY.with(|l| {
let mut library = l.borrow_mut();
if library.is_some() {
*library = None;
*loaded = false;
Ok(())
} else {
Err("a `libclang` shared library is not in use in the current thread".into())
}
})
let l = set_library(None);

if l.is_some() {
Ok(())
} else {
Err("a `libclang` shared library is not in use in the current thread".into())
}
}
)
}



#[cfg(not(feature="runtime"))]
macro_rules! link {
($($(#[cfg($cfg:meta)])* pub fn $name:ident($($pname:ident: $pty:ty), *) $(-> $ret:ty)*;)+) => (
Expand Down

0 comments on commit b207940

Please # to comment.