shape_value/slot.rs
1//! ValueSlot: 8-byte raw value storage for TypedObject fields
2//!
3//! Each slot stores exactly 8 bytes of raw bits. Simple types (f64, i64, bool)
4//! use their native bit representation. Complex types (strings, arrays, objects)
5//! are stored as heap-allocated Box<HeapValue> raw pointers.
6//!
7//! The slot itself does NOT self-describe its type. TypedObject's `heap_mask`
8//! bitmap identifies which slots contain heap pointers (bit N set = slot N is heap).
9
10use crate::heap_value::HeapValue;
11use crate::value_word::ValueWord;
12
13/// A raw 8-byte value slot for TypedObject field storage.
14#[repr(transparent)]
15#[derive(Copy, Clone)]
16pub struct ValueSlot(u64);
17
18impl ValueSlot {
19 /// Store a f64 as raw IEEE 754 bits.
20 pub fn from_number(n: f64) -> Self {
21 Self(n.to_bits())
22 }
23
24 /// Store an i64 as raw two's complement bits. Full 64-bit range, no precision loss.
25 pub fn from_int(i: i64) -> Self {
26 Self(i as u64)
27 }
28
29 /// Store a u64 directly. Only meaningful when the FieldType is known to be U64.
30 pub fn from_u64(v: u64) -> Self {
31 Self(v)
32 }
33
34 /// Read as u64 (caller must know this slot is u64 type).
35 pub fn as_u64(&self) -> u64 {
36 self.0
37 }
38
39 /// Store a bool as 1/0.
40 pub fn from_bool(b: bool) -> Self {
41 Self(if b { 1 } else { 0 })
42 }
43
44 /// Store None as zero bits.
45 pub fn none() -> Self {
46 Self(0)
47 }
48
49 /// Store any HeapValue on the heap. The caller MUST set the corresponding
50 /// bit in `heap_mask` so Drop knows to free this.
51 ///
52 /// Without `gc` feature: allocates via Box (freed by drop_heap).
53 /// With `gc` feature: allocates via GcHeap (freed by garbage collector).
54 #[cfg(not(feature = "gc"))]
55 pub fn from_heap(value: HeapValue) -> Self {
56 let ptr = Box::into_raw(Box::new(value)) as u64;
57 Self(ptr)
58 }
59
60 /// Store any HeapValue on the GC heap.
61 #[cfg(feature = "gc")]
62 pub fn from_heap(value: HeapValue) -> Self {
63 let heap = shape_gc::thread_gc_heap();
64 let ptr = heap.alloc(value) as u64;
65 Self(ptr)
66 }
67
68 /// Read as f64 (caller must know this slot is f64 type).
69 pub fn as_f64(&self) -> f64 {
70 f64::from_bits(self.0)
71 }
72
73 /// Read as i64 (caller must know this slot is i64 type).
74 pub fn as_i64(&self) -> i64 {
75 self.0 as i64
76 }
77
78 /// Read as bool (caller must know this slot is bool type).
79 pub fn as_bool(&self) -> bool {
80 self.0 != 0
81 }
82
83 /// Read as heap HeapValue reference (caller must know this slot is a heap pointer).
84 /// Returns a reference to the pointed-to HeapValue.
85 pub fn as_heap_value(&self) -> &HeapValue {
86 let ptr = self.0 as *const HeapValue;
87 unsafe { &*ptr }
88 }
89
90 /// Create a ValueWord directly from this heap slot (no intermediate conversion).
91 /// Caller must know this slot is a heap pointer.
92 pub fn as_heap_nb(&self) -> ValueWord {
93 ValueWord::from_heap_value(self.as_heap_value().clone())
94 }
95
96 /// Store a ValueWord losslessly. For inline types (f64, i48, bool,
97 /// none, unit, function, module_function), stores the raw NaN-boxed tag bits
98 /// directly. For heap-tagged values, clones the HeapValue into a new Box.
99 /// Returns `(slot, is_heap)` — caller must set the heap_mask bit if `is_heap`.
100 pub fn from_value_word(nb: &ValueWord) -> (Self, bool) {
101 use crate::value_word::NanTag;
102 if nb.tag() == NanTag::Heap {
103 if let Some(hv) = nb.as_heap_ref() {
104 return (Self::from_heap(hv.clone()), true);
105 }
106 return (Self(0), false);
107 }
108 (Self(nb.raw_bits()), false)
109 }
110
111 /// Backward-compatibility alias.
112 pub fn from_nanboxed(nb: &ValueWord) -> (Self, bool) {
113 Self::from_value_word(nb)
114 }
115
116 /// Reconstruct a ValueWord from this slot. `is_heap` must match the value
117 /// returned by `from_value_word` (i.e., whether heap_mask bit is set).
118 pub fn as_value_word(&self, is_heap: bool) -> ValueWord {
119 if is_heap {
120 ValueWord::from_heap_value(self.as_heap_value().clone())
121 } else {
122 // Safety: bits were stored by from_value_word from a valid inline ValueWord.
123 // No heap pointer involved, so no refcount management needed.
124 unsafe { ValueWord::clone_from_bits(self.0) }
125 }
126 }
127
128 /// Backward-compatibility alias.
129 pub fn as_nanboxed(&self, is_heap: bool) -> ValueWord {
130 self.as_value_word(is_heap)
131 }
132
133 /// Raw bits for simple copy.
134 pub fn raw(&self) -> u64 {
135 self.0
136 }
137
138 /// Construct from raw bits.
139 pub fn from_raw(bits: u64) -> Self {
140 Self(bits)
141 }
142
143 /// Drop the heap value. MUST only be called on heap slots.
144 ///
145 /// Without `gc` feature: frees via Box deallocation.
146 /// With `gc` feature: no-op (GC handles deallocation).
147 ///
148 /// # Safety
149 /// Caller must ensure this slot actually contains a valid heap pointer.
150 #[cfg(not(feature = "gc"))]
151 pub unsafe fn drop_heap(&mut self) {
152 if self.0 != 0 {
153 let ptr = self.0 as *mut HeapValue;
154 let _ = unsafe { Box::from_raw(ptr) };
155 self.0 = 0;
156 }
157 }
158
159 /// Drop the heap value (GC path: no-op).
160 #[cfg(feature = "gc")]
161 pub unsafe fn drop_heap(&mut self) {
162 // No-op: garbage collector handles deallocation
163 self.0 = 0;
164 }
165
166 /// Clone a heap slot by cloning the pointed-to HeapValue into a new Box.
167 ///
168 /// Without `gc` feature: deep clones into a new Box allocation.
169 /// With `gc` feature: bitwise copy (GC tracks all references).
170 ///
171 /// # Safety
172 /// Caller must ensure this slot actually contains a valid heap pointer.
173 #[cfg(not(feature = "gc"))]
174 pub unsafe fn clone_heap(&self) -> Self {
175 if self.0 == 0 {
176 return Self(0);
177 }
178 let ptr = self.0 as *const HeapValue;
179 let cloned = unsafe { (*ptr).clone() };
180 Self::from_heap(cloned)
181 }
182
183 /// Clone a heap slot (GC path: bitwise copy).
184 #[cfg(feature = "gc")]
185 pub unsafe fn clone_heap(&self) -> Self {
186 // Under GC, just copy the pointer — GC traces all live references
187 Self(self.0)
188 }
189}
190
191impl std::fmt::Debug for ValueSlot {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 write!(f, "ValueSlot(0x{:016x})", self.0)
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use std::sync::Arc;
201
202 #[test]
203 fn test_number_roundtrip() {
204 let slot = ValueSlot::from_number(3.14);
205 assert_eq!(slot.as_f64(), 3.14);
206 }
207
208 #[test]
209 fn test_int_roundtrip() {
210 let slot = ValueSlot::from_int(-42);
211 assert_eq!(slot.as_i64(), -42);
212
213 let slot = ValueSlot::from_int(i64::MAX);
214 assert_eq!(slot.as_i64(), i64::MAX);
215
216 let slot = ValueSlot::from_int(i64::MIN);
217 assert_eq!(slot.as_i64(), i64::MIN);
218 }
219
220 #[test]
221 fn test_bool_roundtrip() {
222 assert!(ValueSlot::from_bool(true).as_bool());
223 assert!(!ValueSlot::from_bool(false).as_bool());
224 }
225
226 #[test]
227 fn test_heap_string_roundtrip() {
228 let original = HeapValue::String(Arc::new("hello".to_string()));
229 let slot = ValueSlot::from_heap(original.clone());
230 let recovered = slot.as_heap_value();
231 match recovered {
232 HeapValue::String(s) => assert_eq!(s.as_str(), "hello"),
233 other => panic!("Expected HeapValue::String, got {:?}", other),
234 }
235 // Clean up
236 unsafe {
237 let mut slot = slot;
238 slot.drop_heap();
239 }
240 }
241}