code_native/lib.rs
1//! Safe(r) Rust bindings for writing a native `.so` module for the [Code
2//! programming language](https://github.com/codelovesme/code).
3//!
4//! `code_abi.h`'s contract needs two things from a module: agreement on the
5//! `CodeValue` wire layout, and a `code_release` (plus friends) built from
6//! the *real* `runtime.c` rather than a reimplementation that merely looks
7//! compatible — getting refcounting subtly wrong is the kind of bug that
8//! corrupts memory rather than crashing where you'd notice. This crate's
9//! `build.rs` compiles the vendored `runtime.c` and links it into your
10//! `cdylib` directly, so every function below calls the same code the host
11//! runtime and every C module trust.
12//!
13//! # Quick start
14//!
15//! ```rust,ignore
16//! use code_native::*;
17//!
18//! #[no_mangle]
19//! pub extern "C" fn code_module_abi_version() -> u32 {
20//! CODE_ABI_VERSION
21//! }
22//!
23//! #[no_mangle]
24//! pub unsafe extern "C" fn code_module_dispatch(out: *mut CodeValue, particle: *const CodeValue) {
25//! let particle = &*particle;
26//! match read_field_str(particle, "_class") {
27//! Some("Double") => {
28//! let value = read_field_number(particle, "value").unwrap_or(0.0);
29//! make_result(&mut *out, "DoubleResult", |slot| code_number(slot, value * 2.0));
30//! }
31//! _ => code_runtime_error("unknown handler"),
32//! }
33//! }
34//! ```
35//!
36//! Build with `crate-type = ["cdylib"]`, then `link "libmymodule.so" as m`
37//! from `.code` source. See this crate's README for the full walkthrough,
38//! including `.a` static modules and `code_module_vars`.
39//!
40//! `code_module_dispatch` and `code_module_abi_version` are the two required
41//! exports — there is no macro generating them here (unlike the *old*
42//! language's `code-native`): the new ABI dropped the descriptor-table
43//! design for one function a module dispatches through itself, so there is
44//! no boilerplate left to generate. `code_release` needs no Rust code at
45//! all — it comes from the linked `runtime.c` object automatically.
46
47use std::ffi::{c_char, c_int, c_void, CStr};
48
49// ===========================================================================
50// Wire layout — bit-for-bit `code_abi.h`. Only the pointer/int/float shapes
51// matter for ABI compatibility (not what they're named), but names are kept
52// identical to the header so the two are trivially diffable.
53// ===========================================================================
54
55/// Current ABI version. A module's `code_module_abi_version` must return
56/// this.
57pub const CODE_ABI_VERSION: u32 = 1;
58
59/// Byte stride of an array/object element buffer — **not** `size_of::<CodeValue>()`.
60/// This is a frozen ABI constant with headroom for `CodeValue` to grow
61/// without breaking already-compiled modules; always address a buffer
62/// through [`slot_at`], never by casting to `*mut CodeValue` and indexing.
63pub const CODE_VALUE_SLOT_SIZE: usize = 80;
64
65#[repr(C)]
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum CodeTag {
68 Number,
69 Str,
70 Bool,
71 Null,
72 Array,
73 Object,
74}
75
76#[repr(C)]
77pub struct CodeValue {
78 pub tag: CodeTag,
79 pub heap: c_int,
80 pub number: f64,
81 pub str: *const c_char,
82 pub boolean: c_int,
83 /// `CODE_ARRAY`: element buffer; `CODE_OBJECT`: value buffer — both
84 /// strided at [`CODE_VALUE_SLOT_SIZE`], addressed via [`slot_at`].
85 pub items: *mut c_void,
86 /// `CODE_OBJECT` only, parallel to `items`.
87 pub keys: *mut *const c_char,
88 pub len: i64,
89}
90
91impl CodeValue {
92 /// An all-zero value — tag `Number`, `0.0`, not heap-owned. Bit-for-bit
93 /// what `CodeValue x = {0};` produces in C, and the required starting
94 /// state before passing `&mut` to any constructor below (each one calls
95 /// `code_release` on `out` first, exactly like the C ABI expects).
96 pub fn zeroed() -> Self {
97 // SAFETY: an all-zero-bytes CodeValue is a valid Number(0.0), which
98 // `code_release` (called by every constructor before overwriting
99 // `out`) already treats as a safe no-op — the same invariant `{0}`
100 // relies on in every C module.
101 unsafe { std::mem::zeroed() }
102 }
103}
104
105impl Default for CodeValue {
106 fn default() -> Self {
107 Self::zeroed()
108 }
109}
110
111#[repr(C)]
112pub struct CodeVarList {
113 pub count: i64,
114 pub names: *const *const c_char,
115 /// `CODE_VALUE_SLOT_SIZE` stride, `count` slots — see [`slot_at`].
116 pub values: *mut CodeValue,
117}
118
119// Both types carry raw pointers, so Rust doesn't derive Send/Sync for them
120// automatically — but `code_module_vars` (see README) is exactly the case
121// that needs a `static`/`OnceLock<CodeVarList>`, and the host only ever
122// reads this data (once, at `link` time), never mutates it concurrently.
123// Matches the old language's own `code-abi` crate, which needed the same
124// impls for the same reason.
125unsafe impl Send for CodeValue {}
126unsafe impl Sync for CodeValue {}
127unsafe impl Send for CodeVarList {}
128unsafe impl Sync for CodeVarList {}
129
130// ===========================================================================
131// Raw bindings to `runtime.c`'s exported (non-`static`) functions — the same
132// symbols `code_abi.h` declares for a C module. Calling into the actual
133// compiled `runtime.c`, not a port of it, is what keeps this crate free of
134// the layout-drift risk the *old* language's `code-native`/`code-abi` pair
135// needed a dedicated test to guard against.
136// ===========================================================================
137
138extern "C" {
139 fn code_number(out: *mut CodeValue, n: f64);
140 fn code_str(out: *mut CodeValue, s: *const c_char);
141 fn code_bool(out: *mut CodeValue, b: c_int);
142 fn code_null(out: *mut CodeValue);
143 fn code_array(out: *mut CodeValue, items: *mut c_void, len: i64);
144 fn code_object(out: *mut CodeValue, keys: *mut *const c_char, values: *mut c_void, len: i64);
145 fn code_copy(out: *mut CodeValue, src: *const CodeValue);
146 fn code_field(out: *mut CodeValue, obj: *const CodeValue, field: *const c_char);
147 fn code_index(out: *mut CodeValue, arr: *const CodeValue, index: *const CodeValue);
148 fn code_retain(v: *const CodeValue);
149 fn code_values_equal(a: *const CodeValue, b: *const CodeValue) -> c_int;
150 fn code_bool_value(v: *const CodeValue, op: *const c_char) -> c_int;
151 fn code_assert(v: *const CodeValue);
152 fn code_runtime_error(message: *const c_char) -> !;
153
154 // `build.rs` compiles `runtime.c` with `code_release` renamed to this at
155 // the preprocessor level (`-D`), and this crate re-exports it below under
156 // the real name from a function rustc actually treats as part of the
157 // crate (not an archive) — see that `#[no_mangle]` fn's own doc comment
158 // for why the rename is needed at all.
159 fn code_native_vendored_release(v: *mut CodeValue);
160}
161
162/// The ABI's required `code_release` export. Defined here, as a real Rust
163/// function, rather than left as whatever `runtime.c`'s own `code_release`
164/// would otherwise be: `cdylib` targets get `--exclude-libs=ALL` from
165/// rustc by default, which hides every symbol pulled in from a *linked
166/// static archive* (exactly what `build.rs`'s `cc::Build::compile` produces
167/// from `runtime.c`) out of the shared library's dynamic symbol table —
168/// even though this crate's own code calls it just fine internally. A
169/// symbol the crate defines directly (this function) isn't subject to that
170/// exclusion, so renaming the archive's copy and re-exporting it from here
171/// is what makes the host's `dlsym("code_release")` actually find it.
172///
173/// # Safety
174/// `v` must point to a valid, initialized `CodeValue` — the same
175/// requirement `runtime.c`'s own `code_release` has. The host only ever
176/// calls this on values it deep-copied out of your `code_module_dispatch`
177/// result, so you should never need to call it yourself except via
178/// [`release`].
179#[no_mangle]
180pub unsafe extern "C" fn code_release(v: *mut CodeValue) {
181 code_native_vendored_release(v)
182}
183
184/// Addresses slot `index` of a [`CODE_VALUE_SLOT_SIZE`]-strided buffer —
185/// the Rust equivalent of `code_abi.h`'s `code_slot_at`. Pure pointer
186/// arithmetic, safe to reimplement independently (no allocator/refcount
187/// logic to drift from `runtime.c`).
188pub fn slot_at(base: *mut c_void, index: i64) -> *mut CodeValue {
189 (base as *mut u8).wrapping_offset(index as isize * CODE_VALUE_SLOT_SIZE as isize) as *mut CodeValue
190}
191
192fn cstr(s: &str) -> std::ffi::CString {
193 std::ffi::CString::new(s).unwrap_or_else(|_| std::ffi::CString::new("<invalid-utf8>").unwrap())
194}
195
196// ===========================================================================
197// Safe scalar constructors — thin wrappers: `code_release`s `out` first
198// (matching every `runtime.c` constructor's own contract), then delegates.
199// ===========================================================================
200
201/// Write a Number into `out`.
202pub fn number(out: &mut CodeValue, n: f64) {
203 unsafe { code_number(out, n) }
204}
205
206/// Write a Str into `out`, borrowing `s` for `'static` (a string literal or
207/// otherwise permanently-alive buffer) rather than copying it — matching
208/// `code_str`'s own borrowing contract. Use [`owned_str`] for a value built
209/// at runtime that needs its own heap block.
210pub fn borrowed_str(out: &mut CodeValue, s: &'static CStr) {
211 unsafe { code_str(out, s.as_ptr()) }
212}
213
214/// Write a Str into `out` from a freshly-built Rust string. Leaks the
215/// `CString` — acceptable here because the value crosses into the host's
216/// own heap the moment your `code_module_dispatch` returns (the host
217/// deep-copies your result and then calls your module's `code_release` on
218/// it, which only ever frees what `runtime.c`'s own allocator built, never
219/// this leaked buffer).
220pub fn owned_str(out: &mut CodeValue, s: &str) {
221 let c = cstr(s);
222 unsafe { code_str(out, c.as_ptr()) }
223 std::mem::forget(c);
224}
225
226/// Write a Bool into `out`.
227pub fn boolean(out: &mut CodeValue, b: bool) {
228 unsafe { code_bool(out, b as c_int) }
229}
230
231/// Write Null into `out`.
232pub fn null(out: &mut CodeValue) {
233 unsafe { code_null(out) }
234}
235
236/// Release whatever `v` holds — call on every temporary [`CodeValue`] you
237/// built and no longer need (matching `runtime.c`'s own refcounting rule:
238/// every slot that ever named a heap block owns exactly one reference to
239/// it).
240pub fn release(v: &mut CodeValue) {
241 unsafe { code_release(v) }
242}
243
244/// Increment `v`'s refcount — needed only if you're holding onto a
245/// [`CodeValue`] you didn't just build yourself (e.g. a borrowed field from
246/// [`find_field`]) somewhere that will outlive the call it came from.
247/// Every retained value must be balanced by a [`release`].
248pub fn retain(v: &CodeValue) {
249 unsafe { code_retain(v) }
250}
251
252/// `obj.field` field access, exactly like `.code` source's own semantics:
253/// writes Null into `out` on a non-Object or missing field rather than
254/// erroring — see `code_field`'s doc comment in `code_abi.h`.
255pub fn field(out: &mut CodeValue, obj: &CodeValue, name: &str) {
256 let c = cstr(name);
257 unsafe { code_field(out, obj, c.as_ptr()) }
258}
259
260/// `arr[index]` element access, exactly like `.code` source's own
261/// semantics: writes Null on a non-Array or out-of-bounds index.
262pub fn index(out: &mut CodeValue, arr: &CodeValue, i: &CodeValue) {
263 unsafe { code_index(out, arr, i) }
264}
265
266/// Structural equality, matching `.code` source's `=` operator.
267pub fn values_equal(a: &CodeValue, b: &CodeValue) -> bool {
268 unsafe { code_values_equal(a, b) != 0 }
269}
270
271/// Coerce `v` to a `bool` the way a boolean operator does, raising the same
272/// fatal error a type mismatch would in `.code` source itself (`op` is the
273/// operator name, used only for that error message — e.g. `"&&"`).
274pub fn bool_value(v: &CodeValue, op: &str) -> bool {
275 let c = cstr(op);
276 unsafe { code_bool_value(v, c.as_ptr()) != 0 }
277}
278
279/// `assert v` semantics: fatal error (never returns) if `v` isn't `true`.
280pub fn assert_value(v: &CodeValue) {
281 unsafe { code_assert(v) }
282}
283
284/// Raise a fatal module error — mirrors `core`'s own handlers. Never
285/// returns: like `core`, this takes the whole host process down (`code
286/// run` included), the same tradeoff every native-extension mechanism
287/// makes. See `code_abi.h`'s doc comment.
288pub fn runtime_error(message: &str) -> ! {
289 let c = cstr(message);
290 unsafe { code_runtime_error(c.as_ptr()) }
291}
292
293// ===========================================================================
294// Slot buffers — for Array/Object construction, which `runtime.c` expects
295// as a `CODE_VALUE_SLOT_SIZE`-strided scratch buffer of already-built
296// elements (see `code_array`/`code_object`'s doc comments in `runtime.c`;
297// `tests/native_modules/test_math.c`'s `factors`/`meta` exported vars are
298// the C-side version of the same pattern).
299// ===========================================================================
300
301/// A scratch buffer of `count` [`CodeValue`] slots, zero-initialized (so
302/// each slot starts in the same safe state [`CodeValue::zeroed`] documents).
303/// Build each element in place with [`SlotBuffer::slot_mut`], then hand the
304/// buffer to [`array`] or [`object`] — matching `runtime.c`'s "elements are
305/// retained and copied out of this buffer, never adopted by reference"
306/// contract, after which every slot you wrote must still be [`release`]d
307/// (the copy took its own reference; yours is still live until you drop it).
308pub struct SlotBuffer {
309 buf: Vec<u8>,
310 len: i64,
311}
312
313impl SlotBuffer {
314 pub fn new(count: usize) -> Self {
315 Self { buf: vec![0u8; count * CODE_VALUE_SLOT_SIZE], len: count as i64 }
316 }
317
318 /// Slot `index` — write a value into it with [`number`]/[`owned_str`]/etc.
319 pub fn slot_mut(&mut self, index: i64) -> &mut CodeValue {
320 debug_assert!(index >= 0 && index < self.len);
321 unsafe { &mut *slot_at(self.buf.as_mut_ptr() as *mut c_void, index) }
322 }
323
324 fn as_items_ptr(&mut self) -> *mut c_void {
325 self.buf.as_mut_ptr() as *mut c_void
326 }
327
328 /// Release every slot. Call after handing the buffer to [`array`] or
329 /// [`object`] — they copy elements out, they don't take ownership of
330 /// this buffer's own references.
331 pub fn release_all(&mut self) {
332 for i in 0..self.len {
333 unsafe { code_release(slot_at(self.buf.as_mut_ptr() as *mut c_void, i)) }
334 }
335 }
336}
337
338/// Write an Array into `out`, copying (and retaining) `elems`'s slots.
339/// `elems` still owns its own references afterwards — release it once
340/// you're done (see [`SlotBuffer::release_all`]).
341pub fn array(out: &mut CodeValue, elems: &mut SlotBuffer) {
342 unsafe { code_array(out, elems.as_items_ptr(), elems.len) }
343}
344
345/// Write an Object into `out` from parallel `keys` and `values` (a
346/// [`SlotBuffer`] built the same way [`array`] expects). `keys` must
347/// outlive nothing in particular — `code_object` copies the pointers, and
348/// C-string field names are expected to be `'static` (string literals),
349/// matching `code_abi.h`'s own "key pointers are read-only data" note.
350pub fn object(out: &mut CodeValue, keys: &[&'static CStr], values: &mut SlotBuffer) {
351 debug_assert_eq!(keys.len() as i64, values.len);
352 let mut key_ptrs: Vec<*const c_char> = keys.iter().map(|k| k.as_ptr()).collect();
353 unsafe { code_object(out, key_ptrs.as_mut_ptr(), values.as_items_ptr(), values.len) }
354}
355
356// ===========================================================================
357// Reading helpers — for use inside `code_module_dispatch`.
358// ===========================================================================
359
360/// Read a field by name off an Object value. `None` if `v` isn't an
361/// Object or the field doesn't exist — mirrors `code_field`'s own
362/// permissive-null behavior, but as an `Option` instead of writing Null.
363pub fn find_field<'a>(v: &'a CodeValue, name: &str) -> Option<&'a CodeValue> {
364 if v.tag != CodeTag::Object || v.keys.is_null() {
365 return None;
366 }
367 for i in 0..v.len {
368 let key = unsafe { *v.keys.offset(i as isize) };
369 if key.is_null() {
370 continue;
371 }
372 let key_str = unsafe { CStr::from_ptr(key) };
373 if key_str.to_bytes() == name.as_bytes() {
374 return Some(unsafe { &*slot_at(v.items, i) });
375 }
376 }
377 None
378}
379
380/// Read `v` as a `&str`, if it's a Str with a valid UTF-8 payload.
381pub fn read_str(v: &CodeValue) -> Option<&str> {
382 if v.tag != CodeTag::Str || v.str.is_null() {
383 return None;
384 }
385 unsafe { CStr::from_ptr(v.str) }.to_str().ok()
386}
387
388/// Read `v` as an `f64`, if it's a Number.
389pub fn read_number(v: &CodeValue) -> Option<f64> {
390 (v.tag == CodeTag::Number).then_some(v.number)
391}
392
393/// Read `v` as a `bool`, if it's a Bool.
394pub fn read_bool(v: &CodeValue) -> Option<bool> {
395 (v.tag == CodeTag::Bool).then_some(v.boolean != 0)
396}
397
398/// Convenience: [`find_field`] + [`read_str`].
399pub fn read_field_str<'a>(v: &'a CodeValue, name: &str) -> Option<&'a str> {
400 read_str(find_field(v, name)?)
401}
402
403/// Convenience: [`find_field`] + [`read_number`].
404pub fn read_field_number(v: &CodeValue, name: &str) -> Option<f64> {
405 read_number(find_field(v, name)?)
406}
407
408/// Convenience: [`find_field`] + [`read_bool`].
409pub fn read_field_bool(v: &CodeValue, name: &str) -> Option<bool> {
410 read_bool(find_field(v, name)?)
411}
412
413/// Iterate an Array's elements.
414pub fn array_elems(v: &CodeValue) -> impl Iterator<Item = &CodeValue> {
415 let (items, len) = if v.tag == CodeTag::Array { (v.items, v.len) } else { (std::ptr::null_mut(), 0) };
416 (0..len).map(move |i| unsafe { &*slot_at(items, i) })
417}
418
419/// Build a `{ "_class": <class_name>, "value": <fill's result> }` particle
420/// into `out` — the shape `emit ... to <alias> get x` expects a handler's
421/// result to have. Mirrors `runtime.c`'s own `code_make_result`, which a
422/// C module reaches via `#include "runtime.c"` but isn't exported for a
423/// separately-linked module to call directly, so this is a small
424/// reimplementation rather than an FFI binding.
425pub fn make_result(out: &mut CodeValue, class_name: &'static CStr, fill: impl FnOnce(&mut CodeValue)) {
426 let mut value = CodeValue::zeroed();
427 fill(&mut value);
428 let mut buf = SlotBuffer::new(2);
429 borrowed_str(buf.slot_mut(0), class_name);
430 unsafe { code_copy(buf.slot_mut(1), &value) };
431 object(out, &[c"_class", c"value"], &mut buf);
432 buf.release_all();
433 release(&mut value);
434}