gisel-ab 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gisel_ab/__init__.py +120 -0
- gisel_ab/__main__.py +8 -0
- gisel_ab/baseline.py +145 -0
- gisel_ab/bugreport.py +113 -0
- gisel_ab/cli.py +150 -0
- gisel_ab/cohort.py +704 -0
- gisel_ab/cpp/aarch64_generated_ctor.cpp +23 -0
- gisel_ab/cpp/aarch64_query_runner.cpp +380 -0
- gisel_ab/cpp/arm_generated_ctor.cpp +18 -0
- gisel_ab/cpp/arm_query_runner.cpp +58 -0
- gisel_ab/cpp/m68k_generated_ctor.cpp +21 -0
- gisel_ab/cpp/m68k_query_runner.cpp +43 -0
- gisel_ab/cpp/mips_generated_ctor.cpp +16 -0
- gisel_ab/cpp/mips_query_runner.cpp +52 -0
- gisel_ab/cpp/ppc_generated_ctor.cpp +16 -0
- gisel_ab/cpp/ppc_query_runner.cpp +46 -0
- gisel_ab/cpp/query-recovery.h +229 -0
- gisel_ab/cpp/query_runner.cpp +419 -0
- gisel_ab/cpp/query_runner_common.h +417 -0
- gisel_ab/cpp/riscv_generated_ctor.cpp +25 -0
- gisel_ab/cpp/riscv_query_runner.cpp +379 -0
- gisel_ab/cpp/spirv_generated_ctor.cpp +24 -0
- gisel_ab/cpp/spirv_query_runner.cpp +98 -0
- gisel_ab/cpp/wasm_generated_ctor.cpp +17 -0
- gisel_ab/cpp/wasm_query_runner.cpp +52 -0
- gisel_ab/cpp/x86_generated_ctor.cpp +24 -0
- gisel_ab/data/aarch64-contract.json +102 -0
- gisel_ab/data/baseline-2026-11-12.json +1684 -0
- gisel_ab/data/closure/aarch64-D.json +156 -0
- gisel_ab/data/closure/aarch64-G.json +75 -0
- gisel_ab/data/closure/arm-D.json +50 -0
- gisel_ab/data/closure/arm-G.json +57 -0
- gisel_ab/data/closure/m68k-D.json +46 -0
- gisel_ab/data/closure/m68k-G.json +54 -0
- gisel_ab/data/closure/mips-D.json +50 -0
- gisel_ab/data/closure/mips-G.json +87 -0
- gisel_ab/data/closure/ppc-D.json +49 -0
- gisel_ab/data/closure/ppc-G.json +56 -0
- gisel_ab/data/closure/riscv-D.json +59 -0
- gisel_ab/data/closure/riscv-G.json +66 -0
- gisel_ab/data/closure/spirv-D.json +58 -0
- gisel_ab/data/closure/spirv-G.json +58 -0
- gisel_ab/data/closure/wasm-D.json +56 -0
- gisel_ab/data/closure/wasm-G.json +62 -0
- gisel_ab/data/closure/x86-D.json +60 -0
- gisel_ab/data/closure/x86-G.json +66 -0
- gisel_ab/data/domain.json +139 -0
- gisel_ab/data/mir-configs.json +444 -0
- gisel_ab/data/mir-shapes.jsonc +1168 -0
- gisel_ab/data/riscv-contract.json +102 -0
- gisel_ab/data/suite/README.md +81 -0
- gisel_ab/data/suite/aarch64/official.answers.jsonl +12559 -0
- gisel_ab/data/suite/aarch64/queries.jsonl +12559 -0
- gisel_ab/data/suite/aarch64/suite.json +43 -0
- gisel_ab/data/suite/m68k/official.answers.jsonl +35 -0
- gisel_ab/data/suite/m68k/queries.jsonl +35 -0
- gisel_ab/data/suite/m68k/suite.json +24 -0
- gisel_ab/data/suite/riscv/official.answers.jsonl +6086 -0
- gisel_ab/data/suite/riscv/queries.jsonl +6086 -0
- gisel_ab/data/suite/riscv/suite.json +41 -0
- gisel_ab/data/suite/x86/official.answers.jsonl +3014 -0
- gisel_ab/data/suite/x86/queries.jsonl +3014 -0
- gisel_ab/data/suite/x86/suite.json +33 -0
- gisel_ab/data/targets/aarch64.json +39 -0
- gisel_ab/data/targets/arm.json +39 -0
- gisel_ab/data/targets/m68k.json +39 -0
- gisel_ab/data/targets/mips.json +39 -0
- gisel_ab/data/targets/ppc.json +39 -0
- gisel_ab/data/targets/riscv.json +39 -0
- gisel_ab/data/targets/schema.json +266 -0
- gisel_ab/data/targets/spirv.json +39 -0
- gisel_ab/data/targets/wasm.json +39 -0
- gisel_ab/data/targets/x86-ctor.json +39 -0
- gisel_ab/data/targets/x86.json +39 -0
- gisel_ab/data/x86-contract.json +93 -0
- gisel_ab/doctor.py +320 -0
- gisel_ab/domain_plan.py +130 -0
- gisel_ab/evidence.py +892 -0
- gisel_ab/l0/__init__.py +0 -0
- gisel_ab/l0/attribute.py +286 -0
- gisel_ab/l0/closure.py +255 -0
- gisel_ab/l0/diff.py +388 -0
- gisel_ab/l0/g_attribution.py +93 -0
- gisel_ab/l0/inject_opcode.py +123 -0
- gisel_ab/l0/inject_predicate.py +150 -0
- gisel_ab/l0/queries.py +215 -0
- gisel_ab/l0/run.py +688 -0
- gisel_ab/l0/swap.py +257 -0
- gisel_ab/l0/u_attribution.py +192 -0
- gisel_ab/l3.py +271 -0
- gisel_ab/mir/__init__.py +0 -0
- gisel_ab/mir/config_check.py +144 -0
- gisel_ab/mir/from_query.py +717 -0
- gisel_ab/mir/libs.py +252 -0
- gisel_ab/mir/replay.py +548 -0
- gisel_ab/paths.py +151 -0
- gisel_ab/refresh_counts.py +80 -0
- gisel_ab/replay_frozen.py +154 -0
- gisel_ab/setup.py +92 -0
- gisel_ab/suite.py +390 -0
- gisel_ab/targets.py +287 -0
- gisel_ab/vertical.py +654 -0
- gisel_ab-0.9.0.dist-info/METADATA +349 -0
- gisel_ab-0.9.0.dist-info/RECORD +107 -0
- gisel_ab-0.9.0.dist-info/WHEEL +5 -0
- gisel_ab-0.9.0.dist-info/entry_points.txt +2 -0
- gisel_ab-0.9.0.dist-info/top_level.txt +1 -0
gisel_ab/__init__.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""gisel-ab: an A/B workbench around LLVM GlobalISel legalizer rules.
|
|
2
|
+
|
|
3
|
+
The package has one job: answer "does the legalizer this repository replaced
|
|
4
|
+
behave like the one it came from?" Everything a caller needs to locate lives
|
|
5
|
+
here, so no module has to guess its depth in the tree.
|
|
6
|
+
|
|
7
|
+
PKG this package (code, data, cpp)
|
|
8
|
+
AB_ROOT the checkout that contains ``src/`` and ``scripts/``
|
|
9
|
+
DATA target registry, contracts, suite, domain (package data)
|
|
10
|
+
CPP query-runner C++ sources (package data)
|
|
11
|
+
SCRIPTS the shell entry points (checkout only)
|
|
12
|
+
EVIDENCE the frozen evidence tree (checkout only)
|
|
13
|
+
|
|
14
|
+
``DATA`` and ``CPP`` travel with an installed wheel; ``SCRIPTS`` and
|
|
15
|
+
``EVIDENCE`` do not, and the commands that need them say so instead of
|
|
16
|
+
raising a FileNotFoundError three levels down.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import importlib.util
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
PKG = Path(__file__).resolve().parent
|
|
27
|
+
SRC = PKG.parent
|
|
28
|
+
AB_ROOT = SRC.parent
|
|
29
|
+
DATA = PKG / "data"
|
|
30
|
+
CPP = PKG / "cpp"
|
|
31
|
+
SCRIPTS = AB_ROOT / "scripts"
|
|
32
|
+
EVIDENCE = AB_ROOT / "evidence"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _gisel_package_dir() -> Path | None:
|
|
36
|
+
"""The installed ``gisel`` package directory, when it is importable."""
|
|
37
|
+
try:
|
|
38
|
+
spec = importlib.util.find_spec("gisel")
|
|
39
|
+
except (ImportError, ValueError):
|
|
40
|
+
return None
|
|
41
|
+
if spec is None or not spec.submodule_search_locations:
|
|
42
|
+
return None
|
|
43
|
+
return Path(next(iter(spec.submodule_search_locations))).resolve()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def gisel_src(explicit=None) -> Path:
|
|
47
|
+
"""A directory that makes ``import gisel`` work.
|
|
48
|
+
|
|
49
|
+
``GISEL_SRC`` wins; then a sibling checkout; then the installed package,
|
|
50
|
+
whose parent is the directory that belongs on ``sys.path``. A path is
|
|
51
|
+
returned even when nothing is found, so the caller's error names the
|
|
52
|
+
place it looked.
|
|
53
|
+
"""
|
|
54
|
+
value = explicit or os.environ.get("GISEL_SRC")
|
|
55
|
+
if value:
|
|
56
|
+
return Path(value).expanduser().resolve()
|
|
57
|
+
sibling = (AB_ROOT.parent / "gisel").resolve()
|
|
58
|
+
if (sibling / "gisel" / "__init__.py").is_file():
|
|
59
|
+
return sibling
|
|
60
|
+
package = _gisel_package_dir()
|
|
61
|
+
if package is not None:
|
|
62
|
+
return package.parent
|
|
63
|
+
return sibling
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def gisel_package() -> Path | None:
|
|
67
|
+
"""The ``gisel`` package directory, from a checkout or an install."""
|
|
68
|
+
root = gisel_src()
|
|
69
|
+
for candidate in (root / "gisel", root):
|
|
70
|
+
if (candidate / "__init__.py").is_file():
|
|
71
|
+
return candidate
|
|
72
|
+
return _gisel_package_dir()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def gisel_data(name: str) -> Path | None:
|
|
76
|
+
"""The ``corpus``/``cases`` directory, checkout first, wheel second.
|
|
77
|
+
|
|
78
|
+
The wheel ships byte-identical copies under ``gisel/data``; the checkout
|
|
79
|
+
keeps the canonical copies under ``tests/``.
|
|
80
|
+
"""
|
|
81
|
+
package = gisel_package()
|
|
82
|
+
roots = [package.parent, package] if package is not None else [gisel_src()]
|
|
83
|
+
for root in roots:
|
|
84
|
+
for candidate in (root / "tests" / name,
|
|
85
|
+
root / "gisel" / "data" / name,
|
|
86
|
+
root / "data" / name):
|
|
87
|
+
if candidate.is_dir():
|
|
88
|
+
return candidate.resolve()
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def gisel_path_entry() -> str:
|
|
93
|
+
"""The ``sys.path`` entry that makes ``import gisel`` work."""
|
|
94
|
+
return str(gisel_src())
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def ensure_gisel_on_path() -> Path:
|
|
98
|
+
path = gisel_src()
|
|
99
|
+
entry = str(path)
|
|
100
|
+
if entry not in sys.path:
|
|
101
|
+
sys.path.insert(0, entry)
|
|
102
|
+
return path
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def child_env(env=None) -> dict:
|
|
106
|
+
"""A copy of ``env`` (default: os.environ) that children can import from.
|
|
107
|
+
|
|
108
|
+
Subprocesses are started as ``python3 -m gisel_ab.<verb>``; they need
|
|
109
|
+
``SRC`` on ``PYTHONPATH`` and gisel on it too unless it is installed.
|
|
110
|
+
"""
|
|
111
|
+
out = dict(os.environ if env is None else env)
|
|
112
|
+
parts = [str(SRC)]
|
|
113
|
+
gisel = gisel_path_entry()
|
|
114
|
+
if Path(gisel).is_dir():
|
|
115
|
+
parts.append(gisel)
|
|
116
|
+
current = out.get("PYTHONPATH")
|
|
117
|
+
if current:
|
|
118
|
+
parts.extend(part for part in current.split(os.pathsep) if part)
|
|
119
|
+
out["PYTHONPATH"] = os.pathsep.join(dict.fromkeys(parts))
|
|
120
|
+
return out
|
gisel_ab/__main__.py
ADDED
gisel_ab/baseline.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Record the facts a remediation run is measured against.
|
|
3
|
+
|
|
4
|
+
baseline.py [--out data/baseline-YYYY-MM-DD.json]
|
|
5
|
+
|
|
6
|
+
The file is deliberately boring: repository heads, corpus/artifact/feature
|
|
7
|
+
hashes, the target registry, and the numbers already pinned by EVIDENCE.json.
|
|
8
|
+
It does not run a build and it does not change behavior. Later waves compare
|
|
9
|
+
their output with this snapshot instead of trusting a document's memory.
|
|
10
|
+
|
|
11
|
+
Exit codes: 0 = written; 2 = a required input is missing or unreadable.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
from datetime import date
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from gisel_ab import paths
|
|
25
|
+
from gisel_ab import targets as target_specs
|
|
26
|
+
|
|
27
|
+
PKG = Path(__file__).resolve().parent
|
|
28
|
+
AB = PKG.parents[1]
|
|
29
|
+
DATA = PKG / "data"
|
|
30
|
+
GISEL = Path(__import__("os").environ.get("GISEL_SRC", AB.parent / "gisel"))
|
|
31
|
+
UPSTREAM = paths.sibling(AB.parent, "UPSTREAM_SOURCE",
|
|
32
|
+
"gisel-anchor", "upstream-source")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def sha256(path: Path) -> str:
|
|
36
|
+
return hashlib.sha256(path.read_bytes()).hexdigest()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def repo_state(path: Path) -> dict:
|
|
40
|
+
def git(*args: str) -> str:
|
|
41
|
+
proc = subprocess.run(
|
|
42
|
+
["git", "-C", str(path), *args],
|
|
43
|
+
capture_output=True, text=True)
|
|
44
|
+
if proc.returncode != 0:
|
|
45
|
+
raise SystemExit("git %s failed in %s: %s"
|
|
46
|
+
% (" ".join(args), path, proc.stderr.strip()))
|
|
47
|
+
return proc.stdout.strip()
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"head": git("rev-parse", "HEAD"),
|
|
51
|
+
"branch": git("rev-parse", "--abbrev-ref", "HEAD"),
|
|
52
|
+
"dirty": bool(git("status", "--porcelain", "--untracked-files=no")),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def hashes(root: Path, pattern: str) -> dict:
|
|
57
|
+
return {path.relative_to(root).as_posix(): sha256(path)
|
|
58
|
+
for path in sorted(root.glob(pattern)) if path.is_file()}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def evidence_summary(path: Path) -> dict:
|
|
62
|
+
manifest = json.loads(path.read_text())
|
|
63
|
+
entries = {}
|
|
64
|
+
for entry in manifest.get("entries", []):
|
|
65
|
+
expect = entry.get("expect", {})
|
|
66
|
+
entries[entry["id"]] = {
|
|
67
|
+
"direction": entry.get("direction"),
|
|
68
|
+
"target": entry.get("target"),
|
|
69
|
+
"expect": {key: expect[key] for key in sorted(expect)},
|
|
70
|
+
"snapshot": entry.get("snapshot"),
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
"sha256": sha256(path),
|
|
74
|
+
"schema": manifest.get("schema"),
|
|
75
|
+
"generated": manifest.get("generated"),
|
|
76
|
+
"entries": entries,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def target_registry(root: Path) -> dict:
|
|
81
|
+
out = {}
|
|
82
|
+
for path in sorted(target_specs.TARGET_DIR.glob("*.json")):
|
|
83
|
+
data = json.loads(path.read_text())
|
|
84
|
+
out[path.stem] = {
|
|
85
|
+
"sha256": sha256(path),
|
|
86
|
+
"strategy": data.get("strategy"),
|
|
87
|
+
"query_count": data.get("query_count"),
|
|
88
|
+
}
|
|
89
|
+
return out
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def build() -> dict:
|
|
93
|
+
for path in (GISEL, UPSTREAM, AB):
|
|
94
|
+
if not path.is_dir():
|
|
95
|
+
raise SystemExit("missing checkout: %s" % path)
|
|
96
|
+
return {
|
|
97
|
+
"snapshot": "pre-remediation",
|
|
98
|
+
"repos": {
|
|
99
|
+
"gisel": repo_state(GISEL),
|
|
100
|
+
"ab": repo_state(AB),
|
|
101
|
+
"upstream-source": repo_state(UPSTREAM),
|
|
102
|
+
},
|
|
103
|
+
"gisel": {
|
|
104
|
+
"corpora": hashes(GISEL, "tests/corpus/*.gisel"),
|
|
105
|
+
"artifacts": hashes(GISEL, "tests/artifacts/*.inc"),
|
|
106
|
+
"features": hashes(GISEL, "tests/cases/*features*.json"),
|
|
107
|
+
"domain_queries": hashes(GISEL, "tests/cases/*domain*.jsonl"),
|
|
108
|
+
},
|
|
109
|
+
"ab": {
|
|
110
|
+
"evidence": evidence_summary(AB / "EVIDENCE.json"),
|
|
111
|
+
"targets": target_registry(AB),
|
|
112
|
+
"contracts": hashes(DATA, "*-contract.json"),
|
|
113
|
+
"gate_scripts": {
|
|
114
|
+
name: sha256(AB / name)
|
|
115
|
+
for name in (
|
|
116
|
+
"src/gisel_ab/l0/diff.py",
|
|
117
|
+
"src/gisel_ab/l0/queries.py",
|
|
118
|
+
"scripts/verify-evidence.sh",
|
|
119
|
+
"scripts/e2e/run-u-end-to-end.sh",
|
|
120
|
+
"scripts/negative/run-g-negative.sh",
|
|
121
|
+
"scripts/negative/run-predicate-negative.sh",
|
|
122
|
+
)
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
"upstream_source": {
|
|
126
|
+
"pin_manifest": hashes(UPSTREAM, "pin/*.json"),
|
|
127
|
+
"oracle_queries": hashes(UPSTREAM, "oracle/*/*/queries.jsonl*"),
|
|
128
|
+
},
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main(argv=None) -> int:
|
|
133
|
+
parser = argparse.ArgumentParser(prog="baseline")
|
|
134
|
+
parser.add_argument("--out", type=Path, default=None)
|
|
135
|
+
args = parser.parse_args(argv)
|
|
136
|
+
out = args.out or (DATA / ("baseline-%s.json" % date.today()))
|
|
137
|
+
data = build()
|
|
138
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
139
|
+
out.write_text(json.dumps(data, indent=1, sort_keys=True) + "\n")
|
|
140
|
+
print(str(out))
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
sys.exit(main())
|
gisel_ab/bugreport.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""`gisel-ab bug-report`: one JSON file with the facts a reader would ask for.
|
|
2
|
+
|
|
3
|
+
gisel-ab bug-report # stdout
|
|
4
|
+
gisel-ab bug-report --out bug.json # write a file
|
|
5
|
+
|
|
6
|
+
It collects versions, the cohort record, the doctor verdict and the last
|
|
7
|
+
run's ledger line. It deliberately does not collect the evidence tree: a bug
|
|
8
|
+
report is small enough to paste into an issue, and the evidence is fetchable
|
|
9
|
+
from the cohort release.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import platform
|
|
17
|
+
import sys
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from gisel_ab import doctor
|
|
22
|
+
|
|
23
|
+
SCHEMA = 1
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _installed(name: str):
|
|
27
|
+
try:
|
|
28
|
+
import importlib.metadata as metadata
|
|
29
|
+
return metadata.version(name)
|
|
30
|
+
except Exception:
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _module_version(name: str):
|
|
35
|
+
try:
|
|
36
|
+
module = __import__(name)
|
|
37
|
+
except Exception:
|
|
38
|
+
return None
|
|
39
|
+
return getattr(module, "__version__", None)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _release() -> dict:
|
|
43
|
+
path = doctor.AB_ROOT / "RELEASE.json"
|
|
44
|
+
try:
|
|
45
|
+
doc = json.loads(path.read_text())
|
|
46
|
+
except (OSError, ValueError):
|
|
47
|
+
return {}
|
|
48
|
+
keep = ("cohort", "version", "repos", "generated_at")
|
|
49
|
+
return {key: doc[key] for key in keep if key in doc}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _last_run() -> dict | None:
|
|
53
|
+
ledgers = sorted((doctor.AB_ROOT / ".ab").glob("*/ledger.jsonl"))
|
|
54
|
+
if not ledgers:
|
|
55
|
+
return None
|
|
56
|
+
path = ledgers[-1]
|
|
57
|
+
lines = path.read_text().strip().splitlines()
|
|
58
|
+
if not lines:
|
|
59
|
+
return None
|
|
60
|
+
try:
|
|
61
|
+
record = json.loads(lines[-1])
|
|
62
|
+
except ValueError:
|
|
63
|
+
return {"target": path.parent.name, "ledger": str(path),
|
|
64
|
+
"error": "the last line is not JSON"}
|
|
65
|
+
return {"target": path.parent.name, "ledger": str(path), "record": record}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def report(tree=None, build=None) -> dict:
|
|
69
|
+
from gisel_ab.cli import _version
|
|
70
|
+
checks = doctor.checks(tree, build)
|
|
71
|
+
return {
|
|
72
|
+
"schema": SCHEMA,
|
|
73
|
+
"generated_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
74
|
+
"python": sys.version.split()[0],
|
|
75
|
+
"platform": platform.platform(),
|
|
76
|
+
"versions": {
|
|
77
|
+
"gisel-ab": _version(),
|
|
78
|
+
"gisel-anchor": _module_version("gisel_anchor"),
|
|
79
|
+
"gisel": _module_version("gisel"),
|
|
80
|
+
"installed.gisel-ab": _installed("gisel-ab"),
|
|
81
|
+
"installed.gisel-anchor": _installed("gisel-anchor"),
|
|
82
|
+
"installed.gisel": _installed("gisel"),
|
|
83
|
+
},
|
|
84
|
+
"cohort": _release(),
|
|
85
|
+
"doctor": {"ok": not any(c.status == doctor.FAIL for c in checks),
|
|
86
|
+
"checks": [c.as_dict() for c in checks]},
|
|
87
|
+
"last_run": _last_run(),
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def main(argv=None) -> int:
|
|
92
|
+
ap = argparse.ArgumentParser(
|
|
93
|
+
prog="gisel-ab bug-report",
|
|
94
|
+
description="collect versions, the cohort record and the doctor verdict "
|
|
95
|
+
"into one JSON file (no evidence tree)")
|
|
96
|
+
ap.add_argument("--out", help="write here instead of stdout")
|
|
97
|
+
ap.add_argument("--tree", help="LLVM checkout (default: configured)")
|
|
98
|
+
ap.add_argument("--build", help="ninja build (default: configured)")
|
|
99
|
+
args = ap.parse_args(argv)
|
|
100
|
+
text = json.dumps(report(args.tree, args.build), indent=2,
|
|
101
|
+
sort_keys=True) + "\n"
|
|
102
|
+
if args.out:
|
|
103
|
+
path = Path(args.out).expanduser()
|
|
104
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
path.write_text(text)
|
|
106
|
+
print("wrote %s" % path)
|
|
107
|
+
return 0
|
|
108
|
+
print(text, end="")
|
|
109
|
+
return 0
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
sys.exit(main())
|
gisel_ab/cli.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""One verb table for the workbench: ``ab.py <verb> ...``.
|
|
2
|
+
|
|
3
|
+
Before this module each tool was its own script with its own parser, and the
|
|
4
|
+
documentation had to name six different entry points. There is still one
|
|
5
|
+
implementation per verb; this module only routes. Imports are lazy so
|
|
6
|
+
``--help`` works in an environment without LLVM, gisel, or the evidence tree.
|
|
7
|
+
|
|
8
|
+
Verbs that need a checkout rather than an install say so in their own error
|
|
9
|
+
message; nothing here reads the tree.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import importlib
|
|
15
|
+
import re
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
# verb -> (module, callable, prepend the verb to argv)
|
|
20
|
+
#
|
|
21
|
+
# ``run``/``mir``/``verify`` share one parser in ``l0/run.py``: the harness
|
|
22
|
+
# builds, answers and diffs in one process, so they must stay in one module.
|
|
23
|
+
VERBS = {
|
|
24
|
+
"doctor": (("gisel_ab.doctor", "main", False),
|
|
25
|
+
"check tree/build/gisel/state before a run (read-only)"),
|
|
26
|
+
"bug-report": (("gisel_ab.bugreport", "main", False),
|
|
27
|
+
"collect versions, cohort and doctor into one JSON"),
|
|
28
|
+
"setup": (("gisel_ab.setup", "main", False),
|
|
29
|
+
"record the LLVM tree/build/evidence paths (no export)"),
|
|
30
|
+
"run": (("gisel_ab.l0.run", "main", True),
|
|
31
|
+
"build both sides, answer, diff, restore"),
|
|
32
|
+
"mir": (("gisel_ab.l0.run", "main", True),
|
|
33
|
+
"L2: answerless queries -> MIR -> replay both libraries"),
|
|
34
|
+
"verify": (("gisel_ab.l0.run", "main", True),
|
|
35
|
+
"check the build is back on the baseline library"),
|
|
36
|
+
"diff": (("gisel_ab.l0.diff", "main", False),
|
|
37
|
+
"compare two answer files (action or full)"),
|
|
38
|
+
"l3": (("gisel_ab.l3", "main", False),
|
|
39
|
+
"run check-llvm twice and compare it test by test"),
|
|
40
|
+
"suite": (("gisel_ab.suite", "main", False),
|
|
41
|
+
"check/refresh the per-target answer snapshots"),
|
|
42
|
+
"vertical": (("gisel_ab.vertical", "main", False),
|
|
43
|
+
"run or verify one target's whole slice"),
|
|
44
|
+
"cohort": (("gisel_ab.cohort", "main", False),
|
|
45
|
+
"plan, check, tag and verify a frozen cohort"),
|
|
46
|
+
"evidence": (("gisel_ab.evidence", "main", False),
|
|
47
|
+
"verify EVIDENCE.json against live or frozen artifacts"),
|
|
48
|
+
"plan": (("gisel_ab.domain_plan", "main", True),
|
|
49
|
+
"print one target's domain plan (no build)"),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Not in the front page but still reachable: they are the internals the ten
|
|
53
|
+
# verbs call, and keeping them addressable is how a failure gets reproduced.
|
|
54
|
+
INTERNALS = {
|
|
55
|
+
"attribute": ("gisel_ab.l0.attribute", "main"),
|
|
56
|
+
"closure": ("gisel_ab.l0.closure", "main"),
|
|
57
|
+
"queries": ("gisel_ab.l0.queries", "main"),
|
|
58
|
+
"swap": ("gisel_ab.l0.swap", "main"),
|
|
59
|
+
"inject": ("gisel_ab.l0.inject_predicate", "main"),
|
|
60
|
+
"baseline": ("gisel_ab.baseline", "main"),
|
|
61
|
+
"replay-frozen": ("gisel_ab.replay_frozen", "main"),
|
|
62
|
+
"refresh-counts": ("gisel_ab.refresh_counts", "main"),
|
|
63
|
+
"mir-from-query": ("gisel_ab.mir.from_query", "main"),
|
|
64
|
+
"mir-replay": ("gisel_ab.mir.replay", "main"),
|
|
65
|
+
"mir-config-check": ("gisel_ab.mir.config_check", "main"),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _version() -> str:
|
|
70
|
+
"""The checkout's version when there is one, else the installed metadata.
|
|
71
|
+
|
|
72
|
+
The source wins so `python3 ab.py --version` reflects the tree being
|
|
73
|
+
edited, even when a stale editable install is still on the path.
|
|
74
|
+
"""
|
|
75
|
+
pyproject = Path(__file__).resolve().parents[2] / "pyproject.toml"
|
|
76
|
+
try:
|
|
77
|
+
text = pyproject.read_text()
|
|
78
|
+
except OSError:
|
|
79
|
+
text = ""
|
|
80
|
+
match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.M)
|
|
81
|
+
if match:
|
|
82
|
+
return match.group(1)
|
|
83
|
+
try:
|
|
84
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
85
|
+
try:
|
|
86
|
+
return version("gisel-ab")
|
|
87
|
+
except PackageNotFoundError:
|
|
88
|
+
pass
|
|
89
|
+
except ImportError:
|
|
90
|
+
pass
|
|
91
|
+
return "unknown"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# What a verb needs before it can run; the help groups by this so a reader
|
|
95
|
+
# can start with the offline half.
|
|
96
|
+
NEEDS_BUILD = {"run", "mir", "verify", "l3", "vertical"}
|
|
97
|
+
NEEDS_TREE = {"cohort"}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _usage() -> str:
|
|
101
|
+
width = max(len(name) for name in VERBS)
|
|
102
|
+
groups = (
|
|
103
|
+
("without LLVM", [name for name in VERBS
|
|
104
|
+
if name not in NEEDS_BUILD | NEEDS_TREE]),
|
|
105
|
+
("with an LLVM tree and build", [name for name in VERBS
|
|
106
|
+
if name in NEEDS_BUILD]),
|
|
107
|
+
("with a git tree (no build)", [name for name in VERBS
|
|
108
|
+
if name in NEEDS_TREE]),
|
|
109
|
+
)
|
|
110
|
+
chunks = []
|
|
111
|
+
for title, names in groups:
|
|
112
|
+
chunks.append(" %s:" % title)
|
|
113
|
+
for name in names:
|
|
114
|
+
chunks.append(" %-*s %s" % (width, name, VERBS[name][1]))
|
|
115
|
+
return (
|
|
116
|
+
"usage: gisel-ab <verb> [options]\n"
|
|
117
|
+
" (checkout: python3 ab.py <verb>)\n"
|
|
118
|
+
"\n"
|
|
119
|
+
"verbs (each takes --help; gisel-ab --version):\n"
|
|
120
|
+
+ "\n".join(chunks)
|
|
121
|
+
+ "\n\n"
|
|
122
|
+
"Internals stay callable for reproduction: %s\n"
|
|
123
|
+
% ", ".join(sorted(INTERNALS))
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def main(argv=None) -> int:
|
|
128
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
129
|
+
if argv and argv[0] in ("-V", "--version"):
|
|
130
|
+
print("gisel-ab %s" % _version())
|
|
131
|
+
return 0
|
|
132
|
+
if not argv or argv[0] in ("-h", "--help", "help"):
|
|
133
|
+
print(_usage(), end="")
|
|
134
|
+
return 0 if argv else 2
|
|
135
|
+
verb, rest = argv[0], argv[1:]
|
|
136
|
+
if verb in INTERNALS:
|
|
137
|
+
module, name = INTERNALS[verb]
|
|
138
|
+
return getattr(importlib.import_module(module), name)(rest) or 0
|
|
139
|
+
entry = VERBS.get(verb)
|
|
140
|
+
if entry is None:
|
|
141
|
+
print("ab: unknown verb %r\n\n%s" % (verb, _usage()), file=sys.stderr,
|
|
142
|
+
end="")
|
|
143
|
+
return 2
|
|
144
|
+
(module, name, prepend), _ = entry
|
|
145
|
+
args = [verb, *rest] if prepend else rest
|
|
146
|
+
return getattr(importlib.import_module(module), name)(args) or 0
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
sys.exit(main())
|