use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use std::sync::LazyLock;
use catallaxy_gc::{GcVTable, Header};
use rustc_hash::FxHashMap;
use crate::object;
use crate::symbol::Sym;
use crate::value::Value;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ObjKind {
Immediate,
Instance,
Str,
BigInt,
List,
Dict,
Set,
Tuple,
Closure,
Cell,
StringBuilder,
Mutex,
Atomic,
Channel,
Thread,
Native,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Capability {
Owned,
Frozen,
Shared,
}
#[repr(C)]
pub struct Class {
pub gc: GcVTable,
pub kind: ObjKind,
pub id: u32,
pub name: Box<str>,
pub capability: Capability,
pub superclass: Option<&'static Class>,
pub field_names: Vec<Sym>,
methods: AtomicPtr<FxHashMap<Sym, u32>>,
pub vtable: Vec<u32>,
pub variant_tag: Option<u32>,
pub enum_id: Option<u32>,
}
impl Class {
#[inline(always)]
pub fn nfields(&self) -> usize {
self.field_names.len()
}
pub fn lookup(&self, name: Sym) -> Option<u32> {
self.methods().get(&name).copied()
}
pub fn methods(&self) -> &FxHashMap<Sym, u32> {
unsafe { &*self.methods.load(Ordering::Acquire) }
}
pub fn is_subclass_of(&self, other: &Class) -> bool {
let mut c = Some(self);
while let Some(k) = c {
if k.id == other.id {
return true;
}
c = k.superclass;
}
false
}
pub fn field_index(&self, name: Sym) -> Option<usize> {
self.field_names.iter().position(|&f| f == name)
}
}
const MAX_CLASSES: usize = 1 << 16;
struct Registry {
slots: Box<[AtomicPtr<Class>]>,
len: AtomicUsize,
lock: parking_lot::Mutex<()>,
}
static REGISTRY: LazyLock<Registry> = LazyLock::new(|| Registry {
slots: (0..MAX_CLASSES).map(|_| AtomicPtr::new(std::ptr::null_mut())).collect(),
len: AtomicUsize::new(0),
lock: parking_lot::Mutex::new(()),
});
pub struct ClassSpec {
pub kind: ObjKind,
pub name: String,
pub capability: Capability,
pub superclass: Option<&'static Class>,
pub field_names: Vec<Sym>,
pub methods: FxHashMap<Sym, u32>,
pub vtable: Vec<u32>,
pub variant_tag: Option<u32>,
pub enum_id: Option<u32>,
}
impl ClassSpec {
pub fn new(kind: ObjKind, name: &str) -> Self {
ClassSpec {
kind,
name: name.to_string(),
capability: Capability::Owned,
superclass: None,
field_names: Vec::new(),
methods: FxHashMap::default(),
vtable: Vec::new(),
variant_tag: None,
enum_id: None,
}
}
}
pub fn register_class(spec: ClassSpec) -> &'static Class {
let _g = REGISTRY.lock.lock();
let id = REGISTRY.len.load(Ordering::Relaxed);
assert!(id < MAX_CLASSES, "class limit exceeded");
let cls: &'static Class = Box::leak(Box::new(Class {
gc: object::vtable_for(spec.kind),
kind: spec.kind,
id: id as u32,
name: spec.name.into_boxed_str(),
capability: spec.capability,
superclass: spec.superclass,
field_names: spec.field_names,
methods: AtomicPtr::new(Box::into_raw(Box::new(spec.methods))),
vtable: spec.vtable,
variant_tag: spec.variant_tag,
enum_id: spec.enum_id,
}));
REGISTRY.slots[id].store(cls as *const Class as *mut Class, Ordering::Release);
REGISTRY.len.store(id + 1, Ordering::Release);
cls
}
pub fn set_methods(cls: &'static Class, methods: FxHashMap<Sym, u32>) {
cls.methods.store(Box::into_raw(Box::new(methods)), Ordering::Release);
}
pub fn class_by_id(id: u32) -> &'static Class {
let p = REGISTRY.slots[id as usize].load(Ordering::Acquire);
assert!(!p.is_null(), "unknown class id {id}");
unsafe { &*p }
}
pub fn class_count() -> usize {
REGISTRY.len.load(Ordering::Acquire)
}
pub mod ids {
pub const INT: u32 = 0;
pub const FLOAT: u32 = 1;
pub const BOOL: u32 = 2;
pub const NIL: u32 = 3;
pub const STRING: u32 = 4;
pub const BIGINT: u32 = 5;
pub const LIST: u32 = 6;
pub const DICT: u32 = 7;
pub const SET: u32 = 8;
pub const TUPLE: u32 = 9;
pub const CLOSURE: u32 = 10;
pub const CELL: u32 = 11;
pub const STRING_BUILDER: u32 = 12;
pub const MUTEX: u32 = 13;
pub const ATOMIC: u32 = 14;
pub const CHANNEL: u32 = 15;
pub const THREAD: u32 = 16;
pub const RANGE: u32 = 17;
pub const NATIVE: u32 = 18;
pub const FIRST_USER: u32 = 19;
}
pub struct Builtins {
pub int: &'static Class,
pub float: &'static Class,
pub bool: &'static Class,
pub nil: &'static Class,
pub string: &'static Class,
pub bigint: &'static Class,
pub list: &'static Class,
pub dict: &'static Class,
pub set: &'static Class,
pub tuple: &'static Class,
pub closure: &'static Class,
pub cell: &'static Class,
pub string_builder: &'static Class,
pub mutex: &'static Class,
pub atomic: &'static Class,
pub channel: &'static Class,
pub thread: &'static Class,
pub range: &'static Class,
pub native: &'static Class,
}
pub static BUILTINS: LazyLock<Builtins> = LazyLock::new(|| {
let mk = |kind: ObjKind, name: &str, cap: Capability, fields: &[&str], expect: u32| {
let mut spec = ClassSpec::new(kind, name);
spec.capability = cap;
spec.field_names = fields.iter().map(|f| crate::symbol::intern(f)).collect();
let c = register_class(spec);
assert_eq!(c.id, expect, "builtin class {name} registered out of order");
c
};
use Capability::*;
use ObjKind::*;
Builtins {
int: mk(Immediate, "Int", Frozen, &[], ids::INT),
float: mk(Immediate, "Float", Frozen, &[], ids::FLOAT),
bool: mk(Immediate, "Bool", Frozen, &[], ids::BOOL),
nil: mk(Immediate, "Nil", Frozen, &[], ids::NIL),
string: mk(Str, "String", Frozen, &[], ids::STRING),
bigint: mk(BigInt, "Int", Frozen, &[], ids::BIGINT),
list: mk(List, "List", Owned, &[], ids::LIST),
dict: mk(Dict, "Dict", Owned, &[], ids::DICT),
set: mk(Set, "Set", Owned, &[], ids::SET),
tuple: mk(Tuple, "Tuple", Owned, &[], ids::TUPLE),
closure: mk(Closure, "Fn", Owned, &[], ids::CLOSURE),
cell: mk(Cell, "Cell", Owned, &[], ids::CELL),
string_builder: mk(StringBuilder, "StringBuilder", Owned, &[], ids::STRING_BUILDER),
mutex: mk(Mutex, "Mutex", Shared, &[], ids::MUTEX),
atomic: mk(Atomic, "Atomic", Shared, &[], ids::ATOMIC),
channel: mk(Channel, "Channel", Shared, &[], ids::CHANNEL),
thread: mk(Thread, "Thread", Shared, &[], ids::THREAD),
range: mk(Instance, "Range", Frozen, &["first", "last", "exclusive"], ids::RANGE),
native: mk(Native, "NativeHandle", Owned, &[], ids::NATIVE),
}
});
#[inline]
pub fn class_of(v: Value) -> &'static Class {
if v.is_ptr() {
unsafe { heap_class(v.as_ptr()) }
} else {
immediate_class(v)
}
}
#[inline]
pub fn immediate_class(v: Value) -> &'static Class {
let b = &*BUILTINS;
if v.is_small_int() {
b.int
} else if v.is_float() {
b.float
} else if v.is_bool() {
b.bool
} else {
b.nil
}
}
#[inline(always)]
pub unsafe fn heap_class(h: *const Header) -> &'static Class {
&*((*h).vtable() as *const Class)
}
#[inline]
pub fn class_id_of(v: Value) -> u32 {
if v.is_ptr() {
let c = unsafe { heap_class(v.as_ptr()) };
if c.id == ids::BIGINT {
ids::INT
} else {
c.id
}
} else if v.is_small_int() {
ids::INT
} else if v.is_float() {
ids::FLOAT
} else if v.is_bool() {
ids::BOOL
} else {
ids::NIL
}
}