Explainer 11 of 15

Exact field classes across programs

Read first: From byte provenance to a field graph

A caller and a callee often declare one datum under two names, and only the bytes say so. This page finds such pairs from the field graph's byte placements alone, and joins them into classes.

The test

Field A (program P) and field B (program Q, not P) are one class when:

  1. they are the same size;
  2. byte i of A lands on byte i of B, for every i;
  3. no byte of A lands anywhere else in B.

Classes are the connected components of the pairs that pass. Copies inside one program never join: a program copying a field to a work area is not evidence of a shared datum.

The example

Program R hands a commarea to program S. S receives it into a linkage table of 1-byte occurrences T(1) to T(4) and moves bytes out into its own fields.

flowchart LR
    subgraph R
      A["CA.A (2)"]
      C["CA.C (2)"]
      E["CA.E (4)"]
      H["CA.H (2)"]
    end
    subgraph S
      T1["T(1)"]
      T2["T(2)"]
      T3["T(3)"]
      T4["T(4)"]
      B["WS.B (2)"]
      D["WS.D (2)"]
      F["WS.F (2)"]
      G["WS.G (2)"]
      J["WS.J (2)"]
      K["WS.K (2)"]
    end
    A -->|"byte 0"| T1 -->|"to byte 0"| B
    A -->|"byte 1"| T2 -->|"to byte 1"| B
    C -->|"byte 0"| T3 -->|"to byte 1"| D
    C -->|"byte 1"| T4 -->|"to byte 0"| D
    E -->|"bytes 0-1"| F
    B --> G
    H --> J
    K -.->|"no placement"| J

Relays

The table occurrences T(1) to T(4) are not fields anyone would call a datum. They are just how S declared the bytes it received. So they relay: a byte that lands on a relay keeps going to wherever the relay sends it, and only the far end counts.

Each pair, judged

pairbytes landverdict
R:CA.A, S:WS.B0→0, 1→1, via T(1), T(2)joined
R:CA.C, S:WS.D0→1, 1→0no: out of place
R:CA.E, S:WS.F0→0, 1→1, but sizes 4 and 2no: sizes differ
S:WS.B, S:WS.Gin placeno: same program
R:CA.H, S:WS.Jin placeno: J is reached by an edge with no placement

The result is one class: {R:CA.A, S:WS.B}.

The last row is the strict rule. K → J has no known placement, so nobody can say which of J's bytes it overwrote. A field reached by such an edge neither relays nor joins.

One kind of edge without placement does not count. An edge is rebuilt when every statement behind it built the target's value in place, by arithmetic or STRING: ADD Y TO X gives Y → X no placement, but X is not a copy of anything, so it keeps its class.

Through a file

Dataset slots relay the same way, over exact dataset edges. W writes an 11-byte ID; R reads it back as one 11-byte field; the record is cut into slots [0:5] and [5:11]:

edgealignment
W:R.ID → DS[0:5]bytes 0–4 to 0–4
W:R.ID → DS[5:11]bytes 5–10 to 0–5
DS[0:5] → R:R.ID0–4 to 0–4
DS[5:11] → R:R.ID0–5 to 5–10
DS[0:5] → R:R.GRP0–4 to 0–4
DS[0:5] → Q:R.IDpossible, no placement
W:R.ID → Q:R.ID0–10 to 0–10

The class is {Q:R.ID, R:R.ID, W:R.ID}. R:R.GRP is five bytes, so it does not join an 11-byte field. The possible edge into Q is ignored, and does not stop Q joining through its direct edge.

The rule

moves(byte) = bytes an aligned edge carries it to
onward(b) = {b} if b is not on a relay; else b and everything reachable from it
landings(a, i) = { onward(m) | m ∈ moves((a, i)) } restricted to non-relay fields
unplaced = targets of a field-to-field edge with no placement that is not rebuilt
exact(a, b) = program(a) ≠ program(b) ∧ size(a) = size(b)
    ∧ ∀ i: { offsets of b in landings(a, i) } = { i }
classes = connected components of exact pairs, unplaced fields left out

Loosening it

Each condition costs recall. On CardDemo the strict rule gives 111 classes of 510 fields. Ignoring the unplaced rule:

runclassesfieldswhat is added
strict111510
ignore the unplaced rule11358373 fields, 70 of them commarea fields

The other three loosenings were measured on 2026-10-26, when the strict rule gave 90 classes of 394 fields. They are not offered, so they have not been measured since:

loosenedclassesfieldsprecision, by sample
allow joins in one program4411,811mixed: one value fanned out to 42 fields becomes one class
accept 75% of bytes in place90394no effect
a field inside a bigger one45438collapses: chains merge distinct fields

Only ignoring the unplaced rule is offered, as --join-unplaced.

On CardDemo

Through a file. The card number in the cross-reference record is one class of 14 fields over ten programs, though no program writes the file:

CBACT03CCBACT04CCBEXPORTCBSTM03BCBTRN02CCBTRN03CCOACTUPCCOACTVWCCOBIL00CCOTRN02C

All 14 are 16 bytes: CARD-XREF-RECORD.XREF-CARD-NUM in nine of the programs, and the file's own record field FD-XREF-CARD-NUM in the five batch readers that declare one (CBACT03C, CBACT04C, CBSTM03B, CBTRN02C, CBTRN03C). The batch readers and COTRN02C meet in the cross-reference file's slot [0:16]. COACTUPC, COACTVWC and COBIL00C read its alternate-index path, a dataset of its own, and meet in that dataset's slot [0:16]. COTRN02C reads both, which joins the two groups. Two readers of the same bytes stay out:

Through a commarea, and the strict rule. CDEMO-USER-ID in the shared commarea copybook forms two classes under the strict rule, of three programs and two, and two with --join-unplaced, of six and three:

runCDEMO-USER-ID's classes
strictCOCRDLIC, COCRDSLC, COCRDUPC
COTRTLIC, COTRTUPC
--join-unplacedCOACTUPC, COACTVWC, COCRDLIC, COCRDSLC, COCRDUPC, COMEN01C
COADM01C, COTRTLIC, COTRTUPC

The flag changes only one thing, the unplaced rule, so in the four added programs the commarea field is also reached by a move with no known placement, and the strict rule leaves it out. CDEMO-ACCT-ID forms only the COTRTLIC, COTRTUPC class under the strict rule, and with the flag the same two classes as CDEMO-USER-ID, in the same programs.