Repository navigation
Conversation
setupNodesAndTransistors allocates these two arrays but destroyNodesAndTransistors never freed them, leaking 6904 bytes per chip instance. Harmless for cbmbasic, which builds one chip, but measure rebuilds chip state thousands of times via resetChip_test. Verified with valgrind over repeated initAndResetChip/destroyChip cycles: previously "definitely lost: 20,712 bytes in 6 blocks" for three instances, now "All heap blocks were freed -- no leaks are possible". measure's 256-opcode output is byte-identical.
The offsets into dependent_block are prefix sums over the gate counts, so they reserve one slot per gated transistor. add_nodes_dependant() then refuses to insert a node twice and uses fewer, but nothing ever narrowed the ranges to match. So nodes_dependant[i+1] and nodes_left_dependant[i+1] overshoot the entries belonging to node i, and changeNodeValue() reads past the real data into whatever the calloc left there - zero, i.e. node 0 - and queues it for recalculation. On the shipped netlist that is 20 slots across 6 nodes in nodes_dependant and 8 more across 5 nodes in nodes_left_dependant, 28 bogus reads in all. It is harmless only by luck: recalculating node 0 is a no-op, so the bogus work is invisible rather than wrong. Compact both lists once the fill is done. Entries only ever move towards the front, so copying forward in place is safe. measure's 256-opcode characterisation is byte-identical, cbmbasic still reaches READY. in 33155 half-cycles with the same end state, and valgrind reports no errors.
Roxxik
force-pushed
the
correctness-fixes
branch
from
July 27, 2026 19:26
0dbef6c to
698338b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two unrelated defects in
netlist_sim.csetup, found with valgrind whilebenchmarking. Neither is observable in
cbmbasicoutput.A third turned up in the same pass, the uninitialised
groupcountread. #17already fixes it, so this PR does not touch it. The analysis is at the end.
The dependant ranges overshoot the data actually written
The offsets into
dependent_blockare prefix sums over the gate counts, so theyreserve one slot per gated transistor.
add_nodes_dependant()then refuses toinsert a node twice and uses fewer, but nothing ever narrowed the ranges to match.
So
nodes_dependant[i+1]andnodes_left_dependant[i+1]overshoot the entriesbelonging to node
i, andchangeNodeValue()reads past the real data intowhat the
callocleft behind, which is zero, and so queues node 0 forrecalculation. On the shipped netlist that is 20 slots across 6 nodes in
nodes_dependantand 8 more across 5 nodes innodes_left_dependant, 28 bogusreads in all. It is harmless only by luck: recalculating node 0 is a no-op, so
the bogus work is invisible rather than wrong.
Fixed by compacting both lists once the fill is done. Entries only ever move
towards the front, so copying forward in place is safe.
nodes_dependantandnodes_left_dependantare never freedsetupNodesAndTransistorsallocates them anddestroyNodesAndTransistorsdoesnot free them, leaking 6904 bytes per chip instance. Harmless for
cbmbasic,which builds one chip. Not harmless for
measure, which rebuilds chip statethousands of times via
resetChip_test.Verification
measure's 256-opcode characterisation is byte-identical.cbmbasicstillreaches
READY.in 33155 half-cycles with the same final state.Over repeated
initAndResetChip/destroyChipcycles, valgrind's leak reportfor three instances goes from 20,712 bytes definitely lost in 6 blocks to none.
It still reports the uninitialised read in
group_clear(), which is the one#17 fixes.
Not fixed here:
groupcountis read before it is written (#17)Left to #17, which was filed first. The analysis is here because the one-line
change buys more than it appears to.
state_tismalloc'd andgroupcountis never assigned during setup, butgroup_clear()reads it before writing it:The very first call, reached from
setNode()duringinitAndResetChip, walks agarbage number of entries. Only the first, because it resets the count on the way
out, which is why this survives at all.
It is not merely a stale read.
groupcountis auint16_t, so the loop can runto 65535 while
groupholds 1725 entries, reading roughly 127 KB past thatallocation. The values it picks up out there are then used to index
groupbitmap, which is 27 words, and the loop body writes to it, so agarbage count can clear bits up to 8 KB beyond a 216-byte buffer. Both arrays are
calloc'd, so the in-bounds part of the walk is harmless; only the tail past theend of
groupcan do damage.That matches the crashes reported in #17: whether it does damage depends on what
the allocator left behind, which is why it is intermittent. On this machine it
never crashed, and valgrind reports the read on an unmodified tree.
I used an LLM to help me out in this work, but I manually reviewed all changes made.