Track the rel8 displacement budget, so a tight jump is visible before it breaks
Requested by frankA, who proposed a static scan and then withdrew it in favour of this: "a static jump/emitter pairing would have been a fragility model I'd have had to keep true; a max-displacement readout is the fragility itself, measured."
Why a violation scan is not the thing to build
CheckRel8 (compiler/rel8.inc) already hard-errors on a displacement that
does not fit a signed byte, and its coverage is complete — census 2026-09-01:
172 rel8 stores all go through PatchRel8/EmitRel8, Patch8 is deleted, and
the 14 remaining raw Code[..] := Byte( sites are multi-byte little-endian
patches or ModRM/REX fixups, not displacements. A violation cannot ship.
So the missing quantity is the MARGIN. By the time CheckRel8 fires the slack
is already gone, and it fires for whoever compiles next — possibly a user
rather than the author of the emitter that grew. A slack of 17 bytes and a slack
of 90 are the same clean build.
The instrument exists
PXXDBG=a.rel8max (landed 4def2b06b) prints, at end of compile, the largest
displacement that compile passed through CheckRel8 and the slack remaining:
$ PXXDBG=a.rel8max ./compiler/pascal26 --threadsafe -Fulib/rtl prog.pas /tmp/p
PXXDBG a.rel8max max=101 (forward jump) slack=27 bytes to the -128..127 limit
Measured this way, binary-searching a temporarily lowered bound first and then confirming with the probe (which reproduced 101 exactly — that agreement is the probe's positive control):
| build | max | slack |
|---|---|---|
self-host + entire gate.sh quick tier |
in [101, 111] | 17–27 |
any source, --threadsafe |
101 | 27 |
| any source, plain | 54 | 74 |
The max is program-INDEPENDENT. 54 plain and 101 threadsafe on every Pascal and C source tried. So the tightest spans live in the runtime/prologue emitters, not in anything a test program shapes — which is what makes a standing row worth running at all, and what makes 27 bytes a fact about our shared work area rather than about a corpus.
What to build
Compile a small fixed set (one plain, one --threadsafe, ideally one per
target that emits rel8) with PXXDBG=a.rel8max, parse slack=, alert below
16. frankA on the floor: "16 sounds right to me; I have no better number."
Assert the compile SUCCEEDED before reading the number. The report runs at
the end of a successful compile, so a source that fails to compile prints
nothing at all — indistinguishable from a real zero. Measured: plain
test_mutex.pas prints silence because it refuses to compile without
--threadsafe, and reading that as "no rel8 emitted" is one keystroke away.
Branch on the exit code, do not merely print it.
The sibling class this does NOT catch — do not let the row imply otherwise
frankA's cd4af7824 was this same failure with the guard absent, and a budget
row could not have seen it: a hand-written literal jne +8 over a span that
grew when --emit-obj wrapped a global store in push/anchor/pop. It never
overflowed rel8. It stayed in range and landed on the pop INSIDE the wrapper,
so the nested path popped the caller's return address into eax and stored
through it. Clean object, and no relocation count could see it.
So there are two classes and this row addresses one:
- displacement too large —
CheckRel8errors; this row warns first. - displacement in range but landing in the wrong place — needs the
displacement COMPUTED rather than asserted, which is what
PatchRel8gives. frankA converted that stub. The remaining exposure is any hand-written literal jump offset, and finding those is a different job from this one.
Expected drift, already claimed
frankA has since grown the same region: exported data references are now
symbol-relative in --emit-obj, and the i386 address-as-immediate helper adds 6
bytes per site. If a.rel8max moves on --threadsafe i386 at 742e616ec446 or
later, that is theirs and not drift — ask before bisecting.
The hand-written-jump population, sized — 2026-09-02 (frankA, recorded by frankC)
The section above leaves "any hand-written literal jump offset" unquantified. frankA has since sized it, and the instrument matters more than the number.
grep for EmitB($7x); EmitB(...) DOES NOT COUNT JUMPS. It matches ModRM
bytes and the second bytes of two-byte opcodes: EmitB($0F); EmitB($7E); EmitB($C0) is movq rax, xmm0; EmitB($4C); EmitB($89); EmitB($77); EmitB($20)
is mov [rdi+32], r14. Neither is a jump. I quoted "about 25" to frankA off that
grep and frankA had earlier quoted 142 off the same one — two numbers from one
broken instrument, which is not two sources. Neither figure appears in this
ticket and neither should be used.
Filtering on the trailing { mnemonic } comment instead gives 36 jcc + 8 $EB
sites, and that instrument can only UNDERCOUNT — it misses any site whose
comment omits the mnemonic. So ~44 is a FLOOR, not a count.
frankA converted three of them, building the control artefacts with the
pre-change compiler and byte-comparing: every displacement is supposed to be
correct today, so byte-identity is the expected result and a differing byte IS a
bug. Two of the three differed, both by exactly 8, and both were shipping on
master (14bc9d218): WriteLn(s:2,'|') on a ShortString printed |, and
LoadFile into a ShortString reported Length(s)=0 for a 21-byte file. The
third was byte-identical, so the class is not uniform and the control is worth
running per site.
That ticket went 45 -> 70 and stays with frankA; ~41 sites remain. Nothing here changes: the budget row still addresses the OTHER class (displacement too large), and these two were in-range-landing-in-the-wrong-place, which is why no budget number could have shown them.
Deprioritised 2026-09-02 — the Track T tooling backlog was cut as a pile
This ticket is not being called wrong. It was moved as part of a pile, not judged individually, and nothing here disputes its finding.
Owner decision. 73 of the 74 open track: T tickets were filed between
2026-08-31 and 2026-09-02, 58 on one day. The pile was too large to work through
and returned almost nothing, and a ticket nobody will fix does not sit neutrally
— it stays in the ranker forever at zero value, which is the argument CLAUDE.md
already makes for a terminal folder over a low prio.
Four were kept in the ranker on a purely structural test — an active umbrella or
a hard blocked-by: edge from live work:
umbrella-one-full-tier-run-with-no-red-tier,
feature-t-freebsd-image-and-runner, and the two regression-test-core-* reds
that block the umbrella.
Kept, not deleted, for two reasons: so the finding is not rediscovered and refiled from scratch by the next agent who trips over it, and so it can be pulled back if what it touches becomes load-bearing.
To revive it: move it to the owning lane's backlog, set status: backlog,
and say in the ticket WHAT CHANGED to make it matter now. Restoring it because it
reads well is how the pile comes back.