diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -30,6 +30,14 @@ dependencies = [ ] [[package]] +name = "hgdirectffi" +version = "0.1.0" +dependencies = [ + "hg-core 0.1.0", + "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/rust/Cargo.toml b/rust/Cargo.toml --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["hgcli", "hg-core"] +members = ["hgcli", "hg-core", "hg-direct-ffi"] exclude = ["chg"] diff --git a/rust/hg-direct-ffi/Cargo.toml b/rust/hg-direct-ffi/Cargo.toml new file mode 100644 --- /dev/null +++ b/rust/hg-direct-ffi/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "hgdirectffi" +version = "0.1.0" +authors = ["Georges Racinet "] +description = "Low level Python bindings for hg-core, going through existing C extensions" + +[dependencies] +libc = "*" +hg-core = { path = "../hg-core" } + +[lib] +crate-type = ["staticlib"] diff --git a/rust/hg-direct-ffi/src/ancestors.rs b/rust/hg-direct-ffi/src/ancestors.rs new file mode 100644 --- /dev/null +++ b/rust/hg-direct-ffi/src/ancestors.rs @@ -0,0 +1,229 @@ +// Copyright 2018 Georges Racinet +// +// This software may be used and distributed according to the terms of the +// GNU General Public License version 2 or any later version. + +//! Bindings for CPython extension code +//! +//! This exposes methods to build and use a `rustlazyancestors` iterator +//! from C code, using an index and its parents function that are passed +//! from the caller at instantiation. + +use hg::AncestorsIterator; +use hg::{Graph, GraphError, Revision, NULL_REVISION}; +use libc::{c_int, c_long, c_void, ssize_t}; +use std::ptr::null_mut; +use std::slice; + +type IndexPtr = *mut c_void; +type IndexParentsFn = + unsafe extern "C" fn(index: IndexPtr, rev: ssize_t, ps: *mut [c_int; 2], max_rev: c_int) + -> c_int; + +/// A Graph backed up by objects and functions from revlog.c +/// +/// This implementation of the Graph trait, relies on (pointers to) +/// - the C index object (`index` member) +/// - the `index_get_parents()` function (`parents` member) +pub struct Index { + index: IndexPtr, + parents: IndexParentsFn, +} + +impl Index { + pub fn new(index: IndexPtr, parents: IndexParentsFn) -> Self { + Index { + index: index, + parents: parents, + } + } +} + +impl Graph for Index { + /// wrap a call to the C extern parents function + fn parents(&self, rev: Revision) -> Result<(Revision, Revision), GraphError> { + let mut res: [c_int; 2] = [0; 2]; + let code = + unsafe { (self.parents)(self.index, rev as ssize_t, &mut res as *mut [c_int; 2], rev) }; + match code { + 0 => Ok((res[0], res[1])), + _ => Err(GraphError::ParentOutOfRange(rev)), + } + } +} + +/// Wrapping of AncestorsIterator constructor, for C callers. +/// +/// Besides `initrevs`, `stoprev` and `inclusive`, that are converted +/// we receive the index and the parents function as pointers +#[no_mangle] +pub extern "C" fn rustlazyancestors_init( + index: IndexPtr, + parents: IndexParentsFn, + initrevslen: usize, + initrevs: *mut c_long, + stoprev: c_long, + inclusive: c_int, +) -> *mut AncestorsIterator { + unsafe { + raw_init( + Index::new(index, parents), + initrevslen, + initrevs, + stoprev, + inclusive, + ) + } +} + +/// Testable (for any Graph) version of rustlazyancestors_init +#[inline] +unsafe fn raw_init( + graph: G, + initrevslen: usize, + initrevs: *mut c_long, + stoprev: c_long, + inclusive: c_int, +) -> *mut AncestorsIterator { + let inclb = match inclusive { + 0 => false, + 1 => true, + _ => { + return null_mut(); + } + }; + + let slice = slice::from_raw_parts(initrevs, initrevslen); + + Box::into_raw(Box::new(match AncestorsIterator::new( + graph, + slice.into_iter().map(|&r| r as Revision), + stoprev as Revision, + inclb, + ) { + Ok(it) => it, + Err(_) => { + return null_mut(); + } + })) +} + +/// Deallocator to be called from C code +#[no_mangle] +pub extern "C" fn rustlazyancestors_drop(raw_iter: *mut AncestorsIterator) { + raw_drop(raw_iter); +} + +/// Testable (for any Graph) version of rustlazayancestors_drop +#[inline] +fn raw_drop(raw_iter: *mut AncestorsIterator) { + unsafe { + Box::from_raw(raw_iter); + } +} + +/// Iteration main method to be called from C code +/// +/// We convert the end of iteration into NULL_REVISION, +/// it will be up to the C wrapper to convert that back into a Python end of +/// iteration +#[no_mangle] +pub extern "C" fn rustlazyancestors_next(raw: *mut AncestorsIterator) -> c_long { + raw_next(raw) +} + +/// Testable (for any Graph) version of rustlazayancestors_next +#[inline] +fn raw_next(raw: *mut AncestorsIterator) -> c_long { + let as_ref = unsafe { &mut *raw }; + as_ref.next().unwrap_or(NULL_REVISION) as c_long +} + +#[cfg(test)] +mod tests { + use super::*; + use std::thread; + + #[derive(Clone, Debug)] + struct Stub; + + impl Graph for Stub { + fn parents(&self, r: Revision) -> Result<(Revision, Revision), GraphError> { + match r { + 25 => Err(GraphError::ParentOutOfRange(25)), + _ => Ok((1, 2)), + } + } + } + + /// Helper for test_init_next() + fn stub_raw_init( + initrevslen: usize, + initrevs: usize, + stoprev: c_long, + inclusive: c_int, + ) -> usize { + unsafe { + raw_init( + Stub, + initrevslen, + initrevs as *mut c_long, + stoprev, + inclusive, + ) as usize + } + } + + fn stub_raw_init_from_vec( + mut initrevs: Vec, + stoprev: c_long, + inclusive: c_int, + ) -> *mut AncestorsIterator { + unsafe { + raw_init( + Stub, + initrevs.len(), + initrevs.as_mut_ptr(), + stoprev, + inclusive, + ) + } + } + + #[test] + // Test what happens when we init an Iterator as with the exposed C ABI + // and try to use it afterwards + // We spawn new threads, in order to make memory consistency harder + // but this forces us to convert the pointers into shareable usizes. + fn test_init_next() { + let mut initrevs: Vec = vec![11, 13]; + let initrevs_len = initrevs.len(); + let initrevs_ptr = initrevs.as_mut_ptr() as usize; + let handler = thread::spawn(move || stub_raw_init(initrevs_len, initrevs_ptr, 0, 1)); + let raw = handler.join().unwrap() as *mut AncestorsIterator; + + assert_eq!(raw_next(raw), 13); + assert_eq!(raw_next(raw), 11); + assert_eq!(raw_next(raw), 2); + assert_eq!(raw_next(raw), 1); + assert_eq!(raw_next(raw), NULL_REVISION as c_long); + raw_drop(raw); + } + + #[test] + fn test_init_wrong_bool() { + assert_eq!(stub_raw_init_from_vec(vec![11, 13], 0, 2), null_mut()); + } + + #[test] + fn test_empty() { + let raw = stub_raw_init_from_vec(vec![], 0, 1); + assert_eq!(raw_next(raw), NULL_REVISION as c_long); + raw_drop(raw); + } + + #[test] + fn test_init_err_out_of_range() { + assert!(stub_raw_init_from_vec(vec![25], 0, 0).is_null()); + } +} diff --git a/rust/hg-direct-ffi/src/lib.rs b/rust/hg-direct-ffi/src/lib.rs new file mode 100644 --- /dev/null +++ b/rust/hg-direct-ffi/src/lib.rs @@ -0,0 +1,16 @@ +// Copyright 2018 Georges Racinet +// +// This software may be used and distributed according to the terms of the +// GNU General Public License version 2 or any later version. + +//! Bindings for CPython extension code +//! +//! This exposes methods to build and use a `rustlazyancestors` iterator +//! from C code, using an index and its parents function that are passed +//! from the caller at instantiation. + +extern crate hg; +extern crate libc; + +mod ancestors; +pub use ancestors::{rustlazyancestors_drop, rustlazyancestors_init, rustlazyancestors_next};