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    /// Variant (sum type with tagged fields)
151    /// Uses Arc for O(1) cloning - essential for recursive data structures
152    Variant(Arc<VariantData>),
153
154    /// Map (key-value dictionary with O(1) lookup)
155    /// Keys must be hashable types (Int, String, Bool)
156    Map(Box<HashMap<MapKey, Value>>),
157
158    /// Quotation (stateless function with two entry points for calling convention compatibility)
159    /// - wrapper: C-convention entry point for calls from the runtime
160    /// - impl_: tailcc entry point for tail calls from compiled code (enables TCO)
161    Quotation {
162        /// C-convention wrapper function pointer (for runtime calls via patch_seq_call)
163        wrapper: usize,
164        /// tailcc implementation function pointer (for musttail from compiled code)
165        impl_: usize,
166    },
167
168    /// Closure (quotation with captured environment)
169    /// Contains function pointer and Arc-shared array of captured values.
170    /// Arc enables TCO: no cleanup needed after tail call, ref-count handles it.
171    Closure {
172        /// Function pointer (transmuted to function taking Stack + environment)
173        fn_ptr: usize,
174        /// Captured values from creation site (Arc for TCO support)
175        /// Ordered top-down: env[0] is top of stack at creation
176        env: Arc<[Value]>,
177    },
178
179    /// Channel (MPMC sender/receiver pair for CSP-style concurrency)
180    /// Uses Arc for O(1) cloning - duplicating a channel shares the underlying handles.
181    /// Send/receive operations use the handles directly with zero mutex overhead.
182    Channel(Arc<ChannelData>),
183
184    /// Weave context (generator/coroutine communication channels)
185    /// Contains both yield and resume channels for bidirectional communication.
186    /// Travels on the stack - no global registry needed.
187    /// Uses WeaveChannelData with WeaveMessage for type-safe control flow.
188    WeaveCtx {
189        yield_chan: Arc<WeaveChannelData>,
190        resume_chan: Arc<WeaveChannelData>,
191    },
192}
193
194// Safety: Value can be sent and shared between strands (green threads)
195//
196// Send (safe to transfer ownership between threads):
197// - Int, Float, Bool are Copy types (trivially Send)
198// - String (SeqString) implements Send (clone to global on transfer)
199// - Variant contains Arc<VariantData> which is Send when VariantData is Send+Sync
200// - Quotation stores function pointer as usize (Send-safe, no owned data)
201// - Closure: fn_ptr is usize (Send), env is Arc<[Value]> (Send when Value is Send+Sync)
202// - Map contains Box<HashMap> which is Send because keys and values are Send
203// - Channel contains Arc<ChannelData> which is Send (May's Sender/Receiver are Send)
204//
205// Sync (safe to share references between threads):
206// - Value has no interior mutability (no Cell, RefCell, Mutex, etc.)
207// - All operations on Value are read-only or create new values (functional semantics)
208// - Arc requires T: Send + Sync for full thread-safety
209//
210// This is required for:
211// - Channel communication between strands
212// - Arc-based sharing of Variants, Closure environments, and Channels
213unsafe impl Send for Value {}
214unsafe impl Sync for Value {}
215
216/// VariantData: Composite values (sum types)
217///
218/// Fields are stored in a heap-allocated array, NOT linked via next pointers.
219/// This is the key difference from cem2, which used StackCell.next for field linking.
220///
221/// # Arc and Reference Cycles
222///
223/// Variants use `Arc<VariantData>` for O(1) cloning, which could theoretically
224/// create reference cycles. However, cycles are prevented by design:
225/// - VariantData.fields is immutable (no mutation after creation)
226/// - All variant operations create new variants rather than modifying existing ones
227/// - The Seq language has no mutation primitives for variant fields
228///
229/// This functional/immutable design ensures Arc reference counts always reach zero.
230#[derive(Debug, Clone, PartialEq)]
231pub struct VariantData {
232    /// Tag identifies which variant constructor was used
233    pub tag: u32,
234
235    /// Fields stored as an owned array of values
236    /// This is independent of any stack structure
237    pub fields: Box<[Value]>,
238}
239
240impl VariantData {
241    /// Create a new variant with the given tag and fields
242    pub fn new(tag: u32, fields: Vec<Value>) -> Self {
243        Self {
244            tag,
245            fields: fields.into_boxed_slice(),
246        }
247    }
248}
249
250// We'll implement proper cleanup in Drop later
251// For now, Rust's ownership handles most of it
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use std::mem::{align_of, size_of};
257
258    #[test]
259    fn test_value_layout() {
260        // Print sizes for debugging
261        println!("size_of::<Value>() = {}", size_of::<Value>());
262        println!("align_of::<Value>() = {}", align_of::<Value>());
263
264        // Verify Value is exactly 40 bytes to match StackValue layout
265        // This is critical for FFI correctness between LLVM IR and Rust
266        use crate::tagged_stack::StackValue;
267        assert_eq!(
268            size_of::<Value>(),
269            size_of::<StackValue>(),
270            "Value ({} bytes) must match StackValue ({} bytes) for FFI compatibility",
271            size_of::<Value>(),
272            size_of::<StackValue>()
273        );
274        assert_eq!(
275            size_of::<Value>(),
276            40,
277            "Value must be exactly 40 bytes, got {}",
278            size_of::<Value>()
279        );
280
281        // Verify alignment is 8 (for 64-bit pointers)
282        assert_eq!(align_of::<Value>(), 8);
283    }
284
285    #[test]
286    fn test_value_int_layout() {
287        let val = Value::Int(42);
288        let ptr = &val as *const Value as *const u8;
289
290        unsafe {
291            // With #[repr(C)], the discriminant is at offset 0
292            // For 9 variants, discriminant fits in 1 byte but is padded
293            let discriminant_byte = *ptr;
294            assert_eq!(
295                discriminant_byte, 0,
296                "Int discriminant should be 0, got {}",
297                discriminant_byte
298            );
299
300            // The i64 value should be at a fixed offset after the discriminant
301            // With C repr, it's typically at offset 8 (discriminant + padding)
302            let value_ptr = ptr.add(8) as *const i64;
303            let stored_value = *value_ptr;
304            assert_eq!(
305                stored_value, 42,
306                "Int value should be 42 at offset 8, got {}",
307                stored_value
308            );
309        }
310    }
311
312    #[test]
313    fn test_value_bool_layout() {
314        let val_true = Value::Bool(true);
315        let val_false = Value::Bool(false);
316        let ptr_true = &val_true as *const Value as *const u8;
317        let ptr_false = &val_false as *const Value as *const u8;
318
319        unsafe {
320            // Bool is variant index 2 (after Int=0, Float=1)
321            let discriminant = *ptr_true;
322            assert_eq!(
323                discriminant, 2,
324                "Bool discriminant should be 2, got {}",
325                discriminant
326            );
327
328            // The bool value should be at offset 8
329            let value_ptr_true = ptr_true.add(8);
330            let value_ptr_false = ptr_false.add(8);
331            assert_eq!(*value_ptr_true, 1, "true should be 1");
332            assert_eq!(*value_ptr_false, 0, "false should be 0");
333        }
334    }
335}