The SPAIK Lisp implementation, with a live demo!
plus.lisp
commonlisp
(defun plus (&rest xs)
(let ((s 0))
(dolist (x xs)
(set s (+ s x)))
s))
call.rs
``` rust use spaik::spaik::Spaik; use spaik::error::Error;
fn apifunccall() -> Result<(), Error> { let mut vm = Spaik::new()?; vm.load("plus")?; let (x, y, z) = (1, 2, 3); let result: i32 = vm.call("plus", (x, y, z, 4, 5))?; assert_eq!(result, 15); } ```
The implementation includes the following main parts:
See lisp/test.lisp
and the tests/*.lisp
files for an example of a
non-trivial macro, and lisp/self.lisp
for a non-trivial program.
unsafe
This crate heavily utilizes unsafe
, this is required for the following
operations:
SPAIK uses a moving garbage-collector, meaning that pointers are not
necessarily valid after a call to gc.collect()
, even if the pointer is
known to be part of the root set. External references to GC-allocated
memory need to use indirection (see SPV
type,) but this indirection is
not used inside the VM and GC internals because of the overhead.
Instead, the VM and GC maintain the following invariant
A
PV
value retrieved from the garbage collector arena may not be used aftergc.collect()
runs.
In practice this invariant is very easy to uphold in the internals. The
unsafe
blocks themselves are sometimes hidden behind macros for
convenience.
rust
// An example of unsafe GC memory access, done ergonomically using a macro
with_ref_mut!(vec, Vector(v) => {
let elem = v.pop().unwrap_or(PV::Nil);
self.mem.push(elem);
Ok(())
}).map_err(|e| e.op(Builtin::Pop.sym()))?;
Program memory needs to be randomly accessed -- fast. But by default, Rust will use bounds-checking on arrays. This bounds-checking is usually cheap enough, but not for this particular use-case. The VM also only supports relative jumps, which would have required two additions when using indexing, but only one when using pointer arithmetic.
This optimization relies on:
u8
discriminants and enum Thing
SPAIK often needs to convert integer discriminants into enums, and
vice-versa. For this it uses unsafe { mem::transmute(num) }
.