Skip to content

det runtime: truncate arithmetic intermediates as cobc's ARITHMETIC-OSVS does (#4287) - #4654

Merged
squid-protocol merged 7 commits into
mainfrom
fix/4287-arith-osvs
Oct 8, 2026
Merged

squid-protocol merged 7 commits into
mainfrom
fix/4287-arith-osvs

Conversation

@squid-protocol

@squid-protocol squid-protocol commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Closes #4287. Part of #4270.

What

cobc -std=ibm turns on arithmetic-osvs. cobc then works out at compile time where to truncate an arithmetic intermediate, and to how many decimal places, and emits cob_decimal_align there. The det runtime computed every intermediate exactly. It now truncates at the same points:

  • det/osvs.py (new) replays cobc's decision from the GnuCOBOL 3.1.2 sources (typeck.c: build_decimal_assign, cb_build_cond, cb_walk_cond, decimal_expand / decimal_compute / decimal_align; tree.c: constant folding and literal-pair / field-literal relations decided at compile time). It covers:

    • dmax, taken from the receivers and the walk, which skips divisors;
    • the expr_decp stack and what pushes onto it;
    • when a pending align is flushed;
    • folded literals, keeping cobc's scale and literal identity;
    • condition relations, built right to left (gcc's argument order, confirmed from the oracle's generated C);
    • the dmax and stack an EVALUATE leaves for the rest of its sentence.
  • gen.py plans COMPUTE, ADD / SUBTRACT / MULTIPLY / DIVIDE with an expression, and the relations of IF, PERFORM UNTIL, SEARCH WHEN and EVALUATE. It emits Cobol.align(v, n). A literal on the right of an operation becomes a Cobol.Dc, libcob's decimal constant. Unary minus becomes 0 - x.

  • Cobol.java:

    • align is libcob's cob_decimal_align, including its shift in the wrong direction: with fewer places than the target, the value loses that many low-order digits, so 579 aligned to 2 places becomes 500.
    • Dc reproduces libcob changing a constant's scale in place.
    • divide keeps cob_decimal_div's 38 + max(d1-d2, 0) places. It used to keep 2 fewer when the divisor had more places.
    • power trims trailing zeros the way cob_decimal_pow does.
  • Register C2 is rewritten. The det runtime now MATCHES the oracle. The entry lists where GnuCOBOL's ARITHMETIC-OSVS departs from IBM's Appendix A rule:

    • the stack can pair an operation with the wrong operands;
    • the wrong-direction shift;
    • mutable constants;
    • the EVALUATE state that carries over in a sentence.

    It also lists what is not replayed. det_port_design.md is updated to match.

Default chosen (no owner decision needed): the det port models the oracle (cobc 3.1.2 -std=ibm), including its departures from IBM's rule, so a proof can see them. The issue's IBM ARITH(COMPAT) reading matches the oracle on the reproducer. The 30-digit cap is still not modelled on either side, as before.

Refused by name (new holes, none in any case): a negative literal exponent (libcob overwrites the constant), and an EVALUATE whose last WHEN leaves dirty state to its own conditions.

Evidence

  • tests/cobol_mainframe/test_det_osvs.py: 9 planning tests (no Docker) and an end-to-end program run through cobc and the det port in bytes and typed modes. It covers the issue's register-C2 reproducer (COMPUTE R = A / B * C → 099.00), a literal on the right, a binary sum, unary minus, a product with a literal, an IF, a constant scale change, and the EVALUATE leak. It fails on origin/main (both modes) and passes here.
  • Randomized differential run (scratch tool, not committed): over 100 random programs, about 25,000 COMPUTE / ADD / SUBTRACT / IF / EVALUATE statements over zoned, packed and binary items with literals, unary minus, **, ROUNDED, multiple receivers, sentences and compile-time-constant relations. Every output equals the oracle's. The same programs on origin/main differ on about 1 line in 6.
  • E2E: EQUIVALENCE_E2E=1 pytest test_det_programs.py test_det_hfp.py test_numproc.py test_cobolrt.py test_det_funcs.py: 464 passed, 0 skipped.
  • det_port.py check --base-ref origin/main: every port changes, because Cobol.java changed. 12 services change. The only align emitted in any port is POSTTRAN's CREDIT - DEBIT to 2 places, which does not change the value. The other service changes are Cobol.Dc constants.
  • det sweep (proof_sweep.py --det-only --cases on those 12): 10/12 proven, matching the baseline ("sweep: as expected"). The 2 not proven are carddemo-intcalc-generated and mortgage-mpmt, both already listed in det_sweep_baseline.json. Coverage numbers equal the ledger (det_coverage_ledger.py check: as expected). CI runs the full sweep.
  • pr_gates.py --fast: 5/5 pass. --ratchets: 7/7 pass (cics-spec, ports, port-surface, estate, fact-crosscheck, completeness, ground-truth; 0 skipped). A first run without the JDK 17 env failed ports falsely (JDK 21); ports_compile_check.py with the env: all 45 committed ports compile.

Follow-ups filed

🤖 Generated with Claude Code

…SVS does (#4287)

The det translator replays cobc -std=ibm's compile-time ARITHMETIC-OSVS decision
(det/osvs.py, from GnuCOBOL 3.1.2 typeck.c / tree.c): dmax from receivers and
cb_walk_cond, the expr_decp stack with its pushes, pending aligns flushed on the
next load, constant folding, conditions built right to left, the state an
EVALUATE leaves to its sentence. It emits Cobol.align (libcob's
cob_decimal_align, its downward shift included) where cobc emits
cob_decimal_align, and a literal on the right of an operation as a Cobol.Dc
(libcob's decimal constant, whose scale its uses change). Cobol.divide keeps
cob_decimal_div's places and Cobol.power cob_decimal_pow's trimming.

Register C2 rewritten: the det runtime matches the oracle; the oracle's
departures from IBM's rule are listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread gitgalaxy/tools/cobol_to_java/det/gen.py Fixed
Comment thread gitgalaxy/tools/cobol_to_java/det/osvs.py Fixed
Comment thread tests/cobol_mainframe/test_det_osvs.py Fixed
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

🐦‍⬛ Muninn Security Scan

✅ No security issues found.

🐦‍⬛ Powered by Muninn · Skald Lab

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
squid-protocol and others added 4 commits October 8, 2026 10:46
expr imported det.cics (for DFHRESP) and det.cvda lazily, and det.cics imported det.gen, which imports det.osvs, which imports expr. expr now imports DFHRESP from gitgalaxy.standards.cics.resp (its home) and CVDA from det.cvda (no imports) at module level, so expr no longer reaches gen. Also one import form for test_det_programs in test_det_osvs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
# Conflicts:
#	gitgalaxy/tools/cobol_to_java/det/expr.py
…rds.cics.resp)

The CodeQL import-cycle fix made det/expr.py import DFHRESP from the RESP
table directly; pin it in test_cics_spec.IMPORTERS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
squid-protocol added a commit that referenced this pull request Oct 8, 2026
…rds.cics.resp)

det/expr.py imports DFHRESP from the RESP table directly (the CodeQL
import-cycle fix, same as #4654); pin it in test_cics_spec.IMPORTERS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
Conflict in Gen's COMPUTE: keep #4685's p_scaled_expr refusal (first)
and this PR's ARITHMETIC-OSVS plan around the expression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
@squid-protocol
squid-protocol marked this pull request as ready for review October 8, 2026 18:50
@squid-protocol
squid-protocol merged commit b7c8996 into main Oct 8, 2026
37 checks passed
@squid-protocol
squid-protocol deleted the fix/4287-arith-osvs branch October 8, 2026 18:50
squid-protocol added a commit that referenced this pull request Oct 8, 2026
expr.py: keep this PR's NumLit (a numeric literal keeps its spelling)
and main's module-level DFHRESP import; #4654's Lit.text (the literal
as written, for det/osvs.py's literal identity) becomes a property read
from the NumLit's spelling instead of a second copy of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
squid-protocol added a commit that referenced this pull request Oct 8, 2026
#4665) (#4668)

* det: a numeric-edited item compared with a number is a text comparison (#4665)

A numeric-edited item is not numeric: against a number it is compared as
characters (IBM "Comparison of numeric and alphanumeric operands"; GnuCOBOL
likewise), not de-edited and compared by value. The generator passes a
numeric literal against a nonnumeric item as written (expr.NumLit keeps the
spelling: leading zeros, point), the runtime compares a numeric item against
an elementary nonnumeric one as its digits (sign dropped). Where IBM and the
oracle differ -- a signed literal, a SIGN SEPARATE or P-scaled item, an
arithmetic expression -- the comparison is a hole by name (register C13).

Fixes #4665

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: clear CodeQL py/cyclic-import (gitgalaxy/tools/cobol_to_java/det/layout.py)

layout -> expr -> det.cics -> layout: expr's DFHRESP(...) now takes the table from
gitgalaxy.standards.cics.resp, the very object det.cics re-exports, so expr no longer
imports det.cics. det_port check vs 431ceb7: 68/68 ports unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D

* test: det/expr.py is a listed CICS spec consumer (DFHRESP from standards.cics.resp)

det/expr.py imports DFHRESP from the RESP table directly (the CodeQL
import-cycle fix, same as #4654); pin it in test_cics_spec.IMPORTERS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D

* test_det_osvs: a numeric literal is a NumLit (its spelling), not Lit(value, text)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
squid-protocol added a commit that referenced this pull request Oct 8, 2026
Conflicts in oracle_assumptions.md, Cobol.java and gen.py came from #4654's
pre-squash commits: main's side taken, then this PR's own changes
(6e1de82..f6da8e4) re-applied on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
squid-protocol added a commit that referenced this pull request Oct 8, 2026
#4681) (#4683)

* det runtime: truncate arithmetic intermediates as cobc's ARITHMETIC-OSVS does (#4287)

The det translator replays cobc -std=ibm's compile-time ARITHMETIC-OSVS decision
(det/osvs.py, from GnuCOBOL 3.1.2 typeck.c / tree.c): dmax from receivers and
cb_walk_cond, the expr_decp stack with its pushes, pending aligns flushed on the
next load, constant folding, conditions built right to left, the state an
EVALUATE leaves to its sentence. It emits Cobol.align (libcob's
cob_decimal_align, its downward shift included) where cobc emits
cob_decimal_align, and a literal on the right of an operation as a Cobol.Dc
(libcob's decimal constant, whose scale its uses change). Cobol.divide keeps
cob_decimal_div's places and Cobol.power cob_decimal_pow's trimming.

Register C2 rewritten: the det runtime matches the oracle; the oracle's
departures from IBM's rule are listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* oracle_assumptions C2: the differential run's size as measured (#4287)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* det runtime: a zero divisor is libcob's NaN, receivers unchanged (#4655)

The oracle (GnuCOBOL 3.1.2 -std=ibm) leaves every receiver unchanged on a zero
divisor, with or without ON SIZE ERROR; the det port threw ArithmeticException.
Cobol.divide now returns libcob's NaN (scale -32768) and raises the statement's
size error; add/subtract/multiply/negate/power carry it where the translator sees
a division or exponent below; store/storeChecked leave the receiver (lifted
receivers guarded with isNan); Cobol.align truncates a NaN to 0 as libcob does.
A division in a function argument is 0 (cob_intr_binop); FUNCTION MOD/REM by
zero are 0. 0 ** 0 raises the size error; a non-finite exponent is NaN.
Statements with ON SIZE ERROR clear and read the size-error state.
DIVIDE REMAINDER now uses the quotient truncated to the receiver's places
(cob_div_quotient), not the stored quotient.

Register entry oracle_assumptions C14 (IBM: undefined / S0CB on z/OS).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* det gen: a distinct name for the lifted DIVIDE INTO quotient (mypy) (#4655)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* det parse: the abbreviated 'OR NOT = x' / 'AND NOT = x' relation parses (#4681)

Holes 1 and 2 (D0 + 1 > Q3, S3 NOT < 3 + B0 * - D0) already agree with cobc on this base (#4654/#4677); an end-to-end test pins them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* chore: break det osvs/expr import cycle (CodeQL)

expr imported det.cics (for DFHRESP) and det.cvda lazily, and det.cics imported det.gen, which imports det.osvs, which imports expr. expr now imports DFHRESP from gitgalaxy.standards.cics.resp (its home) and CVDA from det.cvda (no imports) at module level, so expr no longer reaches gen. Also one import form for test_det_programs in test_det_osvs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D

* chore: one import form for test_det_programs in test_det_size_error (CodeQL)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
squid-protocol added a commit that referenced this pull request Oct 8, 2026
Takes main's side of #4677 / #4654's pre-squash hunks and keeps #4689's own changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MQNoe4wPJgr7dbUvs3DG6D
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.

det runtime: arithmetic intermediates are not truncated as the oracle's ARITHMETIC-OSVS (IBM's fixed-point decimal places) truncates them (register C2)

2 participants