[PATCH 2 of 6 V2] rust: iterator bindings to C code

Georges Racinet gracinet at anybox.fr
Tue Oct 9 11:22:46 EDT 2018


# HG changeset patch
# User Georges Racinet <gracinet at anybox.fr>
# Date 1538059896 -7200
#      Thu Sep 27 16:51:36 2018 +0200
# Node ID 81b8781de6fad514634713fa2cb9f10c320d1af3
# Parent  e466b892b487a2f258fe2000c76a9e916c7344d8
# EXP-Topic rustancestors-rfc
rust: iterator bindings to C code

In this changeset, still made of Rust code only,
we expose the Rust iterator for instantiation and
consumption from C code.

The idea is that both the index and index_get_parents()
will be passed from the C extension, hence avoiding a hard
link dependency to parsers.so, so that the crate can
still be built and tested independently.

On the other hand, parsers.so will use the symbols
defined in this changeset.

diff -r e466b892b487 -r 81b8781de6fa mercurial/rust/Cargo.lock
--- a/mercurial/rust/Cargo.lock	Thu Sep 27 17:03:16 2018 +0200
+++ b/mercurial/rust/Cargo.lock	Thu Sep 27 16:51:36 2018 +0200
@@ -1,4 +1,14 @@
 [[package]]
 name = "hgancestors"
 version = "0.1.0"
+dependencies = [
+ "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
+[[package]]
+name = "libc"
+version = "0.2.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[metadata]
+"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d"
diff -r e466b892b487 -r 81b8781de6fa mercurial/rust/Cargo.toml
--- a/mercurial/rust/Cargo.toml	Thu Sep 27 17:03:16 2018 +0200
+++ b/mercurial/rust/Cargo.toml	Thu Sep 27 16:51:36 2018 +0200
@@ -2,3 +2,9 @@
 name = "hgancestors"
 version = "0.1.0"
 authors = ["Georges Racinet <gracinet at anybox.fr>"]
+
+[dependencies]
+libc = "*"
+
+[lib]
+crate-type = ["dylib"]
diff -r e466b892b487 -r 81b8781de6fa mercurial/rust/src/cpython.rs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/rust/src/cpython.rs	Thu Sep 27 16:51:36 2018 +0200
@@ -0,0 +1,240 @@
+// Copyright 2018 Georges Racinet <gracinet at anybox.fr>
+//
+// 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 libc;
+
+use self::libc::{c_void, c_int, c_long, ssize_t};
+use std::ptr::null_mut;
+use std::slice;
+use super::{Graph, GraphError, Revision, NULL_REVISION};
+use super::ancestors::AncestorsIterator;
+
+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<Index> 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_long,
+) -> *mut AncestorsIterator<Index> {
+
+    raw_init(
+        Index::new(index, parents),
+        initrevslen,
+        initrevs,
+        stoprev,
+        inclusive,
+    )
+}
+
+/// Testable (for any Graph) version of rustlazyancestors_init
+#[inline]
+fn raw_init<G: Graph>(
+    graph: G,
+    initrevslen: usize,
+    initrevs: *mut c_long,
+    stoprev: c_long,
+    inclusive: c_long,
+) -> *mut AncestorsIterator<G> {
+
+    let inclb = match inclusive {
+        0 => false,
+        1 => true,
+        _ => {
+            return null_mut();
+        }
+    };
+
+    let slice = unsafe { 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<Index>,
+) {
+    raw_drop(raw_iter);
+}
+
+/// Testable (for any Graph) version of rustlazayancestors_drop
+#[inline]
+fn raw_drop<G: Graph>(raw_iter: *mut AncestorsIterator<G>) {
+    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<Index>,
+) -> c_long {
+    raw_next(raw)
+}
+
+/// Testable (for any Graph) version of rustlazayancestors_next
+#[inline]
+fn raw_next<G: Graph>(raw: *mut AncestorsIterator<G>) -> c_long {
+    let as_ref = unsafe { &mut *raw };
+    as_ref.next().unwrap_or(NULL_REVISION) as c_long
+}
+
+#[cfg(test)]
+mod tests {
+    use std::thread;
+    use super::*;
+
+    #[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_long,
+    ) -> usize {
+        raw_init(
+            Stub,
+            initrevslen,
+            initrevs as *mut c_long,
+            stoprev,
+            inclusive,
+        ) as usize
+    }
+
+    #[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<c_long> = 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<Stub>;
+
+        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() {
+        let mut initrevs: Vec<c_long> = vec![11, 13];
+        let raw = raw_init(Stub, initrevs.len(), initrevs.as_mut_ptr(), 0, 2);
+        assert_eq!(raw, null_mut());
+    }
+
+    #[test]
+    fn test_empty() {
+        let mut initrevs: Vec<c_long> = vec![];
+        let raw = raw_init(Stub, initrevs.len(), initrevs.as_mut_ptr(), 0, 1);
+        assert_eq!(raw_next(raw), NULL_REVISION as c_long);
+        raw_drop(raw);
+    }
+
+    #[test]
+    fn test_init_err_out_of_range() {
+        let mut initrevs: Vec<c_long> = vec![25];
+        assert!(
+            raw_init(Stub, initrevs.len(), initrevs.as_mut_ptr(), 0, 0)
+                .is_null()
+        );
+    }
+}
diff -r e466b892b487 -r 81b8781de6fa mercurial/rust/src/lib.rs
--- a/mercurial/rust/src/lib.rs	Thu Sep 27 17:03:16 2018 +0200
+++ b/mercurial/rust/src/lib.rs	Thu Sep 27 16:51:36 2018 +0200
@@ -3,7 +3,10 @@
 // This software may be used and distributed according to the terms of the
 // GNU General Public License version 2 or any later version.
 mod ancestors;
-pub use ancestors::AncestorsIterator;
+
+mod cpython;
+pub use cpython::{rustlazyancestors_init, rustlazyancestors_drop,
+                  rustlazyancestors_next};
 
 /// Mercurial revision numbers
 ///


More information about the Mercurial-devel mailing list