#!/usr/bin/env python3
"""Card 07: gfx1151 device-code parity for every moved .hip translation unit."""
import json, os, subprocess, hashlib, collections, sys
REPO = "/home/mixer/gufo"
HIPBIN = "/nix/store/65dxwr554b4ij9f4aviz07f9v8jgai0a-hipClang/bin"
WORK = "/tmp/card07-work/dev"
os.makedirs(WORK, exist_ok=True)
os.environ["PATH"] = HIPBIN + ":" + os.environ["PATH"]

def load(p):
    return {e["file"].replace(REPO + "/", ""): e for e in json.load(open(p))}

sys.path.insert(0, "/tmp/card07-work")
from audit_parity import map_path  # noqa: E402

def run(args):
    r = subprocess.run(args, capture_output=True, text=True)
    return r

def device_sections(obj, tag):
    fb = f"{WORK}/{tag}.fb"
    if run(["objcopy", "--dump-section", f".hip_fatbin={fb}", obj]).returncode:
        return None
    out = f"{WORK}/{tag}.bin"
    r = run(["clang-offload-bundler", "--unbundle", "--input=" + fb, "--output=" + out,
             "--targets=hipv-amdgcn-amd-amdhsa--gfx1151", "--type=bin"])
    if r.returncode or not os.path.exists(out):
        return "unbundle-failed:" + r.stderr.strip().splitlines()[0] if r.stderr else "unbundle-failed"
    # Per-kernel .text.<kernel> sections of the gfx1151 code object.
    listing = run(["llvm-readelf", "--section-headers", out]).stdout
    names = [line.split()[-1] for line in listing.splitlines()
             if len(line.split()) >= 3 and line.split()[-1].startswith(".text.")]
    digest = {}
    for name in names:
        path = f"{WORK}/{tag}.text.bin"
        if run(["llvm-objcopy", "--dump-section", f"{name}={path}", out]).returncode:
            digest[name] = "dump-failed"
            continue
        digest[name] = hashlib.sha256(open(path, "rb").read()).hexdigest()[:16]
    return digest

old, new = load(f"{REPO}/build/card06-release/compile_commands.json"), \
           load(f"{REPO}/build/card07-release/compile_commands.json")
tus = [s for s in old if s.startswith("src/models/qwen/") and s.endswith(".hip")]
same = diff = skip = 0
notes = []
for src_old in sorted(tus):
    src_new = map_path(src_old)
    if src_new not in new:
        notes.append(f"MISSING {src_old}")
        skip += 1
        continue
    do = device_sections(old[src_old]["output"], f"o{abs(hash(src_old))}")
    dn = device_sections(new[src_new]["output"], f"n{abs(hash(src_new))}")
    if isinstance(do, str) or isinstance(dn, str):
        notes.append(f"{src_old}: device extract problem ({do} / {dn})")
        skip += 1
        continue
    if do == dn:
        same += 1
    else:
        diff += 1
        only_old = sorted(set(do) - set(dn)); only_new = sorted(set(dn) - set(do))
        changed = sorted(k for k in set(do) & set(dn) if do[k] != dn[k])
        notes.append(f"{src_old} -> {src_new}: kernels old={len(do)} new={len(dn)} "
                     f"changed={len(changed)} only-old={only_old[:2]} only-new={only_new[:2]}")
print(f"gfx1151 device-code parity: identical={same} differing={diff} skipped={skip} "
      f"(of {len(tus)} moved .hip translation units)")
for note in notes[:20]:
    print("  " + note)
