1.1--- a/20231105.org Sat Jul 27 02:45:34 2024 -0400
1.2+++ b/20231105.org Sun Aug 11 14:46:59 2024 -0400
1.3@@ -1,6 +1,12 @@
1.4 * DRAFT dylib-skel-1
1.5+:PROPERTIES:
1.6+:ID: b4d1bc91-f344-45fd-becc-cb20f00a3a61
1.7+:END:
1.8 - State "DRAFT" from [2023-11-05 Sun 22:23]
1.9 ** Overview
1.10+:PROPERTIES:
1.11+:ID: 2e490c4b-344e-4790-9184-1c05ba675f15
1.12+:END:
1.13 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
1.14 to rapidly develop high-quality software. As such, it's crucial that these two very
1.15 different languages (i.e. compilers) are able to interoperate seamlessly.
1.16@@ -14,12 +20,18 @@
1.17 code-base, but it's definitely something worth adding to the toolbox in case we need it.
1.18
1.19 ** FFI
1.20+:PROPERTIES:
1.21+:ID: 985019fc-612a-44ab-b726-b9067432ad87
1.22+:END:
1.23 The level of interop we're after in this case is [[https://en.wikipedia.org/wiki/Foreign_function_interface][FFI]].
1.24
1.25 Basically, calling Rust code from Lisp and vice-versa. There's an article about calling
1.26 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
1.27 for those interested.
1.28 *** Rust != C
1.29+:PROPERTIES:
1.30+:ID: 2f71a3c1-0b14-46a6-9d8d-f6ec697729cc
1.31+:END:
1.32 The complication(s) with Rust I mentioned early is really just that /it is not C/. =C=
1.33 is old, i.e. well-supported with a stable ABI, making the process of creating bindings
1.34 for a C library a breeze in many languages.
1.35@@ -31,17 +43,29 @@
1.36 like the C ABI can.
1.37
1.38 *** Overhead
1.39+:PROPERTIES:
1.40+:ID: 4ea79f68-55ec-4da3-a184-8343d49532b6
1.41+:END:
1.42 Using FFI involves some overhead. Check [[https://github.com/dyu/ffi-overhead][here]] for an example benchmark across a few
1.43 languages. While building the NAS-T core, I'm very much aware of this, and will need a
1.44 few sanity benchmarks to make sure the cost doesn't outweigh the benefit. In particular,
1.45 I'm concerned about crossing multiple language barriers (Rust<->C<->Lisp).
1.46
1.47 ** Rust -> C -> Lisp
1.48+:PROPERTIES:
1.49+:ID: a498276c-8525-4a43-aa40-4b05f76a29a9
1.50+:END:
1.51 *** Setup
1.52+:PROPERTIES:
1.53+:ID: 19f96ef7-af92-496e-9d42-70c4d4c85051
1.54+:END:
1.55 For starters, I'm going to assume we all have Rust (via =rustup=) and Lisp (=sbcl= only)
1.56 installed on our GNU/Linux system (some tweaks needed for Darwin/Windows, not covered in
1.57 this post).
1.58 **** Cargo
1.59+:PROPERTIES:
1.60+:ID: c929e0b6-b6f2-4383-9412-1610329ab28c
1.61+:END:
1.62 Create a new library crate. For this example we're focusing on a 'skeleton' for
1.63 /dynamic/ libraries only, so our experiment will be called =dylib-skel= or *dysk* for
1.64 short.
1.65@@ -64,7 +88,13 @@
1.66 This tells Rust to generate a shared C-compatible object with a =.so= extension which we
1.67 can open using [[https://man.archlinux.org/man/dlopen.3.en][dlopen]].
1.68 **** cbindgen
1.69+:PROPERTIES:
1.70+:ID: 256ac288-c5a0-473a-ab65-2d6503bd423c
1.71+:END:
1.72 ***** install
1.73+:PROPERTIES:
1.74+:ID: fc476f64-6b68-417a-8540-ca23ce27fa25
1.75+:END:
1.76 Next, we want the =cbindgen= program which we'll use to generate header files for
1.77 C/C++. This step isn't necessary at all, we just want it for further experimentation.
1.78
1.79@@ -76,6 +106,9 @@
1.80 cbindgen = "0.24"
1.81 #+end_src
1.82 ***** cbindgen.toml
1.83+:PROPERTIES:
1.84+:ID: 111e27f7-0b9c-4eef-9117-f7c8ba3f511c
1.85+:END:
1.86 #+begin_src conf-toml :tangle cbindgen.toml
1.87 language = "C"
1.88 autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
1.89@@ -92,6 +125,9 @@
1.90 header = '"dysk.h"'
1.91 #+end_src
1.92 ***** build.rs
1.93+:PROPERTIES:
1.94+:ID: 9fc271b2-9acb-4f4b-aa61-82d60d2ddb9e
1.95+:END:
1.96 #+begin_src rust :tangle build.rs
1.97 fn main() -> Result<(), cbindgen::Error> {
1.98 if let Ok(b) = cbindgen::generate(std::env::var("CARGO_MANIFEST_DIR").unwrap()) {
1.99@@ -99,6 +135,9 @@
1.100 else { panic!("failed to generate dysk.h from cbindgen.toml") } }
1.101 #+end_src
1.102 *** lib.rs
1.103+:PROPERTIES:
1.104+:ID: 6b524921-2ae0-43f0-bb85-d9955b0e689c
1.105+:END:
1.106 #+begin_src rust :tangle lib.rs
1.107 //! lib.rs --- dysk library
1.108 use std::ffi::{c_char, c_int, CString};
1.109@@ -111,15 +150,24 @@
1.110 pub extern "C" fn dysk_plus1(n:c_int) -> c_int {n+1}
1.111 #+end_src
1.112 *** test.rs
1.113+:PROPERTIES:
1.114+:ID: cc7c6538-33a6-40c6-94ef-2a9c259c975a
1.115+:END:
1.116 #+begin_src rust :tangle test.rs
1.117 //! test.rs --- dysk test
1.118 fn main() { let mut i = 0u32; while i < 500000000 {i+=1; dysk::dysk_plus1(2 as core::ffi::c_int);}}
1.119 #+end_src
1.120 *** compile
1.121+:PROPERTIES:
1.122+:ID: 337a24d1-f305-4e1a-9052-47a53591cb2f
1.123+:END:
1.124 #+begin_src sh
1.125 cargo build --release
1.126 #+end_src
1.127 *** load from SBCL
1.128+:PROPERTIES:
1.129+:ID: a4813269-92fb-4f52-aef0-3a36dce3cf69
1.130+:END:
1.131 #+begin_src lisp :tangle dysk.lisp
1.132 (load-shared-object #P"target/release/libdysk.so")
1.133 (define-alien-routine dysk-hello c-string)
1.134@@ -128,6 +176,9 @@
1.135 (dysk-hello) ;; => "hello from rust"
1.136 #+end_src
1.137 *** benchmark
1.138+:PROPERTIES:
1.139+:ID: 1a8ca441-f290-46c7-b979-1e7e0d1d063b
1.140+:END:
1.141 #+begin_src shell
1.142 time target/release/dysk-test
1.143 #+end_src