changelog shortlog graph tags branches changeset files file revisions raw help

Mercurial > org > blog / annotate draft/dylib-skel.org

changeset 18: d77884ec2b44
child: 84097475a40a
author: Richard Westhaver <ellis@rwest.io>
date: Sun, 28 Apr 2024 19:49:20 -0400
permissions: -rw-r--r--
description: drafts
18
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
1
 #+title: Shared Library Skeletons
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
2
 * Overview
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
3
 + CODE :: [[https://lab.rwest.io/packy/stash/dysk][packy/stash/dysk]]
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
 ** FFI
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
16
 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
17
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
18
 Basically, calling Rust code from Lisp and vice-versa. There's an article about calling
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
19
 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
20
 for those interested.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
21
 *** Rust ABI
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
22
 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
23
 is old, i.e. well-supported with a stable ABI, making the process of creating bindings
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
24
 for a C library a breeze in many languages.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
25
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
26
 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
27
 of the Rustonomicon. Among other things it involves changing the calling-convention of
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
28
 functions with a type signature and editing the Cargo.toml file to produce a
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
29
 C-compatible ABI binary. The Rust default ABI is unstable and can't reliably be used
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
30
 like the C ABI can.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
31
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
32
 [[https://github.com/rodrimati1992/abi_stable_crates][abi_stable_crates]] is a project which addresses some of the ABI concerns, presenting a
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
33
 sort of ABI-API as a Rust library. Perhaps this is the direction the ecosystem will go
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
34
 with in order to maintain an unstable ABI, but for now there is no 'clear' pathway for a
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
35
 friction-less FFI development experience in Rust.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
36
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
37
 *** Overhead
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
38
 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
39
 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
40
 few sanity benchmarks to make sure the cost doesn't outweigh the benefit. In particular,
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
41
 I'm concerned about crossing multiple language barriers (Rust<->C<->Lisp).
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
42
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
43
 * basic example
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
44
 ** Setup
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
45
 For starters, I'm going to assume we all have Rust (via [[https://rustup.rs/][rustup]]) and Lisp ([[https://www.sbcl.org/][sbcl]] only)
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
46
 installed on our GNU/Linux system (some tweaks needed for Darwin/Windows, not covered in
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
47
 this post).
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
48
 *** Cargo
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
49
 Create a new library crate. For this example we're focusing on a 'skeleton' for
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
50
 /dynamic/ libraries only, so our experiment will be called =dylib-skel= or *dysk* for
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
51
 short.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
52
 src_sh[:exports code]{cargo init dysk --lib && cd dysk} 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
53
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
54
 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
55
 making our own =lib.rs= file directly in the root directory (just to be cool).
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
56
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
57
 The next step is to edit your =Cargo.toml= file. Add these lines after the =[package]=
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
58
 section and before =[dependencies]=:
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
59
 #+begin_src conf-toml
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
60
 [lib]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
61
 crate-type = ["cdylib","rlib"]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
62
 path = "lib.rs"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
63
 [[bin]]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
64
 name="dysk-test"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
65
 path="test.rs"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
66
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
67
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
68
 This tells Rust to generate a shared C-compatible object with a =.so= extension which we
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
69
 can open using [[https://man.archlinux.org/man/dlopen.3.en][dlopen]].
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
70
 *** cbindgen
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
71
 **** install
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
72
 Next, we want the =cbindgen= program which we'll use to generate header files for
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
73
 C/C++. This step isn't necessary at all, we just want it for further experimentation.
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
74
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
75
 src_sh[:exports code]{cargo install --force cbindgen}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
76
 
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
77
 We append the =cbindgen= crate as a /build dependency/ to our =Cargo.toml= like so:
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
78
 #+begin_src conf-toml
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
79
 [build-dependencies]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
80
 cbindgen = "0.24"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
81
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
82
 **** cbindgen.toml
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
83
 #+begin_src conf-toml :tangle cbindgen.toml
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
84
 language = "C"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
85
 autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
86
 include_version = true
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
87
 namespace = "dysk"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
88
 cpp_compat = true
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
89
 after_includes = "#define DYSK_VERSION \"0.1.0\""
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
90
 line_length = 88
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
91
 tab_width = 2
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
92
 documentation = true
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
93
 documentation_style = "c99"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
94
 usize_is_size_t = true
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
95
 [cython]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
96
 header = "dysk.h"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
97
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
98
 **** build.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
99
 #+begin_src rust :tangle build.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
100
 fn main() -> Result<(), cbindgen::Error> {
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
101
   if let Ok(b) = cbindgen::generate(std::env::var("CARGO_MANIFEST_DIR").unwrap()) {
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
102
     b.write_to_file("dysk.h"); Ok(())}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
103
   else { panic!("failed to generate dysk.h from cbindgen.toml") } }
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
104
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
105
 ** lib.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
106
 #+begin_src rust :tangle lib.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
107
 //! lib.rs --- dysk library
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
108
 use std::ffi::{c_char, c_int, CString};
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
109
 #[no_mangle]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
110
 pub extern "C" fn hello() -> *const c_char {
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
111
   CString::new("hello from rust").unwrap().into_raw()}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
112
 #[no_mangle]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
113
 pub extern "C" fn plus(a:c_int,b:c_int) -> c_int {a+b}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
114
 #[no_mangle]
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
115
 pub extern "C" fn plus1(n:c_int) -> c_int {n+1}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
116
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
117
 ** test.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
118
 #+begin_src rust :tangle test.rs
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
119
 //! test.rs --- dysk test
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
120
 fn main() { let mut i = 0u32; while i < 500000000 {i+=1; dysk::plus1(2 as core::ffi::c_int);}}
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
121
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
122
 ** compile
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
123
 #+begin_src sh
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
124
 cargo build --release
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
125
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
126
 ** load from SBCL
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
127
 #+begin_src lisp :tangle dysk.lisp
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
128
 ;;; dysk.lisp
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
129
 ;; (dysk:hello) ;; => "hello from rust"
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
130
 (defpackage :dysk
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
131
   (:use :cl :sb-alien)
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
132
   (:export :hello :plus :plus1))
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
133
 (in-package :dysk)
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
134
 (load-shared-object #P"target/release/libdysk.so")
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
135
 (define-alien-routine hello c-string)
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
136
 (define-alien-routine plus int (a int) (b int))
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
137
 (define-alien-routine plus1 int (n int))
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
138
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
139
 ** benchmark
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
140
 #+begin_src shell
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
141
 time target/release/dysk-test
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
142
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
143
 #+begin_src lisp :tangle test.lisp
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
144
 (time (dotimes (_ 500000000) (dysk:plus1 2)))
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
145
 #+end_src
Richard Westhaver <ellis@rwest.io>
parents:
diff changeset
146