From 7bf54fc7e0ac8e16bee277a3804d30300b7bed26 Mon Sep 17 00:00:00 2001 From: Bernie Innocenti Date: Sat, 24 Oct 2026 00:23:55 +0900 Subject: [PATCH] timer.device: GetSysTime and ReadEClock resolve microseconds Both read the host clock through DateStamp's 1/50s ticks, so without --clock-mhz every interval a program timed with ReadEClock was a multiple of 20 ms. Co-authored-by: Claude Opus 5.5 --- crates/volamos-core/src/dosdate.rs | 19 +++++++---------- crates/volamos-core/src/exectask.rs | 32 +++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/crates/volamos-core/src/dosdate.rs b/crates/volamos-core/src/dosdate.rs index 45b66ff..e6a514c 100644 --- a/crates/volamos-core/src/dosdate.rs +++ b/crates/volamos-core/src/dosdate.rs @@ -54,7 +54,7 @@ const TICKS_PER_SECOND: u32 = 50; /// Days from the Unix epoch (1970-01-01) to the Amiga epoch /// (1978-01-01): 8 full years, 2 of them leap (1972, 1976). -const UNIX_TO_AMIGA_EPOCH_DAYS: i64 = 8 * 365 + 2; +pub(crate) const UNIX_TO_AMIGA_EPOCH_DAYS: i64 = 8 * 365 + 2; /// Computes `(ds_Days, ds_Minute, ds_Tick)` for the current host wall /// clock. Saturates to `0` if the host clock is somehow before the @@ -74,17 +74,12 @@ pub(crate) fn now_as_datestamp() -> (i32, i32, i32) { let secs_of_day = amiga_secs % 86_400; let minute = secs_of_day / 60; let secs_of_minute = secs_of_day % 60; - // Sub-second precision (the fractional tick within the current - // second) matters beyond DateStamp's own 1/50s granularity: this - // same (days, minute, tick) triple backs `GetSysTime`'s `tv_micro` - // (see `host_time_secs_micro` in `crate::exectask`) and - // `ReadEClock`'s absolute tick count, both of which real callers use - // for sub-second elapsed-time measurement (e.g. `WaitSelect`'s - // conformance-suite timeout tests). Dropping the fractional second - // here (as an earlier version did, via `.as_secs()` alone) made - // `tick` always an exact multiple of `TICKS_PER_SECOND`, silently - // pinning `tv_micro` to 0 forever -- a real bug, not a rounding - // nicety. + // Keep the fractional second: dropping it (as an earlier version + // did, via `.as_secs()` alone) made `tick` always an exact multiple + // of `TICKS_PER_SECOND`. `GetSysTime` and `ReadEClock` were built on + // this triple then, and that pinned `tv_micro` to 0 forever; they + // now read the host clock directly (see `host_time_secs_micro` in + // `crate::exectask`) to get microseconds. let tick = secs_of_minute * i64::from(TICKS_PER_SECOND) + i64::from(now.subsec_nanos()) / 20_000_000; (days as i32, minute as i32, tick as i32) diff --git a/crates/volamos-core/src/exectask.rs b/crates/volamos-core/src/exectask.rs index fd849b8..aab13e9 100644 --- a/crates/volamos-core/src/exectask.rs +++ b/crates/volamos-core/src/exectask.rs @@ -1388,15 +1388,21 @@ fn abort_io_handler(_ctx: &mut HandlerContext<'_, C>) -> Result<(), Disp /// plus microseconds -- the value `GetSysTime`/`TR_GETSYSTIME` report, /// per the RKRM Devices book's "Timer Device" chapter ("By convention, /// it tells how many seconds have passed since midnight, January 1, -/// 1978"). Built on [`crate::dosdate::now_as_datestamp`], the same -/// clock every other date-facing call here uses. Also reused by -/// `crate::intuition`'s `CurrentTime`, which is documented to report -/// exactly this same epoch/units. +/// 1978"). Reads the same host clock as +/// [`crate::dosdate::now_as_datestamp`], but keeps the microseconds +/// instead of going through DateStamp's 1/50s ticks: `ReadEClock` is +/// built on this too, and programs time short intervals with it. Also +/// reused by `crate::intuition`'s `CurrentTime`, which is documented to +/// report exactly this same epoch/units. pub(crate) fn host_time_secs_micro() -> (u32, u32) { - let (days, minute, tick) = crate::dosdate::now_as_datestamp(); - let secs = (days as i64) * 86_400 + (minute as i64) * 60 + (tick as i64) / 50; - let micro = (tick as i64 % 50) * 20_000; - (secs as u32, micro as u32) + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default(); + let secs = now.as_secs() as i64 - crate::dosdate::UNIX_TO_AMIGA_EPOCH_DAYS * 86_400; + if secs < 0 { + return (0, 0); + } + (secs as u32, now.subsec_micros()) } /// Reads a `struct timeval` (`tv_secs`/`tv_micro`, both `ULONG`) at @@ -3211,6 +3217,16 @@ mod tests { ); } + /// The host clock behind `GetSysTime` and `ReadEClock` must resolve + /// microseconds, not DateStamp's 1/50s ticks: with the latter, every + /// `tv_micro` was a multiple of 20000 and every interval timed with + /// `ReadEClock` a multiple of 20 ms. + #[test] + fn host_time_has_microsecond_resolution() { + let off_tick = (0..1000).any(|_| !host_time_secs_micro().1.is_multiple_of(20_000)); + assert!(off_tick, "every sample fell on a 1/50s boundary"); + } + // --- ReadEClock under --clock-mhz (issue #102) --- #[test]