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// Note: Arc is used for both Closure.env and Variant to enable O(1) cloning.
35// This is essential for functional programming with recursive data structures.
36
37/// MapKey: Hashable subset of Value for use as map keys
38///
39/// Only types that can be meaningfully hashed are allowed as map keys:
40/// Int, String, Bool. Float is excluded due to NaN equality issues.
41#[derive(Debug, Clone, PartialEq, Eq)]
42pub enum MapKey {
43 Int(i64),
44 String(SeqString),
45 Bool(bool),
46}
47
48impl Hash for MapKey {
49 fn hash<H: Hasher>(&self, state: &mut H) {
50 // Discriminant for type safety
51 std::mem::discriminant(self).hash(state);
52 match self {
53 MapKey::Int(n) => n.hash(state),
54 MapKey::String(s) => s.as_str().hash(state),
55 MapKey::Bool(b) => b.hash(state),
56 }
57 }
58}
59
60impl MapKey {
61 /// Try to convert a Value to a MapKey
62 /// Returns None for non-hashable types (Float, Variant, Quotation, Closure, Map)
63 pub fn from_value(value: &Value) -> Option<MapKey> {
64 match value {
65 Value::Int(n) => Some(MapKey::Int(*n)),
66 Value::String(s) => Some(MapKey::String(s.clone())),
67 Value::Bool(b) => Some(MapKey::Bool(*b)),
68 _ => None,
69 }
70 }
71
72 /// Convert MapKey back to Value
73 pub fn to_value(&self) -> Value {
74 match self {
75 MapKey::Int(n) => Value::Int(*n),
76 MapKey::String(s) => Value::String(s.clone()),
77 MapKey::Bool(b) => Value::Bool(*b),
78 }
79 }
80}
81
82/// Value: What the language talks about
83///
84/// This is pure data with no pointers to other values.
85/// Values can be pushed on the stack, stored in variants, etc.
86/// The key insight: Value is independent of Stack structure.
87///
88/// # Memory Layout
89///
90/// Using `#[repr(C)]` ensures a predictable C-compatible layout:
91/// - Discriminant (tag) at offset 0
92/// - Payload data follows at a fixed offset
93///
94/// This allows compiled code to write Values directly without FFI calls,
95/// enabling inline integer/boolean operations for better performance.
96#[repr(C)]
97#[derive(Debug, Clone, PartialEq)]
98pub enum Value {
99 /// Integer value
100 Int(i64),
101
102 /// Floating-point value (IEEE 754 double precision)
103 Float(f64),
104
105 /// Boolean value
106 Bool(bool),
107
108 /// String (arena or globally allocated via SeqString)
109 String(SeqString),
110
111 /// Variant (sum type with tagged fields)
112 /// Uses Arc for O(1) cloning - essential for recursive data structures
113 Variant(Arc<VariantData>),
114
115 /// Map (key-value dictionary with O(1) lookup)
116 /// Keys must be hashable types (Int, String, Bool)
117 Map(Box<HashMap<MapKey, Value>>),
118
119 /// Quotation (stateless function with two entry points for calling convention compatibility)
120 /// - wrapper: C-convention entry point for calls from the runtime
121 /// - impl_: tailcc entry point for tail calls from compiled code (enables TCO)
122 Quotation {
123 /// C-convention wrapper function pointer (for runtime calls via patch_seq_call)
124 wrapper: usize,
125 /// tailcc implementation function pointer (for musttail from compiled code)
126 impl_: usize,
127 },
128
129 /// Closure (quotation with captured environment)
130 /// Contains function pointer and Arc-shared array of captured values.
131 /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
132 Closure {
133 /// Function pointer (transmuted to function taking Stack + environment)
134 fn_ptr: usize,
135 /// Captured values from creation site (Arc for TCO support)
136 /// Ordered top-down: env[0] is top of stack at creation
137 env: Arc<[Value]>,
138 },
139
140 /// Channel (MPMC sender/receiver pair for CSP-style concurrency)
141 /// Uses Arc for O(1) cloning - duplicating a channel shares the underlying handles.
142 /// Send/receive operations use the handles directly with zero mutex overhead.
143 Channel(Arc<ChannelData>),
144
145 /// Weave context (generator/coroutine communication channels)
146 /// Contains both yield and resume channels for bidirectional communication.
147 /// Travels on the stack - no global registry needed.
148 WeaveCtx {
149 yield_chan: Arc<ChannelData>,
150 resume_chan: Arc<ChannelData>,
151 },
152}
153
154// Safety: Value can be sent and shared between strands (green threads)
155//
156// Send (safe to transfer ownership between threads):
157// - Int, Float, Bool are Copy types (trivially Send)
158// - String (SeqString) implements Send (clone to global on transfer)
159// - Variant contains Arc<VariantData> which is Send when VariantData is Send+Sync
160// - Quotation stores function pointer as usize (Send-safe, no owned data)
161// - Closure: fn_ptr is usize (Send), env is Arc<[Value]> (Send when Value is Send+Sync)
162// - Map contains Box<HashMap> which is Send because keys and values are Send
163// - Channel contains Arc<ChannelData> which is Send (May's Sender/Receiver are Send)
164//
165// Sync (safe to share references between threads):
166// - Value has no interior mutability (no Cell, RefCell, Mutex, etc.)
167// - All operations on Value are read-only or create new values (functional semantics)
168// - Arc requires T: Send + Sync for full thread-safety
169//
170// This is required for:
171// - Channel communication between strands
172// - Arc-based sharing of Variants, Closure environments, and Channels
173unsafe impl Send for Value {}
174unsafe impl Sync for Value {}
175
176/// VariantData: Composite values (sum types)
177///
178/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
179/// This is the key difference from cem2, which used StackCell.next for field linking.
180///
181/// # Arc and Reference Cycles
182///
183/// Variants use `Arc<VariantData>` for O(1) cloning, which could theoretically
184/// create reference cycles. However, cycles are prevented by design:
185/// - VariantData.fields is immutable (no mutation after creation)
186/// - All variant operations create new variants rather than modifying existing ones
187/// - The Seq language has no mutation primitives for variant fields
188///
189/// This functional/immutable design ensures Arc reference counts always reach zero.
190#[derive(Debug, Clone, PartialEq)]
191pub struct VariantData {
192 /// Tag identifies which variant constructor was used
193 pub tag: u32,
194
195 /// Fields stored as an owned array of values
196 /// This is independent of any stack structure
197 pub fields: Box<[Value]>,
198}
199
200impl VariantData {
201 /// Create a new variant with the given tag and fields
202 pub fn new(tag: u32, fields: Vec<Value>) -> Self {
203 Self {
204 tag,
205 fields: fields.into_boxed_slice(),
206 }
207 }
208}
209
210// We'll implement proper cleanup in Drop later
211// For now, Rust's ownership handles most of it
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use std::mem::{align_of, size_of};
217
218 #[test]
219 fn test_value_layout() {
220 // Print sizes for debugging
221 println!("size_of::<Value>() = {}", size_of::<Value>());
222 println!("align_of::<Value>() = {}", align_of::<Value>());
223
224 // Verify Value is exactly 40 bytes to match StackValue layout
225 // This is critical for FFI correctness between LLVM IR and Rust
226 use crate::tagged_stack::StackValue;
227 assert_eq!(
228 size_of::<Value>(),
229 size_of::<StackValue>(),
230 "Value ({} bytes) must match StackValue ({} bytes) for FFI compatibility",
231 size_of::<Value>(),
232 size_of::<StackValue>()
233 );
234 assert_eq!(
235 size_of::<Value>(),
236 40,
237 "Value must be exactly 40 bytes, got {}",
238 size_of::<Value>()
239 );
240
241 // Verify alignment is 8 (for 64-bit pointers)
242 assert_eq!(align_of::<Value>(), 8);
243 }
244
245 #[test]
246 fn test_value_int_layout() {
247 let val = Value::Int(42);
248 let ptr = &val as *const Value as *const u8;
249
250 unsafe {
251 // With #[repr(C)], the discriminant is at offset 0
252 // For 9 variants, discriminant fits in 1 byte but is padded
253 let discriminant_byte = *ptr;
254 assert_eq!(
255 discriminant_byte, 0,
256 "Int discriminant should be 0, got {}",
257 discriminant_byte
258 );
259
260 // The i64 value should be at a fixed offset after the discriminant
261 // With C repr, it's typically at offset 8 (discriminant + padding)
262 let value_ptr = ptr.add(8) as *const i64;
263 let stored_value = *value_ptr;
264 assert_eq!(
265 stored_value, 42,
266 "Int value should be 42 at offset 8, got {}",
267 stored_value
268 );
269 }
270 }
271
272 #[test]
273 fn test_value_bool_layout() {
274 let val_true = Value::Bool(true);
275 let val_false = Value::Bool(false);
276 let ptr_true = &val_true as *const Value as *const u8;
277 let ptr_false = &val_false as *const Value as *const u8;
278
279 unsafe {
280 // Bool is variant index 2 (after Int=0, Float=1)
281 let discriminant = *ptr_true;
282 assert_eq!(
283 discriminant, 2,
284 "Bool discriminant should be 2, got {}",
285 discriminant
286 );
287
288 // The bool value should be at offset 8
289 let value_ptr_true = ptr_true.add(8);
290 let value_ptr_false = ptr_false.add(8);
291 assert_eq!(*value_ptr_true, 1, "true should be 1");
292 assert_eq!(*value_ptr_false, 0, "false should be 0");
293 }
294 }
295}