seq_runtime/value.rs
1use crate::seqstring::SeqString;
2use may::sync::mpmc;
3use std::collections::HashMap;
4use std::hash::{Hash, Hasher};
5use std::sync::Arc;
6
7/// Channel data: holds sender and receiver for direct handle passing
8///
9/// Both sender and receiver are Clone (MPMC), so duplicating a Channel value
10/// just clones the Arc. Send/receive operations use the handles directly
11/// with zero mutex overhead.
12#[derive(Debug)]
13pub struct ChannelData {
14 pub sender: mpmc::Sender<Value>,
15 pub receiver: mpmc::Receiver<Value>,
16}
17
18impl Clone for ChannelData {
19 fn clone(&self) -> Self {
20 Self {
21 sender: self.sender.clone(),
22 receiver: self.receiver.clone(),
23 }
24 }
25}
26
27// PartialEq by identity (Arc pointer comparison)
28impl PartialEq for ChannelData {
29 fn eq(&self, other: &Self) -> bool {
30 std::ptr::eq(self, other)
31 }
32}
33
34/// Message type for weave channels.
35///
36/// Using an enum instead of sentinel values ensures no collision with user data.
37/// Any `Value` can be safely yielded/resumed, including `i64::MIN`.
38#[derive(Debug, Clone, PartialEq)]
39pub enum WeaveMessage {
40 /// Normal value being yielded or resumed
41 Value(Value),
42 /// Weave completed naturally (sent on yield_chan)
43 Done,
44 /// Cancellation requested (sent on resume_chan)
45 Cancel,
46}
47
48/// Channel data specifically for weave communication.
49///
50/// Uses `WeaveMessage` instead of raw `Value` to support typed control flow.
51#[derive(Debug)]
52pub struct WeaveChannelData {
53 pub sender: mpmc::Sender<WeaveMessage>,
54 pub receiver: mpmc::Receiver<WeaveMessage>,
55}
56
57impl Clone for WeaveChannelData {
58 fn clone(&self) -> Self {
59 Self {
60 sender: self.sender.clone(),
61 receiver: self.receiver.clone(),
62 }
63 }
64}
65
66// PartialEq by identity (Arc pointer comparison)
67impl PartialEq for WeaveChannelData {
68 fn eq(&self, other: &Self) -> bool {
69 std::ptr::eq(self, other)
70 }
71}
72
73// Note: Arc is used for both Closure.env and Variant to enable O(1) cloning.
74// This is essential for functional programming with recursive data structures.
75
76/// MapKey: Hashable subset of Value for use as map keys
77///
78/// Only types that can be meaningfully hashed are allowed as map keys:
79/// Int, String, Bool. Float is excluded due to NaN equality issues.
80#[derive(Debug, Clone, PartialEq, Eq)]
81pub enum MapKey {
82 Int(i64),
83 String(SeqString),
84 Bool(bool),
85}
86
87impl Hash for MapKey {
88 fn hash<H: Hasher>(&self, state: &mut H) {
89 // Discriminant for type safety
90 std::mem::discriminant(self).hash(state);
91 match self {
92 MapKey::Int(n) => n.hash(state),
93 MapKey::String(s) => s.as_str().hash(state),
94 MapKey::Bool(b) => b.hash(state),
95 }
96 }
97}
98
99impl MapKey {
100 /// Try to convert a Value to a MapKey
101 /// Returns None for non-hashable types (Float, Variant, Quotation, Closure, Map)
102 pub fn from_value(value: &Value) -> Option<MapKey> {
103 match value {
104 Value::Int(n) => Some(MapKey::Int(*n)),
105 Value::String(s) => Some(MapKey::String(s.clone())),
106 Value::Bool(b) => Some(MapKey::Bool(*b)),
107 _ => None,
108 }
109 }
110
111 /// Convert MapKey back to Value
112 pub fn to_value(&self) -> Value {
113 match self {
114 MapKey::Int(n) => Value::Int(*n),
115 MapKey::String(s) => Value::String(s.clone()),
116 MapKey::Bool(b) => Value::Bool(*b),
117 }
118 }
119}
120
121/// Value: What the language talks about
122///
123/// This is pure data with no pointers to other values.
124/// Values can be pushed on the stack, stored in variants, etc.
125/// The key insight: Value is independent of Stack structure.
126///
127/// # Memory Layout
128///
129/// Using `#[repr(C)]` ensures a predictable C-compatible layout:
130/// - Discriminant (tag) at offset 0
131/// - Payload data follows at a fixed offset
132///
133/// This allows compiled code to write Values directly without FFI calls,
134/// enabling inline integer/boolean operations for better performance.
135#[repr(C)]
136#[derive(Debug, Clone, PartialEq)]
137pub enum Value {
138 /// Integer value
139 Int(i64),
140
141 /// Floating-point value (IEEE 754 double precision)
142 Float(f64),
143
144 /// Boolean value
145 Bool(bool),
146
147 /// String (arena or globally allocated via SeqString)
148 String(SeqString),
149
150 /// Symbol (identifier for dynamic variant construction)
151 /// Like Ruby/Clojure symbols - lightweight identifiers used for tags.
152 /// Note: Currently NOT interned (each symbol allocates). Interning may be
153 /// added in the future for O(1) equality comparison.
154 Symbol(SeqString),
155
156 /// Variant (sum type with tagged fields)
157 /// Uses Arc for O(1) cloning - essential for recursive data structures
158 Variant(Arc<VariantData>),
159
160 /// Map (key-value dictionary with O(1) lookup)
161 /// Keys must be hashable types (Int, String, Bool)
162 Map(Box<HashMap<MapKey, Value>>),
163
164 /// Quotation (stateless function with two entry points for calling convention compatibility)
165 /// - wrapper: C-convention entry point for calls from the runtime
166 /// - impl_: tailcc entry point for tail calls from compiled code (enables TCO)
167 Quotation {
168 /// C-convention wrapper function pointer (for runtime calls via patch_seq_call)
169 wrapper: usize,
170 /// tailcc implementation function pointer (for musttail from compiled code)
171 impl_: usize,
172 },
173
174 /// Closure (quotation with captured environment)
175 /// Contains function pointer and Arc-shared array of captured values.
176 /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
177 Closure {
178 /// Function pointer (transmuted to function taking Stack + environment)
179 fn_ptr: usize,
180 /// Captured values from creation site (Arc for TCO support)
181 /// Ordered top-down: `env[0]` is top of stack at creation
182 env: Arc<[Value]>,
183 },
184
185 /// Channel (MPMC sender/receiver pair for CSP-style concurrency)
186 /// Uses Arc for O(1) cloning - duplicating a channel shares the underlying handles.
187 /// Send/receive operations use the handles directly with zero mutex overhead.
188 Channel(Arc<ChannelData>),
189
190 /// Weave context (generator/coroutine communication channels)
191 /// Contains both yield and resume channels for bidirectional communication.
192 /// Travels on the stack - no global registry needed.
193 /// Uses WeaveChannelData with WeaveMessage for type-safe control flow.
194 WeaveCtx {
195 yield_chan: Arc<WeaveChannelData>,
196 resume_chan: Arc<WeaveChannelData>,
197 },
198}
199
200// Safety: Value can be sent and shared between strands (green threads)
201//
202// Send (safe to transfer ownership between threads):
203// - Int, Float, Bool are Copy types (trivially Send)
204// - String (SeqString) implements Send (clone to global on transfer)
205// - Variant contains Arc<VariantData> which is Send when VariantData is Send+Sync
206// - Quotation stores function pointer as usize (Send-safe, no owned data)
207// - Closure: fn_ptr is usize (Send), env is Arc<[Value]> (Send when Value is Send+Sync)
208// - Map contains Box<HashMap> which is Send because keys and values are Send
209// - Channel contains Arc<ChannelData> which is Send (May's Sender/Receiver are Send)
210//
211// Sync (safe to share references between threads):
212// - Value has no interior mutability (no Cell, RefCell, Mutex, etc.)
213// - All operations on Value are read-only or create new values (functional semantics)
214// - Arc requires T: Send + Sync for full thread-safety
215//
216// This is required for:
217// - Channel communication between strands
218// - Arc-based sharing of Variants, Closure environments, and Channels
219unsafe impl Send for Value {}
220unsafe impl Sync for Value {}
221
222/// VariantData: Composite values (sum types)
223///
224/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
225/// This is the key difference from cem2, which used StackCell.next for field linking.
226///
227/// # Arc and Reference Cycles
228///
229/// Variants use `Arc<VariantData>` for O(1) cloning, which could theoretically
230/// create reference cycles. However, cycles are prevented by design:
231/// - VariantData.fields is immutable (no mutation after creation)
232/// - All variant operations create new variants rather than modifying existing ones
233/// - The Seq language has no mutation primitives for variant fields
234///
235/// This functional/immutable design ensures Arc reference counts always reach zero.
236#[derive(Debug, Clone, PartialEq)]
237pub struct VariantData {
238 /// Tag identifies which variant constructor was used (symbol name)
239 /// Stored as SeqString for dynamic variant construction via `wrap-N`
240 pub tag: SeqString,
241
242 /// Fields stored as an owned array of values
243 /// This is independent of any stack structure
244 pub fields: Box<[Value]>,
245}
246
247impl VariantData {
248 /// Create a new variant with the given tag and fields
249 pub fn new(tag: SeqString, fields: Vec<Value>) -> Self {
250 Self {
251 tag,
252 fields: fields.into_boxed_slice(),
253 }
254 }
255}
256
257// We'll implement proper cleanup in Drop later
258// For now, Rust's ownership handles most of it
259
260impl std::fmt::Display for Value {
261 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
262 match self {
263 Value::Int(n) => write!(f, "{}", n),
264 Value::Float(n) => write!(f, "{}", n),
265 Value::Bool(b) => write!(f, "{}", b),
266 Value::String(s) => write!(f, "{:?}", s.as_str()),
267 Value::Symbol(s) => write!(f, ":{}", s.as_str()),
268 Value::Variant(v) => {
269 write!(f, ":{}", v.tag.as_str())?;
270 if !v.fields.is_empty() {
271 write!(f, "(")?;
272 }
273 for (i, field) in v.fields.iter().enumerate() {
274 if i > 0 {
275 write!(f, ", ")?;
276 }
277 write!(f, "{}", field)?;
278 }
279 if !v.fields.is_empty() {
280 write!(f, ")")?;
281 }
282 Ok(())
283 }
284 Value::Map(m) => {
285 write!(f, "{{")?;
286 for (i, (k, v)) in m.iter().enumerate() {
287 if i > 0 {
288 write!(f, ", ")?;
289 }
290 write!(f, "{}: {}", k.to_value(), v)?;
291 }
292 write!(f, "}}")
293 }
294 Value::Quotation { .. } => write!(f, "<quotation>"),
295 Value::Closure { .. } => write!(f, "<closure>"),
296 Value::Channel(_) => write!(f, "<channel>"),
297 Value::WeaveCtx { .. } => write!(f, "<weave-ctx>"),
298 }
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305 use std::mem::{align_of, size_of};
306
307 // In non-nanbox mode, Value and StackValue must have matching layouts
308 #[test]
309 #[cfg(not(feature = "nanbox"))]
310 fn test_value_layout() {
311 // Print sizes for debugging
312 println!("size_of::<Value>() = {}", size_of::<Value>());
313 println!("align_of::<Value>() = {}", align_of::<Value>());
314
315 // Verify Value is exactly 40 bytes to match StackValue layout
316 // This is critical for FFI correctness between LLVM IR and Rust
317 use crate::tagged_stack::StackValue;
318 assert_eq!(
319 size_of::<Value>(),
320 size_of::<StackValue>(),
321 "Value ({} bytes) must match StackValue ({} bytes) for FFI compatibility",
322 size_of::<Value>(),
323 size_of::<StackValue>()
324 );
325 assert_eq!(
326 size_of::<Value>(),
327 40,
328 "Value must be exactly 40 bytes, got {}",
329 size_of::<Value>()
330 );
331
332 // Verify alignment is 8 (for 64-bit pointers)
333 assert_eq!(align_of::<Value>(), 8);
334 }
335
336 // In nanbox mode, StackValue is 8 bytes (NaN-boxed), Value is still 40 bytes
337 #[test]
338 #[cfg(feature = "nanbox")]
339 fn test_value_layout_nanbox() {
340 use crate::tagged_stack::StackValue;
341 // StackValue is 8 bytes in nanbox mode
342 assert_eq!(
343 size_of::<StackValue>(),
344 8,
345 "StackValue must be 8 bytes in nanbox mode, got {}",
346 size_of::<StackValue>()
347 );
348 // Value is still 40 bytes (it's used for conversion)
349 assert_eq!(
350 size_of::<Value>(),
351 40,
352 "Value must be exactly 40 bytes, got {}",
353 size_of::<Value>()
354 );
355 // Both should be 8-byte aligned
356 assert_eq!(align_of::<Value>(), 8);
357 assert_eq!(align_of::<StackValue>(), 8);
358 }
359
360 #[test]
361 fn test_value_int_layout() {
362 let val = Value::Int(42);
363 let ptr = &val as *const Value as *const u8;
364
365 unsafe {
366 // With #[repr(C)], the discriminant is at offset 0
367 // For 9 variants, discriminant fits in 1 byte but is padded
368 let discriminant_byte = *ptr;
369 assert_eq!(
370 discriminant_byte, 0,
371 "Int discriminant should be 0, got {}",
372 discriminant_byte
373 );
374
375 // The i64 value should be at a fixed offset after the discriminant
376 // With C repr, it's typically at offset 8 (discriminant + padding)
377 let value_ptr = ptr.add(8) as *const i64;
378 let stored_value = *value_ptr;
379 assert_eq!(
380 stored_value, 42,
381 "Int value should be 42 at offset 8, got {}",
382 stored_value
383 );
384 }
385 }
386
387 #[test]
388 fn test_value_bool_layout() {
389 let val_true = Value::Bool(true);
390 let val_false = Value::Bool(false);
391 let ptr_true = &val_true as *const Value as *const u8;
392 let ptr_false = &val_false as *const Value as *const u8;
393
394 unsafe {
395 // Bool is variant index 2 (after Int=0, Float=1)
396 let discriminant = *ptr_true;
397 assert_eq!(
398 discriminant, 2,
399 "Bool discriminant should be 2, got {}",
400 discriminant
401 );
402
403 // The bool value should be at offset 8
404 let value_ptr_true = ptr_true.add(8);
405 let value_ptr_false = ptr_false.add(8);
406 assert_eq!(*value_ptr_true, 1, "true should be 1");
407 assert_eq!(*value_ptr_false, 0, "false should be 0");
408 }
409 }
410
411 #[test]
412 fn test_value_display() {
413 // Test Display impl formats values correctly
414 assert_eq!(format!("{}", Value::Int(42)), "42");
415 assert_eq!(format!("{}", Value::Float(2.5)), "2.5");
416 assert_eq!(format!("{}", Value::Bool(true)), "true");
417 assert_eq!(format!("{}", Value::Bool(false)), "false");
418
419 // String shows with quotes (Debug-style)
420 let s = Value::String(SeqString::from("hello"));
421 assert_eq!(format!("{}", s), "\"hello\"");
422
423 // Symbol shows with : prefix
424 let sym = Value::Symbol(SeqString::from("my-symbol"));
425 assert_eq!(format!("{}", sym), ":my-symbol");
426 }
427}