From bf7b6ceac2fe8db9a63093943e6ff55e106deb77 Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 18:56:43 -0400 Subject: [PATCH] phase12: merge 22 (665 bodies) + the pool band filter, and I had the first call backwards MERGE 22: +2 bodies, both from worker D's un-attempted band. 0x801092C0 (52 B) and 0x80085B44 (60 B), both re-verified by me from fresh --work dirs first. Gate: c_regions=674, differing_bytes=0, MATCH. POOL FILTER, SECOND LEAK, MY ERROR CORRECTED. D found 0x80012D54 -- the charter-blocked 0x80012xxx primitive-init family, with class , so the address-parsing rule I installed after the FIRST leak (0x80012A98) had no text to match on. D read its bytes and confirmed the family. I had explicitly DECLINED band-blocking the first time, arguing it 'would silently discard unclassified rows on an inference the charter does not make'. That was backwards: the charter names the family as 'the 0x80012xxx primitive-init family', i.e. BY ADDRESS BAND, and earlier phases already acted on it -- 0x80012A10/AE0/B20/CFC, all class '-', are excluded BY NAME in the Makefile. Two rows leaked by label phrasing and the second had no label at all. The band is the predicate. Blocked 8 -> 15, pools 70 -> 54. Also applying my own corrected commit idiom: this commit names the two claim sources rather than using 'git add -A src/', which swept three of D's in-flight rows into an earlier commit. New levers recorded: LOAD/STORE INTERLEAVING NEEDS BOTH SIDES VOLATILE (a volatile load can still hoist above a plain store and vice versa; cc1 orders volatile only against volatile) -- and from 0x801092C0, unsigned-parameter signedness read from the instruction AFTER the test, volatile on a table pointer for an alias reason, cookbook 19 generalising to a forward loop, and `p += i + start; p->p0 = 0;` over the subscript form. --- config/regions.tsv | 2 ++ phase-ends/logs/Phase12.md | 48 +++++++++++++++++++++++++++++++++ tools/sf3_negpool | 40 ++++++++++++++------------- tools/tests/test_sf3_negpool.py | 14 +++++----- 4 files changed, 80 insertions(+), 24 deletions(-) diff --git a/config/regions.tsv b/config/regions.tsv index 2e3c149..d388c79 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -377,6 +377,7 @@ 0x800834B8 0x80083504 src/func_800834B8.c 0x80083504 0x8008352C src/func_80083504.c 0x8008352C 0x8008355C src/func_8008352C.c +0x80085B44 0x80085B80 src/func_80085B44.c 0x80085B80 0x80085B90 src/func_80085B80.c 0x80089314 0x80089338 src/func_80089314.c 0x800893E8 0x80089434 src/func_800893E8.c @@ -668,6 +669,7 @@ 0x80108710 0x80108734 src/func_80108710.c 0x80108740 0x801087C8 src/func_80108740.c maspsx=epilogue 0x80108990 0x801089B4 src/func_80108990.c +0x801092C0 0x801092F4 src/func_801092C0.c 0x80109300 0x80109314 src/func_80109300.c 0x80109314 0x80109338 src/func_800F8F9C.c 0x80109778 0x80109790 src/func_80109778.c diff --git a/phase-ends/logs/Phase12.md b/phase-ends/logs/Phase12.md index ac710d1..7e3c6e4 100644 --- a/phase-ends/logs/Phase12.md +++ b/phase-ends/logs/Phase12.md @@ -1049,3 +1049,51 @@ instruction count, and from the allocator class, which permutes registers — th A spelling that moved the base's assignment after the two calls put the `la` exactly where the original has it and changed neither the hoist nor the fourth save, which is what makes it a class rather than a missed spelling. + +### The pool filter's SECOND leak, and my first call was backwards (worker D) + +Worker D found `0x80012D54` in its un-attempted band: **the `0x80012xxx` primitive-init family, which +charter section 7 blocks, and with class `-`** — so the address-parsing rule I installed after the +FIRST leak (`0x80012A98`, which at least names its siblings by address) had no text to match on. D read +the bytes and confirmed the family: `li v0,4 / sh v0,6(a0) / lui v0,0x400 / sw v0,8(a0) / +lui v0,0x5000 / sw zero,0(a0)`. + +**I had explicitly declined band-blocking the first time, and the reasoning was backwards.** I argued +that blocking the whole `0x80012xxx` band "would silently discard unclassified rows on an inference the +charter does not make". But **the charter makes exactly that inference — it names the family as "the +`0x80012xxx` primitive-init family", i.e. BY ADDRESS BAND** — and earlier phases had already acted on +it, since `0x80012A10`, `0x80012AE0`, `0x80012B20` and `0x80012CFC`, all with class `-`, are excluded +**by name** in the Makefile. Two rows leaked by label phrasing, the second with no label at all, and +that is the evidence that settled it: **the band is the predicate.** Blocked rows still report their +reason. The blocked count went 8 -> 15 and the pools 70 -> **54** rows. + +Recorded as a coordinator error with its correction, because the first fix looked reasonable and was +the reason the second row survived. + +### Worker D's un-attempted band is producing bodies, and it was the right call to build it + +D took the band I generated from its own criterion (rows with no mechanism named, never pushed to a +floor) and closed **2 of 4 rows attempted**, both at 4-6 spellings, and **both correct-LENGTH +candidates** — so the band's rows are genuinely structural rather than misidentified. D's own summary +is the one to remember: *the failures are allocator-shaped, not "wrong function"-shaped.* + +**`0x80085B44` (60 B) gave the phase a sharp new lever: LOAD/STORE INTERLEAVING NEEDS BOTH SIDES +VOLATILE.** Plain C batches (`lw/lw/lw` then three stores, 25 differing bytes); `volatile int *src` +alone is still 25 bytes, **because a volatile LOAD can still hoist above a plain store**; `volatile` +globals alone is still 25 bytes, **because a volatile STORE does not stop a plain load hoisting above +it**; both together match. **cc1 orders volatile accesses only against other volatile accesses.** That +is a precise statement of a boundary that four workers have been circling all phase. + +**`0x801092C0` (52 B) carried four levers, three new:** +* **UNSIGNED parameters** — `int` gives `blez`/signed `slt`, `unsigned` gives the original's `beqz` + AND `sltu`. **The entry test alone cannot tell you; the instruction AFTER it names the signedness.** +* **`volatile` on the table POINTER for an alias reason** — with a plain `Rec *` global cc1 hoists the + pointer load out of the loop, because the store's type (a pointer field of the record) is provably + not the global's own type; the original reloads every iteration. A proof by emission, not a taste. +* **Cookbook 19 generalises to a FORWARD loop** — the unused `addiu sp,sp,-8 / addiu sp,sp,8` pair + appears for `for (i=0;ip0 = 0;` rather than `p[i + start].p0 = 0;`** — the subscript form writes the + address sum into the OFFSET's register and rotates the loop; accumulating into a pointer makes the + sum's destination the BASE register (the original's `addu v1,v1,v0`). diff --git a/tools/sf3_negpool b/tools/sf3_negpool index c326d1c..993faae 100755 --- a/tools/sf3_negpool +++ b/tools/sf3_negpool @@ -151,14 +151,27 @@ BLOCKED_CLASS_SUBSTRINGS = ("trapping-arithmetic", "primitive-init") # -- it names the blocked family **by address**, so the keyword substring test passes it straight # through to prio 1. This is the same "half-right class string" pattern the workers kept hitting. # -# The fix is to read the ADDRESSES out of the class text and block the row if any of them is itself -# excluded. That is deliberately preferred over blocking the whole `0x80012xxx` band, which was the -# other option D offered, **and the choice is measured**: the band holds 9 index rows and only TWO -# are the family (`0x80012A48` by keyword, `0x80012A98` by this rule). The other seven record class -# `-`, i.e. merely unclassified -- four of them are excluded by name in the Makefile anyway, and -# band-blocking would silently discard the remaining three on an inference the charter does not make. -# Rows that reference a blocked family are reported, not guessed at. +# First fix: read the ADDRESSES out of the class text and block the row if any of them is itself +# excluded. That catches `0x80012A98`, which names its family as "same family as +# 0x80012A10/0x80012AE0". +# +# **THAT WAS NOT ENOUGH, AND WORKER D FOUND THE SECOND LEAK: `0x80012D54`, whose class is `-`.** +# There is no class text to parse, so no text rule can ever catch it -- and D read its BYTES and +# confirmed it IS the family: `li v0,4 / sh v0,6(a0) / lui v0,0x400 / sw v0,8(a0) / lui v0,0x5000 / +# sw zero,0(a0)`. +# +# So the band itself is blocked, which is what the CHARTER actually says: it names the family as +# **"the `0x80012xxx` primitive-init family"**, i.e. BY ADDRESS BAND. I first declined this because +# band-blocking would discard unclassified rows "on an inference the charter does not make" -- and +# that was backwards: the charter makes exactly that inference, and it had already been acted on, +# since `0x80012A10`, `0x80012AE0`, `0x80012B20` and `0x80012CFC` (all class `-`) are excluded BY NAME +# in the Makefile. **Two workers found the leak twice and the second instance had no text to match on, +# which is the evidence that settled it.** Blocked rows are still REPORTED with their reason. FAMILY_ADDRESS_RE = re.compile(r"0[xX][0-9A-Fa-f]{6,8}") +BLOCKED_ADDRESS_BANDS = ( + # The charter's own wording. See the comment above for why the band, not a string. + (0x80012000, 0x80013000, "charter s7: the `0x80012xxx` primitive-init family"), +) # *** THE TRIAGE KEY WAS IMPLEMENTED, TESTED AGAINST THE PHASE'S OWN RECORD, AND IT FAILED *** # @@ -427,9 +440,6 @@ def main(): dropped_done = [] dropped_blocked = [] dropped_own = [] - # Rows in a charter-named FAMILY band that the index never classified. Not blocked on an - # inference, but reported, so a human can decide rather than having it decided silently. - unclassified_family_band = [] for addr, size, status, klass in unregistered: bucket = names[zlib.crc32(f"{addr:#010x}".encode()) % args.workers] # CHARTER SECTION 7. A blocked class must not be attempted at all, so it must not be @@ -438,7 +448,9 @@ def main(): blob = f"{status} {klass}".lower() # A family reference BY ADDRESS counts exactly like a keyword. D's `0x80012A98` case. family_refs = {int(x, 16) for x in FAMILY_ADDRESS_RE.findall(klass or "")} + band = next((reason for lo, hi, reason in BLOCKED_ADDRESS_BANDS if lo <= addr < hi), None) if ("blocked" in status + or band is not None or any(s in blob for s in BLOCKED_CLASS_SUBSTRINGS) or (family_refs & (excluded | blocked_addrs))): dropped_blocked.append((addr, bucket, status, klass)) @@ -469,8 +481,6 @@ def main(): prio = min(prio, 2) if "deferred" in status: prio = 4 - if 0x80012000 <= addr < 0x80013000 and not (klass or "").strip().strip("-"): - unclassified_family_band.append((addr, bucket)) # A NOTE column, because free-text staging cannot be parsed reliably enough to decide # "done" vs "cheap retry" in every case. Where the tool CAN decide it drops the row (the # absence-token case, above); where it cannot, it says who classified it and lets the worker @@ -533,12 +543,6 @@ def main(): if len(dropped_done) > 5: print(f" ... and {len(dropped_done) - 5} more") print(f" dropped, this bucket's OWN prior classification {len(dropped_own)}") - if unclassified_family_band: - print(f" NOT blocked but FLAGGED: {len(unclassified_family_band)} row(s) inside a" - f" charter-named family band that the index never classified:") - for addr, bucket in sorted(unclassified_family_band): - print(f" {addr:#010x} (pool {bucket}) -- class '-', so the charter's family" - f" membership is an INFERENCE, not a record. Decide deliberately.") print(f"pool rows written {sum(written.values())}" f" (check: {len(unregistered) - len(leaked) - len(dropped_done) - len(dropped_blocked) - len(dropped_own)})") for n in names: diff --git a/tools/tests/test_sf3_negpool.py b/tools/tests/test_sf3_negpool.py index b3d7e2e..8f552a7 100644 --- a/tools/tests/test_sf3_negpool.py +++ b/tools/tests/test_sf3_negpool.py @@ -278,15 +278,17 @@ class TestSyntheticPools(unittest.TestCase): self.assertEqual(self.pool_rows(), [], "a class naming an EXCLUDED address is a family reference, not a pass") - def test_a_family_band_row_with_no_class_is_flagged_not_blocked(self): - """The other option D offered was to block the whole `0x80012xxx` band. Measured: the band - holds 9 index rows and only TWO are the family. Blocking by prefix would silently discard - unclassified rows on an inference the charter does not make, so they are KEPT and reported.""" + def test_a_family_band_row_with_NO_CLASS_is_blocked(self): + """I first kept these rows, arguing that blocking by band discards unclassified rows on an + inference the charter does not make. Worker D then produced `0x80012D54`: class `-`, no text + to match on, and its BYTES confirm it IS the family. The charter names the family BY BAND + ("the `0x80012xxx` primitive-init family"), and earlier phases already excluded four class-`-` + band rows by name -- so the band is the right predicate and my first call was backwards.""" self.write_negatives([("0x8001278C", "-", "near-match")]) r = self.run_tool() self.assertEqual(r.returncode, 0, r.stderr) - self.assertEqual(len(self.pool_rows()), 1, - "an unclassified band row is not blocked, only flagged") + self.assertEqual(self.pool_rows(), [], + "the charter names the family by BAND, and class `-` cannot be matched on") def test_rare_epilogue_order_is_NOT_blocked(self): """Worker A's distinction: the epilogue ORDER is work (cookbook 140/147/165 shipped