A transfer is qualified — and becomes logical entry 0, pushing the
rest down — when four things align: the current mode’s enable bit is
set, its type isn’t filtered out, sctrstatus.FROZEN is clear, and the
instruction actually retires. The subtleties are all at privilege
boundaries.
Recording across privilege transitions
| Recorded? | Rationale | |
|---|---|---|
| enabled → enabled (trap or xRET) | Yes, full source + target | Both sides already visible to the profiler. |
| disabled → enabled trap | Yes, ctrsource = 0 | The handler entry is profilable; where it came from is not. |
| enabled → disabled trap ("external trap") | Only via the TE opt-in chain, with ctrtarget = 0 | A disabled mode’s handler address must not leak into a buffer readable below it; TE lets it volunteer the trap’s existence while still zeroing the address. |
| enabled → disabled xRET | Yes, ctrtarget = 0 | The return instruction itself ran in an enabled mode. |
| disabled → anything xRET | No | Nothing about a disabled mode’s execution is observable. |
| anything ↔ Debug Mode | No | Debug is invisible by construction. |
Under virtualization the enables come from each world’s own register —
sctrctl.S for HS, vsctrctl.S/U for VS/VU — while all other config
(filters, RASEMU, freezes) follows vsctrctl whenever V=1. The spec
directs hypervisors to clear the S enable before entering a guest:
the buffer is shared physical state, and stale host qualification must
not bleed into (or out of) the guest’s records.
The type taxonomy
ctrdata.TYPE uses the E-Trace itype encodings: 1 exception, 2
interrupt, 3 trap return, 4 not-taken branch (opt-IN via NTBREN — it
doubles record volume), 5 taken branch, then the jump taxonomy 8–15
classified purely by whether rd/rs1 are link registers
(x1/x5) — the same Table-3 comparators as
Vol I’s RAS hints: indirect/direct call (rd = link),
return (JALR x1/x5 with rs1 = link, rd ≠ link), co-routine
swap (JALR x1,x5 / JALR x5,x1 / C.JALR x5 — both sides link),
plain jumps (no linkage), and “other with linkage”. Zcmt’s cm.jalt
counts as direct call, cm.jt as direct jump, Zcmp’s cm.popret as
return. Filters are optional to implement, and the spec’s performance
clause cuts one way: the default (record-everything) configuration
must not cost performance — filtering may.
Cycle counting
CtrCycleCounter accumulates active cycles (stalls in low-power
states don’t count) between qualified transfers, then compresses into
the record: CC = 12-bit mantissa CCM + 4-bit exponent CCE, value
= CCE ? (4096 + CCM) << (CCE−1) : CCM, saturating at the top. It’s a
tiny float: undercount averages (2^(CCE−1) − 1)/2, so relative error
stays bounded from 1-cycle hot loops to ~2²⁷-cycle stalls. CCV = 0
flags an untrustworthy count; the counter resets on any xctrctl
write and on SCTRCLR, so the next record after reconfiguration
carries CCV = 0.
RASEMU and freeze
With RASEMU = 1 the buffer inverts from history to shadow call
stack: calls push, returns pop (clear entry 0’s V bit, decrement
WRPTR), co-routine swaps overwrite entry 0, and every other type is
inhibited regardless of filters. Unmodified binaries get
hardware-collected call stacks for perf — with honest caveats: only
the innermost depth frames survive, and setjmp/longjmp or
exception unwinding desyncs the emulated stack.
Hardware sets FROZEN — halting all recording until software clears
it — when a local-counter-overflow interrupt traps to M/S (if
LCOFIFRZ) or a breakpoint exception targets M/S (if BPFRZ); the
freezing trap itself is not recorded. That’s the sampling handshake:
the PMU overflow that triggers a perf sample freezes the branch
history at exactly that instant, and the handler reads a coherent
snapshot.
Hardware Designer Notes
The whole behavioral layer is commit-stage combinational logic plus one saturating counter: type classification from decode bits you already have, a 3-term qualification AND, and the zero-muxes on the two PC fields. The expensive design review is the privilege firewall — walk Table 31 against your trap unit case by case.
Minimal Linux-boot hart MUST
- Gate record-write at retirement on: mode enable ∧ type not inhibited ∧ !FROZEN — mispredicted-path transfers never record
- Zero ctrsource on disabled→enabled traps and ctrtarget on enabled→disabled transitions — the privilege firewall lives in this mux
- Implement the TE chain check as an AND across the TE bits of target and every intervening mode
MAY simplify / trap-and-emulate
- Tie NTBREN, all filters, MISP, and cycle counting to 0 — a bare push-only recorder is conformant
- Reuse the branch predictor’s existing rd/rs1 link-register comparators for the TYPE encoder — Table 34 is deliberately identical to Vol I Table 3
- Report CCV = 0 whenever RASEMU’s pop/push accounting makes cycle attribution ambiguous
Check yourself — CTR behavior
1.A trap goes from CTR-enabled S-mode into CTR-disabled M-mode. When is it recorded?
2.Decode the transfer-type taxonomy: what distinguishes an 'indirect call' from a 'co-routine swap'?
3.The CC field is a 12-bit mantissa + 4-bit exponent 'float'. Why this shape for cycle counts?
4.In RASEMU mode, what happens on a function return?
5.Why must a hypervisor clear sctrctl's S-enable before VS-entry if the guest gets CTR access?