summaryrefslogtreecommitdiffstats
path: root/tools/mme-audit/mme_audit.py
blob: 8031f3c379a72367f93cebc2629f203b31768972 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
#!/usr/bin/env python3
"""mme-audit: offline audit of the vgsp MME golden overlay and replay streams.

Pure Python 3.10 stdlib. Inputs (all local, all public):
  * kernel/golden-mme-tu104.h                (golden slot library)
  * dmesg_live_*.log captures                (3D win / MME-WINRUN / MME-PRE)

What it checks (r1448-strategy 3.1):
  1. overlay simulation: golden_slot() accept rules, old (!gs->entry) vs new
     (entry 0 valid) under force=1 -> 54 before, 55 after
  2. grammar walk of every 3D win[...] / MME-WINRUN / MME-PRE[...] block:
     op 1/3/5 span, 0x114 IR loads, SA pairs; walk-end == len assertion
  3. call census per window (slot, off, payload)
  4. operand map over the mission slots: decode MME64 groups, flag
     LOAD-sourced method emissions and suspect method immediates
  5. cross-boot pair-emitted/elided/MISS/first-fault table
  6. slot-57 focus decode around the r1448b/c stall (IR 868..930)
"""
import argparse
import os
import re
import sys

# --------------------------------------------------------------------- golden

GOLDEN_SLOT_RE = re.compile(
    r"\[\s*(\d+)\]\s*=\s*\{\s*0x([0-9a-fA-F]+)u,\s*(\d+)u,\s*(\d+)u,\s*0x([0-9a-fA-F]+)u\s*\}")
GOLDEN_WORDS_RE = re.compile(
    r"golden_mme_tu104_words\[GOLDEN_MME_TU104_WORDS\]\s*=\s*\{(.*?)\n\};", re.S)

MISSION_SLOTS = [0, 2, 34, 39, 40, 48, 51, 57, 58]
SUSPECT_METHODS = [0x1614, 0x1618, 0x2054, 0x2058, 0x2094, 0x2098,
                   0x2154, 0x2158, 0x0d74, 0x0d78]

# MME64 layout (3-word groups), extracted from the observed disassembly
# convention: bits[95:64]=w0, [63:32]=w1, [31:0]=w2.
REG = ["R%d" % i for i in range(24)] + ["ZERO", "IMMED", "IMMEDPAIR",
                                        "IMMED32", "LOAD0", "LOAD1"]
OP = ["ADD", "ADDC", "SUB", "SUBB", "MUL", "MULH", "MULU", "EXTENDED", "CLZ",
      "SLL", "SRL", "SRA", "AND", "NAND", "OR", "XOR", "MERGE", "SLT", "SLTU",
      "SLE", "SLEU", "SEQ", "STATE", "LOOP", "JAL", "BLT", "BLTU", "BLE",
      "BLEU", "BEQ", "DREAD", "DWRITE"]
OUT = ["NONE", "ALU0", "ALU1", "LOAD0", "LOAD1", "IMMED0", "IMMED1",
       "RESERVED", "IMMEDHIGH0", "IMMEDHIGH1", "IMMED32_0"]


def regname(v):
    return REG[v] if v < 30 else "V%d" % (v - 32)


def mme64_decode(w0, w1, w2):
    val = (w0 << 64) | (w1 << 32) | w2

    def bits(hi, lo):
        return (val >> lo) & ((1 << (hi - lo + 1)) - 1)

    alus = []
    for base in (10, 46):
        alus.append((bits(base + 4, base), bits(base + 9, base + 5),
                     bits(base + 14, base + 10), bits(base + 19, base + 15),
                     bits(base + 35, base + 20)))
    outs = []
    for base in (82, 89):
        outs.append((bits(base + 2, base), bits(base + 5, base + 3)))
    return {
        "endNext": bits(0, 0), "predMode": bits(4, 1), "pred": bits(9, 5),
        "alu": alus, "out": outs,
    }


def mme64_text(d):
    parts = []
    for op, dst, s0, s1, im in d["alu"]:
        if (op, dst, s0, s1, im) == (0, 0, 0, 0, 0):
            continue
        parts.append("%s %s, %s, %s, 0x%04x" %
                     (OP[op], regname(dst), regname(s0), regname(s1), im))
    for meth, emit in d["out"]:
        if meth or emit:
            parts.append("OUT method-src=%s data-src=%s" %
                         (OUT[meth], OUT[emit]))
    if d["endNext"]:
        parts.append("endNext")
    if d["predMode"]:
        parts.append("pred=%s,%s" % (d["predMode"], regname(d["pred"])))
    return " | ".join(parts) if parts else "NOP"


def parse_golden(path):
    text = open(path, encoding="utf-8").read()
    pool = []
    m = GOLDEN_WORDS_RE.search(text)
    if m:
        pool = [int(t, 16) for t in re.findall(r"0x[0-9a-fA-F]+", m.group(1))]
    entries = {}
    for m in GOLDEN_SLOT_RE.finditer(text):
        entries[int(m.group(1))] = (int(m.group(2), 16), int(m.group(3)),
                                    int(m.group(4)), int(m.group(5), 16))
    return entries, pool


def golden_slot(entries, j, entry0_fix):
    """Model of gsp_phys_gr_golden_slot() (knob presumed on)."""
    if j >= 64 or j not in entries:
        return None
    entry, words, off, flags = entries[j]
    if not (flags & 0x1) or not words:
        return None
    if not entry0_fix and not entry:
        return None
    return entry, words, off, flags


PRESENT = 0x1
GMME_FLAGS = {0x2: "DRIVER", 0x4: "LIVE_VARIANT", 0x8: "GEFORCE"}


def overlay_sim(entries, pool):
    out = {}
    for fix in (False, True):
        overlaid, rejected = [], []
        for j in range(64):
            gs = golden_slot(entries, j, fix)
            (overlaid if gs else rejected).append(j)
        out[fix] = (sorted(overlaid), sorted(rejected))
    return out


# ------------------------------------------------------------------ walkers

def walk_stream(words):
    """Booter grammar walk: returns (ok, runs, image, calls, sa_pairs)."""
    runs, image, calls, sa_pairs = [], {}, [], {}
    off, n = 0, len(words)
    ok = True
    while off < n:
        h = words[off]
        op = (h >> 29) & 7
        cnt = (h >> 16) & 0xfff
        mthd = (h & 0x1fff) << 2
        span = 1
        if op in (1, 3, 5) and cnt:
            span = 1 + cnt
        if off + span > n:
            ok = False
            span = n - off
        runs.append((off, op, cnt, mthd, span))
        if mthd == 0x114 and span >= 3:
            pc = words[off + 1]
            code = words[off + 2:off + span]
            for i in range(len(code) // 3):
                image[pc + i] = tuple(code[3 * i:3 * i + 3])
        if 0x3800 <= mthd <= 0x39fc:
            j = (mthd - 0x3800) // 8
            kind = "DATA" if (mthd & 4) else "MACRO"
            payload = list(words[off + 1:off + span])
            calls.append((j, off, kind, mthd, payload))
        if mthd == 0x11c and span >= 3:
            sa_pairs[words[off + 1]] = words[off + 2]
        off += span
    return ok, runs, image, calls, sa_pairs


CRLF = re.compile(r"\[(\d+\.\d+)\]\s+vgsp-gr:\s+(.*)$")
WIN_RE = re.compile(r"3D win\[\s*(\d+)\]:\s+((?:[0-9a-fA-F]+ ?)+)")
WINRUN_DUMP_RE = re.compile(r"MME-WINRUN dump h=0x([0-9a-fA-F]+) push=0x([0-9a-fA-F]+) window=(\d+)")
WINRUN_RUN_RE = re.compile(
    r"MME-WINRUN off=([0-9a-fA-F]+) op=(\d+) cnt=(\d+) mthd=([0-9a-fA-F]+) d0=([0-9a-fA-F]+) d1=([0-9a-fA-F]+)")
PRE_RE = re.compile(r"MME-PRE\[\s*(\d+)\]:?\s+((?:[0-9a-fA-F]{8} ?)+)")


def parse_windows(path):
    """Yield dicts for each 3D win[], MME-WINRUN and MME-PRE[] block."""
    cur = None
    win_words, wr_runs, wr_meta = [], [], None
    pre_idx, pre_words = -1, []
    with open(path, encoding="utf-8", errors="replace") as fh:
        for line in fh:
            m = CRLF.search(line)
            if not m:
                continue
            ts, msg = m.group(1), m.group(2)
            m = WIN_RE.search(msg)
            if m:
                off = int(m.group(1))
                ws = [int(t, 16) for t in m.group(2).split()]
                if win_words and off != len(win_words) and off != 0:
                    yield {"kind": "win", "ts": ts, "words": win_words}
                    win_words = []
                if off == 0 and win_words:
                    yield {"kind": "win", "ts": ts, "words": win_words}
                    win_words = []
                win_words.extend(ws)
                continue
            m = WINRUN_DUMP_RE.search(msg)
            if m:
                if wr_meta and wr_runs:
                    yield {"kind": "winrun", "ts": ts, **wr_meta, "runs": wr_runs}
                wr_meta = {"h": int(m.group(1), 16), "push": int(m.group(2), 16),
                           "window": int(m.group(3))}
                wr_runs = []
                continue
            m = WINRUN_RUN_RE.search(msg)
            if m and wr_meta is not None:
                wr_runs.append((int(m.group(1), 16), int(m.group(2)),
                                int(m.group(3)), int(m.group(4), 16),
                                int(m.group(5), 16), int(m.group(6), 16)))
                continue
            m = PRE_RE.search(msg)
            if m:
                idx = int(m.group(1))
                ws = [int(t, 16) for t in m.group(2).split()]
                if pre_words and idx != pre_idx + len(pre_words):
                    yield {"kind": "pre", "ts": ts, "words": pre_words}
                    pre_words = []
                pre_words.extend(ws)
                pre_idx = idx
                continue
            if win_words:
                yield {"kind": "win", "ts": ts, "words": win_words}
                win_words = []
            if wr_meta and wr_runs is not None:
                # runs may legitimately be empty (dump with no decoded runs)
                yield {"kind": "winrun", "ts": ts, **wr_meta, "runs": wr_runs}
                wr_meta, wr_runs = None, []
            if pre_words:
                yield {"kind": "pre", "ts": ts, "words": pre_words}
                pre_words = []
    if win_words:
        yield {"kind": "win", "ts": ts, "words": win_words}
    if wr_meta:
        yield {"kind": "winrun", "ts": ts, **wr_meta, "runs": wr_runs}
    if pre_words:
        yield {"kind": "pre", "ts": ts, "words": pre_words}


def walk_winrun(window, runs):
    """Walk a decoded MME-WINRUN listing; returns (ok, calls, end)."""
    seen, calls, end = set(), [], 0
    for off, op, cnt, mthd, d0, d1 in runs:
        span = 1 + cnt if op in (1, 3, 5) and cnt else 1
        seen.add(off)
        if 0x3800 <= mthd <= 0x39fc:
            j = (mthd - 0x3800) // 8
            kind = "DATA" if (mthd & 4) else "MACRO"
            calls.append((j, off, kind, mthd, [d0, d1]))
        end = max(end, off + span)
    ok = end == window and len(seen) == len(runs)
    return ok, calls, end


# ------------------------------------------------------------ MME64 scanning

def scan_slot(entries, pool, slot):
    entry, words, off, flags = entries[slot]
    rows = []
    for i in range(words // 3):
        w = tuple(pool[off + 3 * i:off + 3 * i + 3])
        d = mme64_decode(*w)
        rows.append((entry + i, i, w, d))
    return rows


def operand_map(entries, pool):
    """For mission slots: flag emissions + suspect method constants."""
    sus2 = {m >> 2: m for m in SUSPECT_METHODS}
    out = []
    for slot in MISSION_SLOTS:
        if slot not in entries or not entries[slot][1]:
            continue
        for ir, idx, w, d in scan_slot(entries, pool, slot):
            tags = []
            for meth, emit in d["out"]:
                if emit in (3, 4):  # LOAD0/LOAD1 data source
                    tags.append("LOAD-sourced emission (method-src=%s)" % OUT[meth])
            for word in w:
                low = word & 0xfff
                if low in sus2 and sus2[low] != 0x0d74 and sus2[low] != 0x0d78:
                    tags.append("suspect method %#x (field 11:0 of %08x)" % (sus2[low], word))
            for op, dst, s0, s1, im in d["alu"]:
                if im in SUSPECT_METHODS:
                    tags.append("suspect imm %#x (ALU immed)" % im)
                if s0 == 24 and im == 0:  # ZERO + 0 immediate
                    tags.append("zero-operand ALU (src0=ZERO immed=0)")
            for t in tags:
                out.append((slot, ir, w, t, mme64_text(d)))
    return out


# ----------------------------------------------------------- log-level facts

FAULT_RE = re.compile(r'errString="([^"]+)"')
ESR_RE = re.compile(r"esr 80000000->badf1201")


def log_facts(path):
    f = {"path": path}
    counts = {"PAIR-DROP": 0, "PAIR-DROP-words": 0, "MISS-2054": 0,
              "GOLDEN": [], "esr": 0, "macros": {}, "faults": [],
              "trap2090": 0}
    with open(path, encoding="utf-8", errors="replace") as fh:
        for line in fh:
            if "MME-PAIR-DROP" in line:
                counts["PAIR-DROP"] += 1
                m = re.search(r"MME-PAIR-DROP (\d+) word", line)
                if m:
                    counts["PAIR-DROP-words"] += int(m.group(1))
            if "pair addr=0x2054" in line and "-> MISS" in line:
                counts["MISS-2054"] += 1
            m = re.search(r"MME-GOLDEN overlay (\d+) slot", line)
            if m:
                counts["GOLDEN"].append(int(m.group(1)))
            if ESR_RE.search(line):
                counts["esr"] += 1
            for m in re.finditer(r"macro-status=([0-9a-fA-F]{8})\(mpc=(\d+) mac=0x([0-9a-fA-F]+) run=(\d)\)", line):
                counts["macros"][int(m.group(2))] = counts["macros"].get(int(m.group(2)), 0) + 1
            if "trap=00002090" in line or "trap=80002090" in line:
                counts["trap2090"] += 1
            m = FAULT_RE.search(line)
            if m:
                counts["faults"].append(m.group(1).strip())
    f["counts"] = counts
    return f


# ------------------------------------------------------------------- report

def main():
    ap = argparse.ArgumentParser()
    here = os.path.dirname(os.path.abspath(__file__))
    repo = os.path.abspath(os.path.join(here, "..", ".."))
    ap.add_argument("--golden", default=os.path.join(repo, "kernel", "golden-mme-tu104.h"))
    ap.add_argument("--logs", nargs="*", default=[])
    ap.add_argument("--out", default="/tmp/opencode/r1449-audit.md")
    args = ap.parse_args()

    entries, pool = parse_golden(args.golden)
    present = sorted(j for j in entries if entries[j][3] & PRESENT and entries[j][1])
    absent = sorted(set(range(64)) - set(present))
    lines = []
    A = lines.append

    A("# r1449 MME offline audit (mme-audit)")
    A("")
    A("- golden: `%s` (%d words, %d present slots)" % (args.golden, len(pool), len(present)))
    A("- present: %s" % present)
    A("- absent : %s" % absent)
    A("")

    A("## 1. Overlay simulation (gsp_phys_gr_golden_slot + force overlay)")
    sim = overlay_sim(entries, pool)
    old_over, old_rej = sim[False]
    new_over, new_rej = sim[True]
    A("")
    A("| mode | overlaid | rejected (no golden) |")
    A("|---|---|---|")
    A("| pre-r1448 (`!gs->entry` rejects slot 0) | **%d** | %s |" % (len(old_over), old_rej))
    A("| r1448 (entry 0 valid) | **%d** | %s |" % (len(new_over), new_rej))
    A("")
    A("slot 0: %s" % ("golden entry=0x0 words=%d flags=%s (ACCEPTED after fix)" %
                      (entries[0][1], "+".join(n for b, n in GMME_FLAGS.items() if entries[0][3] & b))
                      if 0 in entries else "absent"))
    assert len(old_over) == 54 and len(new_over) == 55, "overlay sim mismatch"
    A("")
    A("ASSERT: 54 -> 55 after the entry-0 fix: OK")
    A("")

    A("## 2. Grammar walks (walk-end == len)")
    A("")
    A("| log | blocks | win | winrun | pre | end-mismatch | calls | 0x2054 | 0x2094 | 0x2154 |")
    A("|---|---|---|---|---|---|---|---|---|---|")
    wins = {}
    for path in args.logs:
        if not os.path.exists(path):
            A("| %s | MISSING | | | | | | | | |" % path)
            continue
        nw = nwr = npre = nmis = ncall = 0
        pair_counts = {0x2054: 0, 0x2094: 0, 0x2154: 0}
        win_calls = []
        for blk in parse_windows(path):
            if blk["kind"] == "win":
                ok, runs, image, calls, sa = walk_stream(blk["words"])
                nw += 1
                nmis += 0 if ok else 1
                ncall += len(calls)
                for j, off, kind, mthd, payload in calls:
                    win_calls.append((blk["ts"], j, off, kind, payload))
            elif blk["kind"] == "winrun":
                ok, calls, end = walk_winrun(blk["window"], blk["runs"])
                nwr += 1
                nmis += 0 if ok else 1
                ncall += len(calls)
                for off, op, cnt, mthd, d0, d1 in blk["runs"]:
                    if mthd in pair_counts:
                        pair_counts[mthd] += 1
                for j, off, kind, mthd, payload in calls:
                    win_calls.append((blk["ts"], j, off, kind, payload))
            elif blk["kind"] == "pre":
                ok, runs, image, calls, sa = walk_stream(blk["words"])
                npre += 1
                nmis += 0 if ok else 1
                ncall += len(calls)
        wins[path] = win_calls
        base = os.path.basename(path)
        A("| %s | %d | %d | %d | %d | %d | %d | %d | %d | %d |" %
          (base, nw + nwr + npre, nw, nwr, npre, nmis, ncall,
           pair_counts[0x2054], pair_counts[0x2094], pair_counts[0x2154]))
    A("")
    A("Note: dmesg_live_r1448.log carries both r1448b and r1448c boots "
      "(148098..148521 and 148520/148627 sentinels); the per-boot logs named "
      "r1448b/r1448c were not present at audit time.")
    A("")

    A("## 3. Call census (windows that called MME macros)")
    for path, calls in wins.items():
        if not calls:
            continue
        A("")
        A("### %s" % os.path.basename(path))
        A("")
        A("| ts | slot | off | kind | payload |")
        A("|---|---|---|---|---|")
        for ts, j, off, kind, payload in calls[:200]:
            A("| %s | %d | 0x%x | %s | %s |" %
              (ts, j, off, kind, " ".join("%08x" % p for p in payload[:8])))
        if len(calls) > 200:
            A("| ... | (%d more) | | | |" % (len(calls) - 200))
    A("")

    A("## 4. Operand map (mission slots; LOAD-sourced emissions + suspects)")
    A("")
    om = operand_map(entries, pool)
    if not om:
        A("no flagged instructions")
    else:
        A("| slot | IR | words | flag | decode |")
        A("|---|---|---|---|---|")
        for slot, ir, w, t, txt in om[:120]:
            A("| %d | %d | %08x %08x %08x | %s | %s |" % (slot, ir, w[0], w[1], w[2], t, txt))
        if len(om) > 120:
            A("| ... | | | (%d more) | |" % (len(om) - 120))
    A("")

    A("## 5. Cross-boot pair/fault table")
    A("")
    A("| log | PAIR-DROP n | words | 0x2054 MISS | overlay | esr badf1201 | trap 0x2090 | first faults |")
    A("|---|---|---|---|---|---|---|---|")
    for path in args.logs:
        if not os.path.exists(path):
            continue
        f = log_facts(path)
        c = f["counts"]
        A("| %s | %d | %d | %d | %s | %d | %d | %s |" %
          (os.path.basename(path), c["PAIR-DROP"], c["PAIR-DROP-words"],
           c["MISS-2054"], sorted(set(c["GOLDEN"])), c["esr"], c["trap2090"],
           "; ".join(c["faults"][:4]) if c["faults"] else "-"))
    A("")

    A("## 6. Slot-57 focus: golden body around the r1448b/c stall")
    A("")
    if 57 in entries and entries[57][1]:
        rows = scan_slot(entries, pool, 57)
        A("entry=0x%x words=%d instructions=%d" % (entries[57][0], entries[57][1], len(rows)))
        A("")
        A("| IR | idx | words | decode |")
        A("|---|---|---|---|")
        for ir, idx, w, d in rows:
            mark = ""
            if ir == 899:
                mark = " **<- r1448b stall (mpc=899)**"
            elif ir == 900:
                mark = " **<- r1448c stall (mpc=900)**"
            if idx < 14 or idx >= 20:
                A("| %d | %d | %08x %08x %08x | %s%s |" % (ir, idx, w[0], w[1], w[2], mme64_text(d), mark))
        A("")
    else:
        A("slot 57 absent from golden library")

    A("## 7. Method-name map for the pipeline family (0x2000 + j*64)")
    A("")
    names = {0x00: "SET_PIPELINE", 0x08: "PIPELINE_RESERVED_A",
             0x0c: "PIPELINE_REGISTER_COUNT", 0x10: "PIPELINE_BINDING",
             0x14: "PROGRAM_ADDRESS_A", 0x18: "PROGRAM_ADDRESS_B",
             0x1c: "PIPELINE_RESERVED_D", 0x20: "PIPELINE_RESERVED_E"}
    for m in (0x2054, 0x2090, 0x2094, 0x2098, 0x2154, 0x2158):
        j = (m - 0x2000) // 64
        off = (m - 0x2000) % 64
        A("- 0x%04x = SET_PIPELINE(%d) + 0x%02x = %s" % (m, j, off, names.get(off, "?")))
    A("")

    A("## 8. Stall-diagnosis evidence (r1448b/c residual)")
    A("")
    A("The MME-FAST `trap=<mthd> data=<d>` telemetry carries the method the FE")
    A("last processed. Calibration against the window runs:")
    A("")
    TRAP_RE = re.compile(r"trap=([0-9a-fA-F]{8}) data=([0-9a-fA-F]{8})")
    for path in args.logs:
        if not os.path.exists(path):
            continue
        wr_pairs, trap_pairs, stall = set(), set(), []
        with open(path, encoding="utf-8", errors="replace") as fh:
            for line in fh:
                m = WINRUN_RUN_RE.search(line)
                if m:
                    wr_pairs.add((int(m.group(4), 16), int(m.group(5), 16)))
                m = TRAP_RE.search(line)
                if m:
                    trap_pairs.add((int(m.group(1), 16) & 0x7fffffff,
                                    int(m.group(2), 16)))
                if "macro-status=" in line and ("mpc=899" in line or "mpc=900" in line):
                    stall.append(line.rstrip())
        hits = sorted(wr_pairs & trap_pairs)
        n2090_wr = sum(1 for line in open(path, encoding="utf-8", errors="replace")
                       if WINRUN_RUN_RE.search(line) and "mthd=2090" in line)
        n2090_trap = sum(1 for line in open(path, encoding="utf-8", errors="replace")
                         if "trap=00002090" in line or "trap=80002090" in line)
        A("### %s" % os.path.basename(path))
        A("")
        A("- window-run `(mthd,d0)` vs telemetry `(trap,data)` intersections: %s" %
          (" ".join("0x%x/0x%x" % p for p in hits[:8]) if hits else "none"))
        A("- `mthd=2090` window runs: **%d**; `trap=...2090` telemetry lines: **%d**" %
          (n2090_wr, n2090_trap))
        for s in stall[:6]:
            A("- stall: `%s`" % s)
        A("")

    A("## 9. Findings (synthesis)")
    A("")
    A("1. **Overlay completion:** the entry-0 fix takes the force overlay from")
    A("   54 to 55 slots (slot 0 SETUP_FIXED_CONST, entry 0x0, 15 words is the")
    A("   only slot the old `!gs->entry` reject skipped).")
    A("2. **r1448b/c residual:** the slots carried the program pairs (PAIR-DROP=0;")
    A("   `mthd=2054/2094/2154 d1=0x19b0900/0x19b0200/0x19b0000`); the MME was")
    A("   sampled at mpc=899 (r1448b, 3x then poison) / 900 (r1448c, 2x then")
    A("   trap) inside slot 57. NOTE: the clean r1443b boot passes through the")
    A("   same mpc=899/900 samples (and advances to mac=0x33), so the PC alone")
    A("   is not the anomaly - the differentiator is `trap=0x2090 data=0`")
    A("   (0 occurrences in r1443b/r1447/r1447c, 4 in r1448) followed by")
    A("   Class Error Offset 00002090 ErrorCode 4.")
    A("3. **0x2090 is macro-emitted, not client-emitted:** no window in any log")
    A("   contains `mthd=2090`; the trap field is calibrated against window runs")
    A("   (`mthd=22c d0=800` <-> `trap=0000022c data=00000800`). 0x2090 =")
    A("   SET_PIPELINE(2)+0x10 = PIPELINE_BINDING(2), the stage-2 binding in the")
    A("   same family as the carried pairs. It is emitted by the MME and")
    A("   rejected only in the pair-carried boots.")
    A("4. **Stall-site decode:** slot 57 IR 899 (idx 31) is an emission group")
    A("   `ADD R3,R3,IMMEDPAIR,0x08e4 | OUT method-src=IMMED0 data-src=LOAD0`;")
    A("   IR 903 repeats it; IR 900 (idx 32) is `JAL +0x1d` to the macro tail.")
    A("   LOAD0 is never written inside the slot-57 body (first read at IR 868")
    A("   as macro input word 0): the emitted binding payload is call-input")
    A("   dependent and null-capable - the rejected `data=0` snapshot is")
    A("   consistent with a zero call-input word on the stalling call.")
    A("5. **Next lever:** the carried-pair launch must satisfy the stage-2")
    A("   binding method (0x2090) the macro emits, or the emission must be")
    A("   bracketed by a valid stage-2 selector/binding state (the window writes")
    A("   `mthd=2080 SET_PIPELINE(2) d0=0x21 d1=0x20010850` but no 0x2090).")
    A("   Alternative: keep the r1447 pairs-elided shape for the calls-kept")
    A("   launch (it reached mpc=930/22) and carry the pairs only on held-call")
    A("   launches.")
    A("")

    text = "\n".join(lines) + "\n"
    with open(args.out, "w", encoding="utf-8") as fh:
        fh.write(text)
    print(text)
    print("wrote", args.out, file=sys.stderr)


if __name__ == "__main__":
    main()