Skip to main content

nifty_rt/
value.rs

1//! Values as compiled code holds them: every value fits in a 64-bit [`Slot`].
2//!
3//! An `int` is its two's-complement bits, a `float` its IEEE bits, a `bool` 0 or 1, and a
4//! string, list or struct a pointer to a reference-counted object. Zero in a slot of one of
5//! those types means "no value yet", and retaining or releasing it does nothing. Lists and
6//! structs share their contents until a copy is changed, so values keep value semantics.
7
8use std::cmp::Ordering;
9use std::fmt::{self, Write as _};
10use std::rc::Rc;
11
12use crate::ffi::Slice;
13
14pub type Slot = u64;
15
16/// What kind of value a [`TypeDesc`] describes; compiled programs store it as a `u8`.
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18#[repr(u8)]
19pub enum TypeKind {
20    Int,
21    Float,
22    Bool,
23    String,
24    /// Its children are the element type.
25    List,
26    /// Its children are the field types, in order.
27    Struct,
28}
29
30/// A type, as a compiled program describes it. Struct types may lead back to themselves
31/// through their fields, so these form a graph of statics.
32#[derive(Debug)]
33#[repr(C)]
34pub struct TypeDesc {
35    kind: TypeKind,
36    children: Slice<&'static TypeDesc>,
37}
38
39pub use crate::symbols::{BOOL, FLOAT, INT, STRING};
40
41impl TypeKind {
42    #[must_use]
43    pub const fn code(self) -> u8 {
44        self as u8
45    }
46}
47
48impl TypeDesc {
49    /// Where the kind and the children sit, for code that emits types as data.
50    pub const KIND_OFFSET: usize = std::mem::offset_of!(Self, kind);
51    pub const CHILDREN_OFFSET: usize = std::mem::offset_of!(Self, children);
52
53    pub(crate) const fn scalar(kind: TypeKind) -> Self {
54        Self {
55            kind,
56            children: Slice::EMPTY,
57        }
58    }
59
60    #[must_use]
61    pub const fn list(element: &'static [&'static Self; 1]) -> Self {
62        Self {
63            kind: TypeKind::List,
64            children: Slice::new(element),
65        }
66    }
67
68    #[must_use]
69    pub const fn structure(fields: &'static [&'static Self]) -> Self {
70        Self {
71            kind: TypeKind::Struct,
72            children: Slice::new(fields),
73        }
74    }
75
76    #[must_use]
77    pub fn kind(&self) -> TypeKind {
78        self.kind
79    }
80
81    /// The element type of a list, or the field types of a struct.
82    #[must_use]
83    pub fn children(&self) -> &'static [&'static Self] {
84        self.children.items()
85    }
86
87    fn element(&self) -> &'static Self {
88        self.children()[0]
89    }
90
91    fn is_object(&self) -> bool {
92        matches!(
93            self.kind,
94            TypeKind::String | TypeKind::List | TypeKind::Struct
95        )
96    }
97}
98
99pub(crate) struct StringObject(pub Box<str>);
100
101pub(crate) struct ListObject {
102    pub element: &'static TypeDesc,
103    pub items: Vec<Slot>,
104}
105
106pub(crate) struct StructObject {
107    pub ty: &'static TypeDesc,
108    pub fields: Box<[Slot]>,
109}
110
111impl Clone for ListObject {
112    fn clone(&self) -> Self {
113        for item in &self.items {
114            retain(*item, self.element);
115        }
116        Self {
117            element: self.element,
118            items: self.items.clone(),
119        }
120    }
121}
122
123impl Drop for ListObject {
124    fn drop(&mut self) {
125        for item in &self.items {
126            release(*item, self.element);
127        }
128    }
129}
130
131impl Clone for StructObject {
132    fn clone(&self) -> Self {
133        for (field, ty) in self.fields.iter().zip(self.ty.children()) {
134            retain(*field, ty);
135        }
136        Self {
137            ty: self.ty,
138            fields: self.fields.clone(),
139        }
140    }
141}
142
143impl Drop for StructObject {
144    fn drop(&mut self) {
145        for (field, ty) in self.fields.iter().zip(self.ty.children()) {
146            release(*field, ty);
147        }
148    }
149}
150
151/// `[string]`, the type of the paths a file dialog answers with and of what is dropped.
152pub(crate) static LIST_OF_STRINGS: TypeDesc = TypeDesc::list(&[&STRING]);
153
154/// A `[string]` holding `texts`, in order.
155pub(crate) fn strings_slot(texts: Vec<String>) -> Slot {
156    list_slot(
157        &LIST_OF_STRINGS,
158        texts.into_iter().map(string_slot).collect(),
159    )
160}
161
162/// A list of type `ty` holding `items`, which it takes ownership of.
163pub(crate) fn list_slot(ty: &TypeDesc, items: Vec<Slot>) -> Slot {
164    into_slot(Rc::new(ListObject {
165        element: ty.element(),
166        items,
167    }))
168}
169
170/// The slot holding an object, which the slot now owns.
171pub(crate) fn into_slot<T>(object: Rc<T>) -> Slot {
172    Rc::into_raw(object).expose_provenance() as Slot
173}
174
175fn pointer<T>(slot: Slot) -> *const T {
176    let address = usize::try_from(slot).expect("Nifty only runs on 64-bit systems");
177    std::ptr::with_exposed_provenance(address)
178}
179
180/// Takes the object a slot owns.
181///
182/// # Safety
183/// `slot` came from [`into_slot`] with the same `T`, and its ownership passes to the result.
184pub(crate) unsafe fn from_slot<T>(slot: Slot) -> Rc<T> {
185    // SAFETY: by the caller's contract, the pointer came from `Rc::into_raw::<T>`.
186    unsafe { Rc::from_raw(pointer(slot)) }
187}
188
189/// The object a slot holds, borrowed for as long as the slot keeps it alive.
190///
191/// # Safety
192/// `slot` came from [`into_slot`] with the same `T`, and is still owned by someone for `'a`.
193pub(crate) unsafe fn borrow<'a, T>(slot: Slot) -> &'a T {
194    // SAFETY: by the caller's contract, the pointer leads to a live `T` inside an `Rc`.
195    unsafe { &*pointer::<T>(slot) }
196}
197
198/// Adds an owner to the value in `slot`.
199pub(crate) fn retain(slot: Slot, ty: &TypeDesc) {
200    if slot == 0 || !ty.is_object() {
201        return;
202    }
203    // SAFETY: a non-zero slot of an object type holds a pointer from `into_slot` with the
204    // object type its kind names; compiled code keeps slots and types together.
205    unsafe {
206        match ty.kind() {
207            TypeKind::String => Rc::increment_strong_count(pointer::<StringObject>(slot)),
208            TypeKind::List => Rc::increment_strong_count(pointer::<ListObject>(slot)),
209            TypeKind::Struct => Rc::increment_strong_count(pointer::<StructObject>(slot)),
210            TypeKind::Int | TypeKind::Float | TypeKind::Bool => {}
211        }
212    }
213}
214
215/// Removes an owner from the value in `slot`, freeing it with its last owner.
216pub(crate) fn release(slot: Slot, ty: &TypeDesc) {
217    if slot == 0 || !ty.is_object() {
218        return;
219    }
220    // SAFETY: as in `retain`; the caller gives up the owner this removes.
221    unsafe {
222        match ty.kind() {
223            TypeKind::String => Rc::decrement_strong_count(pointer::<StringObject>(slot)),
224            TypeKind::List => Rc::decrement_strong_count(pointer::<ListObject>(slot)),
225            TypeKind::Struct => Rc::decrement_strong_count(pointer::<StructObject>(slot)),
226            TypeKind::Int | TypeKind::Float | TypeKind::Bool => {}
227        }
228    }
229}
230
231pub(crate) fn string_slot(text: impl Into<Box<str>>) -> Slot {
232    into_slot(Rc::new(StringObject(text.into())))
233}
234
235/// The text of a string slot that someone owns for `'a`.
236pub(crate) fn as_str<'a>(slot: Slot) -> &'a str {
237    // SAFETY: compiled code passes only string slots here, and they stay owned while used.
238    unsafe { &borrow::<StringObject>(slot).0 }
239}
240
241pub(crate) fn as_list<'a>(slot: Slot) -> &'a ListObject {
242    // SAFETY: compiled code passes only list slots here, and they stay owned while used.
243    unsafe { borrow(slot) }
244}
245
246pub(crate) fn as_struct<'a>(slot: Slot) -> &'a StructObject {
247    // SAFETY: compiled code passes only struct slots here, and they stay owned while used.
248    unsafe { borrow(slot) }
249}
250
251/// Makes the list in `place` its only owner's, copying it if shared, and returns it.
252pub(crate) fn list_mut(place: &mut Slot) -> &mut ListObject {
253    unique::<ListObject>(place)
254}
255
256pub(crate) fn struct_mut(place: &mut Slot) -> &mut StructObject {
257    unique::<StructObject>(place)
258}
259
260fn unique<T: Clone>(place: &mut Slot) -> &mut T {
261    // SAFETY: `place` owns its object, whose type the caller names.
262    let mut object = unsafe { from_slot::<T>(*place) };
263    Rc::make_mut(&mut object);
264    *place = into_slot(object);
265    // SAFETY: the object is now uniquely owned by `place`, which the borrow keeps alive.
266    unsafe { &mut *pointer::<T>(*place).cast_mut() }
267}
268
269pub(crate) fn int(slot: Slot) -> i64 {
270    i64::from_ne_bytes(slot.to_ne_bytes())
271}
272
273pub(crate) fn int_slot(value: i64) -> Slot {
274    Slot::from_ne_bytes(value.to_ne_bytes())
275}
276
277/// Whether two values of type `ty` are equal, as `==` decides.
278pub(crate) fn equal(a: Slot, b: Slot, ty: &TypeDesc) -> bool {
279    match ty.kind() {
280        TypeKind::Int | TypeKind::Bool => a == b,
281        // IEEE equality, as `==` on floats: NaN is unequal to everything, and -0 equals 0.
282        TypeKind::Float => {
283            f64::from_bits(a).partial_cmp(&f64::from_bits(b)) == Some(Ordering::Equal)
284        }
285        TypeKind::String => as_str(a) == as_str(b),
286        TypeKind::List => {
287            let (a, b) = (as_list(a), as_list(b));
288            a.items.len() == b.items.len()
289                && a.items
290                    .iter()
291                    .zip(&b.items)
292                    .all(|(a, b)| equal(*a, *b, ty.element()))
293        }
294        TypeKind::Struct => {
295            let (a, b) = (as_struct(a), as_struct(b));
296            a.fields
297                .iter()
298                .zip(b.fields.iter())
299                .zip(ty.children())
300                .all(|((a, b), ty)| equal(*a, *b, ty))
301        }
302    }
303}
304
305/// Writes a value of type `ty` as it reads inside an interpolated string.
306pub(crate) fn write(text: &mut String, slot: Slot, ty: &TypeDesc) {
307    let written = match ty.kind() {
308        TypeKind::Int => write!(text, "{}", int(slot)),
309        TypeKind::Float => write!(text, "{}", f64::from_bits(slot)),
310        TypeKind::Bool => write!(text, "{}", slot != 0),
311        TypeKind::String => text.write_str(as_str(slot)),
312        TypeKind::List => {
313            let list = as_list(slot);
314            write_items(text, list.items.iter().map(|item| (*item, list.element)));
315            Ok(())
316        }
317        TypeKind::Struct => {
318            let object = as_struct(slot);
319            let fields = object.fields.iter().copied();
320            write_items(text, fields.zip(object.ty.children().iter().copied()));
321            Ok(())
322        }
323    };
324    written.expect("writing to a String cannot fail");
325}
326
327fn write_items(text: &mut String, items: impl Iterator<Item = (Slot, &'static TypeDesc)>) {
328    text.push('[');
329    for (index, (item, ty)) in items.enumerate() {
330        if index > 0 {
331            text.push_str(", ");
332        }
333        write(text, item, ty);
334    }
335    text.push(']');
336}
337
338/// A value the runtime owns, released when dropped.
339pub(crate) struct Value {
340    slot: Slot,
341    ty: &'static TypeDesc,
342}
343
344impl Value {
345    /// Takes ownership of `slot`.
346    pub(crate) fn owned(slot: Slot, ty: &'static TypeDesc) -> Self {
347        Self { slot, ty }
348    }
349
350    pub(crate) fn slot(&self) -> Slot {
351        self.slot
352    }
353}
354
355impl Clone for Value {
356    fn clone(&self) -> Self {
357        retain(self.slot, self.ty);
358        Self {
359            slot: self.slot,
360            ty: self.ty,
361        }
362    }
363}
364
365impl Drop for Value {
366    fn drop(&mut self) {
367        release(self.slot, self.ty);
368    }
369}
370
371impl fmt::Debug for Value {
372    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
373        let mut text = String::new();
374        write(&mut text, self.slot, self.ty);
375        f.debug_tuple("Value").field(&text).finish()
376    }
377}
378
379#[cfg(test)]
380mod tests;