Skip to content

fix(as370): an undefined symbol is IFO188, and the instruction is zeroed (#82) - #87

Merged
mgrossmann merged 1 commit into
mainfrom
fix/undefined-symbol-ifo188
Aug 29, 2026
Merged

fix(as370): an undefined symbol is IFO188, and the instruction is zeroed (#82)#87
mgrossmann merged 1 commit into
mainfrom
fix/undefined-symbol-ifo188

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Closes #82.

x_factor returned 0 for a symbol in no table, in pass 2 as much as in pass 1, and recorded nothing. IFOX00 rejects it — IFO188 <symbol> IS AN UNDEFINED SYMBOL, severity 8 (erms.asm:193 / jermsgcd.asm SEV188) — and assembles the whole machine instruction as zero.

The bytes matter as much as the message. IFOX00's all-zero instruction is an invalid opcode and program-checks when reached; as370's kept its opcode and ran — a branch to address 0, a load from base+0, an MVC into offset 0 of whatever the base register held.

Shape of the fix

statement kind what happens
machine instruction scan_undef_terms in the else if before the format switch — instruction zeroed, hasa1 (and hasa2 for SS) set to 0
DC/DS/EQU/ORG/USING/END recorded from x_factor, pass 2 only — IFOX zeroes the value, not the statement
literal (=A(NOVAL)) not zeroed. IFOX assembles L 4,=A(NOVAL) normally and flags the pool; emit_lit flags the statement that wrote the literal, the way IFO158 already does

Because a flagged instruction never reaches resolve()/expr_val(), the two halves cannot report the same symbol twice.

The predicate is neither !sym_find nor !defined. sym_get enters symbols that are only referenced — EXTRN/WXTRN, a V-con, a =V literal — and those keep defined == 0 for the whole assembly. !defined alone would have flagged every external in the corpus. They carry S_ER, and nothing else undefined does: !s || (!s->defined && s->type != S_ER).

At the x_factor site the value and the relocation count are deliberately untouched — the change there is diagnostic only, which is why no deck moves.

Counted whether or not printed, per bce32d8: list stops at 512, the overflow says so (verified with 600 undefined symbols: 512 printed, ... and 88 further undefined-symbol diagnostics, RC 8).

Verification

result
fixture vs. listref/ifox-listing-undefsym.txt object bytes and ADDR columns identical; literal stays 5840 F018; RC 8, 5× IFO188
826 ecosystem modules 0 raise IFO188; the same 825 assemble as on the baseline
tests/corpus/check.sh no module moved — 743 decks byte-identical
tests/run.sh 41 OK, 0 failures, ALL SAMPLES BYTE-IDENTICAL TO IFOX00 (incl. hello/litmove/arith/ksdsnatr with a SYS1.MACLIB extract)
tests/listref/check.sh all four references still column-exact

Modules probed: libc370 (743), rexx370, ufsd, ftpd, httpd, httplua, httprexx, lua370, mvsmf, lstring370, nsf370.

The positive control. nsf370 has since fixed its eaten cards (9012263), so its current tree is clean. Assembled at 9012263^ — the state the issue measured — the two modules report exactly the symbols the issue names:

nsfctcio.asm  DCBDDNAM  IHADCB  DCBOFLGS  IHADCB  DCBOFOPN
nsfvsvc.asm   ANCVERNO  ASCBASID  DOUNSTG

L R3,REQFUNC(,R8) is absent from that list because the card was eaten whole — that one is #72's diagnostic, not this one.

Tests

The #82 tripwire is replaced by the oracle's numbers: RC 8, the five messages at their statements, the 14 zero bytes followed by an intact L, and the single RLD entry (so an undefined term invents no relocation).

A second case guards what must not be read as a symbol — X'40', C'A', B'1111', L'FIELD, =A(…) — with every symbol a forward reference, which is what proves the diagnostic stays silent in pass 1. Without the self-defining-term skip, MVI FLAG,X'40' would report an undefined symbol named X and zero a good instruction.

Known divergences, documented not fixed

  • NUMBER OF STATEMENTS FLAGGED: IFOX says 4, as370 says 5 — as370's counter counts messages. Second recorder to inherit this knowingly; filed separately.
  • A EQU B with B defined further down: IFOX resolves EQU in pass 1 and flags IFO188; as370's pass-2 gate keeps it silent and gives A the pass-1 value 0. Its own defect, adjacent to this one; filed separately.
  • tests/undefsym.s is frozen — IFOX00's listing prints its five comment cards verbatim and numbers every statement from them, so its now-stale header cannot be corrected without the fixture and the oracle drifting apart. Noted in tests/run.sh and tests/listref/README.md.

…oed (#82)

x_factor returned 0 for a symbol that is in no table, in pass 2 as much as in
pass 1, and recorded nothing. IFOX00 rejects it -- IFO188 <symbol> IS AN
UNDEFINED SYMBOL, severity 8 (erms.asm:193 / jermsgcd.asm SEV188) -- and
assembles the whole machine instruction as zero.

The bytes matter as much as the message. IFOX00's all-zero instruction is an
invalid opcode and program-checks the moment it is reached; as370's kept its
opcode and ran -- a branch to address 0, a load from base+0, an MVC into offset 0
of whatever the base register happened to hold. Silent corruption where the guest
gives an S0C1. That is how nsf370's two modules lost a DCBD, two EQUs and a whole
instruction to the column-72 continuation rule (#72) without one diagnostic
between them: the swallowed cards defined symbols, and every reference to them
went quiet.

Machine instructions take the IFO236 path -- scan_undef_terms runs in the else-if
before the format switch, so a flagged statement never reaches resolve() and the
same symbol cannot be reported twice. Every other statement (DC/DS/EQU/ORG/USING/
END) is recorded from x_factor, where IFOX zeroes the value but leaves the
statement alone; the value and the relocation count there are deliberately
untouched, so no deck moves. A literal is the one operand not zeroed: IFOX
assembles `L 4,=A(NOVAL)` normally and flags NOVAL against the pool, which
emit_lit now does against the statement that wrote the literal, the way IFO158
already attributes one.

"Undefined" is neither !sym_find nor !defined: sym_get enters EXTRN/WXTRN, V-con
and =V symbols that keep defined == 0 for the whole assembly, so !defined alone
would have flagged every external in the corpus. They carry S_ER, and nothing
else undefined does.

Counted whether or not printed, per bce32d8: the list stops at 512 and the
overflow says so.

Measured over 826 ecosystem modules (libc370 743, rexx370, ufsd, ftpd, httpd,
httplua, httprexx, lua370, mvsmf, lstring370, nsf370): zero raise IFO188, the
same 825 assemble as before, and the differential corpus gate reports no module
moved. Against nsf370 at 9012263^ -- before its own fix for the eaten cards --
the two modules the issue names now report five and three undefined symbols:
DCBDDNAM, IHADCB, DCBOFLGS, DCBOFOPN and ANCVERNO, ASCBASID, DOUNSTG.

tests/run.sh replaces the #82 tripwire with the oracle's numbers and adds the
control for what must NOT be read as a symbol: X'40', C'A', B'1111', L'FIELD and
a literal, every reference forward, at RC 0.
@mgrossmann
mgrossmann force-pushed the fix/undefined-symbol-ifo188 branch from df180fb to 1ae5a84 Compare August 29, 2026 06:16
@mgrossmann
mgrossmann merged commit 9369020 into main Aug 29, 2026
2 checks passed
@mgrossmann
mgrossmann deleted the fix/undefined-symbol-ifo188 branch August 29, 2026 06:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

as370: an undefined symbol assembles as zero, silently — IFOX00 flags IFO188 (severity 8) and zeroes the instruction

1 participant