chore(A): adopt AllocRecVar at the 20 remaining AllocVar(…, tyRecord) sites
AllocVar sizes a tyRecord symbol from LastTypeRecId, a global
side-channel left behind by whatever declaration the parser last saw. Every
caller that wants a record temp of a known type therefore has to set that
global first and put it back afterwards, by hand, at the right moment. Five
sites in ir.inc did; one did not, and the result was a 240-byte record
allocated 8 bytes (RecSize(REC_NONE) = 8) and then zeroed with 240 — an
out-of-bounds BSS write that was invisible for as long as the page round-up
slack absorbed it, and became a hard SIGSEGV the day 75d2ba662 page-aligned
the image. Diagnosis: regression-test-asm-compiler-2 (frankA). Fix:
AllocRecVar(name, recId) in symtab.inc, which makes "forgot to set
LastTypeRecId" unrepresentable, and the five ir.inc sites converted.
This ticket is the rest of the conversion. Twenty AllocVar(…, tyRecord)
calls remain, and they are in files this lane does not own:
| file | sites |
|---|---|
pasparser_expr.inc |
694, 7160 |
pasparser_stmt.inc |
4996 |
pyparser.inc |
48015 |
cparser.inc |
2968, 8712, 10775 |
rparser.inc |
1516, 1583, 2383, 2497, 3382, 3421, 3433, 3450 |
zparser.inc |
1015, 1033, 1100, 1153, 1184 |
Each needs the same reading ir.inc got, one at a time: does this site
already know the record id? If it does, it should pass it (and any
save/restore of LastTypeRecId around it deletes). If it genuinely means "the
declaration the parser just parsed", it is correct as it stands and should get
a one-line comment saying so, because that is the case a reader cannot tell
from the call. Do not convert mechanically — the point is to remove the
side-channel where it is not wanted, not to rename every call.
The stronger guard, and why it is not built yet
AllocVar(name, tyRecord) with LastTypeRecId = REC_NONE is, as far as
anything measured shows, always a defect: it allocates 8 bytes for a record.
Measured on an instrumented build at 32ef5081fe89 — a writeln(StdErr) on
exactly that condition — the count is 0 for compiler/compiler.pas
(37k lines, every frontend), lib/rtl/system.pas, test_asmcore_x64,
test_interfaces, test_records, test_generics, test_classes,
c_builtin_bits.c, lib_codecs.npy and lib_mimic_collections_abc.npy. So
turning the condition into a hard Error would fire nowhere today and would
have caught this bug at the moment it was written.
It is not in this session's commit because the blast radius lands in R and Z,
whose suites this lane cannot run, and a fatal that fires in someone else's
frontend on a Saturday is a worse trade than the bug it prevents. Turn it on
with the conversion above, once each of the twenty sites has been read: at
that point a REC_NONE record allocation is not merely unmeasured, it is
accounted for.
Gate
make compiler/pascal26 (self-host fixedpoint) + the owning frontend's tests
for whichever files a given commit touches. Land per-file, not in one sweep.