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