1use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
8use std::sync::LazyLock;
9
10use catallaxy_gc::{GcVTable, Header};
11use rustc_hash::FxHashMap;
12
13use crate::object;
14use crate::symbol::Sym;
15use crate::value::Value;
16
17#[derive(Copy, Clone, Debug, PartialEq, Eq)]
19pub enum ObjKind {
20 Immediate,
22 Instance,
24 Str,
25 BigInt,
26 List,
27 Dict,
28 Set,
29 Tuple,
30 Closure,
31 Cell,
32 StringBuilder,
33 Mutex,
34 Atomic,
35 Channel,
36 Thread,
37 Native,
39}
40
41#[derive(Copy, Clone, Debug, PartialEq, Eq)]
43pub enum Capability {
44 Owned,
45 Frozen,
46 Shared,
47}
48
49#[repr(C)]
50pub struct Class {
51 pub gc: GcVTable,
52 pub kind: ObjKind,
53 pub id: u32,
54 pub instance_fields: u32,
57 pub name: Box<str>,
58 pub capability: Capability,
59 pub superclass: Option<&'static Class>,
60 pub field_names: Vec<Sym>,
61 methods: AtomicPtr<FxHashMap<Sym, u32>>,
64 pub vtable: Vec<u32>,
66 pub variant_tag: Option<u32>,
68 pub enum_id: Option<u32>,
70}
71
72impl Class {
73 #[inline(always)]
74 pub fn nfields(&self) -> usize {
75 self.field_names.len()
76 }
77
78 pub fn lookup(&self, name: Sym) -> Option<u32> {
79 self.methods().get(&name).copied()
80 }
81
82 pub fn methods(&self) -> &FxHashMap<Sym, u32> {
83 unsafe { &*self.methods.load(Ordering::Acquire) }
85 }
86
87 pub fn is_subclass_of(&self, other: &Class) -> bool {
89 let mut c = Some(self);
90 while let Some(k) = c {
91 if k.id == other.id {
92 return true;
93 }
94 c = k.superclass;
95 }
96 false
97 }
98
99 pub fn field_index(&self, name: Sym) -> Option<usize> {
100 self.field_names.iter().position(|&f| f == name)
101 }
102}
103
104const MAX_CLASSES: usize = 1 << 16;
108
109struct Registry {
110 slots: Box<[AtomicPtr<Class>]>,
111 len: AtomicUsize,
112 lock: parking_lot::Mutex<()>,
113}
114
115static REGISTRY: LazyLock<Registry> = LazyLock::new(|| Registry {
116 slots: (0..MAX_CLASSES).map(|_| AtomicPtr::new(std::ptr::null_mut())).collect(),
117 len: AtomicUsize::new(0),
118 lock: parking_lot::Mutex::new(()),
119});
120
121pub struct ClassSpec {
123 pub kind: ObjKind,
124 pub name: String,
125 pub capability: Capability,
126 pub superclass: Option<&'static Class>,
127 pub field_names: Vec<Sym>,
128 pub methods: FxHashMap<Sym, u32>,
129 pub vtable: Vec<u32>,
130 pub variant_tag: Option<u32>,
131 pub enum_id: Option<u32>,
132}
133
134impl ClassSpec {
135 pub fn new(kind: ObjKind, name: &str) -> Self {
136 ClassSpec {
137 kind,
138 name: name.to_string(),
139 capability: Capability::Owned,
140 superclass: None,
141 field_names: Vec::new(),
142 methods: FxHashMap::default(),
143 vtable: Vec::new(),
144 variant_tag: None,
145 enum_id: None,
146 }
147 }
148}
149
150pub fn register_class(spec: ClassSpec) -> &'static Class {
152 let _g = REGISTRY.lock.lock();
153 let id = REGISTRY.len.load(Ordering::Relaxed);
154 assert!(id < MAX_CLASSES, "class limit exceeded");
155 let cls: &'static Class = Box::leak(Box::new(Class {
156 gc: object::vtable_for(spec.kind),
157 kind: spec.kind,
158 id: id as u32,
159 instance_fields: if spec.kind == ObjKind::Instance { spec.field_names.len() as u32 } else { 0 },
160 name: spec.name.into_boxed_str(),
161 capability: spec.capability,
162 superclass: spec.superclass,
163 field_names: spec.field_names,
164 methods: AtomicPtr::new(Box::into_raw(Box::new(spec.methods))),
165 vtable: spec.vtable,
166 variant_tag: spec.variant_tag,
167 enum_id: spec.enum_id,
168 }));
169 REGISTRY.slots[id].store(cls as *const Class as *mut Class, Ordering::Release);
170 REGISTRY.len.store(id + 1, Ordering::Release);
171 cls
172}
173
174pub fn set_methods(cls: &'static Class, methods: FxHashMap<Sym, u32>) {
177 cls.methods.store(Box::into_raw(Box::new(methods)), Ordering::Release);
178}
179
180pub fn class_by_id(id: u32) -> &'static Class {
181 let p = REGISTRY.slots[id as usize].load(Ordering::Acquire);
182 assert!(!p.is_null(), "unknown class id {id}");
183 unsafe { &*p }
184}
185
186pub fn class_count() -> usize {
187 REGISTRY.len.load(Ordering::Acquire)
188}
189
190pub mod ids {
194 pub const INT: u32 = 0;
195 pub const FLOAT: u32 = 1;
196 pub const BOOL: u32 = 2;
197 pub const NIL: u32 = 3;
198 pub const STRING: u32 = 4;
199 pub const BIGINT: u32 = 5;
200 pub const LIST: u32 = 6;
201 pub const DICT: u32 = 7;
202 pub const SET: u32 = 8;
203 pub const TUPLE: u32 = 9;
204 pub const CLOSURE: u32 = 10;
205 pub const CELL: u32 = 11;
206 pub const STRING_BUILDER: u32 = 12;
207 pub const MUTEX: u32 = 13;
208 pub const ATOMIC: u32 = 14;
209 pub const CHANNEL: u32 = 15;
210 pub const THREAD: u32 = 16;
211 pub const RANGE: u32 = 17;
212 pub const NATIVE: u32 = 18;
213 pub const FIRST_USER: u32 = 19;
214}
215
216pub struct Builtins {
217 pub int: &'static Class,
218 pub float: &'static Class,
219 pub bool: &'static Class,
220 pub nil: &'static Class,
221 pub string: &'static Class,
222 pub bigint: &'static Class,
223 pub list: &'static Class,
224 pub dict: &'static Class,
225 pub set: &'static Class,
226 pub tuple: &'static Class,
227 pub closure: &'static Class,
228 pub cell: &'static Class,
229 pub string_builder: &'static Class,
230 pub mutex: &'static Class,
231 pub atomic: &'static Class,
232 pub channel: &'static Class,
233 pub thread: &'static Class,
234 pub range: &'static Class,
235 pub native: &'static Class,
236}
237
238pub static BUILTINS: LazyLock<Builtins> = LazyLock::new(|| {
239 let mk = |kind: ObjKind, name: &str, cap: Capability, fields: &[&str], expect: u32| {
240 let mut spec = ClassSpec::new(kind, name);
241 spec.capability = cap;
242 spec.field_names = fields.iter().map(|f| crate::symbol::intern(f)).collect();
243 let c = register_class(spec);
244 assert_eq!(c.id, expect, "builtin class {name} registered out of order");
245 c
246 };
247 use Capability::*;
248 use ObjKind::*;
249 Builtins {
250 int: mk(Immediate, "Int", Frozen, &[], ids::INT),
251 float: mk(Immediate, "Float", Frozen, &[], ids::FLOAT),
252 bool: mk(Immediate, "Bool", Frozen, &[], ids::BOOL),
253 nil: mk(Immediate, "Nil", Frozen, &[], ids::NIL),
254 string: mk(Str, "String", Frozen, &[], ids::STRING),
255 bigint: mk(BigInt, "Int", Frozen, &[], ids::BIGINT),
256 list: mk(List, "List", Owned, &[], ids::LIST),
257 dict: mk(Dict, "Dict", Owned, &[], ids::DICT),
258 set: mk(Set, "Set", Owned, &[], ids::SET),
259 tuple: mk(Tuple, "Tuple", Owned, &[], ids::TUPLE),
260 closure: mk(Closure, "Fn", Owned, &[], ids::CLOSURE),
261 cell: mk(Cell, "Cell", Owned, &[], ids::CELL),
262 string_builder: mk(StringBuilder, "StringBuilder", Owned, &[], ids::STRING_BUILDER),
263 mutex: mk(Mutex, "Mutex", Shared, &[], ids::MUTEX),
264 atomic: mk(Atomic, "Atomic", Shared, &[], ids::ATOMIC),
265 channel: mk(Channel, "Channel", Shared, &[], ids::CHANNEL),
266 thread: mk(Thread, "Thread", Shared, &[], ids::THREAD),
267 range: mk(Instance, "Range", Frozen, &["first", "last", "exclusive"], ids::RANGE),
268 native: mk(Native, "NativeHandle", Owned, &[], ids::NATIVE),
269 }
270});
271
272#[inline]
274pub fn class_of(v: Value) -> &'static Class {
275 if v.is_ptr() {
276 unsafe { heap_class(v.as_ptr()) }
277 } else {
278 immediate_class(v)
279 }
280}
281
282#[inline]
283pub fn immediate_class(v: Value) -> &'static Class {
284 let b = &*BUILTINS;
285 if v.is_small_int() {
286 b.int
287 } else if v.is_float() {
288 b.float
289 } else if v.is_bool() {
290 b.bool
291 } else {
292 b.nil
293 }
294}
295
296#[inline(always)]
301pub unsafe fn heap_class(h: *const Header) -> &'static Class {
302 &*((*h).vtable() as *const Class)
303}
304
305#[inline]
307pub fn class_id_of(v: Value) -> u32 {
308 if v.is_ptr() {
309 let c = unsafe { heap_class(v.as_ptr()) };
310 if c.id == ids::BIGINT {
311 ids::INT
312 } else {
313 c.id
314 }
315 } else if v.is_small_int() {
316 ids::INT
317 } else if v.is_float() {
318 ids::FLOAT
319 } else if v.is_bool() {
320 ids::BOOL
321 } else {
322 ids::NIL
323 }
324}