use crate::interpreter::RuntimeValue;
pub struct Narrow(u64);
const QNAN: u64 = 0xFFF8_0000_0000_0000;
const CANONICAL_NAN: u64 = 0xFFFF_FFFF_FFFF_FFFF;
const TAG_SHIFT: u64 = 48;
const TAG_MASK: u64 = 0x3;
const PAYLOAD_MASK: u64 = (1 << 48) - 1;
const TAG_INT: u64 = 0;
const TAG_BOOL: u64 = 1;
const TAG_NOTHING: u64 = 2;
const TAG_HEAP: u64 = 3;
const INT_INLINE_MIN: i64 = -(1 << 47);
const INT_INLINE_MAX: i64 = (1 << 47) - 1;
#[inline]
fn is_tagged(bits: u64) -> bool {
bits >= QNAN && bits != CANONICAL_NAN
}
#[inline]
fn tag_of(bits: u64) -> u64 {
(bits >> TAG_SHIFT) & TAG_MASK
}
impl Narrow {
#[inline]
pub fn int(n: i64) -> Self {
if (INT_INLINE_MIN..=INT_INLINE_MAX).contains(&n) {
let payload = (n as u64) & PAYLOAD_MASK;
Narrow(QNAN | (TAG_INT << TAG_SHIFT) | payload)
} else {
Narrow::heap(RuntimeValue::Int(n))
}
}
#[inline]
pub fn float(f: f64) -> Self {
let bits = f.to_bits();
if f.is_nan() {
Narrow(CANONICAL_NAN)
} else {
Narrow(bits)
}
}
#[inline]
pub fn bool(b: bool) -> Self {
Narrow(QNAN | (TAG_BOOL << TAG_SHIFT) | (b as u64))
}
#[inline]
pub fn nothing() -> Self {
Narrow(QNAN | (TAG_NOTHING << TAG_SHIFT))
}
#[inline]
fn heap(rv: RuntimeValue) -> Self {
let ptr = Box::into_raw(Box::new(rv)) as u64;
debug_assert_eq!(
ptr & !PAYLOAD_MASK,
0,
"heap pointer exceeds 48 bits — NaN-boxing pointer assumption violated"
);
Narrow(QNAN | (TAG_HEAP << TAG_SHIFT) | (ptr & PAYLOAD_MASK))
}
#[inline]
pub fn as_int(&self) -> Option<i64> {
if !is_tagged(self.0) {
return None;
}
match tag_of(self.0) {
TAG_INT => {
let payload = (self.0 & PAYLOAD_MASK) as i64;
Some((payload << 16) >> 16)
}
TAG_HEAP => match self.heap_ref() {
RuntimeValue::Int(n) => Some(*n),
_ => None,
},
_ => None,
}
}
#[inline]
pub fn as_float(&self) -> Option<f64> {
if is_tagged(self.0) {
None
} else {
Some(f64::from_bits(self.0))
}
}
#[inline]
pub fn as_bool(&self) -> Option<bool> {
if is_tagged(self.0) && tag_of(self.0) == TAG_BOOL {
Some((self.0 & PAYLOAD_MASK) != 0)
} else {
None
}
}
#[inline]
pub fn is_nothing(&self) -> bool {
is_tagged(self.0) && tag_of(self.0) == TAG_NOTHING
}
#[inline]
pub fn is_heap(&self) -> bool {
is_tagged(self.0) && tag_of(self.0) == TAG_HEAP
}
#[inline]
#[allow(dead_code)]
pub fn is_inline_int(&self) -> bool {
is_tagged(self.0) && tag_of(self.0) == TAG_INT
}
#[inline]
fn heap_ref(&self) -> &RuntimeValue {
let ptr = (self.0 & PAYLOAD_MASK) as *const RuntimeValue;
unsafe { &*ptr }
}
#[inline]
pub fn as_heap(&self) -> Option<&RuntimeValue> {
if self.is_heap() {
Some(self.heap_ref())
} else {
None
}
}
#[inline]
pub fn make_heap_mut(&mut self) -> &mut RuntimeValue {
if !self.is_heap() {
let rv = self.to_runtime();
*self = Narrow::heap(rv);
}
let ptr = (self.0 & PAYLOAD_MASK) as *mut RuntimeValue;
unsafe { &mut *ptr }
}
#[inline]
pub fn from_runtime(rv: RuntimeValue) -> Self {
match rv {
RuntimeValue::Int(n) => Narrow::int(n),
RuntimeValue::Float(f) => Narrow::float(f),
RuntimeValue::Bool(b) => Narrow::bool(b),
RuntimeValue::Nothing => Narrow::nothing(),
other => Narrow::heap(other),
}
}
#[inline]
pub fn into_runtime(self) -> RuntimeValue {
let bits = self.0;
if !is_tagged(bits) {
std::mem::forget(self);
return RuntimeValue::Float(f64::from_bits(bits));
}
match tag_of(bits) {
TAG_INT => {
std::mem::forget(self);
let payload = (bits & PAYLOAD_MASK) as i64;
RuntimeValue::Int((payload << 16) >> 16)
}
TAG_BOOL => {
std::mem::forget(self);
RuntimeValue::Bool((bits & PAYLOAD_MASK) != 0)
}
TAG_NOTHING => {
std::mem::forget(self);
RuntimeValue::Nothing
}
TAG_HEAP => {
let ptr = (bits & PAYLOAD_MASK) as *mut RuntimeValue;
std::mem::forget(self);
*unsafe { Box::from_raw(ptr) }
}
_ => unreachable!("invalid tag"),
}
}
#[inline]
pub fn to_runtime(&self) -> RuntimeValue {
if !is_tagged(self.0) {
return RuntimeValue::Float(f64::from_bits(self.0));
}
match tag_of(self.0) {
TAG_INT => {
let payload = (self.0 & PAYLOAD_MASK) as i64;
RuntimeValue::Int((payload << 16) >> 16)
}
TAG_BOOL => RuntimeValue::Bool((self.0 & PAYLOAD_MASK) != 0),
TAG_NOTHING => RuntimeValue::Nothing,
TAG_HEAP => self.heap_ref().clone(),
_ => unreachable!("invalid tag"),
}
}
#[inline]
pub fn int_add(&self, rhs: &Narrow) -> Option<Narrow> {
match (self.as_inline_int(), rhs.as_inline_int()) {
(Some(a), Some(b)) => Some(match a.checked_add(b) {
Some(s) => Narrow::int(s),
None => Narrow::from_runtime(
crate::semantics::arith::add(RuntimeValue::Int(a), RuntimeValue::Int(b)).unwrap(),
),
}),
_ => None,
}
}
#[inline]
pub fn int_sub(&self, rhs: &Narrow) -> Option<Narrow> {
match (self.as_inline_int(), rhs.as_inline_int()) {
(Some(a), Some(b)) => Some(match a.checked_sub(b) {
Some(s) => Narrow::int(s),
None => Narrow::from_runtime(
crate::semantics::arith::subtract(RuntimeValue::Int(a), RuntimeValue::Int(b)).unwrap(),
),
}),
_ => None,
}
}
#[inline]
pub fn int_mul(&self, rhs: &Narrow) -> Option<Narrow> {
match (self.as_inline_int(), rhs.as_inline_int()) {
(Some(a), Some(b)) => Some(match a.checked_mul(b) {
Some(p) => Narrow::int(p),
None => Narrow::from_runtime(
crate::semantics::arith::multiply(RuntimeValue::Int(a), RuntimeValue::Int(b)).unwrap(),
),
}),
_ => None,
}
}
#[inline]
pub fn int_lt(&self, rhs: &Narrow) -> Option<bool> {
match (self.as_inline_int(), rhs.as_inline_int()) {
(Some(a), Some(b)) => Some(a < b),
_ => None,
}
}
#[inline]
pub fn int_eq(&self, rhs: &Narrow) -> Option<bool> {
match (self.as_inline_int(), rhs.as_inline_int()) {
(Some(a), Some(b)) => Some(a == b),
_ => None,
}
}
#[inline]
pub fn as_inline_int(&self) -> Option<i64> {
if is_tagged(self.0) && tag_of(self.0) == TAG_INT {
let payload = (self.0 & PAYLOAD_MASK) as i64;
Some((payload << 16) >> 16)
} else {
None
}
}
}
impl Drop for Narrow {
#[inline]
fn drop(&mut self) {
if self.is_heap() {
let ptr = (self.0 & PAYLOAD_MASK) as *mut RuntimeValue;
unsafe { drop(Box::from_raw(ptr)) }
}
}
}
impl Clone for Narrow {
#[inline]
fn clone(&self) -> Self {
if self.is_heap() {
Narrow::heap(self.heap_ref().clone())
} else {
Narrow(self.0)
}
}
}
impl std::fmt::Debug for Narrow {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(n) = self.as_int() {
write!(f, "Narrow::Int({n})")
} else if let Some(x) = self.as_float() {
write!(f, "Narrow::Float({x})")
} else if let Some(b) = self.as_bool() {
write!(f, "Narrow::Bool({b})")
} else if self.is_nothing() {
write!(f, "Narrow::Nothing")
} else {
write!(f, "Narrow::Heap({:?})", self.heap_ref())
}
}
}
#[cfg(test)]
mod nanbox_tests {
use super::*;
use crate::interpreter::{ListRepr, MapStorage};
use std::cell::RefCell;
use std::rc::Rc;
const INT_EDGES: [i64; 16] = [
i64::MIN,
i64::MIN + 1,
-4611686018427387904, INT_INLINE_MIN - 1, INT_INLINE_MIN, -1000003,
-2,
-1,
0,
1,
2,
1000003,
INT_INLINE_MAX, INT_INLINE_MAX + 1, 4611686018427387903, i64::MAX,
];
fn inline_edges() -> Vec<i64> {
INT_EDGES
.iter()
.copied()
.filter(|&n| (INT_INLINE_MIN..=INT_INLINE_MAX).contains(&n))
.collect()
}
fn float_edges() -> Vec<f64> {
vec![
f64::NAN,
-f64::NAN,
f64::INFINITY,
f64::NEG_INFINITY,
0.0,
-0.0,
f64::MIN_POSITIVE, f64::MIN_POSITIVE / 2.0, f64::MAX,
f64::MIN,
1.0,
-1.0,
0.1,
std::f64::consts::PI,
-2.5,
]
}
fn assert_runtime_eq(a: &RuntimeValue, b: &RuntimeValue, ctx: &str) {
match (a, b) {
(RuntimeValue::Float(x), RuntimeValue::Float(y)) => assert_eq!(
x.to_bits(),
y.to_bits(),
"{ctx}: float bits diverged ({x} vs {y})"
),
(RuntimeValue::Int(_), _)
| (RuntimeValue::Bool(_), _)
| (RuntimeValue::Nothing, _)
| (RuntimeValue::Char(_), _)
| (RuntimeValue::Duration(_), _)
| (RuntimeValue::Date(_), _)
| (RuntimeValue::Moment(_), _)
| (RuntimeValue::Span { .. }, _)
| (RuntimeValue::Time(_), _)
| (RuntimeValue::Text(_), _) => assert_eq!(a, b, "{ctx}: round-trip diverged"),
_ => {
assert_eq!(a.type_name(), b.type_name(), "{ctx}: type diverged");
assert_eq!(
a.to_display_string(),
b.to_display_string(),
"{ctx}: display diverged"
);
}
}
}
#[test]
fn nanbox_runtime_value_is_16_bytes() {
assert_eq!(
std::mem::size_of::<RuntimeValue>(),
16,
"RuntimeValue is expected to be 16 bytes"
);
}
#[test]
fn nanbox_narrow_is_8_bytes() {
assert_eq!(std::mem::size_of::<Narrow>(), 8, "Narrow must be 8 bytes");
assert_eq!(std::mem::align_of::<Narrow>(), 8, "Narrow must be 8-aligned");
assert!(
std::mem::size_of::<Narrow>() < std::mem::size_of::<RuntimeValue>(),
"Narrow ({}) must be smaller than RuntimeValue ({})",
std::mem::size_of::<Narrow>(),
std::mem::size_of::<RuntimeValue>(),
);
}
#[test]
fn nanbox_int_round_trips_over_edge_grid() {
for &n in &INT_EDGES {
let rv = RuntimeValue::Int(n);
let narrow = Narrow::from_runtime(rv.clone());
assert_eq!(narrow.as_int(), Some(n), "as_int({n})");
let back = narrow.into_runtime();
assert_runtime_eq(&rv, &back, &format!("Int({n})"));
let inline = (INT_INLINE_MIN..=INT_INLINE_MAX).contains(&n);
assert_eq!(
Narrow::int(n).is_inline_int(),
inline,
"Int({n}) inline-classification"
);
}
}
#[test]
fn nanbox_float_round_trips_bit_exact_incl_nan_zero_inf_subnormal() {
for f in float_edges() {
let rv = RuntimeValue::Float(f);
let n = Narrow::from_runtime(rv.clone());
assert!(n.as_float().is_some(), "as_float present for {f}");
let got = n.as_float().unwrap();
if f.is_nan() {
assert!(got.is_nan(), "NaN must round-trip as a NaN ({f})");
} else {
assert_eq!(got.to_bits(), f.to_bits(), "float bits for {f}");
}
let back = n.into_runtime();
match (&rv, &back) {
(RuntimeValue::Float(a), RuntimeValue::Float(b)) if a.is_nan() => {
assert!(b.is_nan(), "NaN round-trip")
}
_ => assert_runtime_eq(&rv, &back, &format!("Float({f})")),
}
}
}
#[test]
fn nanbox_bool_and_nothing_round_trip() {
for b in [true, false] {
let rv = RuntimeValue::Bool(b);
let back = Narrow::from_runtime(rv.clone()).into_runtime();
assert_runtime_eq(&rv, &back, &format!("Bool({b})"));
assert_eq!(Narrow::bool(b).as_bool(), Some(b));
}
let back = Narrow::from_runtime(RuntimeValue::Nothing).into_runtime();
assert_runtime_eq(&RuntimeValue::Nothing, &back, "Nothing");
assert!(Narrow::nothing().is_nothing());
}
#[test]
fn nanbox_heap_types_round_trip_and_preserve_identity() {
let text = RuntimeValue::Text(Rc::new("hello world".to_string()));
let back = Narrow::from_runtime(text.clone()).into_runtime();
assert_runtime_eq(&text, &back, "Text");
let list = RuntimeValue::List(Rc::new(RefCell::new(ListRepr::Ints(vec![1, 2, 3]))));
let narrow = Narrow::from_runtime(list.clone());
assert!(narrow.is_heap());
if let RuntimeValue::List(rc) = &list {
rc.borrow_mut().push(RuntimeValue::Int(4));
}
if let Some(RuntimeValue::List(rc)) = narrow.as_heap() {
assert_eq!(rc.borrow().len(), 4, "Narrow shares the original List's Rc");
} else {
panic!("heap payload was not a List");
}
let back = narrow.into_runtime();
if let RuntimeValue::List(rc) = back {
assert_eq!(rc.borrow().len(), 4);
} else {
panic!("List did not round-trip");
}
let mut storage = MapStorage::default();
storage.insert(RuntimeValue::Int(7), RuntimeValue::Text(Rc::new("v".into())));
let map = RuntimeValue::Map(Rc::new(RefCell::new(storage)));
let back = Narrow::from_runtime(map.clone()).into_runtime();
if let (RuntimeValue::Map(a), RuntimeValue::Map(b)) = (&map, &back) {
assert_eq!(a.borrow().len(), b.borrow().len());
} else {
panic!("Map did not round-trip");
}
for rv in [
RuntimeValue::Char('x'),
RuntimeValue::Duration(42),
RuntimeValue::Date(20260620),
RuntimeValue::Moment(123456789),
RuntimeValue::Span { months: 3, days: 14 },
RuntimeValue::Time(987654321),
RuntimeValue::Tuple(Rc::new(vec![RuntimeValue::Int(1), RuntimeValue::Bool(true)])),
RuntimeValue::Set(Rc::new(RefCell::new(vec![RuntimeValue::Int(9)]))),
] {
let back = Narrow::from_runtime(rv.clone()).into_runtime();
assert_runtime_eq(&rv, &back, "heap-path scalar/collection");
}
}
#[test]
fn nanbox_to_runtime_matches_into_runtime_without_consuming() {
let cases = [
RuntimeValue::Int(-99),
RuntimeValue::Int(i64::MAX), RuntimeValue::Float(std::f64::consts::E),
RuntimeValue::Bool(true),
RuntimeValue::Nothing,
RuntimeValue::Text(Rc::new("abc".into())),
];
for rv in cases {
let n = Narrow::from_runtime(rv.clone());
let borrowed = n.to_runtime();
let consumed = n.into_runtime();
assert_runtime_eq(&borrowed, &consumed, "to_runtime vs into_runtime");
assert_runtime_eq(&borrowed, &rv, "to_runtime vs original");
}
}
#[test]
fn nanbox_int_arith_is_bit_identical_to_kernel() {
use crate::ast::stmt::BinaryOpKind;
use crate::semantics::{arith, compare};
let edges = inline_edges();
for &a in &edges {
for &b in &edges {
let (na, nb) = (Narrow::int(a), Narrow::int(b));
let add = na.int_add(&nb).unwrap().into_runtime();
assert_eq!(
add,
arith::add(RuntimeValue::Int(a), RuntimeValue::Int(b)).unwrap(),
"add({a},{b})"
);
let sub = na.int_sub(&nb).unwrap().into_runtime();
assert_eq!(
sub,
arith::subtract(RuntimeValue::Int(a), RuntimeValue::Int(b)).unwrap(),
"sub({a},{b})"
);
let mul = na.int_mul(&nb).unwrap().into_runtime();
assert_eq!(
mul,
arith::multiply(RuntimeValue::Int(a), RuntimeValue::Int(b)).unwrap(),
"mul({a},{b})"
);
let lt = na.int_lt(&nb).unwrap();
let lt_kernel = compare::compare(
BinaryOpKind::Lt,
&RuntimeValue::Int(a),
&RuntimeValue::Int(b),
)
.unwrap();
assert_eq!(RuntimeValue::Bool(lt), lt_kernel, "lt({a},{b})");
let eq = na.int_eq(&nb).unwrap();
assert_eq!(
eq,
compare::values_equal(&RuntimeValue::Int(a), &RuntimeValue::Int(b)),
"eq({a},{b})"
);
}
}
}
#[test]
fn nanbox_arith_wrap_past_inline_range_stays_lossless() {
use crate::semantics::arith;
let big = INT_INLINE_MAX;
let n = Narrow::int(big).int_add(&Narrow::int(big)).unwrap();
assert!(!n.is_inline_int(), "out-of-range sum must box");
assert_eq!(
n.into_runtime(),
arith::add(RuntimeValue::Int(big), RuntimeValue::Int(big)).unwrap(),
"wrapped sum equals the kernel"
);
}
#[test]
fn nanbox_non_inline_int_operands_do_not_take_the_int_fast_path() {
let i = Narrow::int(2);
let f = Narrow::float(0.5);
let t = Narrow::from_runtime(RuntimeValue::Text(Rc::new("x".into())));
let big = Narrow::int(i64::MAX); assert!(i.int_add(&f).is_none(), "Int+Float must not take int path");
assert!(f.int_add(&i).is_none());
assert!(i.int_mul(&t).is_none(), "Int*Text must not take int path");
assert!(i.int_add(&big).is_none(), "inline+boxed must not take inline path");
assert_eq!(i.int_lt(&f), None);
assert_eq!(i.int_eq(&f), None);
}
#[test]
fn nanbox_nan_payload_is_not_aliased_with_heap() {
let real_nan = Narrow::float(f64::NAN);
assert!(real_nan.as_float().unwrap().is_nan(), "real NaN reads as a NaN float");
assert!(!real_nan.is_heap(), "a real NaN must not look like a heap value");
assert!(real_nan.as_int().is_none(), "a real NaN must not read back as Int");
assert!(real_nan.as_bool().is_none());
assert!(!real_nan.is_nothing());
assert!(real_nan.as_heap().is_none());
let heap = Narrow::from_runtime(RuntimeValue::Text(Rc::new("nan".into())));
assert!(heap.is_heap());
assert!(heap.as_float().is_none(), "a heap value must never read as Float");
assert!(heap.as_heap().is_some());
for bits in [0x7FF0_0000_0000_0001u64, 0xFFF0_0000_0000_0001, 0x7FF8_0000_0000_0000] {
let f = f64::from_bits(bits);
assert!(f.is_nan());
let n = Narrow::float(f);
assert!(n.as_float().unwrap().is_nan(), "{bits:#x} round-trips as NaN");
assert!(n.as_int().is_none() && n.as_bool().is_none() && !n.is_heap());
}
assert_eq!(Narrow::float(f64::INFINITY).as_float(), Some(f64::INFINITY));
assert_eq!(Narrow::float(f64::NEG_INFINITY).as_float(), Some(f64::NEG_INFINITY));
assert!(!Narrow::float(f64::INFINITY).is_heap());
let minus_one = Narrow::int(-1);
assert_eq!(minus_one.0, 0xFFF8_FFFF_FFFF_FFFF, "Int(-1) bit pattern");
assert_ne!(minus_one.0, CANONICAL_NAN, "Int(-1) must not alias the NaN");
assert_eq!(minus_one.as_int(), Some(-1));
assert!(minus_one.as_float().is_none());
for tag in 0u64..=3 {
let hi = QNAN | (tag << TAG_SHIFT) | PAYLOAD_MASK;
assert!(hi < CANONICAL_NAN, "tag {tag} max value must stay below the NaN");
}
}
#[test]
fn nanbox_clone_and_drop_are_memory_safe_for_heap() {
let rc = Rc::new(RefCell::new(ListRepr::Ints(vec![1, 2, 3])));
let outer = Rc::clone(&rc); let n1 = Narrow::from_runtime(RuntimeValue::List(Rc::clone(&rc))); assert_eq!(Rc::strong_count(&rc), 3);
let n2 = n1.clone(); assert_eq!(Rc::strong_count(&rc), 4);
drop(n2); assert_eq!(Rc::strong_count(&rc), 3);
let _ = n1.into_runtime(); assert_eq!(Rc::strong_count(&rc), 2);
drop(outer); assert_eq!(Rc::strong_count(&rc), 1);
}
#[test]
fn nanbox_boxed_large_int_is_memory_safe_through_clone_and_drop() {
let n = Narrow::int(i64::MIN);
assert!(!n.is_inline_int());
let c = n.clone();
assert_eq!(c.as_int(), Some(i64::MIN));
assert_eq!(n.as_int(), Some(i64::MIN));
drop(c);
assert_eq!(n.into_runtime(), RuntimeValue::Int(i64::MIN));
}
#[test]
fn nanbox_scalar_clone_is_a_value_copy() {
let a = Narrow::int(123);
let b = a.clone();
assert_eq!(a.as_int(), b.as_int());
let c = Narrow::float(f64::NAN);
let d = c.clone();
assert!(c.as_float().unwrap().is_nan() && d.as_float().unwrap().is_nan());
let e = Narrow::nothing();
assert!(e.clone().is_nothing());
}
}