1use crate::stack::{Stack, pop, push};
29use crate::value::Value;
30use std::time::{Duration, SystemTime, UNIX_EPOCH};
31
32#[unsafe(no_mangle)]
42pub unsafe extern "C" fn patch_seq_time_now(stack: Stack) -> Stack {
43 let micros = SystemTime::now()
44 .duration_since(UNIX_EPOCH)
45 .map(|d| d.as_micros() as i64)
46 .unwrap_or(0);
47
48 unsafe { push(stack, Value::Int(micros)) }
49}
50
51#[unsafe(no_mangle)]
62pub unsafe extern "C" fn patch_seq_time_nanos(stack: Stack) -> Stack {
63 let nanos = elapsed_nanos();
64 unsafe { push(stack, Value::Int(nanos)) }
65}
66
67#[inline]
72fn elapsed_nanos() -> i64 {
73 use std::sync::atomic::{AtomicI64, Ordering};
74
75 static BASE_NANOS: AtomicI64 = AtomicI64::new(0);
77
78 let current = raw_monotonic_nanos();
79
80 let base = BASE_NANOS.load(Ordering::Relaxed);
82 if base != 0 {
83 return current.saturating_sub(base);
84 }
85
86 match BASE_NANOS.compare_exchange(0, current, Ordering::Relaxed, Ordering::Relaxed) {
88 Ok(_) => 0, Err(actual_base) => current.saturating_sub(actual_base), }
91}
92
93#[inline]
101#[cfg(unix)]
102fn raw_monotonic_nanos() -> i64 {
103 let mut ts = libc::timespec {
104 tv_sec: 0,
105 tv_nsec: 0,
106 };
107 unsafe {
109 libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
110 }
111 #[allow(clippy::unnecessary_cast)] let secs = (ts.tv_sec as i64).saturating_mul(1_000_000_000);
115 #[allow(clippy::unnecessary_cast)]
116 secs.saturating_add(ts.tv_nsec as i64)
117}
118
119#[inline]
122#[cfg(not(unix))]
123fn raw_monotonic_nanos() -> i64 {
124 use std::sync::OnceLock;
125 use std::time::Instant;
126
127 static BASE: OnceLock<Instant> = OnceLock::new();
128 let base = BASE.get_or_init(Instant::now);
129 base.elapsed().as_nanos().try_into().unwrap_or(i64::MAX)
130}
131
132#[unsafe(no_mangle)]
142pub unsafe extern "C" fn patch_seq_time_sleep_ms(stack: Stack) -> Stack {
143 assert!(!stack.is_null(), "time.sleep-ms: stack is empty");
144
145 let (rest, value) = unsafe { pop(stack) };
146
147 match value {
148 Value::Int(ms) => {
149 if ms < 0 {
150 panic!("time.sleep-ms: duration must be non-negative, got {}", ms);
151 }
152
153 may::coroutine::sleep(Duration::from_millis(ms as u64));
155
156 rest
157 }
158 _ => panic!(
159 "time.sleep-ms: expected Int duration on stack, got {:?}",
160 value
161 ),
162 }
163}
164
165pub use patch_seq_time_nanos as time_nanos;
167pub use patch_seq_time_now as time_now;
168pub use patch_seq_time_sleep_ms as time_sleep_ms;
169
170#[cfg(test)]
171mod tests {
172 use super::*;
173 use crate::stack::pop;
174 use std::time::Instant;
175
176 #[test]
177 fn test_time_now_returns_positive() {
178 unsafe {
179 let stack = crate::stack::alloc_test_stack();
180 let stack = patch_seq_time_now(stack);
181 let (_, value) = pop(stack);
182
183 match value {
184 Value::Int(micros) => {
185 assert!(micros > 1_577_836_800_000_000); }
188 _ => panic!("Expected Int"),
189 }
190 }
191 }
192
193 #[test]
194 fn test_time_nanos_monotonic() {
195 unsafe {
196 let stack = crate::stack::alloc_test_stack();
197 let stack = patch_seq_time_nanos(stack);
198 let (_, value1) = pop(stack);
199
200 std::thread::sleep(Duration::from_micros(100));
202
203 let stack = crate::stack::alloc_test_stack();
204 let stack = patch_seq_time_nanos(stack);
205 let (_, value2) = pop(stack);
206
207 match (value1, value2) {
208 (Value::Int(t1), Value::Int(t2)) => {
209 assert!(t2 > t1, "time.nanos should be monotonically increasing");
210 }
211 _ => panic!("Expected Int values"),
212 }
213 }
214 }
215
216 #[test]
217 fn test_time_nanos_cross_thread() {
218 use std::sync::mpsc;
220 use std::thread;
221
222 let (tx1, rx1) = mpsc::channel();
223 let (tx2, rx2) = mpsc::channel();
224
225 let t1 = raw_monotonic_nanos();
227
228 let handle = thread::spawn(move || {
230 let t2 = raw_monotonic_nanos();
231 tx1.send(t2).unwrap();
232 rx2.recv().unwrap() });
234
235 let t2 = rx1.recv().unwrap();
236
237 let t3 = raw_monotonic_nanos();
239 tx2.send(()).unwrap();
240 handle.join().unwrap();
241
242 assert!(t2 > t1, "t2 ({}) should be > t1 ({})", t2, t1);
244 assert!(t3 > t2, "t3 ({}) should be > t2 ({})", t3, t2);
245 }
246
247 #[test]
248 fn test_time_sleep_ms() {
249 unsafe {
250 let stack = crate::stack::alloc_test_stack();
252 let stack = push(stack, Value::Int(1));
253
254 let start = Instant::now();
255 let _stack = patch_seq_time_sleep_ms(stack);
256 let elapsed = start.elapsed();
257
258 assert!(elapsed >= Duration::from_millis(1));
260 }
262 }
263}