use crate::stack::{Stack, pop, push};
use crate::value::Value;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_time_now(stack: Stack) -> Stack {
let micros = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_micros() as i64)
.unwrap_or(0);
unsafe { push(stack, Value::Int(micros)) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_time_nanos(stack: Stack) -> Stack {
let nanos = elapsed_nanos();
unsafe { push(stack, Value::Int(nanos)) }
}
#[inline]
fn elapsed_nanos() -> i64 {
use std::sync::atomic::{AtomicI64, Ordering};
static BASE_NANOS: AtomicI64 = AtomicI64::new(0);
let current = raw_monotonic_nanos();
let base = BASE_NANOS.load(Ordering::Relaxed);
if base != 0 {
return current.saturating_sub(base);
}
match BASE_NANOS.compare_exchange(0, current, Ordering::Relaxed, Ordering::Relaxed) {
Ok(_) => 0, Err(actual_base) => current.saturating_sub(actual_base), }
}
#[inline]
#[cfg(unix)]
fn raw_monotonic_nanos() -> i64 {
let mut ts = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts);
}
#[allow(clippy::unnecessary_cast)] let secs = (ts.tv_sec as i64).saturating_mul(1_000_000_000);
#[allow(clippy::unnecessary_cast)]
secs.saturating_add(ts.tv_nsec as i64)
}
#[inline]
#[cfg(not(unix))]
fn raw_monotonic_nanos() -> i64 {
use std::sync::OnceLock;
use std::time::Instant;
static BASE: OnceLock<Instant> = OnceLock::new();
let base = BASE.get_or_init(Instant::now);
base.elapsed().as_nanos().try_into().unwrap_or(i64::MAX)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn patch_seq_time_sleep_ms(stack: Stack) -> Stack {
assert!(!stack.is_null(), "time.sleep-ms: stack is empty");
let (rest, value) = unsafe { pop(stack) };
match value {
Value::Int(ms) => {
if ms < 0 {
panic!("time.sleep-ms: duration must be non-negative, got {}", ms);
}
may::coroutine::sleep(Duration::from_millis(ms as u64));
rest
}
_ => panic!(
"time.sleep-ms: expected Int duration on stack, got {:?}",
value
),
}
}
pub use patch_seq_time_nanos as time_nanos;
pub use patch_seq_time_now as time_now;
pub use patch_seq_time_sleep_ms as time_sleep_ms;
#[cfg(test)]
mod tests {
use super::*;
use crate::stack::pop;
use std::time::Instant;
#[test]
fn test_time_now_returns_positive() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = patch_seq_time_now(stack);
let (_, value) = pop(stack);
match value {
Value::Int(micros) => {
assert!(micros > 1_577_836_800_000_000); }
_ => panic!("Expected Int"),
}
}
}
#[test]
fn test_time_nanos_monotonic() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = patch_seq_time_nanos(stack);
let (_, value1) = pop(stack);
std::thread::sleep(Duration::from_micros(100));
let stack = crate::stack::alloc_test_stack();
let stack = patch_seq_time_nanos(stack);
let (_, value2) = pop(stack);
match (value1, value2) {
(Value::Int(t1), Value::Int(t2)) => {
assert!(t2 > t1, "time.nanos should be monotonically increasing");
}
_ => panic!("Expected Int values"),
}
}
}
#[test]
fn test_time_nanos_cross_thread() {
use std::sync::mpsc;
use std::thread;
let (tx1, rx1) = mpsc::channel();
let (tx2, rx2) = mpsc::channel();
let t1 = raw_monotonic_nanos();
let handle = thread::spawn(move || {
let t2 = raw_monotonic_nanos();
tx1.send(t2).unwrap();
rx2.recv().unwrap() });
let t2 = rx1.recv().unwrap();
let t3 = raw_monotonic_nanos();
tx2.send(()).unwrap();
handle.join().unwrap();
assert!(t2 > t1, "t2 ({}) should be > t1 ({})", t2, t1);
assert!(t3 > t2, "t3 ({}) should be > t2 ({})", t3, t2);
}
#[test]
fn test_time_sleep_ms() {
unsafe {
let stack = crate::stack::alloc_test_stack();
let stack = push(stack, Value::Int(1));
let start = Instant::now();
let _stack = patch_seq_time_sleep_ms(stack);
let elapsed = start.elapsed();
assert!(elapsed >= Duration::from_millis(1));
}
}
}