3 :ID: b4d1bc91-f344-45fd-becc-cb20f00a3a61 5 - State "DRAFT" from [2023-11-05 Sun 22:23]
8 :ID: 2e490c4b-344e-4790-9184-1c05ba675f15 10 Our core languages are
[[https://www.rust-lang.org/][Rust]] and
[[https://lisp-lang.org/][Lisp]] - this is the killer combo which will allow NAS-T
11 to rapidly develop high-quality software. As such, it's crucial that these two very
12 different languages (i.e. compilers) are able to interoperate seamlessly.
14 Some interop methods are easy to accomodate via the OS - such as IPC or data sharing,
15 but others are a bit more difficult.
17 In this 2-part series we'll build a FFI bridge between Rust and Lisp, which is something
18 that
/can/ be difficult, due to some complications with Rust and because this is not the
19 most popular software stack (yet ;). This is an experiment and may not make it to our
20 code-base, but it's definitely something worth adding to the toolbox in case we need it.
24 :ID: 985019fc-612a-44ab-b726-b9067432ad87 26 The level of interop we're after in this case is
[[https://en.wikipedia.org/wiki/Foreign_function_interface][FFI]].
28 Basically, calling Rust code from Lisp and vice-versa. There's an article about calling
29 Rust from Common Lisp
[[https://dev.to/veer66/calling-rust-from-common-lisp-45c5][here]] which shows the basics and serves as a great starting point
33 :ID: 2f71a3c1-0b14-46a6-9d8d-f6ec697729cc 35 The complication(s) with Rust I mentioned early is really just that
/it is not C/.
=C= 36 is old, i.e. well-supported with a stable ABI, making the process of creating bindings
37 for a C library a breeze in many languages.
39 For a Rust library we need to first appease the compiler, as explained in
[[https://doc.rust-lang.org/nomicon/ffi.html#calling-rust-code-from-c][this section]] 40 of the Rustonomicon. Among other things it involves changing the calling-convention of
41 functions with a type signature and editing the Cargo.toml file to produce a
42 C-compatible ABI binary. The Rust default ABI is unstable and can't reliably be used
47 :ID: 4ea79f68-55ec-4da3-a184-8343d49532b6 49 Using FFI involves some overhead. Check
[[https://github.com/dyu/ffi-overhead][here]] for an example benchmark across a few
50 languages. While building the NAS-T core, I'm very much aware of this, and will need a
51 few sanity benchmarks to make sure the cost doesn't outweigh the benefit. In particular,
52 I'm concerned about crossing multiple language barriers (Rust
<->C
<->Lisp).
56 :ID: a498276c-8525-4a43-aa40-4b05f76a29a9 60 :ID: 19f96ef7-af92-496e-9d42-70c4d4c85051 62 For starters, I'm going to assume we all have Rust (via
=rustup=) and Lisp (
=sbcl= only)
63 installed on our GNU/Linux system (some tweaks needed for Darwin/Windows, not covered in
67 :ID: c929e0b6-b6f2-4383-9412-1610329ab28c 69 Create a new library crate. For this example we're focusing on a 'skeleton' for
70 /dynamic/ libraries only, so our experiment will be called
=dylib-skel= or
*dysk* for
72 src_sh[:exports code]{cargo init dysk --lib && cd dysk}
74 A
=src/lib.rs= will be generated for you. Go ahead and delete that. We're going to be
75 making our own
=lib.rs= file directly in the root directory (just to be cool).
77 The next step is to edit your
=Cargo.toml= file. Add these lines after the
=[package]= 78 section and before
=[dependencies]=:
81 crate-type = ["cdylib","rlib"]
88 This tells Rust to generate a shared C-compatible object with a
=.so= extension which we
89 can open using
[[https://man.archlinux.org/man/dlopen.3.en][dlopen]].
92 :ID: 256ac288-c5a0-473a-ab65-2d6503bd423c 96 :ID: fc476f64-6b68-417a-8540-ca23ce27fa25 98 Next, we want the
=cbindgen= program which we'll use to generate header files for
99 C/C++. This step isn't necessary at all, we just want it for further experimentation.
101 src_sh[:exports code]{cargo install --force cbindgen}
103 We append the
=cbindgen= crate as a
/build dependency/ to our
=Cargo.toml= like so:
104 #+begin_src conf-toml 110 :ID: 111e27f7-0b9c-4eef-9117-f7c8ba3f511c 112 #+begin_src conf-toml :tangle cbindgen.toml 114 autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
115 include_version = true
118 after_includes = "#define DYSK_VERSION \"0.1.0\""
122 documentation_style = "c99"
123 usize_is_size_t = true
129 :ID: 9fc271b2-9acb-4f4b-aa61-82d60d2ddb9e 131 #+begin_src rust :tangle build.rs 132 fn main() -> Result<(), cbindgen::Error> {
133 if let Ok(b) = cbindgen::generate(std::env::var("CARGO_MANIFEST_DIR").unwrap()) {
134 b.write_to_file("dysk.h"); Ok(())}
135 else { panic!("failed to generate dysk.h from cbindgen.toml") } }
139 :ID: 6b524921-2ae0-43f0-bb85-d9955b0e689c 141 #+begin_src rust :tangle lib.rs 142 //! lib.rs --- dysk library
143 use std::ffi::{c_char, c_int, CString};
145 pub extern "C" fn dysk_hello() -> *const c_char {
146 CString::new("hello from rust").unwrap().into_raw()}
148 pub extern "C" fn dysk_plus(a:c_int,b:c_int) -> c_int {a+b}
150 pub extern "C" fn dysk_plus1(n:c_int) -> c_int {n+1}
154 :ID: cc7c6538-33a6-40c6-94ef-2a9c259c975a 156 #+begin_src rust :tangle test.rs 157 //! test.rs --- dysk test
158 fn main() { let mut i = 0u32; while i < 500000000 {i+=1; dysk::dysk_plus1(2 as core::ffi::c_int);}}
162 :ID: 337a24d1-f305-4e1a-9052-47a53591cb2f 165 cargo build --release
169 :ID: a4813269-92fb-4f52-aef0-3a36dce3cf69 171 #+begin_src lisp :tangle dysk.lisp 172 (load-shared-object #P"target/release/libdysk.so")
173 (define-alien-routine dysk-hello c-string)
174 (define-alien-routine dysk-plus int (a int) (b int))
175 (define-alien-routine dysk-plus1 int (n int))
176 (dysk-hello) ;; => "hello from rust"
180 :ID: 1a8ca441-f290-46c7-b979-1e7e0d1d063b 183 time target/release/dysk-test
185 #+begin_src lisp :tangle test.lisp 186 (time (dotimes (_ 500000000) (dysk-plus1 2)))