Skip to content

Decouple tests from measured benchmark parameters - #70

Merged
byeongjee merged 9 commits into
mainfrom
byeongjee/sunstar
Aug 16, 2026
Merged

Decouple tests from measured benchmark parameters#70
byeongjee merged 9 commits into
mainfrom
byeongjee/sunstar

Conversation

@byeongjee

Copy link
Copy Markdown
Owner

Commit 93fded7 refreshed benchmarks/assembly_params.json from new board data, scaling every energy value by ~2.7x. Three tests failed:

  • test_rockclimb.py::TestCallHandling::test_external_call_costed_as_expensive_instruction
  • test_rockclimb_preprocess.py::test_compile_rockclimb_handles_full_unroll_in_nested_loops
  • test_schematic.py::test_schematic_o3_dijkstra_loop_budget_uses_rare_inner_branch

Not a regression — the commit is data-only, and reverting just that file makes all three pass. Each assertion was pinned to an absolute magnitude read out of a file that gets regenerated whenever the board is re-characterized.

Changes

New tests/assembly_params.json — 153 synthetic instruction costs from an explicit tier model (1.5 x units; an immediate source adds 1, a memory source adds 2, a memory destination adds 1 more when read back; __mspabi_divi and friends are set far above any test capacity). Every value lands within 0.69–1.33x of what the tests were tuned against, so no threshold moved. The file documents that it must never be regenerated from benchmarks/.

Other decouplings

  • test_rockclimb_preprocess.py read benchmarks/config_5uF.json / config_1uF.json, which carry measured E_pro / E_epi / register costs (14 and 6 commits of churn). Replaced with a _write_board_like_config helper using round values in the same regime. Also swapped benchmarks/sample_energy_config_ir.json for the existing tests/estimator_ir_weighted.json, with capacities written as multiples of a named CONSTANT_LOOP_ITER_ENERGY rather than bare 20.0 / 200.0.
  • fully unrolled ... N=4 K=4 now asserts that some loop fully unrolled with N == K. The regression being guarded is a crash on full unroll inside a nested loop; N=4 was incidental to aes.c.
  • The dijkstra loop-budget test replaced inner > 70, outer > 2400, numIt < 25 with outer.e_loop >= inner.e_loop * inner.max_trip_count and outer.num_it <= available_energy / that. This states the intent directly (the outer budget folds in the rarely-taken inner branch) and holds under any scaling.
  • test_capacitor_support.py hardcoded the live capacitor list and 243000.0; it now compares against _DEFAULT_CAPS and CAPACITY_MAP.

Verification

  • 252 passed.
  • Suite also passes with HEAD~1's assembly_params.json and both old capacitor configs restored — the coupling is gone in both directions.
  • The dijkstra test survives scaling benchmarks/assembly_params.json by 0.25x and 4x.
  • ruff and pyright clean.

Not addressed

  • CAPACITY_MAP in scripts/ckpt/analysis/strip_mining.py disagrees with the configs: 50uF = 243000 and 1uF = 4860, while config_50uF.json says 182000 and config_1uF.json says 3640. One is stale. That is a production bug, not a test one, so it is left alone here.
  • test_rockclimb_preprocess.py still compiles the real crc.c / aes.c, and the dijkstra test still names the IR loop headers for.body18.i / while.body.i. Those are the actual regression subjects; freezing copies would duplicate hundreds of lines, and if the sources change these fail loudly with a missing-log-line message rather than a confusing threshold miss.

Refreshing benchmarks/assembly_params.json scaled every energy value by
~2.7x and broke three tests that asserted absolute magnitudes. Tests now
own their energy configs and assert scale-invariant relations instead.
jcc_symbolic, zext_register and the two adjcallstack pseudo-ops were absent
from tests/assembly_params.json, so the MIR estimator costed them at
defaultEnergy_ (1.0) with only a warning. The fixture now fails when the pass
reports any missing key.
E_safe is capacity - E_pro - E_epi - (N_reg - 2) * reg_restore_energy, which
tests/rockclimb_params.json makes 471.87, not ~476.
max_unroll=4 matched RockClimbMaxUnrollFactorOpt's default, so the flag test
passed whether or not the flag was read; it now uses 3. UnrollLoop only reports
a full unroll when Count equals the trip count, making the N == K check on that
log line vacuous.
E_loop is logged before refineLoopBudgetWithConvergence and numIt after, so
reading availableEnergy alongside E_loop compared two different budgets. The
assertion built on it was implied by the E_loop one and is gone.
Comparing discovery against _DEFAULT_CAPS alone still passes if 50uF is dropped
from both.
@byeongjee

Copy link
Copy Markdown
Owner Author

Review addressed in 5 commits on top of 74cf402. I verified each finding independently before acting rather than taking the report at face value.

f3eed6b — missing energy keys (HIGH). Confirmed. jcc_symbolic, zext_register, adjcallstackdown, adjcallstackup were absent, and EnergyModel::getEnergy returns defaultEnergy_ = 1.0 with only a warning. Added all four (jumps 3.0, zext 1.5, adjcallstack 0.0 — they expand to no code when every argument is register-passed) and a guard in run_rockclimb_machine that fails when the pass reports any missing key. Verified the guard fires: deleting jcc_symbolic again turns 12 of 18 tests red.

8dabac0 — wrong E_safe in docstring (MEDIUM). Confirmed, and it was my error: RockClimbConfig.h:18 is capacity - E_pro - E_epi - (N_reg - 2) * reg_restore_energy = 471.87, not the ~476 I wrote. A live run prints E_safe: 471.87.

ee25e46 — assertions that cannot fail (LOW x2). Both confirmed. RockClimbMaxUnrollFactorOpt is cl::init(4), so max_unroll=4 proved nothing; now 3, and I checked the two paths diverge (K=4 without the flag, K=3 with it). Dropped the N == K check — UnrollLoop only reports a full unroll when Count == TripCount.

3184da3 — pre/post-convergence mixing (MEDIUM). Confirmed at LoopAnalyzer.cpp:971 vs :1008. LoopBudget no longer carries availableEnergy; maxTripCount now comes from the numIt line so each assertion pairs values from one line. The assertion built on availableEnergy was implied by the E_loop one, so it is gone rather than repaired. Also widened the number pattern to accept scientific notation.

085741d — lost 50uF guard (MEDIUM). Restored an explicit "50uF" in _DEFAULT_CAPS pin.

Not done: I did not restore the 243000.0 literal. It is the value this PR flags as inconsistent with config_50uF.json's 182000; pinning a number that is probably wrong entrenches it. The CAPACITY_MAP reference still catches a wrong-key lookup, which returns 0.0.

Correction to this PR's description: the "within 0.69–1.33x" claim has three exceptions — ret and reti were 0.0 before and are 1.5 now, and mov_immediate_symbolic is 1.335x.

Also extended _cost_model to cover push/pop/br, the flat-1.5 constants, the adjcallstack pseudo-ops, and how library-call magnitudes are chosen, plus an _adding_a_key note — the generator was a throwaway script, so the file has to be self-describing.

252 passed; ruff and pyright clean.

@byeongjee
byeongjee merged commit 9b2be2d into main Aug 16, 2026
7 checks passed
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.

1 participant