feat: value
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
mod value;
|
||||||
|
|||||||
364
fix/src/runtime/value.rs
Normal file
364
fix/src/runtime/value.rs
Normal 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>),
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user