feat: value

This commit is contained in:
2026-03-12 17:47:46 +08:00
parent 0c9a391618
commit 40d00a6c47
2 changed files with 365 additions and 0 deletions

View File

@@ -0,0 +1 @@
mod value;

364
fix/src/runtime/value.rs Normal file
View File

@@ -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<Gc<'gc, ()>>,
}
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::<i32>().unwrap_unchecked()
}),
Some(bool::TAG) => write!(f, "Bool({:?})", unsafe {
self.as_inline::<bool>().unwrap_unchecked()
}),
Some(Null::TAG) => write!(f, "Null"),
Some(SmallStringId::TAG) => {
write!(f, "SmallString({:?})", unsafe {
self.as_inline::<SmallStringId>().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(<Box<[Value<'_>]> 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<T>(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<T>(&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<T: InlineStorable>(val: T) -> Self {
Self::from_raw_value(RawValue::store(Self::mk_tag(false, T::TAG.1), val))
}
#[inline]
pub(crate) fn new_gc<T: GcStorable>(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<T: Storable>(&self) -> bool {
self.tag() == Some(T::TAG)
}
}
impl<'gc> Value<'gc> {
#[inline]
pub(crate) fn as_float(&self) -> Option<f64> {
self.raw.float().copied()
}
#[inline]
pub(crate) fn as_inline<T: InlineStorable>(&self) -> Option<T> {
if self.is::<T>() {
Some(unsafe {
let rv = self.raw.value().unwrap_unchecked();
T::from_val(rv)
})
} else {
None
}
}
#[inline]
pub(crate) fn as_gc<T: GcStorable>(&self) -> Option<Gc<'gc, T>> {
if self.is::<T>() {
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<str>` owned by this struct; the GC only manages
/// the outer allocation.
#[derive(Collect)]
#[collect(require_static)]
pub(crate) struct NixString {
data: Box<str>,
// TODO: string context for derivation dependency tracking
}
impl NixString {
pub(crate) fn new(s: impl Into<Box<str>>) -> 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<AttrSetEntry<'gc>>,
}
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>),
}