Skip to content

fix(as370): a duplication factor may be a parenthesised expression (#93) - #95

Merged
mgrossmann merged 1 commit into
mainfrom
fix/dc-parenthesised-dup-factor
Aug 29, 2026
Merged

fix(as370): a duplication factor may be a parenthesised expression (#93)#95
mgrossmann merged 1 commit into
mainfrom
fix/dc-parenthesised-dup-factor

Conversation

@mgrossmann

Copy link
Copy Markdown
Contributor

Closes #93.

Assembler XF takes an absolute expression in parentheses wherever a decimal self-defining term may stand as a DC/DS duplication factor. xdcds.asm:103 scans it — a digit takes the self-defining path, anything else falls to DUPF:

DUPF     CLI   CHAR1,JLPARN             SEE IF EXPRESSION
         BNE   TYPE                     OMITTED FIELD
         ST    R5,ADUPFAC               SAVE ADDRESS OF EXPRESSION

The character is the constant's type only when it is neither a digit nor (. as370 had no DUPF arm and went straight to the type test, so every such operand came out ERR198 invalid type and reserved nothing — moving every symbol after it in the section.

Measured, not assumed

tests/oracle/capture.py on mvsdev, JOB02900, listing committed as tests/listref/ifox-listing-dupfac.txt. Seven cases, and three decided the design:

case IFOX00
((AREAEND-AREASTA)/8)X'00' backward 5 bytes
(4)X'00' 4 bytes
((AREAEND-AREASTA)/8)X as DS 5 reserved
(0)X'00' legal and silent — reserves nothing, no diagnostic
4X'00' control 4 bytes
forward reference IFO231 + IFO217 + IFO206, reserves nothing
negative IFO206, reserves nothing

RC 12, two statements flagged over four messages.

The forward-reference rule is what makes this implementable

By pass 2 the forward symbol is defined. Re-deriving the verdict there would allocate where pass 1 allocated nothing, the location counter would move between the passes, and every symbol after it would shift — silently. Because IFOX rejects it outright (SYMBOL NOT PREVIOUSLY DEFINED, and the statement reserves nothing), pass 1's verdict can simply be recorded, and pass 2 looks it up instead of re-evaluating.

That is the whole reason the fixture asks the guest about a forward reference at all.

Two traps in the implementation

The scan must be balanced, not strchr(')'). The common shape carries an inner parenthesis, ((A-B)/8), where the first ) is in the wrong place. A truncated expression still yields a number, so this would size the constant wrongly in silence — dupfac_nested pins it on its own. (The neighbouring L(expr) length modifier still has the strchr shape; adjacent, and not this change.)

expr_val could not be used as it stands. Its leading-( guard reads a parenthesis as a subscript — right for a machine operand like (R1), wrong for an expression that merely begins with one, which it valued at 0. expr_val_full is the same evaluator without that guard. This cost a debugging round: with expr_val, the backward-reference case silently reserved nothing and the negative case silently became legal.

Severity

IFO217 is severity 12 (jermsgcd.asm SEV217) where IFO231 and IFO206 are 8, so one forward reference takes the whole assembly to RC 12. Reproduced rather than flattened to 8 — matching the oracle's return code is the reason these diagnostics exist.

Verification

fixture vs. oracle every LOC, the section length 0x3A, both TXT cards, RC 12, 2 statements flagged over 4 messages
tests/corpus/check.sh no module moved — 743 decks byte-identical
826 ecosystem modules unchanged, 825 assemble as before
tests/run.sh 47 OK, 0 failures, ALL SAMPLES BYTE-IDENTICAL TO IFOX00
tests/listref/check.sh all references still column-exact

Both new cases fail against main (expected RC 12, got 8 / expected RC 0, got 8).

On a local tree of real IBM MVS assembler — not committed here — 20 of a 277-module spread sample now assemble clean that did not before (36% → 43%), none lost. Among them IFNX6C.ASM, which is IFOX00's own source.

Built warning-clean with real gcc 14.2 -Werror on mvsdev as well as clang; it caught a -Wmisleading-indentation that clang accepts.

No deck oracle: IFOX00 does not punch at severity 12, so capture.py returned the listing alone. Sufficient here — the LOC and OBJECT CODE columns are exactly what a duplication factor decides.

Assembler XF takes an absolute expression in parentheses wherever a decimal
self-defining term may stand as a DC/DS duplication factor. xdcds.asm:103 scans
it: a digit takes the self-defining path, anything else falls to DUPF, which
tests for a left parenthesis and evaluates what follows as an expression --

    DUPF     CLI   CHAR1,JLPARN             SEE IF EXPRESSION
             BNE   TYPE                     OMITTED FIELD
             ST    R5,ADUPFAC               SAVE ADDRESS OF EXPRESSION

-- so the character is the constant's TYPE only when it is neither a digit nor
'('. as370 had no DUPF arm and went straight to the type test, so every such
operand came out ERR198 "invalid type" and reserved nothing, moving every symbol
after it in the section.

The scan is balanced rather than strchr(')'): the common shape carries an inner
parenthesis, ((A-B)/8), where the first ')' is in the wrong place. A test pins
that on its own, because a truncated expression still yields a number and would
size the constant wrongly in silence.

expr_val could not be used as it stands. Its leading-'(' guard reads a
parenthesis as a subscript -- right for a machine operand like (R1), wrong for an
expression that merely begins with one, which it valued at 0. expr_val_full is
the same evaluator without that guard.

Measured on the guest rather than assumed (JOB02900, listing committed as
tests/listref/ifox-listing-dupfac.txt):

  (0)X'00'        legal and SILENT -- reserves nothing, no diagnostic
  forward ref     IFO231 + IFO217 + IFO206, reserves nothing
  negative        IFO206, reserves nothing

The forward-reference rule is what makes the construct implementable in two
passes. By pass 2 the symbol is defined, so re-deriving the verdict there would
allocate where pass 1 allocated nothing and the location counter would move
between the passes -- every symbol after it shifting silently. Because IFOX
rejects it outright, pass 1's verdict is recorded and pass 2 looks it up.

IFO217 is severity 12 (jermsgcd.asm SEV217) where IFO231 and IFO206 are 8, so one
forward reference takes the assembly to RC 12. That is the oracle's return code
and it is reproduced rather than flattened to 8.

Every location counter, the section length and the flagged-statement count now
match the oracle line for line: 5/4/5/0/4 bytes, length 0x3A, RC 12, two
statements flagged over four messages.

No deck oracle: IFOX00 does not punch at severity 12. The listing carries the LOC
and OBJECT CODE columns, which is what a duplication factor decides, and run.sh
asserts the section length and both TXT cards against them.

The ecosystem is untouched -- 826 modules unchanged, no corpus module moved. On a
local tree of real IBM MVS assembler, 20 of a 277-module sample now assemble
clean that did not before (36% -> 43%), none lost.
@mgrossmann
mgrossmann merged commit caaca22 into main Aug 29, 2026
2 checks passed
@mgrossmann
mgrossmann deleted the fix/dc-parenthesised-dup-factor branch August 29, 2026 19:02
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: a parenthesised duplication factor on DC/DS is rejected — Assembler XF evaluates it as an expression

1 participant