Skip to content

perf(rsp): census which COP2 ops actually run — 62% is one dispatch function - #250

Merged
doublegate merged 3 commits into
mainfrom
perf/vu-opcode-census
Aug 1, 2026
Merged

perf(rsp): census which COP2 ops actually run — 62% is one dispatch function#250
doublegate merged 3 commits into
mainfrom
perf/vu-opcode-census

Conversation

@doublegate

Copy link
Copy Markdown
Owner

Motivation — scoping B2 before writing any unsafe

vu.rs is 143 functions and ~8.5% of a frame, and vectorizing it means writing unsafe intrinsics under a scoped ADR exception. Hand-vectorizing 143 functions to recover the cost of the few that matter would be the expensive way round.

So: count first, exactly as the Bus census did — and that one paid for itself immediately by surfacing the read_u32 asymmetry (#249, 1.32%).

What this adds

A 64-slot histogram of COP2 computational funct values on Rsp, behind work-counters, reported by examples/work_bench.rs. #[serde(skip)], retired-work tally, default-OFF.

The census — Super Mario 64, 120 frames, 14,577,323 COP2 computational ops

That is ~121,478 per frame against 294,983 RSP instructions per frame, so 41% of everything the RSP executes is a VU computation. Only 32 of the 64 possible funct values ever appear.

funct instruction share cumulative
0x0e VMADN 17.60% 17.60%
0x0f VMADH 14.05% 31.65%
0x0d VMADM 8.93% 40.58%
0x04 VMUDL 8.60% 49.18%
0x11 VSUB 5.43% 54.61%
0x06 VMUDN 5.14% 59.76%
0x15 VSUBC 4.11% 63.87%
0x32 VRCPH/VRSQH class 3.69% 67.56%
0x10 VADD 3.52% 71.07%
0x05 VMUDM 3.29% 74.36%
0x33 VMOV 3.19% 77.55%
0x1d VSAR 3.14% 80.69%

Four operations are half the work. Twelve are 81%.

The result that decides the shape of B2

funct 0x00..=0x0F — the whole multiply / multiply-accumulate family — is 61.64%, and it is dispatched by a single function, multiply_lane.

Vectorizing one function covers 62% of the VU's computational work. It is also the natural SIMD target on its own merits: eight independent 16×16 lane products accumulating into a 48-bit accumulator is precisely what a vector unit does.

And it bounds the ambition, which matters more

The VU is ~8.5% of a frame. 62% of that is ~5.3%. A perfect vectorization of multiply_lane cannot exceed it, and the real figure will be lower because the dispatch, the register reads and the accumulator writeback do not vanish.

Any SIMD work here gets measured against 5.3%, not 8.5%. Three figures in docs/performance.md have already turned out to belong to configurations that were not being run — the VI's 4.64%, the RDP's 6.36%, and the shared-device plan's "double PCIe crossing". Each was arrived at by reading rather than counting. This is the same trap, and this PR is how it is avoided rather than repeated.

One implementation note

[u64; 64] does not implement Default — the standard impls stop at 32 — so #[serde(skip)] alone does not compile and the field needs an explicit default = "zeroed_funct_histogram". That is the compiler catching, at the type level, the same class of mistake #245 shipped at runtime: a skipped field that deserializes into something unusable.

Gates run locally

cargo fmt --all --check · cargo clippy --workspace --all-targets -- -D warnings · the same for work-counters on rsp and frontend · cargo test --workspace · cargo test -p rustyn64-rsp --features work-counters · RUSTDOCFLAGS="-D warnings" cargo doc --workspace --no-deps · pre-commit run markdownlint --all-files · scripts/check_en_us.sh

…unction

`vu.rs` is 143 functions and ~8.5% of a frame, and vectorizing it means writing
`unsafe` intrinsics. Hand-vectorizing 143 functions to recover the cost of the
few that matter is the expensive way round, so: count first.

`work-counters` gains a 64-slot histogram of COP2 computational `funct` values,
reported by `work_bench`. Super Mario 64, 120 frames, 14,577,323 ops:

    0x0e VMADN  17.60%   ->  17.60% cumulative
    0x0f VMADH  14.05%   ->  31.65%
    0x0d VMADM   8.93%   ->  40.58%
    0x04 VMUDL   8.60%   ->  49.18%
    ... twelve ops reach 80.69%

Only 32 of the 64 possible `funct` values ever appear, and at ~121,478 per frame
against 294,983 RSP instructions, **41% of everything the RSP executes is a VU
computation**.

The result that decides the shape of the work: `funct 0x00..=0x0F` — the whole
multiply/multiply-accumulate family — is **61.64%**, and it is dispatched by ONE
function, `multiply_lane`. That function is also the natural SIMD target: eight
independent 16x16 lane products into a 48-bit accumulator.

It bounds the ambition too, which matters more. The VU is ~8.5% of a frame and
62% of it is ~5.3%; a perfect vectorization cannot exceed that, and the real
figure will be lower because dispatch, register reads and accumulator writeback
do not vanish. SIMD work here gets measured against 5.3%, not 8.5%.

`[u64; 64]` has no `Default`, so the field needs an explicit
`default = "zeroed_funct_histogram"` — the compiler catching, at the type level,
the same class of mistake #245 shipped at runtime.
Copilot AI review requested due to automatic review settings August 1, 2026 07:45
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@doublegate, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 46af8643-2930-4b10-bbde-4e748edbb41c

📥 Commits

Reviewing files that changed from the base of the PR and between ccb426c and 927e6d5.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • crates/rustyn64-frontend/examples/work_bench.rs
  • crates/rustyn64-rsp/src/lib.rs
  • docs/performance.md
  • docs/rsp.md
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added optional performance counters for tracking vector-unit computational operations.
    • Added benchmark reporting of the most frequently used operations and their overall distribution.
  • Documentation

    • Added performance analysis covering observed vector workloads and potential optimisation limits.
    • Identified multiply and multiply-accumulate operations as the primary optimisation target.

Walkthrough

The RSP now records COP2 function-code frequencies under work-counters. The benchmark reports the distribution and multiply/accumulate share. The changelog and performance documentation record the measured workload and estimated vectorisation ceiling.

Changes

COP2 histogram instrumentation

Layer / File(s) Summary
Histogram storage and dispatch
crates/rustyn64-rsp/src/lib.rs, crates/rustyn64-rsp/src/su.rs
Rsp stores a feature-gated 64-slot histogram. COP2 execution records the masked funct value before dispatch.
Benchmark histogram reporting
crates/rustyn64-frontend/examples/work_bench.rs
The benchmark ranks recorded operations, prints the top twelve with shares and cumulative coverage, and reports multiply/accumulate-family usage.
Measured workload documentation
docs/performance.md, CHANGELOG.md
Documentation records the observed opcode distribution, the 61.64% multiply-family share, and the approximately 5.3% frame-level ceiling.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant work_bench
  participant Rsp
  participant cop2
  participant VU_dispatch
  work_bench->>Rsp: run benchmark
  cop2->>Rsp: record funct value
  cop2->>VU_dispatch: dispatch COP2 instruction
  work_bench->>Rsp: read vu_funct_histogram()
  Rsp-->>work_bench: return operation counts
  work_bench-->>work_bench: calculate and print workload shares
Loading

Possibly related PRs

  • doublegate/RustyN64#41: Both modify RSP vector-unit computational opcode handling, including the multiply/accumulate family.
  • doublegate/RustyN64#42: Both modify COP2/VU computational opcode handling in the RSP.
  • doublegate/RustyN64#248: Both extend work-counters instrumentation across Rsp, su.rs, work_bench.rs, and performance documentation.

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 8 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title describes the census change but is 76 characters and does not use imperative mood, violating the Conventional Commits rule. Shorten the title to 72 characters or fewer and use an imperative subject, such as "perf(rsp): census executed COP2 operations".
Docs-As-Spec Sync ⚠️ Warning The RSP gains observable work-counters state and public vu_funct_histogram; docs/rsp.md is unchanged, violating its same-PR SPEC rule. The PR body gives no reason to omit it. Update docs/rsp.md with the gated histogram, reset and save-state behaviour, or explain in the PR body why this observable RSP change needs no subsystem documentation.
✅ Passed checks (8 passed)
Check name Status Explanation
Description check ✅ Passed The description directly explains the COP2 histogram, its measurements, and the intended optimisation scope.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Oracle Number Is Stated ✅ Passed The diff adds only work-counters instrumentation, benchmark reporting, and documentation; it does not change emulation behaviour. The rule explicitly exempts tooling and docs-only changes.
Changelog Entry For User-Visible Changes ✅ Passed CHANGELOG.md contains a COP2 opcode census entry under [Unreleased] > Added, documenting the new work-counters histogram and its user-facing performance scope.
Measured, Never Tuned ✅ Passed No new hardware timing constant or emulated behaviour was added: the 64-slot/0x3F histogram mirrors the existing funct decode, and the new figures are workload measurements.
Unsafe Stays Out Of The Chip Crates ✅ Passed PASS: The patch adds no unsafe Rust item or block, and all six chip/core crates retain #![forbid(unsafe_code)]; no frontend safety comment is required.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/rustyn64-rsp/src/lib.rs`:
- Around line 146-149: Revise the hotspot-census wording to avoid presenting
unsafe RSP SIMD or vectorization of vu.rs as an available implementation path,
while preserving that the census identifies optimization hotspots for a future
policy-compliant design. Update the documentation at
crates/rustyn64-rsp/src/lib.rs lines 146-149,
crates/rustyn64-frontend/examples/work_bench.rs lines 173-175,
docs/performance.md lines 2189-2192, and CHANGELOG.md lines 97-100; reflect that
unsafe remains restricted to the frontend and FFI.
- Around line 277-306: Add a feature-gated execution test near the existing
deserialization/reset coverage that dispatches one COP2 computational
instruction, asserts the corresponding vu_funct_histogram bucket increments
exactly once, deserializes the state, and then verifies that bucket resets.
Ensure the test uses the real execution path and fails when count_vu_funct does
not update the histogram.
- Around line 143-155: Update docs/rsp.md to document the work-counters feature
as default-off, describe that vu_funct tracks executions for all 64 COP2
computational funct slots, and state that the histogram is skipped during
serialization and reset on deserialization. Keep the implementation unchanged
and do not use docs/performance.md as the feature contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3f0ea481-ef61-4dbe-816a-f788761b59c7

📥 Commits

Reviewing files that changed from the base of the PR and between 42aa958 and ccb426c.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • crates/rustyn64-frontend/examples/work_bench.rs
  • crates/rustyn64-rsp/src/lib.rs
  • crates/rustyn64-rsp/src/su.rs
  • docs/performance.md

Comment thread crates/rustyn64-rsp/src/lib.rs
Comment thread crates/rustyn64-rsp/src/lib.rs Outdated
Comment thread crates/rustyn64-rsp/src/lib.rs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a work-counters-gated COP2 “computational funct” histogram to the RSP so real workloads can be censused before attempting SIMD/vectorization work, and documents the resulting dispatch shape/upper-bound for expected speedups.

Changes:

  • Add a 64-slot per-funct execution histogram to Rsp (behind work-counters) and increment it on COP2 computational dispatch.
  • Expose the histogram via Rsp::vu_funct_histogram() and report it from examples/work_bench.rs.
  • Document the census results and implications in docs/performance.md and the CHANGELOG.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
docs/performance.md Documents the COP2 funct census results and the implied SIMD ceiling.
crates/rustyn64-rsp/src/su.rs Hooks the COP2 computational dispatch path to increment the histogram.
crates/rustyn64-rsp/src/lib.rs Adds the histogram field/API and the counter increment helper under work-counters.
crates/rustyn64-frontend/examples/work_bench.rs Prints the histogram ranking and a 0x00..=0x0F rollup in the bench output.
CHANGELOG.md Records the new census feature and its headline findings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/rustyn64-rsp/src/lib.rs
Comment thread crates/rustyn64-rsp/src/lib.rs Outdated
Comment thread crates/rustyn64-rsp/src/lib.rs
Comment thread crates/rustyn64-frontend/examples/work_bench.rs Outdated
Comment thread crates/rustyn64-frontend/examples/work_bench.rs
…fe is available

Eight review findings. Two matter.

THE MEASUREMENT BUG. The histogram was reported RAW while every other figure in
`work_bench` is a delta over the timed window, so ~36 warm-up frames were folded
into a table captioned "120 frames". Now snapshotted before the loop and
subtracted. The effect is 0.06% (14,577,323 -> 14,569,003) because boot barely
exercises the VU, and the family share moves 61.64% -> 61.62% — but a number
that is right by accident is still not measured.

THE POLICY CLAIM. `crates/rustyn64-rsp` is `#![forbid(unsafe_code)]`, so
"vectorizing means unsafe intrinsics" presented a path that is not currently
available. The maintainer has agreed in principle to a scoped exception by ADR;
that ADR does not exist. The census identifies a hotspot; it does not authorize
a technique. Corrected in the field doc, the example, docs/performance.md and
the CHANGELOG.

Also:

- `docs/rsp.md` gains the work-counter contract — what each counts, why
  `retired` counts executed rather than stepped, the `#[serde(skip)]` reset
  semantics, and the forbid(unsafe_code) note. Chip changes update the chip doc.
- `a_cop2_instruction_increments_its_own_bucket` executes a real `VMUDN` and
  asserts its OWN bucket moved and that exactly one bucket moved, then round
  trips a save-state. Two properties together, because an all-zero histogram
  passes a reset check and a wrong-bucket counter passes a total check.
- `count_vu_funct` uses `wrapping_add`, matching the two counters standardized
  last PR.
- The vacuity assert's wrapped-space run is gone.

REJECTED: that `[u64; 64]` implements `Default` on 1.96.0 and the explicit serde
default is unnecessary. It does not — `let _: [u64; 64] = Default::default();`
fails to compile with E0277 on this toolchain. The doc now states the observed
compiler behavior and gives that one-line reproduction, rather than asserting a
rule about where the array impls stop.
The wrapped-space run in the vacuity assert was NOT fixed in 8416198 — the
replacement anchor missed because rustfmt had already reflowed the literal, and
I reported it adopted without re-reading the line. It rendered as
"would be a          table of zeros". Now fixed and verified by rendering the
literal the way rustc does (backslash-newline eats the newline AND the leading
whitespace) and asserting no run of three spaces survives.

Also from the same review: `filter().map()` becomes `filter_map`, and
`count_vu_funct` binds the masked index once instead of computing it twice.
@doublegate

Copy link
Copy Markdown
Owner Author

Adjudication — Antigravity review

Two nitpicks adopted, one suggestion adopted-late (I had reported it fixed and it was not), one suggestion rejected, and the blocking issue refuted by running the exact command.

Blocking: build breaks with work-counters off — refuted

work_bench carries required-features = ["work-counters"] (crates/rustyn64-frontend/Cargo.toml:161), so Cargo declines to build the target rather than building it and failing:

$ cargo check --example work_bench
error: target `work_bench` in package `rustyn64-frontend` requires the features: `work-counters`
Consider enabling them by passing, e.g., `--features="work-counters"`

That is Cargo's own message, not a compile error — the mechanism working as designed. And the default-feature workspace build is clean:

$ cargo check --workspace --all-targets     # no errors

CI agrees: all 11 checks pass, including the default test (ubuntu-latest) leg that builds --all-targets.

The declaration exists precisely because #246 hit the version of this that is real: an example reading a feature-gated API without required-features, which made every --all-targets lint red on a tree that was fine.

Suggestion: fix the multiline string formatting — adopted, and I owe a correction

You are right, and so was Copilot on the same line. I reported this fixed in 8416198 and it was not. My replacement anchor missed because rustfmt had already reflowed the literal, and I did not re-read the line before saying so. It really did render as "would be a table of zeros".

Fixed now, and verified rather than eyeballed — rendering the literal the way rustc does (a backslash-newline eats the newline and the leading whitespace) and asserting no run of three spaces survives:

RENDERED: "no COP2 computational instructions executed in the timed window; the histogram would be a table of zeros, which reads as a result"
has a run of 3+ spaces: False

Suggestion: saturating_sub for the histogram delta — rejected

The counters are monotonic within a run: nothing resets them, and work_bench constructs one EmuCore and never reloads it. So an underflow would mean an invariant broke, and I would want the debug panic that reports it rather than a silently plausible zero.

That is the same call made elsewhere in this session — the zero-window guard is an assert! rather than a substituted value, and read_u32's bound rejects a partial word rather than clamping it. A benchmark's job is to produce numbers people quote; masking a broken invariant to keep it running produces a quotable wrong number.

Nitpicks — both adopted

filter().map()filter_map(|(i, &n)| (n > 0).then_some((i, n))), and count_vu_funct binds the masked index once rather than computing (funct & 0x3F) as usize twice.

@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

Antigravity review (Gemini via Ultra)

This PR adds an optional 64-slot COP2 funct execution counter gated behind the work-counters feature flag and reports opcode distribution metrics in work_bench.rs.

Blocking issues

  • Unconditional call to feature-gated vu_funct_histogram breaks default builds: In crates/rustyn64-frontend/examples/work_bench.rs, core.system().bus.rsp.vu_funct_histogram() and report_vu_histogram are called unconditionally. Because vu_funct_histogram is gated behind #[cfg(feature = "work-counters")] in crates/rustyn64-rsp/src/lib.rs, compiling work_bench under default features (work-counters disabled) fails to compile. The caller in work_bench.rs must be guarded with #[cfg(feature = "work-counters")] or rustyn64-rsp must provide a stub fallback method when the feature is inactive.

Suggestions

  • Avoid raw subtraction on counter deltas: In crates/rustyn64-frontend/examples/work_bench.rs (line 183), vu_funct_histogram()[i] - before[i] uses direct subtraction. If state is reset or reloaded during execution, before[i] can exceed current values and trigger an underflow panic in debug builds. Use saturating_sub instead.
  • Tautological unit test assertion: In crates/rustyn64-rsp/src/lib.rs (line 506), restored.vu_funct_histogram().len() checks the .len() of &[u64; 64]. This is a type-level constant fixed to 64 at compile time and does not verify whether deserialization allocated or initialized state properly.

Nitpicks

  • Manual box initialization helper: crates/rustyn64-rsp/src/lib.rs (line 64) adds a standalone zeroed_funct_histogram() function to satisfy serde default attributes; if Box<[u64; 64]> initialization is required, inline it or verify if standard derives suffice on the target toolchain version.

Automated first-pass review by agy on a self-hosted runner -- not a human review.

@doublegate
doublegate merged commit 4fcac18 into main Aug 1, 2026
13 checks passed
@doublegate
doublegate deleted the perf/vu-opcode-census branch August 1, 2026 08:07
doublegate added a commit that referenced this pull request Aug 1, 2026
"A design that checks a feature flag per instruction has spent the win
before it starts" is a performance claim with no experiment behind it,
inside a document whose entire verdict is arithmetic over measured
shares. Nothing here has built or timed a dispatch shape.

Restated as a named risk with its reasoning: the 5.3% ceiling is spread
over ~121,478 VU operations per frame (#250), so the per-operation
budget is small enough that a branch plus a non-inlinable call could
plausibly consume it. Gate 3's A-B-A is what would settle it.

The advice survives because hoisting the check costs nothing to prefer
up front — which is the only defensible reason to state a preference
before measuring it.
doublegate added a commit that referenced this pull request Aug 1, 2026
…1.056x) (#252)

* docs(adr): ADR 0016 — a scoped unsafe exception for the RSP vector unit

Three merged documents currently say the same thing: that vectorizing the VU
needs an `unsafe` exception, that the maintainer agreed to one in principle, and
that the ADR does not exist. This is that ADR, so the claim stops being a
forward reference.

It permits `core::arch` intrinsics in `vu.rs` ONLY — not raw pointers, not
`transmute`, not `get_unchecked`, not FFI — with the crate-level `forbid`
becoming `deny` and a single module-level `allow`. Every other chip crate is
untouched.

Four gates, and they are gates rather than aspirations:

1. A scalar/vector EQUIVALENCE test over the operand space, including
   saturation, the VMUDL/VMUDN signedness asymmetry and accumulator wrap.
   Conformance to the ROM suite is necessary and explicitly NOT sufficient: the
   suite exercises what microcode happens to use, and a divergence outside that
   set is exactly the bug intrinsics invite.
2. `rsp_categories_report_no_failures` stays at 0 failing.
3. A-B-A against the 5.3% ceiling the census established, neutral-or-worse
   reverted like the family hoist and PGO were.
4. A tested fallback — SSE2 is baseline on x86_64, SSSE3/SSE4.1 are not, and
   aarch64 is supported.

It authorizes a technique; it does not schedule the work, and it does not
pretend the trade is good. The costs are stated: the chip crates stop having
zero `unsafe`, the VU acquires a portability surface, and two implementations
must agree forever. Against a 5.3% ceiling that is marginal, and the ADR says so
and names the comparison — the CPU is 32.29%, roughly six times larger, and
anyone reaching for this should first have a reason not to spend the effort
there instead.

Implementation note recorded for whoever takes it: `multiply_lane` is PER-LANE,
so this is a restructure rather than an annotation — the 48-bit accumulator has
no native vector type and needs 16-bit planes, as parallel-rsp does.

* docs(adr): state that restriction 1 is review-enforced, not compiler-enforced

Seven review findings on ADR 0016, and one of them is the kind of error the ADR
itself is about.

`unsafe_code` is a BINARY lint. It cannot permit a `core::arch` call and reject
a `transmute` in the same module, so "intrinsics only" is enforced by REVIEW,
not by the compiler. Writing it as a scope restriction alongside two that the
compiler does enforce made it read as mechanical. That is a rule stated as if
implemented — at policy level, which is worse than in a comment, because the
whole point of an ADR is to be trusted later without re-derivation.

Now said outright, with the narrow module scope named as what makes the review
tractable, and a CI grep suggested for when the exception is actually used.

The scope item was also self-contradictory: "the rest of the crate keeps
`forbid`" while the crate-level attribute becomes `deny` so `vu.rs` can opt out.
`forbid` exists precisely so an inner `allow` cannot override it, so the two
cannot both hold. Spelled out per-attribute, and the consequence promoted into
its own cost bullet: the crate drops from `forbid` to `deny`, which is a
strictly larger weakening than "vu.rs may use intrinsics" and the part most
likely to be forgotten.

Also adopted: `#[target_feature]`'s structural cost noted at gate 4 (those
functions are unsafe to call and do not inline across the boundary, so the
dispatch must be amortized above the hot loop — a per-instruction feature check
has spent the win before it starts); `docs/rsp.md`'s heading no longer says
"does not authorize a technique" when the section now says it does; the CHANGELOG
census bullet no longer claims the ADR does not exist two entries below the ADR;
and the bare issue numbers are links.

* docs(adr,policy): sync AGENTS.md with ADR 0016, and correct a claim it had outrun

A reviewer pointed out that the ADR's `Amends` line named two policy files while
the PR touched neither, so the tree would carry the old blanket rule alongside
the new exception. Chasing that turned up two errors.

The `Amends` line named `docs/architecture.md`, which says NOTHING about
`unsafe`. That was my invention. It now names `AGENTS.md` only, and says so.

`AGENTS.md` claimed "there is zero `unsafe` in the tree today — keep it that
way". That has been FALSE since #241: `rustyn64-rdp-gpu` has 12 `unsafe` blocks,
the parallel-rdp FFI shim, quarantined there deliberately under ADR 0014. The
policy line had outrun the tree by four PRs and nothing failed, because a
statement about a repository is not checked by anything — the same decay this
project has already recorded three times for "undocumented".

`AGENTS.md` now states the rule, names the FFI shim as the existing exception,
and names ADR 0016 as a conditional one that is UNUSED today.

Three more from the same review:

- The negative results carry provenance and status. "Measured neutral" cites its
  `docs/performance.md` section and is marked MEASURED; the decode cache's 0.29%
  is marked an INFERRED UPPER BOUND, because no implementation was ever built.
- The `// SAFETY:` requirement covers "block OR OPERATION", quoting the repo rule
  in full. Block alone would leave an `unsafe` operation inside an `unsafe fn`
  unexplained — the likeliest case here, since intrinsics are usually called from
  `#[target_feature]` functions, which are `unsafe fn`.
- Gate 4 is a named target matrix instead of "a tested fallback". The one that
  decides the design is `thumbv7em-none-eabihf --no-default-features`: the chip
  stack must keep building `no_std + alloc` there and it has no SIMD at all, so
  the scalar path is a first-class implementation a supported target depends on
  — which is why gate 1 is equivalence rather than conformance of the fast path.

* docs(adr): the ADR repeated the invented citation it had just fixed

The Amends line was corrected in 8848c84 to stop citing
`docs/architecture.md`, which says nothing about `unsafe`. The Context
section six lines below still asserted that same file "makes zero
`unsafe` in the chip crates a property of the design" — the identical
error, in the identical document, surviving the fix aimed at it.

`grep -in "unsafe\|forbid" docs/architecture.md` returns nothing. The
policy lives in `AGENTS.md`, and it is the crate attributes that enforce
it; the Context now says so.

The same sentence also claimed "the tree has never had any", which has
been false since #241 (12 blocks in the parallel-rdp FFI shim, ADR 0014)
and contradicted the AGENTS.md correction this very PR makes. Now split
explicitly: the CHIP crates have never had any, the tree has.

Also drops PR-process narrative from the Amends line. The load-bearing
half — architecture.md must not be cited as a source for this policy —
is kept and stated as a checkable fact rather than a story about a draft.

* docs(adr): the dispatch-cost claim was asserted, not measured

"A design that checks a feature flag per instruction has spent the win
before it starts" is a performance claim with no experiment behind it,
inside a document whose entire verdict is arithmetic over measured
shares. Nothing here has built or timed a dispatch shape.

Restated as a named risk with its reasoning: the 5.3% ceiling is spread
over ~121,478 VU operations per frame (#250), so the per-operation
budget is small enough that a branch plus a non-inlinable call could
plausibly consume it. Gate 3's A-B-A is what would settle it.

The advice survives because hoisting the check costs nothing to prefer
up front — which is the only defensible reason to state a preference
before measuring it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants