Skip to main content

cjc_runtime/
lib.rs

1//! CJC Runtime System
2//!
3//! Provides the core runtime infrastructure for the CJC programming language:
4//! - `Buffer<T>`: Deterministic memory allocation with COW (Copy-On-Write) semantics
5//! - `Tensor`: N-dimensional tensor with element-wise ops, matmul, and stable reductions
6//! - `ObjectSlab` / `GcRef`: Deterministic RC-backed object slab (replaces mark-sweep GC)
7//! - `FrameArena` / `ArenaStore`: Bump-arena per function frame for non-escaping values
8//! - `Value`: Tagged union for the CJC interpreter
9//! - `accumulator`: BinnedAccumulator for order-invariant deterministic summation
10//! - `dispatch`: Hybrid summation strategy dispatch (Kahan vs Binned)
11
12// --- Existing standalone modules ---
13pub mod accumulator;
14pub mod complex;
15pub mod dispatch;
16pub mod f16;
17pub mod quantized;
18
19// --- Shared builtin dispatch (used by both cjc-eval and cjc-mir-exec) ---
20pub mod builtins;
21
22// --- Newly extracted modules (from the former monolithic lib.rs) ---
23pub mod buffer;
24pub mod tensor;
25pub mod scratchpad;
26pub mod aligned_pool;
27mod kernel_bridge;
28pub use kernel_bridge::kernel;
29pub mod paged_kv;
30pub mod binned_alloc;
31pub mod frame_arena;
32pub mod object_slab;
33pub mod gc;
34pub mod sparse;
35pub mod sparse_solvers;
36pub mod tensor_tiled;
37pub mod tensor_simd;
38pub mod tensor_pool;
39pub mod det_map;
40pub mod linalg;
41pub mod value;
42pub mod error;
43pub mod lib_registry;
44pub mod json;
45pub mod datetime;
46pub mod window;
47pub mod stats;
48pub mod distributions;
49pub mod hypothesis;
50pub mod ml;
51pub mod fft;
52pub mod stationarity;
53pub mod ode;
54pub mod sparse_eigen;
55pub mod interpolate;
56pub mod optimize;
57pub mod clustering;
58pub mod tensor_dtype;
59pub mod timeseries;
60pub mod integrate;
61pub mod differentiate;
62
63// --- Re-exports for backward compatibility ---
64// All downstream crates that were doing `use cjc_runtime::Tensor` etc. continue to work.
65pub use buffer::Buffer;
66pub use tensor::Tensor;
67pub use scratchpad::Scratchpad;
68pub use aligned_pool::{AlignedPool, AlignedByteSlice};
69pub use paged_kv::{KvBlock, PagedKvCache};
70pub use gc::{GcRef, GcHeap};
71pub use binned_alloc::BinnedAllocator;
72pub use frame_arena::{FrameArena, ArenaStore};
73pub use object_slab::{ObjectSlab, SlabRef};
74pub use sparse::{SparseCsr, SparseCoo};
75pub use tensor_tiled::TiledMatmul;
76pub use det_map::{DetMap, murmurhash3, murmurhash3_finalize, value_hash, values_equal_static};
77pub use value::{Value, Bf16, FnValue};
78pub use error::RuntimeError;
79pub use tensor_dtype::{DType, TypedStorage};
80
81// ---------------------------------------------------------------------------
82// Tests — remain here so they can use `super::*` to access all re-exports.
83// ---------------------------------------------------------------------------
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use std::rc::Rc;
89    use cjc_repro::Rng;
90
91    // -- Buffer tests -------------------------------------------------------
92
93    #[test]
94    fn test_buffer_alloc_get_set() {
95        let mut buf = Buffer::alloc(5, 0.0f64);
96        assert_eq!(buf.len(), 5);
97        assert_eq!(buf.get(0), Some(0.0));
98        assert_eq!(buf.get(4), Some(0.0));
99        assert_eq!(buf.get(5), None);
100
101        buf.set(2, 42.0).unwrap();
102        assert_eq!(buf.get(2), Some(42.0));
103
104        assert!(buf.set(10, 1.0).is_err());
105    }
106
107    #[test]
108    fn test_buffer_from_vec() {
109        let buf = Buffer::from_vec(vec![1, 2, 3, 4, 5]);
110        assert_eq!(buf.len(), 5);
111        assert_eq!(buf.get(0), Some(1));
112        assert_eq!(buf.get(4), Some(5));
113        assert_eq!(buf.as_slice(), vec![1, 2, 3, 4, 5]);
114    }
115
116    #[test]
117    fn test_buffer_cow_behavior() {
118        let buf_a = Buffer::from_vec(vec![10, 20, 30]);
119        let mut buf_b = buf_a.clone();
120
121        assert_eq!(buf_a.refcount(), 2);
122        assert_eq!(buf_b.refcount(), 2);
123
124        buf_b.set(0, 99).unwrap();
125
126        assert_eq!(buf_a.refcount(), 1);
127        assert_eq!(buf_b.refcount(), 1);
128        assert_eq!(buf_a.get(0), Some(10));
129        assert_eq!(buf_b.get(0), Some(99));
130    }
131
132    #[test]
133    fn test_buffer_clone_buffer_forces_deep_copy() {
134        let buf_a = Buffer::from_vec(vec![1, 2, 3]);
135        let buf_b = buf_a.clone_buffer();
136
137        assert_eq!(buf_a.refcount(), 1);
138        assert_eq!(buf_b.refcount(), 1);
139        assert_eq!(buf_a.as_slice(), buf_b.as_slice());
140    }
141
142    // -- Tensor tests -------------------------------------------------------
143
144    #[test]
145    fn test_tensor_creation_and_indexing() {
146        let t = Tensor::zeros(&[2, 3]);
147        assert_eq!(t.shape(), &[2, 3]);
148        assert_eq!(t.ndim(), 2);
149        assert_eq!(t.len(), 6);
150        assert_eq!(t.get(&[0, 0]).unwrap(), 0.0);
151        assert_eq!(t.get(&[1, 2]).unwrap(), 0.0);
152
153        assert!(t.get(&[2, 0]).is_err());
154        assert!(t.get(&[0]).is_err());
155    }
156
157    #[test]
158    fn test_tensor_from_vec_and_set() {
159        let mut t = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[2, 3]).unwrap();
160        assert_eq!(t.get(&[0, 0]).unwrap(), 1.0);
161        assert_eq!(t.get(&[0, 2]).unwrap(), 3.0);
162        assert_eq!(t.get(&[1, 0]).unwrap(), 4.0);
163        assert_eq!(t.get(&[1, 2]).unwrap(), 6.0);
164
165        t.set(&[1, 1], 99.0).unwrap();
166        assert_eq!(t.get(&[1, 1]).unwrap(), 99.0);
167    }
168
169    #[test]
170    fn test_tensor_elementwise_ops() {
171        let a = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]).unwrap();
172        let b = Tensor::from_vec(vec![5.0, 6.0, 7.0, 8.0], &[2, 2]).unwrap();
173
174        let sum = a.add(&b).unwrap();
175        assert_eq!(sum.to_vec(), vec![6.0, 8.0, 10.0, 12.0]);
176
177        let diff = a.sub(&b).unwrap();
178        assert_eq!(diff.to_vec(), vec![-4.0, -4.0, -4.0, -4.0]);
179
180        let prod = a.mul_elem(&b).unwrap();
181        assert_eq!(prod.to_vec(), vec![5.0, 12.0, 21.0, 32.0]);
182
183        let quot = b.div_elem(&a).unwrap();
184        assert_eq!(quot.to_vec(), vec![5.0, 3.0, 7.0 / 3.0, 2.0]);
185    }
186
187    #[test]
188    fn test_tensor_matmul_correctness() {
189        let a = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0], &[2, 2]).unwrap();
190        let b = Tensor::from_vec(vec![5.0, 6.0, 7.0, 8.0], &[2, 2]).unwrap();
191
192        let c = a.matmul(&b).unwrap();
193        assert_eq!(c.shape(), &[2, 2]);
194        assert_eq!(c.get(&[0, 0]).unwrap(), 19.0);
195        assert_eq!(c.get(&[0, 1]).unwrap(), 22.0);
196        assert_eq!(c.get(&[1, 0]).unwrap(), 43.0);
197        assert_eq!(c.get(&[1, 1]).unwrap(), 50.0);
198    }
199
200    #[test]
201    fn test_tensor_matmul_nonsquare() {
202        let a = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[2, 3]).unwrap();
203        let b = Tensor::from_vec(vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0], &[3, 2]).unwrap();
204
205        let c = a.matmul(&b).unwrap();
206        assert_eq!(c.shape(), &[2, 2]);
207        assert_eq!(c.get(&[0, 0]).unwrap(), 58.0);
208        assert_eq!(c.get(&[0, 1]).unwrap(), 64.0);
209        assert_eq!(c.get(&[1, 0]).unwrap(), 139.0);
210        assert_eq!(c.get(&[1, 1]).unwrap(), 154.0);
211    }
212
213    #[test]
214    fn test_tensor_reshape_shares_buffer() {
215        let t = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0], &[2, 3]).unwrap();
216        let r = t.reshape(&[3, 2]).unwrap();
217
218        assert_eq!(r.shape(), &[3, 2]);
219        assert_eq!(r.get(&[0, 0]).unwrap(), 1.0);
220        assert_eq!(r.get(&[2, 1]).unwrap(), 6.0);
221
222        assert_eq!(t.buffer.refcount(), 2);
223
224        assert!(t.reshape(&[4, 2]).is_err());
225    }
226
227    #[test]
228    fn test_tensor_sum_and_mean() {
229        let t = Tensor::from_vec(vec![1.0, 2.0, 3.0, 4.0], &[4]).unwrap();
230        assert!((t.sum() - 10.0).abs() < 1e-12);
231        assert!((t.mean() - 2.5).abs() < 1e-12);
232    }
233
234    // -- GC tests -----------------------------------------------------------
235
236    #[test]
237    fn test_gc_alloc_and_read() {
238        let mut heap = GcHeap::new(100);
239        let r1 = heap.alloc(42i64);
240        let r2 = heap.alloc("hello".to_string());
241
242        assert_eq!(heap.live_count(), 2);
243        assert_eq!(*heap.get::<i64>(r1).unwrap(), 42);
244        assert_eq!(heap.get::<String>(r2).unwrap().as_str(), "hello");
245
246        assert!(heap.get::<f64>(r1).is_none());
247    }
248
249    #[test]
250    fn test_gc_collect_is_noop_rc_backed() {
251        // In the RC-backed ObjectSlab, collect() is a no-op.
252        // All objects survive regardless of roots provided.
253        let mut heap = GcHeap::new(100);
254        let r1 = heap.alloc(1i64);
255        let r2 = heap.alloc(2i64);
256        let r3 = heap.alloc(3i64);
257
258        assert_eq!(heap.live_count(), 3);
259
260        // collect with partial roots — all objects survive (RC, not GC)
261        heap.collect(&[r1, r2]);
262
263        assert_eq!(heap.live_count(), 3, "RC keeps all objects alive");
264        assert_eq!(*heap.get::<i64>(r1).unwrap(), 1);
265        assert_eq!(*heap.get::<i64>(r2).unwrap(), 2);
266        assert_eq!(*heap.get::<i64>(r3).unwrap(), 3);
267    }
268
269    #[test]
270    fn test_gc_explicit_free_and_slot_reuse() {
271        // Slot reuse now requires explicit free() — no automatic GC collection.
272        let mut heap = GcHeap::new(100);
273        let r1 = heap.alloc(1i64);
274        let r2 = heap.alloc(2i64);
275        let r3 = heap.alloc(3i64);
276
277        // Explicitly free all three slots
278        heap.free(r1);
279        heap.free(r2);
280        heap.free(r3);
281        assert_eq!(heap.live_count(), 0);
282        assert_eq!(heap.free_list().len(), 3);
283
284        // New alloc reuses freed slot (LIFO)
285        let r4 = heap.alloc(99i64);
286        assert!(r4.index < 3, "should reuse a freed slot");
287        assert_eq!(*heap.get::<i64>(r4).unwrap(), 99);
288    }
289
290    // -- Stable summation test ----------------------------------------------
291
292    #[test]
293    fn test_stable_summation_via_tensor() {
294        let n = 100_000;
295        let data: Vec<f64> = (0..n).map(|_| 0.00001).collect();
296        let t = Tensor::from_vec(data, &[n]).unwrap();
297        let result = t.sum();
298        let expected = 0.00001 * n as f64;
299        assert!(
300            (result - expected).abs() < 1e-10,
301            "Kahan sum drift: expected {expected}, got {result}"
302        );
303    }
304
305    // -- Tensor randn determinism test --------------------------------------
306
307    #[test]
308    fn test_tensor_randn_deterministic() {
309        let mut rng1 = Rng::seeded(42);
310        let mut rng2 = Rng::seeded(42);
311
312        let t1 = Tensor::randn(&[3, 4], &mut rng1);
313        let t2 = Tensor::randn(&[3, 4], &mut rng2);
314
315        assert_eq!(t1.to_vec(), t2.to_vec());
316    }
317
318    // -- Value display test -------------------------------------------------
319
320    #[test]
321    fn test_value_display() {
322        assert_eq!(format!("{}", Value::Int(42)), "42");
323        assert_eq!(format!("{}", Value::Bool(true)), "true");
324        assert_eq!(format!("{}", Value::Void), "void");
325        assert_eq!(format!("{}", Value::String(Rc::new("hi".into()))), "hi");
326    }
327
328    #[test]
329    fn test_cow_string_clone_shares() {
330        let s = Value::String(Rc::new("hello".into()));
331        let s2 = s.clone();
332        if let (Value::String(a), Value::String(b)) = (&s, &s2) {
333            assert!(Rc::ptr_eq(a, b));
334        } else {
335            panic!("expected String values");
336        }
337    }
338
339    #[test]
340    fn test_cow_string_display() {
341        let s = Value::String(Rc::new("world".into()));
342        assert_eq!(format!("{}", s), "world");
343    }
344
345    // -- ByteSlice / StrView / U8 tests ------------------------------------
346
347    #[test]
348    fn test_byteslice_value_display_utf8() {
349        let bs = Value::ByteSlice(Rc::new(b"hello".to_vec()));
350        assert_eq!(format!("{}", bs), r#"b"hello""#);
351    }
352
353    #[test]
354    fn test_byteslice_value_display_hex() {
355        let bs = Value::ByteSlice(Rc::new(vec![0xff, 0x00, 0x41]));
356        assert_eq!(format!("{}", bs), r#"b"\xff\x00A""#);
357    }
358
359    #[test]
360    fn test_strview_value_display() {
361        let sv = Value::StrView(Rc::new(b"world".to_vec()));
362        assert_eq!(format!("{}", sv), "world");
363    }
364
365    #[test]
366    fn test_u8_value_display() {
367        assert_eq!(format!("{}", Value::U8(65)), "65");
368    }
369
370    #[test]
371    fn test_byteslice_hash_deterministic() {
372        let a = Value::ByteSlice(Rc::new(b"hello".to_vec()));
373        let b = Value::ByteSlice(Rc::new(b"hello".to_vec()));
374        assert_eq!(value_hash(&a), value_hash(&b));
375    }
376
377    #[test]
378    fn test_byteslice_hash_different_content() {
379        let a = Value::ByteSlice(Rc::new(b"hello".to_vec()));
380        let b = Value::ByteSlice(Rc::new(b"world".to_vec()));
381        assert_ne!(value_hash(&a), value_hash(&b));
382    }
383
384    #[test]
385    fn test_byteslice_equality() {
386        let a = Value::ByteSlice(Rc::new(b"abc".to_vec()));
387        let b = Value::ByteSlice(Rc::new(b"abc".to_vec()));
388        let c = Value::ByteSlice(Rc::new(b"def".to_vec()));
389        assert!(values_equal_static(&a, &b));
390        assert!(!values_equal_static(&a, &c));
391    }
392
393    #[test]
394    fn test_strview_equality() {
395        let a = Value::StrView(Rc::new(b"test".to_vec()));
396        let b = Value::StrView(Rc::new(b"test".to_vec()));
397        assert!(values_equal_static(&a, &b));
398    }
399
400    #[test]
401    fn test_u8_hash_and_equality() {
402        let a = Value::U8(42);
403        let b = Value::U8(42);
404        let c = Value::U8(99);
405        assert_eq!(value_hash(&a), value_hash(&b));
406        assert_ne!(value_hash(&a), value_hash(&c));
407        assert!(values_equal_static(&a, &b));
408        assert!(!values_equal_static(&a, &c));
409    }
410
411    #[test]
412    fn test_byteslice_clone_shares_rc() {
413        let bs = Value::ByteSlice(Rc::new(b"data".to_vec()));
414        let bs2 = bs.clone();
415        if let (Value::ByteSlice(a), Value::ByteSlice(b)) = (&bs, &bs2) {
416            assert!(Rc::ptr_eq(a, b));
417        } else {
418            panic!("expected ByteSlice values");
419        }
420    }
421
422    #[test]
423    fn test_byteslice_in_detmap() {
424        let mut map = DetMap::new();
425        let key = Value::ByteSlice(Rc::new(b"token".to_vec()));
426        map.insert(key.clone(), Value::Int(1));
427
428        let lookup = Value::ByteSlice(Rc::new(b"token".to_vec()));
429        assert!(map.contains_key(&lookup));
430        match map.get(&lookup) {
431            Some(Value::Int(1)) => {},
432            _ => panic!("expected Int(1)"),
433        }
434    }
435
436    #[test]
437    fn test_murmurhash3_byteslice_stability() {
438        let h1 = murmurhash3(b"hello");
439        let h2 = murmurhash3(b"hello");
440        assert_eq!(h1, h2);
441
442        let h3 = murmurhash3(b"");
443        let h4 = murmurhash3(b"");
444        assert_eq!(h3, h4);
445
446        assert_ne!(murmurhash3(b"hello"), murmurhash3(b"world"));
447    }
448}