tu: let a promoted TU own the vtable its key function emits - #1880
tu: let a promoted TU own the vtable its key function emits#1880andrewboudreau wants to merge 1 commit into
Conversation
Multi-function isolation refused any non-text content, so a reconstructed TU could be promoted only while it emitted no data. Under the Itanium ABI that is exactly backwards: a class's vtable and RTTI go to the TU that defines its KEY FUNCTION, which for almost every actor here is the destructor the TU exists to hold. The moment a TU is real enough to place its own structors, mwcc hands it _ZTV/_ZTI/_ZTS -- and isolation threw it out. Measured over the 25 entries that were otherwise ready: 23 of them, 9 to 13 data symbols each, 258 in total and every one _ZTV/_ZTI/_ZTS. plan_many now does what plan has always done for singletons, generalised from one kept section to N: drop the non-text content, externalise its symbols so a weak gap-object import can never bind to our address 0 instead of the cartridge's carved-out address, and take the vtable address-point preamble back off the surviving relocations. Every RTTI reloc check plan makes, this makes. Two things stay refusals, deliberately: an unlicensed .text body, because the spanning delinks entry means dsd no longer fills that range from the ROM and a stray function would be placed inside it; and non-RTTI data. An ordinary owned definition -- a table, a string, a global the source declares -- is not carved out of the cartridge by name, so externalising it would invent an import nothing can satisfy. Refusing it here says so in a sentence instead of at the link in an undefined symbol. A genuinely text-only object still takes no surgery and comes back byte-identical. Promotes arm9/Scene (21 functions) and ov029/ArrowLift (9), the two remaining ready entries the compiler already emits in ROM order. ArrowLift is a key-function TU with 11 data symbols and is the proof this works: it promotes with the image unchanged. tu_promote.py performs the mechanical half of a promotion -- delinks entries in and out, the src_tu -> src rename, the legacy git rm, the manifest status, and one attribution override per absorbed symbol so a many-to-one consolidation reads as consolidated rather than N authors lost. It compiles nothing: the proof of a promotion is rombuild reporting 106/106 afterwards, and running that once over a batch beats running it once per entry. Two prose references followed the absorbed dScene_c files to src/actors/Scene.cpp. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VregK5ZWRa2NbUcneaprG6
❌ PR validation — FailedValidation failed: full-ROM result regressed from the base commit; full-ROM validation failed Full merge validation
Byte-verified means the range carries Full ROM build output (mwccarm failed, exit 1)Warnings: 30 address range(s) left the byte-verified set while enrolled totals held steady: arm9:0x0202e140-0x0202e170, arm9:0x0202e170-0x0202e1b4, arm9:0x0202e1b4-0x0202e1e0, arm9:0x0202e1e0-0x0202e26c, arm9:0x0202e26c-0x0202e300, +25 more; 28 more function(s) now claim a match that nothing compiles; enroll them in a delinks.txt to have the ROM build check them. The private worker commits a test merge, builds the stock ROM profile, compares every executable module, measures matched and source-built code, checks contributor lineage, and verifies affected relocations. The mod profile is opt-in and is not part of this merge gate. |
Both come from the #1880 -> #1914 promotion chain, which bundled them with the manifest rows and sources they produce. The PR validator restores all of `tools/` from base, so no PR in that chain could ever test its own tool change -- every one of them is UNSTABLE for that reason alone. These are lifted out and land alone, off main, so the content PRs that follow are judged against tools that are already there. `tu_order_check.py` compiles a manifest TU once and reports, per licensed function, bytes and emission order. Production isolation places an object's `.text` into the spanning delink in emission order, so when a TU defines its destructor out of line and mwcc emits `D2, D0, D1` against the cartridge's `D1, D0`, the range does not link. The tool makes that visible before a promotion is attempted rather than after. `tu_promote.py` performs the mechanical half of a promotion and guesses at nothing: it collapses the per-function `delinks.txt` entries into one `complete` entry spanning the manifest's sections, `git mv`s the `src_tu/` source to its production path (R100, so credit follows), `git rm`s every `legacy_source`, rewrites the entry to `status: promoted`, and adds one `attribution.json` override per absorbed symbol. It compiles nothing -- the proof of a promotion is `rombuild.py` reporting 106/106 with `mismatching: 0` over the whole batch. The chain's `objisolate.py`/`rombuild.py`/`romdata_check.py` deltas are deliberately NOT carried: they are the predecessor of the deadstrip-data work that has since landed as #1977, #1987 and #1989, and main's versions supersede them. The remaining three files are comment-only, correcting paths that the promotion moves. Verified against main at c05617c, not against the chain's 350-commit-old base: `tu_promote --dry-run ov002/BlueCoinSwitch ov002/Exit ov045/PoleLift` resolves all three to their production paths, and refuses `ov100/daObjPathLift_c` as already promoted; `tu_order_check ov002/BlueCoinSwitch ov045/PoleLift` compiles both and reports the `D0, D1` + extra `D2` against the wanted `D1, D0`. Claude-Session: https://claude.ai/code/session_01QhhAeJwXBnfPp7B5DNjCwh Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
Triage: leaving this open. The working assumption was that #1993 ( #1993 promotes 26 TUs. This PR promotes 2, and neither is in #1993's set:
Both files are still under Unlike the four TUs #1993 documents as deliberately excluded, and unlike This branch is nonetheless stale: its base is |
|
Gatekeeper review — keep open. This is NOT superseded by #1993. I checked the coverage rather than assuming it. The #1880 → #1882 → #1884 chain promotes 66 TUs against #1993's 26, and five promotions are uncovered by anything else in the queue: What I need before this can merge, in order:
Do not run If any premise above is wrong, say so on the PR with evidence rather than routing around it — I have been corrected three times today and each correction was right. |
|
Status note, so nobody spends a validator slot chasing this. The red That is three days old. It predates #2007's re-bank, #2011, and today's merges (#1991, #1978, #2000, #1985). This is almost certainly the base-desync shape rather than a real backslide: a PR cut before a re-bank merges against a baseline that predates it and inherits the red. The fix for that is to merge Two corrections to what I told people earlier, both mine and both wrong:
Why this is parked rather than refreshed#1993 is the restack of this chain, and it is the queue chokepoint: it regenerates Order I am walking: #2002 → #2005 → #1995 → #1993, then re-assess what of this chain is still unlanded. Current signal is that the remainder is smaller than the diffstat suggests — a parallel review of #1914 found all 37 of its uniquely-promoted sources already present on Nothing here is closed and nothing is being discarded. Coverage on main is holding at 2,066,772 B built from source, 0 bytes handed back to the cartridge, and I will re-check it after every merge. |
|
Closing as superseded. The body work here was real, but every mechanism that made it legal The blocker is RTTI = ("_ZTV", "_ZTI", "_ZTS")
for index, sec in extra:
if sec.name.startswith(".text"): continue
for name in occupants_of(index):
if not name.startswith(RTTI):
owned.append(...) # only NON-RTTI is refused
...
drop = [index for index, _sec in extra] # every RTTI section droppedSo the title — "let a promoted TU own the vtable its key function emits" — is the inverse of Main considered this and refused it. The
Main solved the same goal strictly in #1987, "Verify class data emitted by promoted C++ TUs":
Every Also, for the record: the base of this PR (#1878, Nothing to salvage from this one — |
Stacked on #1878 (which is stacked on #1876).
The wall
Multi-function isolation refused any non-text content, so a reconstructed TU could be promoted only while it emitted no data. Under the Itanium ABI that is exactly backwards: a class's vtable and RTTI go to the TU that defines its key function — which, for almost every actor here, is the destructor the TU exists to hold. The moment a TU is real enough to place its own structors, mwcc hands it
_ZTV/_ZTI/_ZTS, and isolation threw the object out:Measured over the 25 entries that were otherwise ready: 23 of them, 9–13 data symbols each, 258 in total and every one
_ZTV/_ZTI/_ZTS. The manifest notes call this "Phase F" and defer it in five separate places.What changed
plan_manynow does whatplanhas always done for singletons, generalised from one kept section to N:Every RTTI relocation check
planmakes, this makes._applywas already written in terms of a set of kept sections; it just had no caller that passed more than one.Two things stay refusals, deliberately:
.textbodyA genuinely text-only object still takes no surgery and comes back byte-identical.
Promotions
arm9/Scene(21 functions) andov029/ArrowLift(9). ArrowLift is a key-function TU with 11 data symbols and is the proof this works: it promotes with the image unchanged.ov002/Treewas ready and is deliberately not here — its TU is still the un-migrated C form (extern void* _ZTV4Tree[]with hand-written vptr stores), so it imports the vtable rather than defining it, and consolidating would have cost_ZTV4Tree's data verification. Caught by diffing the per-record dump; it is a real loss, not a counting artifact.tools/tu_promote.pyThe mechanical half of a promotion: delinks entries in and out, the
src_tu→srcrename, the legacygit rm, the manifest status, and oneattribution.jsonoverride per absorbed symbol. It compiles nothing — the proof of a promotion isrombuildreporting 106/106 afterwards, and running that once over a batch beats once per entry.--dry-runreports what each would touch.Verification
rombuild -j16 --no-rom: 106/106 exact,mismatching: 0, PASSprepush_attribution: 35 consolidated with credit intact, 0 changed, 0 lostlayout_checkclean ·port_refcheck407/407 ·check_dead_referencesno new (two prose references followed the absorbeddScene_cfiles tosrc/actors/Scene.cpp) ·check_src_tu_compilesallSHN_UNDEF, every dropped section is zero-sized, and the object never changes lengthWhat is left
The other 22 ready entries are blocked on one thing, and it is not tooling: their TUs define the destructor out of line, so mwcc emits
D2, D0, D1while the ROM hasD1thenD0. The fix is the source form the class-form notes already describe — an inline in-class destructor plus a real instantiation — which also removes the homelessD2each of them carries. That is source work, one class at a time, and it is the same change that makes these files period-accurate C++.🤖 Generated with Claude Code
https://claude.ai/code/session_01VregK5ZWRa2NbUcneaprG6