seq_runtime/
time_ops.rs

1//! Time operations for Seq
2//!
3//! Provides timing primitives for performance measurement and delays.
4//!
5//! # Usage from Seq
6//!
7//! ```seq
8//! time.now      # ( -- Int ) microseconds since epoch
9//! time.nanos    # ( -- Int ) nanoseconds (monotonic, for timing)
10//! 100 time.sleep-ms  # ( Int -- ) sleep for N milliseconds
11//! ```
12//!
13//! # Example: Measuring execution time
14//!
15//! ```seq
16//! : benchmark ( -- )
17//!   time.nanos    # start time
18//!   do-work
19//!   time.nanos    # end time
20//!   swap -        # elapsed nanos
21//!   1000000 /     # convert to ms
22//!   "Elapsed: " write
23//!   int->string write
24//!   "ms" write-line
25//! ;
26//! ```
27
28use crate::stack::{Stack, pop, push};
29use crate::value::Value;
30use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
31
32// Thread-local monotonic clock base for consistent nanosecond timing
33thread_local! {
34    static CLOCK_BASE: Instant = Instant::now();
35}
36
37/// Get current time in microseconds since Unix epoch
38///
39/// Stack effect: ( -- Int )
40///
41/// Returns wall-clock time. Good for timestamps.
42/// For measuring durations, prefer `time.nanos` which uses a monotonic clock.
43///
44/// # Safety
45/// - `stack` must be a valid stack pointer (may be null for empty stack)
46#[unsafe(no_mangle)]
47pub unsafe extern "C" fn patch_seq_time_now(stack: Stack) -> Stack {
48    let micros = SystemTime::now()
49        .duration_since(UNIX_EPOCH)
50        .map(|d| d.as_micros() as i64)
51        .unwrap_or(0);
52
53    unsafe { push(stack, Value::Int(micros)) }
54}
55
56/// Get monotonic nanoseconds for precise timing
57///
58/// Stack effect: ( -- Int )
59///
60/// Returns nanoseconds from an arbitrary starting point (process start).
61/// Uses a monotonic clock - values always increase, unaffected by system
62/// clock changes. Perfect for measuring elapsed time.
63///
64/// Note: Saturates at i64::MAX (~292 years of uptime) to prevent overflow.
65///
66/// # Safety
67/// - `stack` must be a valid stack pointer (may be null for empty stack)
68#[unsafe(no_mangle)]
69pub unsafe extern "C" fn patch_seq_time_nanos(stack: Stack) -> Stack {
70    let nanos = CLOCK_BASE.with(|base| base.elapsed().as_nanos().try_into().unwrap_or(i64::MAX));
71    unsafe { push(stack, Value::Int(nanos)) }
72}
73
74/// Sleep for a specified number of milliseconds
75///
76/// Stack effect: ( Int -- )
77///
78/// Yields the current strand to the scheduler while sleeping.
79/// Uses `may::coroutine::sleep` for cooperative scheduling.
80///
81/// # Safety
82/// - `stack` must be a valid, non-null stack pointer with an Int on top
83#[unsafe(no_mangle)]
84pub unsafe extern "C" fn patch_seq_time_sleep_ms(stack: Stack) -> Stack {
85    assert!(!stack.is_null(), "time.sleep-ms: stack is empty");
86
87    let (rest, value) = unsafe { pop(stack) };
88
89    match value {
90        Value::Int(ms) => {
91            if ms < 0 {
92                panic!("time.sleep-ms: duration must be non-negative, got {}", ms);
93            }
94
95            // Use may's coroutine-aware sleep for cooperative scheduling
96            may::coroutine::sleep(Duration::from_millis(ms as u64));
97
98            rest
99        }
100        _ => panic!(
101            "time.sleep-ms: expected Int duration on stack, got {:?}",
102            value
103        ),
104    }
105}
106
107// Public re-exports
108pub use patch_seq_time_nanos as time_nanos;
109pub use patch_seq_time_now as time_now;
110pub use patch_seq_time_sleep_ms as time_sleep_ms;
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::stack::pop;
116    use std::ptr;
117
118    #[test]
119    fn test_time_now_returns_positive() {
120        unsafe {
121            let stack = ptr::null_mut();
122            let stack = patch_seq_time_now(stack);
123            let (_, value) = pop(stack);
124
125            match value {
126                Value::Int(micros) => {
127                    // Should be a reasonable timestamp (after year 2020)
128                    assert!(micros > 1_577_836_800_000_000); // 2020-01-01
129                }
130                _ => panic!("Expected Int"),
131            }
132        }
133    }
134
135    #[test]
136    fn test_time_nanos_monotonic() {
137        unsafe {
138            let stack = ptr::null_mut();
139            let stack = patch_seq_time_nanos(stack);
140            let (_, value1) = pop(stack);
141
142            // Small delay
143            std::thread::sleep(Duration::from_micros(100));
144
145            let stack = ptr::null_mut();
146            let stack = patch_seq_time_nanos(stack);
147            let (_, value2) = pop(stack);
148
149            match (value1, value2) {
150                (Value::Int(t1), Value::Int(t2)) => {
151                    assert!(t2 > t1, "time.nanos should be monotonically increasing");
152                }
153                _ => panic!("Expected Int values"),
154            }
155        }
156    }
157
158    #[test]
159    fn test_time_sleep_ms() {
160        unsafe {
161            // Push 1ms sleep duration
162            let stack = ptr::null_mut();
163            let stack = push(stack, Value::Int(1));
164
165            let start = Instant::now();
166            let stack = patch_seq_time_sleep_ms(stack);
167            let elapsed = start.elapsed();
168
169            // Should sleep at least 1ms
170            assert!(elapsed >= Duration::from_millis(1));
171            // Stack should be empty after popping the duration
172            assert!(stack.is_null());
173        }
174    }
175}