seq_runtime/
closures.rs

1//! Closure support for Seq
2//!
3//! Provides runtime functions for creating and managing closures (quotations with captured environments).
4//!
5//! A closure consists of:
6//! - Function pointer (the compiled quotation code)
7//! - Environment (Arc-shared array of captured values for TCO support)
8//!
9//! Note: These extern "C" functions use Value and slice pointers, which aren't technically FFI-safe,
10//! but they work correctly when called from LLVM-generated code (not actual C interop).
11//!
12//! ## Type Support Status
13//!
14//! Currently supported capture types:
15//! - **Int** (via `env_get_int`)
16//! - **Bool** (via `env_get_bool`) - returns i64 (0/1)
17//! - **Float** (via `env_get_float`) - returns f64
18//! - **String** (via `env_get_string`)
19//! - **Quotation** (via `env_get_quotation`) - returns function pointer as i64
20//!
21//! Types to be added in future PR:
22//! - Closure (nested closures with their own environments)
23//! - Variant (tagged unions)
24//!
25//! See https://github.com/navicore/patch-seq for roadmap.
26
27use crate::stack::{Stack, pop, push};
28use crate::value::Value;
29use std::sync::Arc;
30
31/// Maximum number of captured values allowed in a closure environment.
32/// This prevents unbounded memory allocation and potential resource exhaustion.
33pub const MAX_CAPTURES: usize = 1024;
34
35/// Create a closure environment (array of captured values)
36///
37/// Called from generated LLVM code to allocate space for captured values.
38/// Returns a raw pointer to a boxed slice that will be filled with values.
39///
40/// # Safety
41/// - Caller must populate the environment with `env_set` before using
42/// - Caller must eventually pass ownership to a Closure value (via `make_closure`)
43// Allow improper_ctypes_definitions: Called from LLVM IR (not C), both sides understand layout
44#[allow(improper_ctypes_definitions)]
45#[unsafe(no_mangle)]
46pub extern "C" fn patch_seq_create_env(size: i32) -> *mut [Value] {
47    if size < 0 {
48        panic!("create_env: size cannot be negative: {}", size);
49    }
50
51    let size_usize = size as usize;
52    if size_usize > MAX_CAPTURES {
53        panic!(
54            "create_env: size {} exceeds MAX_CAPTURES ({})",
55            size_usize, MAX_CAPTURES
56        );
57    }
58
59    let mut vec: Vec<Value> = Vec::with_capacity(size_usize);
60
61    // Fill with placeholder values (will be replaced by env_set)
62    for _ in 0..size {
63        vec.push(Value::Int(0));
64    }
65
66    Box::into_raw(vec.into_boxed_slice())
67}
68
69/// Set a value in the closure environment
70///
71/// Called from generated LLVM code to populate captured values.
72///
73/// # Safety
74/// - env must be a valid pointer from `create_env`
75/// - index must be in bounds [0, size)
76/// - env must not have been passed to `make_closure` yet
77// Allow improper_ctypes_definitions: Called from LLVM IR (not C), both sides understand layout
78#[allow(improper_ctypes_definitions)]
79#[unsafe(no_mangle)]
80pub unsafe extern "C" fn patch_seq_env_set(env: *mut [Value], index: i32, value: Value) {
81    if env.is_null() {
82        panic!("env_set: null environment pointer");
83    }
84
85    if index < 0 {
86        panic!("env_set: index cannot be negative: {}", index);
87    }
88
89    let env_slice = unsafe { &mut *env };
90    let idx = index as usize;
91
92    if idx >= env_slice.len() {
93        panic!(
94            "env_set: index {} out of bounds for environment of size {}",
95            index,
96            env_slice.len()
97        );
98    }
99
100    env_slice[idx] = value;
101}
102
103/// Get a value from the closure environment
104///
105/// Called from generated closure function code to access captured values.
106/// Takes environment as separate data pointer and length (since LLVM can't handle fat pointers).
107///
108/// # Safety
109/// - env_data must be a valid pointer to an array of Values
110/// - env_len must match the actual array length
111/// - index must be in bounds [0, env_len)
112// Allow improper_ctypes_definitions: Called from LLVM IR (not C), both sides understand layout
113#[allow(improper_ctypes_definitions)]
114#[unsafe(no_mangle)]
115pub unsafe extern "C" fn patch_seq_env_get(
116    env_data: *const Value,
117    env_len: usize,
118    index: i32,
119) -> Value {
120    if env_data.is_null() {
121        panic!("env_get: null environment pointer");
122    }
123
124    if index < 0 {
125        panic!("env_get: index cannot be negative: {}", index);
126    }
127
128    let idx = index as usize;
129
130    if idx >= env_len {
131        panic!(
132            "env_get: index {} out of bounds for environment of size {}",
133            index, env_len
134        );
135    }
136
137    // Clone the value from the environment
138    unsafe { (*env_data.add(idx)).clone() }
139}
140
141/// Get an Int value from the closure environment
142///
143/// This is a type-specific helper that avoids passing large Value enums through LLVM IR.
144/// Returns primitive i64 instead of Value to avoid FFI issues with by-value enum passing.
145///
146/// # Safety
147/// - env_data must be a valid pointer to an array of Values
148/// - env_len must match the actual array length
149/// - index must be in bounds [0, env_len)
150/// - The value at index must be Value::Int
151///
152/// # FFI Notes
153/// This function is ONLY called from LLVM-generated code, not from external C code.
154/// The signature is safe for LLVM IR but would be undefined behavior if called from C
155/// with incorrect assumptions about type layout.
156#[unsafe(no_mangle)]
157pub unsafe extern "C" fn patch_seq_env_get_int(
158    env_data: *const Value,
159    env_len: usize,
160    index: i32,
161) -> i64 {
162    if env_data.is_null() {
163        panic!("env_get_int: null environment pointer");
164    }
165
166    if index < 0 {
167        panic!("env_get_int: index cannot be negative: {}", index);
168    }
169
170    let idx = index as usize;
171
172    if idx >= env_len {
173        panic!(
174            "env_get_int: index {} out of bounds for environment of size {}",
175            index, env_len
176        );
177    }
178
179    // Access the value at the index
180    let value = unsafe { &*env_data.add(idx) };
181
182    match value {
183        Value::Int(n) => *n,
184        _ => panic!(
185            "env_get_int: expected Int at index {}, got {:?}",
186            index, value
187        ),
188    }
189}
190
191/// Get a String value from the environment at the given index
192///
193/// # Safety
194/// - env_data must be a valid pointer to an array of Values
195/// - env_len must be the actual length of that array
196/// - index must be within bounds
197/// - The value at index must be a String
198///
199/// This function returns a SeqString by-value.
200/// This is safe for FFI because it's only called from LLVM-generated code, not actual C code.
201#[allow(improper_ctypes_definitions)]
202#[unsafe(no_mangle)]
203pub unsafe extern "C" fn patch_seq_env_get_string(
204    env_data: *const Value,
205    env_len: usize,
206    index: i32,
207) -> crate::seqstring::SeqString {
208    if env_data.is_null() {
209        panic!("env_get_string: null environment pointer");
210    }
211
212    if index < 0 {
213        panic!("env_get_string: index cannot be negative: {}", index);
214    }
215
216    let idx = index as usize;
217
218    if idx >= env_len {
219        panic!(
220            "env_get_string: index {} out of bounds for environment of size {}",
221            index, env_len
222        );
223    }
224
225    // Access the value at the index
226    let value = unsafe { &*env_data.add(idx) };
227
228    match value {
229        Value::String(s) => s.clone(),
230        _ => panic!(
231            "env_get_string: expected String at index {}, got {:?}",
232            index, value
233        ),
234    }
235}
236
237/// Push a String from the closure environment directly onto the stack
238///
239/// This combines getting and pushing in one operation to avoid returning
240/// SeqString by value through FFI, which has calling convention issues on Linux.
241///
242/// # Safety
243/// - Stack pointer must be valid
244/// - env_data must be a valid pointer to an array of Values
245/// - env_len must match the actual array length
246/// - index must be in bounds [0, env_len)
247/// - The value at index must be Value::String
248#[unsafe(no_mangle)]
249pub unsafe extern "C" fn patch_seq_env_push_string(
250    stack: Stack,
251    env_data: *const Value,
252    env_len: usize,
253    index: i32,
254) -> Stack {
255    if env_data.is_null() {
256        panic!("env_push_string: null environment pointer");
257    }
258
259    if index < 0 {
260        panic!("env_push_string: index cannot be negative: {}", index);
261    }
262
263    let idx = index as usize;
264
265    if idx >= env_len {
266        panic!(
267            "env_push_string: index {} out of bounds for environment of size {}",
268            index, env_len
269        );
270    }
271
272    // Access the value at the index
273    let value = unsafe { &*env_data.add(idx) };
274
275    match value {
276        Value::String(s) => unsafe { push(stack, Value::String(s.clone())) },
277        _ => panic!(
278            "env_push_string: expected String at index {}, got {:?}",
279            index, value
280        ),
281    }
282}
283
284/// Get a Bool value from the closure environment
285///
286/// Returns i64 (0 for false, 1 for true) to match LLVM IR representation.
287/// Bools are stored as i64 in the generated code for simplicity.
288///
289/// # Safety
290/// - env_data must be a valid pointer to an array of Values
291/// - env_len must match the actual array length
292/// - index must be in bounds [0, env_len)
293/// - The value at index must be Value::Bool
294#[unsafe(no_mangle)]
295pub unsafe extern "C" fn patch_seq_env_get_bool(
296    env_data: *const Value,
297    env_len: usize,
298    index: i32,
299) -> i64 {
300    if env_data.is_null() {
301        panic!("env_get_bool: null environment pointer");
302    }
303
304    if index < 0 {
305        panic!("env_get_bool: index cannot be negative: {}", index);
306    }
307
308    let idx = index as usize;
309
310    if idx >= env_len {
311        panic!(
312            "env_get_bool: index {} out of bounds for environment of size {}",
313            index, env_len
314        );
315    }
316
317    let value = unsafe { &*env_data.add(idx) };
318
319    match value {
320        Value::Bool(b) => {
321            if *b {
322                1
323            } else {
324                0
325            }
326        }
327        _ => panic!(
328            "env_get_bool: expected Bool at index {}, got {:?}",
329            index, value
330        ),
331    }
332}
333
334/// Get a Float value from the closure environment
335///
336/// Returns f64 directly for efficient LLVM IR integration.
337///
338/// # Safety
339/// - env_data must be a valid pointer to an array of Values
340/// - env_len must match the actual array length
341/// - index must be in bounds [0, env_len)
342/// - The value at index must be Value::Float
343#[unsafe(no_mangle)]
344pub unsafe extern "C" fn patch_seq_env_get_float(
345    env_data: *const Value,
346    env_len: usize,
347    index: i32,
348) -> f64 {
349    if env_data.is_null() {
350        panic!("env_get_float: null environment pointer");
351    }
352
353    if index < 0 {
354        panic!("env_get_float: index cannot be negative: {}", index);
355    }
356
357    let idx = index as usize;
358
359    if idx >= env_len {
360        panic!(
361            "env_get_float: index {} out of bounds for environment of size {}",
362            index, env_len
363        );
364    }
365
366    let value = unsafe { &*env_data.add(idx) };
367
368    match value {
369        Value::Float(f) => *f,
370        _ => panic!(
371            "env_get_float: expected Float at index {}, got {:?}",
372            index, value
373        ),
374    }
375}
376
377/// Get a Quotation impl_ function pointer from the closure environment
378///
379/// Returns i64 (the impl_ function pointer as usize) for LLVM IR.
380/// Returns the tailcc impl_ pointer for TCO when called from compiled code.
381/// Quotations are stateless, so only the function pointer is needed.
382///
383/// # Safety
384/// - env_data must be a valid pointer to an array of Values
385/// - env_len must match the actual array length
386/// - index must be in bounds [0, env_len)
387/// - The value at index must be Value::Quotation
388#[unsafe(no_mangle)]
389pub unsafe extern "C" fn patch_seq_env_get_quotation(
390    env_data: *const Value,
391    env_len: usize,
392    index: i32,
393) -> i64 {
394    if env_data.is_null() {
395        panic!("env_get_quotation: null environment pointer");
396    }
397
398    if index < 0 {
399        panic!("env_get_quotation: index cannot be negative: {}", index);
400    }
401
402    let idx = index as usize;
403
404    if idx >= env_len {
405        panic!(
406            "env_get_quotation: index {} out of bounds for environment of size {}",
407            index, env_len
408        );
409    }
410
411    let value = unsafe { &*env_data.add(idx) };
412
413    match value {
414        Value::Quotation { impl_, .. } => *impl_ as i64,
415        _ => panic!(
416            "env_get_quotation: expected Quotation at index {}, got {:?}",
417            index, value
418        ),
419    }
420}
421
422/// Create a closure value from a function pointer and environment
423///
424/// Takes ownership of the environment (converts raw pointer to Arc).
425/// Arc enables TCO: no cleanup needed after tail calls.
426///
427/// # Safety
428/// - fn_ptr must be a valid function pointer (will be transmuted when called)
429/// - env must be a valid pointer from `create_env`, fully populated via `env_set`
430/// - env ownership is transferred to the Closure value
431// Allow improper_ctypes_definitions: Called from LLVM IR (not C), both sides understand layout
432#[allow(improper_ctypes_definitions)]
433#[unsafe(no_mangle)]
434pub unsafe extern "C" fn patch_seq_make_closure(fn_ptr: u64, env: *mut [Value]) -> Value {
435    if fn_ptr == 0 {
436        panic!("make_closure: null function pointer");
437    }
438
439    if env.is_null() {
440        panic!("make_closure: null environment pointer");
441    }
442
443    // Take ownership of the environment and convert to Arc for TCO support
444    let env_box = unsafe { Box::from_raw(env) };
445    let env_arc: Arc<[Value]> = Arc::from(env_box);
446
447    Value::Closure {
448        fn_ptr: fn_ptr as usize,
449        env: env_arc,
450    }
451}
452
453/// Create closure from function pointer and stack values (all-in-one helper)
454///
455/// Pops `capture_count` values from stack (top-down order), creates environment,
456/// makes closure, and pushes it onto the stack.
457///
458/// This is a convenience function for LLVM codegen that handles the entire
459/// closure creation process in one call. Uses Arc for TCO support.
460///
461/// # Safety
462/// - fn_ptr must be a valid function pointer
463/// - stack must have at least `capture_count` values
464#[unsafe(no_mangle)]
465pub unsafe extern "C" fn patch_seq_push_closure(
466    mut stack: Stack,
467    fn_ptr: u64,
468    capture_count: i32,
469) -> Stack {
470    if fn_ptr == 0 {
471        panic!("push_closure: null function pointer");
472    }
473
474    if capture_count < 0 {
475        panic!(
476            "push_closure: capture_count cannot be negative: {}",
477            capture_count
478        );
479    }
480
481    let count = capture_count as usize;
482
483    // Pop values from stack (captures are in top-down order)
484    let mut captures: Vec<Value> = Vec::with_capacity(count);
485    for _ in 0..count {
486        let (new_stack, value) = unsafe { pop(stack) };
487        captures.push(value);
488        stack = new_stack;
489    }
490
491    // Create closure value with Arc for TCO support
492    let closure = Value::Closure {
493        fn_ptr: fn_ptr as usize,
494        env: Arc::from(captures.into_boxed_slice()),
495    };
496
497    // Push onto stack
498    unsafe { push(stack, closure) }
499}
500
501// Public re-exports with short names for internal use
502pub use patch_seq_create_env as create_env;
503pub use patch_seq_env_get as env_get;
504pub use patch_seq_env_get_bool as env_get_bool;
505pub use patch_seq_env_get_float as env_get_float;
506pub use patch_seq_env_get_int as env_get_int;
507pub use patch_seq_env_get_quotation as env_get_quotation;
508pub use patch_seq_env_get_string as env_get_string;
509pub use patch_seq_env_push_string as env_push_string;
510pub use patch_seq_env_set as env_set;
511pub use patch_seq_make_closure as make_closure;
512pub use patch_seq_push_closure as push_closure;
513
514#[cfg(test)]
515mod tests {
516    use super::*;
517
518    #[test]
519    fn test_create_env() {
520        let env = create_env(3);
521        assert!(!env.is_null());
522
523        // Clean up
524        unsafe {
525            let _ = Box::from_raw(env);
526        }
527    }
528
529    #[test]
530    fn test_env_set_and_get() {
531        let env = create_env(3);
532
533        // Set values
534        unsafe {
535            env_set(env, 0, Value::Int(42));
536            env_set(env, 1, Value::Bool(true));
537            env_set(env, 2, Value::Int(99));
538        }
539
540        // Get values (convert to data pointer + length)
541        unsafe {
542            let env_slice = &*env;
543            let env_data = env_slice.as_ptr();
544            let env_len = env_slice.len();
545            assert_eq!(env_get(env_data, env_len, 0), Value::Int(42));
546            assert_eq!(env_get(env_data, env_len, 1), Value::Bool(true));
547            assert_eq!(env_get(env_data, env_len, 2), Value::Int(99));
548        }
549
550        // Clean up
551        unsafe {
552            let _ = Box::from_raw(env);
553        }
554    }
555
556    #[test]
557    fn test_make_closure() {
558        let env = create_env(2);
559
560        unsafe {
561            env_set(env, 0, Value::Int(5));
562            env_set(env, 1, Value::Int(10));
563
564            let closure = make_closure(0x1234, env);
565
566            match closure {
567                Value::Closure { fn_ptr, env } => {
568                    assert_eq!(fn_ptr, 0x1234);
569                    assert_eq!(env.len(), 2);
570                    assert_eq!(env[0], Value::Int(5));
571                    assert_eq!(env[1], Value::Int(10));
572                }
573                _ => panic!("Expected Closure value"),
574            }
575        }
576    }
577
578    // Note: We don't test panic behavior for FFI functions as they use
579    // extern "C" which cannot unwind. The functions will still panic at runtime
580    // if called incorrectly, but we can't test that behavior with #[should_panic].
581
582    #[test]
583    fn test_push_closure() {
584        use crate::stack::{pop, push};
585        use crate::value::Value;
586
587        // Create a stack with some values
588        let mut stack = std::ptr::null_mut();
589        stack = unsafe { push(stack, Value::Int(10)) };
590        stack = unsafe { push(stack, Value::Int(5)) };
591
592        // Create a closure that captures both values
593        let fn_ptr = 0x1234;
594        stack = unsafe { push_closure(stack, fn_ptr, 2) };
595
596        // Pop the closure
597        let (stack, closure_value) = unsafe { pop(stack) };
598
599        // Verify it's a closure with correct captures
600        match closure_value {
601            Value::Closure { fn_ptr: fp, env } => {
602                assert_eq!(fp, fn_ptr as usize);
603                assert_eq!(env.len(), 2);
604                assert_eq!(env[0], Value::Int(5)); // Top of stack
605                assert_eq!(env[1], Value::Int(10)); // Second from top
606            }
607            _ => panic!("Expected Closure value, got {:?}", closure_value),
608        }
609
610        // Stack should be empty now
611        assert!(stack.is_null());
612    }
613
614    #[test]
615    fn test_push_closure_zero_captures() {
616        use crate::stack::pop;
617        use crate::value::Value;
618
619        // Create empty stack
620        let stack = std::ptr::null_mut();
621
622        // Create a closure with no captures
623        let fn_ptr = 0x5678;
624        let stack = unsafe { push_closure(stack, fn_ptr, 0) };
625
626        // Pop the closure
627        let (stack, closure_value) = unsafe { pop(stack) };
628
629        // Verify it's a closure with no captures
630        match closure_value {
631            Value::Closure { fn_ptr: fp, env } => {
632                assert_eq!(fp, fn_ptr as usize);
633                assert_eq!(env.len(), 0);
634            }
635            _ => panic!("Expected Closure value, got {:?}", closure_value),
636        }
637
638        // Stack should be empty
639        assert!(stack.is_null());
640    }
641
642    #[test]
643    fn test_env_get_bool() {
644        let env = create_env(2);
645
646        unsafe {
647            env_set(env, 0, Value::Bool(true));
648            env_set(env, 1, Value::Bool(false));
649
650            let env_slice = &*env;
651            let env_data = env_slice.as_ptr();
652            let env_len = env_slice.len();
653
654            assert_eq!(env_get_bool(env_data, env_len, 0), 1);
655            assert_eq!(env_get_bool(env_data, env_len, 1), 0);
656
657            let _ = Box::from_raw(env);
658        }
659    }
660
661    #[test]
662    fn test_env_get_float() {
663        let env = create_env(2);
664
665        unsafe {
666            env_set(env, 0, Value::Float(1.234));
667            env_set(env, 1, Value::Float(-5.678));
668
669            let env_slice = &*env;
670            let env_data = env_slice.as_ptr();
671            let env_len = env_slice.len();
672
673            assert!((env_get_float(env_data, env_len, 0) - 1.234).abs() < 0.0001);
674            assert!((env_get_float(env_data, env_len, 1) - (-5.678)).abs() < 0.0001);
675
676            let _ = Box::from_raw(env);
677        }
678    }
679
680    #[test]
681    fn test_env_get_quotation() {
682        let env = create_env(1);
683        let wrapper: usize = 0xDEADBEEF;
684        let impl_: usize = 0xCAFEBABE;
685
686        unsafe {
687            env_set(env, 0, Value::Quotation { wrapper, impl_ });
688
689            let env_slice = &*env;
690            let env_data = env_slice.as_ptr();
691            let env_len = env_slice.len();
692
693            // env_get_quotation returns the impl_ pointer for TCO
694            assert_eq!(env_get_quotation(env_data, env_len, 0), impl_ as i64);
695
696            let _ = Box::from_raw(env);
697        }
698    }
699}