Repository navigation
A clock read takes RDTSCP and holds nothing after it, and std's Instant is the page's nanoseconds - #1240
Conversation
…nt is the page's nanoseconds `osbench time`'s `time.now` read 21.0 ns on the T14 against Linux's 14.3 (issues/a-clock-read-costs-toyos-half-again-linuxs-on-the-t14.md). Three costs stood between `Instant::now` and the counter: - The counter was `LFENCE; RDTSC; LFENCE`. It is now `RDTSCP`, which waits for every earlier instruction to execute and every earlier load to be globally visible (SDM Vol. 2B, RDTSCP; AMD APM Vol. 3, RDTSCP: "forces all older instructions to retire before reading the time-stamp counter"), the ordering the clock needs before the read. The leading LFENCE gave that on AMD only where LFENCE is dispatch serializing (DE_CFG bit 1 or CPUID Fn8000_0021 EAX bit 2, AMD's "Software Techniques for Managing Speculation", mitigation G-2), which the kernel neither sets nor checks; RDTSCP needs neither. The kernel's clock already refuses a CPU without RDTSCP. AArch64 drops the trailing ISB the same way. - The trailing fence held later instructions back so that the latch's recheck and the demoted word could not be answered before the counter. Nothing needs that. A line replaced in that window pairs the old line with a counter a little past where the new one starts, as every reading between the kernel's counter reading for the new line and its publication already does; the line bends there and never steps. A demotion's handover floors each CPU in a kick handler, and an interrupt either finds the reader's whole read retired, counter included, or throws away its early load of the demoted word. - std's `Instant` held a `Duration`, so every `now` divided by 10^9. It now holds the page's nanoseconds as a `u64` and converts only on subtraction. An `Instant` reaches 584 years past boot; a later one is `None` from `checked_add`, as a `Duration` past its own range was. `nanos_since_boot` and its read are inlined; the page-without-a-clock panic is a cold function. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 of #1240 at 03f8841Architecture. The read stays where it belongs: the ABI's x86-64 ordering is sound.
u64 and precision: no finding. The page answers integer nanoseconds, so Blockers
Evidence
What metal must show
On the A/B rule (request.txt)Sound in design:
To state before the boots:
SEND BACK. Blocker 1 is a one-hunk revert. Blocker 2 is a doc sentence or a kernel alignment, plus the record. Blocker 3 is a record. After those it can land once the guest run is green and the A/B results above are recorded. |
…s the read section's reader orders the recheck - AArch64's counter is `ISB; MRS; ISB` again. The Arm ARM (K.a D12.2.2) lets a counter read happen speculatively and out of order against other instructions; nothing architectural orders it before a later store that does not depend on its value, so x86's argument does not carry over, and no AArch64 bench could measure the change. Its doc no longer says it reads as the x86-64 one does. - The latch no longer says nothing orders its recheck after `inside`'s read: the kernel reads the same page through `cpu::counter_ordered`, whose trailing fence does. Whether the recheck may be answered first is `inside`'s choice, and the clock page's header says which reader pairs a line with a counter read just after it was replaced. - `issues/the-kernel-relies-on-an-amd-lfence-it-never-makes-dispatch-serializing.md` records that `cpu::rdtsc_ordered` and `cpu::wrmsr_fence` rely on an AMD LFENCE the kernel never makes dispatch serializing; #1243 is its exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Response to review r1, head 3d06ecc.
Rechecked at 3d06ecc: 🤖 Generated with Claude Code |
Review r2 of #1240 at 3d06ecc (follow-up to r1 at 03f8841)Scope. This round checks the r1 blockers, the code changed in 03f8841..3d06ecc, and the A/B rule's revision. Everything else is settled. Architecture. Unchanged since r1. The whole branch is now +82/-27 in 6 files, including a 37-line issue record. The AArch64 counter's code and safety comment match main's again; only the parity sentence is gone. r1 blockers
Evidence
A/B rule r2 (
|
`cpu::wrmsr_fence` is ordered by its MFENCE, which is serializing on AMD (APM Vol. 3, MFENCE), and Intel's LFENCE always serializes, so the record names `cpu::rdtsc_ordered` alone and says the AMD bench's case turns on C001_1029 bit 1 as firmware leaves it, which no boot reads. The x86-64 counter's doc points at the clock page's header, which says what an unordered later instruction costs, and that header names `arch::counter` without a link to the private module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Response to review r2, head 9c1911d (text only).
🤖 Generated with Claude Code |
|
Orchestrator: verified 9c1911d against follow-up review r2's two text blockers. |
Both benches are down. #1240 lands on its host checks and guest rows, and `issues/a-clock-read-costs-toyos-half-again-linuxs-on-the-t14.md` records what only metal can establish: the A/B of main before and after it on both benches, with its controls, and the Linux comparison. Its speed-up is not yet measured. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Orchestrator verification, landing under the benches-down rule (metal unavailable; review + host + guest, metal owed):
|
Problem
osbench time'stime.now(std'sInstant::now, answered from the clock page) read 21.0 ns on the T14against Linux's 14.3 ns (
issues/a-clock-read-costs-toyos-half-again-linuxs-on-the-t14.md). The readfenced the counter on both sides (
LFENCE; RDTSC; LFENCE), and std'sInstantheld aDuration, so everynowdivided by 10^9.Resulting behavior
RDTSCPand nothing after it. RDTSCP waits for every earlierinstruction to execute and every earlier load to be globally visible (SDM Vol. 2B, RDTSCP); AMD's APM
Vol. 3: it "forces all older instructions to retire before reading the time-stamp counter". That is the
ordering the clock needs before the read. The old leading
LFENCEgave it on AMD only where LFENCE isdispatch serializing (DE_CFG bit 1, or CPUID Fn8000_0021 EAX bit 2), which the kernel neither sets nor
checks (see below). The kernel's clock already refuses a CPU without RDTSCP.
ISB; MRS; ISB): the Arm ARM lets a counter read happen out of order againstother instructions, and nothing architectural orders it before a later store that does not depend on it,
so the x86 argument below does not carry over; and no AArch64 bench could measure it (review r1).
Instantis the page's nanoseconds as au64, converted toDurationonly by subtraction.nanos_since_bootand its read are#[inline]; the page-without-a-clock panic is a cold function.Why dropping the trailing fence is correct
For a process's read, it held later instructions back so the latch's recheck and the page's demoted word
could not be answered before the counter. Neither needs it:
attaken before itpublishes, and the new line starts where the old one stands (
clock::retune). Every reader betweenatand the publication already pairs the old line with a counter past
at; the out-of-order windowlengthens that interval, it adds no new case. The clock bends there and never steps.
taken at an instruction boundary: either the reader's whole read, counter included, retired before it
(so its counter is before the floor), or its early load of the demoted word is discarded and executed
again after it, and sees the mark. The handover's stated contract is unchanged.
The kernel reads the same page through its own
cpu::counter_ordered(RDTSCP; LFENCE), which thischange leaves as it is. So the latch now says only that whether its recheck may be answered before
inside's read isinside's to decide, and the clock page's header says which reader may pair a linewith a counter read just after it was replaced.
AMD's LFENCE (asked of this branch)
The kernel neither sets MSR C001_1029[1] (AMD 70586, G-2; families 10h, 12h and 14h to 17h) nor reads
CPUID Fn8000_0021 EAX[2]
LFenceAlwaysSerializing(APM Vol. 3, LFENCE). Onlycpu::rdtsc_ordered(
RDTSCP; LFENCE) relies on LFENCE holding later instructions back: the kernel's own clock read(
cpu::counter_orderedinside the clock page's latch) and, inarch/x86_64/clocksource.rs, the demotion'stwo readings,
floor_here,stamp_now, the AP start's trip,check, and the brackets around each HPET andPM timer sample.
cpu::wrmsr_fence(MFENCE; LFENCEbefore an x2APIC WRMSR) is ordered by its MFENCE,which is serializing on AMD (APM Vol. 3, MFENCE), and Intel's LFENCE always serializes. A process's clock
read relies on none of it.
This PR records it as
issues/the-kernel-relies-on-an-amd-lfence-it-never-makes-dispatch-serializing.md,assigned to harden, with #1243 as its exit. If #1243 lands first, this branch drops the record before it
lands; otherwise #1243 deletes it.
Checks
Targeted, per the testing policy: only what this change can affect. Host checks rerun for
3d06ecc70(logsr4); the std type-check ran at
03f88418e, and the review commits change nothing std compiles but anAArch64 counter they restore to main's.
9c1911df5(review r2) changes the issue record and doc commentsonly;
cargo doc -p toyos-abi --no-depsexit 0, with no warning from its links.cargo test -p toyos-abi clockcargo clippy -p toyos-abi --all-targets --keep-going --target {aarch64,x86_64}-apple-darwin --target-dir target/clippy-abi -- -W clippy::undocumented_unsafe_blocks -D warningsbuild/clippy.rs'stoyos-abishape, once per architecture so bothcounterarms are lintedbuild/CLAUDE.md's scratch-crate method,-Z build-std=std,panic_abortforx86_64-unknown-toyosandaarch64-unknown-toyossdk/std/sys/time.rshas no host testNot run:
kernel/loom'sclock_latchmodel (the latch's code is unchanged; only its doc comment changed);the full
--ci host(no build-system or harness change).Guest rows: run 43050350414 at
03f88418e, green:a_stepped_tsc_is_slewed_then_demoted(x86-64, q35:processes' readings after a demotion go to the kernel),
udp_receive_times_out(x86-64: stdInstantarithmetic in a timeout),
virt_wall_clock_utc(AArch64). The head differs from03f88418ein comments, twoissue records, and one code line: AArch64's
counterregains its trailingISB, which is main's code again;the x86-64 code is identical. QEMU shows neither the fence's cost nor reordering; these rows establish that
the read still answers and the demotion still reaches processes.
Owed on metal
Both benches are down, so this lands on its review, host checks and guest rows. The speed-up is not yet
measured on metal.
issues/a-clock-read-costs-toyos-half-again-linuxs-on-the-t14.mdrecords what is owed:osbench time'stime.now, fourboots a side in rotated order, each boot's p50, exact two-sided Mann-Whitney, with the rest of
osbench time,files,ipcandschedas controls; and Linux's TSC-timedosbench timeon each bench;time.nowlower than main before in every T14 boot, set against Linux's 14.3 ns, withno control row 5% or more worse unless this change explains it; on the AMD bench a rise is recorded as
undetermined between ordering's price and a regression, unless a ToyOS boot logs MSR C001_1029 bit 1 before
writing it (A pure function decides a CPU's speculation mitigations, and each boot says its verdict #1243's boot line).
Risks
Instantreaches 584 years past boot;Instant + dpast that panics where it used to succeed, andchecked_addanswersNone, as std's other platforms do past their own range.🤖 Generated with Claude Code