Skip to content

Remove two recalcNodeList calls that have been no-ops - #23

Open
Roxxik wants to merge 1 commit into
mist64:masterfrom
Roxxik:drop-redundant-recalc
Open

Remove two recalcNodeList calls that have been no-ops#23
Roxxik wants to merge 1 commit into
mist64:masterfrom
Roxxik:drop-redundant-recalc

Conversation

@Roxxik

@Roxxik Roxxik commented Jul 31, 2026

Copy link
Copy Markdown

setNode ends with its own recalcNodeList. The calls that follow it in step
and initAndResetChip do nothing, and have done nothing since aed0d9a moved
recalcNodeList into setNode. Both call sites predate that commit and were
simply not removed with it.

The code as it stands says the opposite: that setNode hands back an unsettled
netlist and the caller is responsible for settling it. That has not been true since that commit,
and 14983af already relies on it not being true: it batches
writeNodes precisely because setNode recalculates on every call.

Why it is a no-op

recalcNodeList leaves both worklists empty, not just the output list:

  • The loop exits at the !listin_count(state) test, which is reached straight
    after lists_switch has swapped an empty listout into listin. So listin
    is empty on return.
  • listout_clear then zeroes listout.count and memsets listout_bitmap.

A second call switches two empty lists, sees an empty listin, and breaks on
iteration zero without visiting a node. The one thing it does is a bitmap_clear
over an already-zero bitmap: 216 bytes memset per half-cycle.

This also holds on the loop-limiter path. If the 50-iteration guard fires,
listin can be left non-empty, but the next call's lists_switch swaps the
just-cleared listout in before the count is tested, so it still breaks
immediately.

Verification

cbmbasic --benchmark is the only workload in this repo, and it is a poor test
of a solver change: one path through the netlist, no undocumented opcode, no
interrupt, barely any decimal mode. So the checking below was done with two
harnesses that are not in this repo, Klaus Dormann's 6502 functional test and
Tom Harte's SingleStepTests, plus a digest that folds all 1725 node values into
a hash at a fixed cycle interval, so two builds can be compared node by node
instead of at the registers.

First, that the second call has no work to do. An instrumented build checks
listin.count, listout.count and every word of listout_bitmap immediately
before the second call in step:

workload step calls with pending work
cbmbasic to READY. 33,171 0
SingleStepTests, 300 cases/opcode 4,091,694 0

The recalcNodeList loop-limiter warning did not fire once in either run, so the
pathological path above was never taken and is argued rather than exercised.

Then, that removing it changes nothing. Node digests are identical, so the
two builds agree on all 1725 nodes at every sample point:

baseline with this change
SingleStepTests passed / failed 67,159 / 0 67,159 / 0
cbmbasic --benchmark half-cycles 33155 33155
cbmbasic final chipStatus AB:FFCF D:FF PC:FFCF A:00 X:00 Y:0A SP:F5 P:16 IR:20 identical

The functional test by Klaus Dormann was run to 2M cycles rather than to completion. It is a
hash comparison between two builds, not a pass/fail run, and the hashes
diverge at the first differing node whenever they are going to diverge at all.

Removing only the step call gives the same hashes as removing both, so the two
sites were checked separately as well as together.

Performance

There is none, and none is claimed. Three interleaved pairs of
cbmbasic --benchmark:

baseline with this change
cycles/sec 38555, 38918, 38704 39042, 39172, 38748

Under 1%, inside run-to-run noise. A 216-byte memset is nothing next to the
~1725-node solve it sits beside. This is a clarity change: it removes a line that
misstates setNode's contract.

Machine

AMD Ryzen 5 7640U (Zen 4), 6C/12T. Kernel 7.1.4-arch1-1, gcc 16.1.1, -O3.
Benchmark runs interleaved, both sides built up front and never rebuilt between
runs. The figures above are three raw runs per side, not medians. The point of
the row is that the two ranges overlap, not what either value is.

I used an LLM to help me out in this work, but I manually reviewed all changes made.

setNode has settled the netlist itself since aed0d9a moved
recalcNodeList into it. The calls that follow setNode in step and
initAndResetChip predate that commit and have been no-ops ever since.

recalcNodeList leaves both worklists empty: its loop exits when
lists_switch has just swapped an empty listout into listin, and
listout_clear then zeroes listout and its bitmap. A second call
switches two empty lists, sees an empty listin, and breaks on the
first iteration without visiting a node. All it costs is a 216-byte
memset per half-cycle, so no simulation result changes and the
benchmark does not move.
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