From 40d00a6c4792edef4211743556ac03a4300d444d Mon Sep 17 00:00:00 2001 From: imxyy_soope_ Date: Thu, 12 Mar 2026 17:47:46 +0800 Subject: [PATCH] feat: value --- fix/src/runtime.rs | 1 + fix/src/runtime/value.rs | 364 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 365 insertions(+) create mode 100644 fix/src/runtime/value.rs diff --git a/fix/src/runtime.rs b/fix/src/runtime.rs index e69de29..0a15cad 100644 --- a/fix/src/runtime.rs +++ b/fix/src/runtime.rs @@ -0,0 +1 @@ +mod value; diff --git a/fix/src/runtime/value.rs b/fix/src/runtime/value.rs new file mode 100644 index 0000000..3aa4f30 --- /dev/null +++ b/fix/src/runtime/value.rs @@ -0,0 +1,364 @@ +use std::fmt; +use std::marker::PhantomData; + +use boxing::nan::raw::{RawBox, RawStore, RawTag, Value as RawValue}; +use gc_arena::{Collect, Gc}; +use hashbrown::HashTable; + +trait Storable { + const TAG: (bool, u8); +} +trait InlineStorable: Storable + RawStore {} +trait GcStorable: Storable {} + +macro_rules! inline_types { + ($($type:ty => $id:expr),*$(,)?) => { + $( + impl Storable for $type { + const TAG: (bool, u8) = (false, $id); + } + impl InlineStorable for $type {} + )* + }; +} +macro_rules! gc_types { + ($($type:ty => $id:expr),*$(,)?) => { + $( + impl Storable for $type { + const TAG: (bool, u8) = (true, $id); + } + impl GcStorable for $type {} + )* + }; +} + +inline_types! { + i32 => 1, + bool => 2, + Null => 3, + SmallStringId => 4, +} +gc_types! { + i64 => 1, + NixString => 2, + SmallAttrSet<'_> => 3, + AttrSet<'_> => 4, + Box<[Value<'_>]> => 5, +} + +/// # Nix runtime value representation +/// +/// NaN-boxed value fitting in 8 bytes. Morally equivalent to: +/// ```ignore +/// enum Value<'gc> { +/// Float(SingleNaNF64), +/// SmallInt(i32), +/// BigInt(Gc<'gc, i64>), +/// Bool(bool), +/// Null, +/// SmallString(SmallStringId), +/// String(Gc<'gc, NixString>), +/// SmallAttrSet(Gc<'gc, SmallAttrSet<'gc>>), +/// AttrSet(Gc<'gc, AttrSet<'gc>>), +/// List(Gc<'gc, Box<[Value<'gc>]>>), +/// } +/// ``` +#[repr(transparent)] +pub(crate) struct Value<'gc> { + raw: RawBox, + _marker: PhantomData>, +} + +impl Clone for Value<'_> { + #[inline] + fn clone(&self) -> Self { + Self { + raw: self.raw.clone(), + _marker: PhantomData, + } + } +} + +macro_rules! trace_impl { + ($self:expr, $cc:expr, $tag:expr; $($type:ty),*$(,)?) => { + match $tag.neg_val() { + // Positive tags are inline data - skip it. + (false, _) => (), + + // Negative tags are Gc pointers - reconstruct and trace it. + $( + <$type as Storable>::TAG => unsafe { $self.load_gc::<$type>().trace($cc) }, + )* + + (true, val) => unreachable!("invalid negative tag value: {val}"), + } + }; +} + +unsafe impl<'gc> Collect for Value<'gc> { + fn trace(&self, cc: &gc_arena::Collection) { + // No tag - raw float + let Some(tag) = self.raw.tag() else { return }; + trace_impl! { + self, cc, tag; + i64, + NixString, + SmallAttrSet<'gc>, + AttrSet<'gc>, + Box<[Value<'gc>]>, + } + } + + fn needs_trace() -> bool + where + Self: Sized, + { + true + } +} + +macro_rules! debug_impl { + ($self:expr; $($type:ty),*$(,)?) => { + match $self.tag() { + None => write!(f, "Float({:?})", unsafe { + self.raw.float().unwrap_unchecked() + }), + + $( + Some() + )* + } + }; +} + +impl fmt::Debug for Value<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.tag() { + None => write!(f, "Float({:?})", unsafe { + self.raw.float().unwrap_unchecked() + }), + Some(i32::TAG) => write!(f, "SmallInt({:?})", unsafe { + self.as_inline::().unwrap_unchecked() + }), + Some(bool::TAG) => write!(f, "Bool({:?})", unsafe { + self.as_inline::().unwrap_unchecked() + }), + Some(Null::TAG) => write!(f, "Null"), + Some(SmallStringId::TAG) => { + write!(f, "SmallString({:?})", unsafe { + self.as_inline::().unwrap_unchecked().0 + }) + } + Some(i64::TAG) => write!(f, "BigInt(Gc<..>)"), + Some(NixString::TAG) => write!(f, "String(Gc<..>)"), + Some(SmallAttrSet::TAG) => write!(f, "SmallAttrSet(Gc<..>)"), + Some(AttrSet::TAG) => write!(f, "AttrSet(Gc<..>)"), + Some(]> as Storable>::TAG) => write!(f, "List(Gc<..>)"), + Some((neg, val)) => write!(f, "Unknown(neg={neg}, val={val})"), + } + } +} + +impl<'gc> Value<'gc> { + #[inline(always)] + fn mk_tag(neg: bool, val: u8) -> RawTag { + debug_assert!((1..=7).contains(&val)); + // Safety: val is asserted to be in 1..=7. + unsafe { RawTag::new_unchecked(neg, val) } + } + + #[inline(always)] + fn from_raw_value(rv: RawValue) -> Self { + Self { + raw: RawBox::from_value(rv), + _marker: PhantomData, + } + } + + /// Store a GC pointer with the given (negative) tag value. + #[inline(always)] + fn store_gc(tag_val: u8, gc: Gc<'gc, T>) -> Self { + let ptr = Gc::as_ptr(gc); + Self::from_raw_value(RawValue::store(Self::mk_tag(true, tag_val), ptr)) + } + + /// Load a GC pointer from a value with a negative tag. + /// + /// # Safety + /// + /// The value must actually store a `Gc<'gc, T>` with the matching type. + #[inline(always)] + unsafe fn load_gc(&self) -> Gc<'gc, T> { + unsafe { + let rv = self.raw.value().unwrap_unchecked(); + let ptr: *const T = <*const T as RawStore>::from_val(rv); + Gc::from_ptr(ptr) + } + } + + /// Returns the `(negative, val)` tag, or `None` for a float. + #[inline(always)] + fn tag(&self) -> Option<(bool, u8)> { + self.raw.tag().map(|t| t.neg_val()) + } +} + +impl<'gc> Value<'gc> { + #[inline] + pub(crate) fn new_float(val: f64) -> Self { + Self { + raw: RawBox::from_float(val), + _marker: PhantomData, + } + } + + #[inline] + pub(crate) fn new_inline(val: T) -> Self { + Self::from_raw_value(RawValue::store(Self::mk_tag(false, T::TAG.1), val)) + } + + #[inline] + pub(crate) fn new_gc(gc: Gc<'gc, T>) -> Self { + let ptr = Gc::as_ptr(gc); + Self::from_raw_value(RawValue::store(Self::mk_tag(true, T::TAG.1), ptr)) + } +} + +impl<'gc> Value<'gc> { + #[inline] + pub(crate) fn is_float(&self) -> bool { + self.raw.is_float() + } + + #[inline] + pub(crate) fn is(&self) -> bool { + self.tag() == Some(T::TAG) + } +} + +impl<'gc> Value<'gc> { + #[inline] + pub(crate) fn as_float(&self) -> Option { + self.raw.float().copied() + } + + #[inline] + pub(crate) fn as_inline(&self) -> Option { + if self.is::() { + Some(unsafe { + let rv = self.raw.value().unwrap_unchecked(); + T::from_val(rv) + }) + } else { + None + } + } + + #[inline] + pub(crate) fn as_gc(&self) -> Option> { + if self.is::() { + Some(unsafe { + let rv = self.raw.value().unwrap_unchecked(); + let ptr: *const T = <*const T as RawStore>::from_val(rv); + Gc::from_ptr(ptr) + }) + } else { + None + } + } +} + +pub(crate) struct Null; +impl RawStore for Null { + fn to_val(self, value: &mut RawValue) { + value.set_data([0; 6]); + } + fn from_val(_: &RawValue) -> Self { + Self + } +} + +// TODO: size? +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Collect)] +#[collect(require_static)] +pub(crate) struct SmallStringId(u32); +impl RawStore for SmallStringId { + fn to_val(self, value: &mut RawValue) { + self.0.to_val(value); + } + fn from_val(value: &RawValue) -> Self { + Self(u32::from_val(value)) + } +} + +/// Heap-allocated Nix string. +/// +/// Stored on the GC heap via `Gc<'gc, NixString>`. The string data itself +/// lives in a standard `Box` owned by this struct; the GC only manages +/// the outer allocation. +#[derive(Collect)] +#[collect(require_static)] +pub(crate) struct NixString { + data: Box, + // TODO: string context for derivation dependency tracking +} + +impl NixString { + pub(crate) fn new(s: impl Into>) -> Self { + Self { data: s.into() } + } + + pub(crate) fn as_str(&self) -> &str { + &self.data + } +} + +impl fmt::Debug for NixString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(&self.data, f) + } +} + +/// Fixed-size attribute set (up to 8 entries). +#[derive(Collect)] +#[collect(no_drop)] +pub(crate) struct SmallAttrSet<'gc> { + // TODO: proper key storage, length tracking, and lookup + inner: [Value<'gc>; 8], +} + +/// Hash-table-backed attribute set. +pub(crate) struct AttrSet<'gc> { + inner: HashTable>, +} + +unsafe impl<'gc> Collect for AttrSet<'gc> { + fn trace(&self, cc: &gc_arena::Collection) { + for entry in self.inner.iter() { + Collect::trace(&entry.key, cc); + Collect::trace(&entry.value, cc); + } + } + + fn needs_trace() -> bool + where + Self: Sized, + { + true + } +} + +#[derive(Collect)] +#[collect(no_drop)] +struct AttrSetEntry<'gc> { + key: AttrKey<'gc>, + value: Value<'gc>, +} + +#[derive(Collect)] +#[collect(no_drop)] +pub(crate) enum AttrKey<'gc> { + Small(SmallStringId), + Large(Gc<'gc, str>), +}