changelog shortlog graph tags branches changeset files file revisions raw help

Mercurial > org > notes / annotate 20231105.org

changeset 0: 87b04952fb18
child: 04e86b94ef1a
author: Richard Westhaver <ellis@rwest.io>
date: Sun, 28 Apr 2024 19:51:45 -0400
permissions: -rw-r--r--
description: init
0
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
1
 * DRAFT dylib-skel-1
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
2
 - State "DRAFT"      from              [2023-11-05 Sun 22:23]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
3
 ** Overview
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
4
 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
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
5
 to rapidly develop high-quality software. As such, it's crucial that these two very
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
6
 different languages (i.e. compilers) are able to interoperate seamlessly.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
7
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
8
 Some interop methods are easy to accomodate via the OS - such as IPC or data sharing,
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
9
 but others are a bit more difficult.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
10
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
11
 In this 2-part series we'll build a FFI bridge between Rust and Lisp, which is something
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
12
 that /can/ be difficult, due to some complications with Rust and because this is not the
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
13
 most popular software stack (yet ;). This is an experiment and may not make it to our
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
14
 code-base, but it's definitely something worth adding to the toolbox in case we need it.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
15
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
16
 ** FFI
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
17
 The level of interop we're after in this case is [[https://en.wikipedia.org/wiki/Foreign_function_interface][FFI]].
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
18
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
19
 Basically, calling Rust code from Lisp and vice-versa. There's an article about calling
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
20
 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
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
21
 for those interested.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
22
 *** Rust != C
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
23
 The complication(s) with Rust I mentioned early is really just that /it is not C/. =C=
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
24
 is old, i.e. well-supported with a stable ABI, making the process of creating bindings
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
25
 for a C library a breeze in many languages.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
26
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
27
 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]]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
28
 of the Rustonomicon. Among other things it involves changing the calling-convention of
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
29
 functions with a type signature and editing the Cargo.toml file to produce a
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
30
 C-compatible ABI binary. The Rust default ABI is unstable and can't reliably be used
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
31
 like the C ABI can.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
32
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
33
 *** Overhead
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
34
 Using FFI involves some overhead. Check [[https://github.com/dyu/ffi-overhead][here]] for an example benchmark across a few
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
35
 languages. While building the NAS-T core, I'm very much aware of this, and will need a
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
36
 few sanity benchmarks to make sure the cost doesn't outweigh the benefit. In particular,
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
37
 I'm concerned about crossing multiple language barriers (Rust<->C<->Lisp).
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
38
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
39
 ** Rust -> C -> Lisp
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
40
 *** Setup
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
41
 For starters, I'm going to assume we all have Rust (via =rustup=) and Lisp (=sbcl= only)
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
42
 installed on our GNU/Linux system (some tweaks needed for Darwin/Windows, not covered in
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
43
 this post).
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
44
 **** Cargo
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
45
 Create a new library crate. For this example we're focusing on a 'skeleton' for
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
46
 /dynamic/ libraries only, so our experiment will be called =dylib-skel= or *dysk* for
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
47
 short.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
48
 src_sh[:exports code]{cargo init dysk --lib && cd dysk} 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
49
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
50
 A =src/lib.rs= will be generated for you. Go ahead and delete that. We're going to be
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
51
 making our own =lib.rs= file directly in the root directory (just to be cool).
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
52
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
53
 The next step is to edit your =Cargo.toml= file. Add these lines after the =[package]=
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
54
 section and before =[dependencies]=:
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
55
 #+begin_src conf-toml
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
56
 [lib]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
57
 crate-type = ["cdylib","rlib"]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
58
 path = "lib.rs"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
59
 [[bin]]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
60
 name="dysk-test"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
61
 path="test.rs"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
62
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
63
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
64
 This tells Rust to generate a shared C-compatible object with a =.so= extension which we
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
65
 can open using [[https://man.archlinux.org/man/dlopen.3.en][dlopen]].
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
66
 **** cbindgen
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
67
 ***** install
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
68
 Next, we want the =cbindgen= program which we'll use to generate header files for
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
69
 C/C++. This step isn't necessary at all, we just want it for further experimentation.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
70
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
71
 src_sh[:exports code]{cargo install --force cbindgen}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
72
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
73
 We append the =cbindgen= crate as a /build dependency/ to our =Cargo.toml= like so:
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
74
 #+begin_src conf-toml
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
75
 [build-dependencies]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
76
 cbindgen = "0.24"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
77
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
78
 ***** cbindgen.toml
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
79
 #+begin_src conf-toml :tangle cbindgen.toml
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
80
 language = "C"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
81
 autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
82
 include_version = true
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
83
 namespace = "dysk"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
84
 cpp_compat = true
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
85
 after_includes = "#define DYSK_VERSION \"0.1.0\""
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
86
 line_length = 88
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
87
 tab_width = 2
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
88
 documentation = true
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
89
 documentation_style = "c99"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
90
 usize_is_size_t = true
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
91
 [cython]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
92
 header = '"dysk.h"'
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
93
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
94
 ***** build.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
95
 #+begin_src rust :tangle build.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
96
 fn main() -> Result<(), cbindgen::Error> {
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
97
   if let Ok(b) = cbindgen::generate(std::env::var("CARGO_MANIFEST_DIR").unwrap()) {
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
98
     b.write_to_file("dysk.h"); Ok(())}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
99
   else { panic!("failed to generate dysk.h from cbindgen.toml") } }
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
100
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
101
 *** lib.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
102
 #+begin_src rust :tangle lib.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
103
 //! lib.rs --- dysk library
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
104
 use std::ffi::{c_char, c_int, CString};
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
105
 #[no_mangle]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
106
 pub extern "C" fn dysk_hello() -> *const c_char {
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
107
   CString::new("hello from rust").unwrap().into_raw()}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
108
 #[no_mangle]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
109
 pub extern "C" fn dysk_plus(a:c_int,b:c_int) -> c_int {a+b}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
110
 #[no_mangle]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
111
 pub extern "C" fn dysk_plus1(n:c_int) -> c_int {n+1}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
112
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
113
 *** test.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
114
 #+begin_src rust :tangle test.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
115
 //! test.rs --- dysk test
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
116
 fn main() { let mut i = 0u32; while i < 500000000 {i+=1; dysk::dysk_plus1(2 as core::ffi::c_int);}}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
117
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
118
 *** compile
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
119
 #+begin_src sh
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
120
 cargo build --release
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
121
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
122
 *** load from SBCL
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
123
 #+begin_src lisp :tangle dysk.lisp
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
124
 (load-shared-object #P"target/release/libdysk.so")
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
125
 (define-alien-routine dysk-hello c-string)
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
126
 (define-alien-routine dysk-plus int (a int) (b int))
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
127
 (define-alien-routine dysk-plus1 int (n int))
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
128
 (dysk-hello) ;; => "hello from rust"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
129
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
130
 *** benchmark
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
131
 #+begin_src shell
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
132
 time target/release/dysk-test
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
133
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
134
 #+begin_src lisp :tangle test.lisp
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
135
 (time (dotimes (_ 500000000) (dysk-plus1 2)))
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
136
 #+end_src