#!/usr/bin/env python3
"""Interleaved api-compile variant tester. Each variant has its own incremental cache."""
import json, subprocess, time, statistics, os, sys, shutil

ROOT = "/Users/oscar/.worktree/hot-reload/labs/hot-reload"
SRC = os.path.join(ROOT, "api-common/src/lib.rs")
REC = os.path.join(ROOT, "target/kan-hot/debug/rustc")
BASEINC = os.path.join(ROOT, "target/kan-hot/debug/exp-inc")
AC = json.load(open(os.path.join(REC, "api_common.json")))
AP = json.load(open(os.path.join(REC, "api.json")))
ENV = dict(AP["env"])

def strip_flag(argv, name):
    out = []; i = 0
    while i < len(argv):
        if argv[i] == "-C" and i + 1 < len(argv) and argv[i + 1].startswith(name):
            i += 2; continue
        if argv[i].startswith("-C" + name):
            i += 1; continue
        out.append(argv[i]); i += 1
    return out

def set_flag(argv, name, value):
    return strip_flag(argv, name) + ["-C", f"{name}={value}"]

def emit(argv, spec):
    return [f"--emit={spec}" if a.startswith("--emit=") else a for a in argv]

def variant(name):
    if name == "asis":
        return list(AP["argv"]), None, False
    argv = list(AP["argv"])
    inc = os.path.join(BASEINC, name)
    argv = [a for a in argv if not (a.startswith("-C") and "incremental=" in a)]
    # remove paired -C incremental=X
    out=[]; i=0
    while i < len(argv):
        if argv[i] == "-C" and i+1 < len(argv) and argv[i+1].startswith("incremental="):
            i += 2; continue
        out.append(argv[i]); i += 1
    argv = out + ["-C", "debuginfo=0", "-C", "incremental=" + inc]
    boot = False
    if name == "base":
        pass
    elif name == "cu4": argv = set_flag(argv, "codegen-units", "4")
    elif name == "cu8": argv = set_flag(argv, "codegen-units", "8")
    elif name == "cu16": argv = set_flag(argv, "codegen-units", "16")
    elif name == "cu64": argv = set_flag(argv, "codegen-units", "64")
    elif name == "no-inc":
        argv = [a for a in argv if not (("incremental=" in a) or (a == "-C" and "incremental" in "".join(argv)))]
        out=[]; i=0
        while i < len(argv):
            if argv[i] == "-C" and i+1 < len(argv) and argv[i+1].startswith("incremental="):
                i += 2; continue
            out.append(argv[i]); i += 1
        argv = out
    elif name == "emit-link": argv = emit(argv, "dep-info,link")
    elif name == "emit-obj": argv = emit(argv, "dep-info,obj")
    elif name == "prefer-dynamic": argv += ["-C", "prefer-dynamic"]
    elif name == "debuginfo2": argv = strip_flag(argv, "debuginfo=") + ["-C", "debuginfo=2"]
    elif name == "debugassert-off": argv += ["-C", "debug-assertions=off"]
    elif name == "threads8": argv += ["-Zthreads=8"]; boot = True
    elif name == "sg-no": argv += ["-Zshare-generics=no"]; boot = True
    elif name == "threads8-sg-no": argv += ["-Zthreads=8", "-Zshare-generics=no"]; boot = True
    elif name == "alwaysmir": argv += ["-Zalways-encode-mir"]; boot = True
    elif name == "cu16-link": argv = set_flag(argv, "codegen-units", "16"); argv = emit(argv, "dep-info,link")
    elif name == "link-pref": argv = emit(argv, "dep-info,link"); argv += ["-C", "prefer-dynamic"]
    elif name == "cu16-pref": argv = set_flag(argv, "codegen-units", "16"); argv += ["-C", "prefer-dynamic"]
    elif name == "strip": argv += ["-C", "strip=symbols"]
    elif name == "split-off": argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]
    elif name == "tip": argv = emit(argv, "link"); argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]; argv += ["-C", "codegen-units=8"]
    elif name == "tip16": argv = emit(argv, "link"); argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]; argv = set_flag(argv, "codegen-units", "16")
    elif name == "tip4": argv = emit(argv, "link"); argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]; argv = set_flag(argv, "codegen-units", "4")
    elif name == "tip-nojson": argv = emit(argv, "link"); argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]
    elif name == "nopre": argv = emit(argv, "link") + ["-C", "no-prepopulate-passes"]
    elif name == "tip-nopre": argv = emit(argv, "link"); argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]; argv += ["-C", "no-prepopulate-passes"]
    elif name == "tip-nopre16": argv = emit(argv, "link"); argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]; argv = set_flag(argv, "codegen-units", "16"); argv += ["-C", "no-prepopulate-passes"]
    elif name == "tip-inline0": argv = emit(argv, "link"); argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]; argv += ["-C", "llvm-args=--inline-threshold=0"]
    elif name in ("tip10", "tip12", "tip20", "tip24"):
        argv = emit(argv, "link"); argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]
        argv = set_flag(argv, "codegen-units", name[3:])
    elif name == "tip-ignorespans":
        argv = emit(argv, "link"); argv = strip_flag(argv, "split-debuginfo=") + ["-C", "split-debuginfo=off"]
        argv += ["-Zincremental-ignore-spans=yes"]; boot = True
    else: raise SystemExit("unknown " + name)
    return argv, inc, boot

def run(argv, env):
    t = time.monotonic()
    r = subprocess.run(argv, cwd=AP["cwd"], env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    return (time.monotonic() - t) * 1000, r.returncode

def main():
    names = sys.argv[1].split(",")
    n = int(sys.argv[2]) if len(sys.argv) > 2 else 8
    shutil.rmtree(BASEINC, ignore_errors=True)
    specs = {nm: variant(nm) for nm in names}
    original = open(SRC).read()
    times = {nm: [] for nm in names}
    acs = []
    try:
        for i in range(n):
            open(SRC, "w").write(original.replace("nested-v1", f"iv-{i}-{time.time_ns()}"))
            t = time.monotonic()
            subprocess.run(AC["argv"], cwd=AC["cwd"], env=dict(AC["env"]), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
            acs.append((time.monotonic() - t) * 1000)
            for nm in names:
                argv, _, boot = specs[nm]
                env = dict(ENV)
                if boot: env["RUSTC_BOOTSTRAP"] = "1"
                d, rc = run(argv, env)
                times[nm].append(d)
                if rc: print("FAIL", nm, i, rc)
            time.sleep(0.12)
    finally:
        open(SRC, "w").write(original)
    lo = 2  # skip warmup
    print(f"ac median {statistics.median(acs):.1f}  (iters {n}, skip first {lo})")
    for nm in names:
        v = times[nm][lo:]
        print(f"{nm:16s} api med={statistics.median(v):6.1f} mean={statistics.mean(v):6.1f} min={min(v):6.1f} max={max(v):6.1f}")

if __name__ == "__main__":
    main()
