mirror of
https://github.com/Cesarus85/ringfall-mjolnir-vr.git
synced 2026-10-22 03:00:30 +02:00
199 lines
6.9 KiB
Python
199 lines
6.9 KiB
Python
"""Validate and list functions parked on documented compiler-codegen ties.
|
|
|
|
The manifest is deliberately evidence-only: it does not decide which function
|
|
should be worked next. Queue tooling can consume the validated ``active``
|
|
keys, while stale measurements fail validation and therefore reopen the
|
|
function for investigation.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from .coff_compare import CoffError, load, section_info, section_infos_equal
|
|
|
|
|
|
ALLOWED_CLASSES = {
|
|
"instruction-scheduling",
|
|
"private-register-convention",
|
|
"register-allocation",
|
|
"tu-context-optimization",
|
|
}
|
|
|
|
|
|
class ParkedFunctionsError(RuntimeError):
|
|
pass
|
|
|
|
|
|
def _snapshot(info):
|
|
return {
|
|
"size": info["size"],
|
|
"relocation_count": info["relocation_count"],
|
|
"normalized_sha256": info["normalized_sha256"],
|
|
}
|
|
|
|
|
|
def _differences(expected, current, prefix):
|
|
differences = []
|
|
for key in ("size", "relocation_count", "normalized_sha256"):
|
|
if expected.get(key) != current.get(key):
|
|
differences.append({
|
|
"measurement": f"{prefix}.{key}",
|
|
"expected": expected.get(key),
|
|
"current": current.get(key),
|
|
})
|
|
return differences
|
|
|
|
|
|
def validate_parked_functions(project_root, report_path, config_path, manifest_path):
|
|
project_root = Path(project_root)
|
|
report = json.loads(Path(report_path).read_text(encoding="utf-8"))
|
|
config = json.loads(Path(config_path).read_text(encoding="utf-8"))
|
|
manifest = json.loads(Path(manifest_path).read_text(encoding="utf-8"))
|
|
|
|
if manifest.get("version") != 1 or not isinstance(manifest.get("entries"), list):
|
|
raise ParkedFunctionsError("parked manifest must have version 1 and an entries list")
|
|
|
|
report_units = {unit["name"]: unit for unit in report.get("units", [])}
|
|
config_units = {unit["name"]: unit for unit in config.get("units", [])}
|
|
seen = set()
|
|
active = []
|
|
stale = []
|
|
invalid = []
|
|
|
|
for entry in manifest["entries"]:
|
|
unit_name = entry.get("unit")
|
|
function_name = entry.get("function")
|
|
key = f"{unit_name}:{function_name}"
|
|
reason = None
|
|
|
|
if not isinstance(unit_name, str) or not isinstance(function_name, str):
|
|
reason = "unit and function must be strings"
|
|
elif key in seen:
|
|
reason = "duplicate parked-function key"
|
|
elif entry.get("class") not in ALLOWED_CLASSES:
|
|
reason = "unknown blocker class"
|
|
elif not isinstance(entry.get("evidence"), str) or not entry["evidence"].strip():
|
|
reason = "non-empty evidence is required"
|
|
elif unit_name not in report_units or unit_name not in config_units:
|
|
reason = "unit missing from report or objdiff config"
|
|
seen.add(key)
|
|
|
|
if reason:
|
|
invalid.append({"key": key, "reason": reason})
|
|
continue
|
|
|
|
functions = [
|
|
function for function in report_units[unit_name].get("functions", [])
|
|
if function.get("name") == function_name
|
|
]
|
|
if len(functions) != 1:
|
|
invalid.append({
|
|
"key": key,
|
|
"reason": f"expected one report function, found {len(functions)}",
|
|
})
|
|
continue
|
|
|
|
config_unit = config_units[unit_name]
|
|
try:
|
|
target_info = section_info(
|
|
load(project_root / config_unit["target_path"]), function_name
|
|
)
|
|
base_info = section_info(
|
|
load(project_root / config_unit["base_path"]), function_name
|
|
)
|
|
except (CoffError, KeyError, OSError) as error:
|
|
invalid.append({"key": key, "reason": f"cannot measure function: {error}"})
|
|
continue
|
|
|
|
if section_infos_equal(target_info, base_info):
|
|
invalid.append({"key": key, "reason": "function is now semantically exact"})
|
|
continue
|
|
|
|
current = {
|
|
"target": _snapshot(target_info),
|
|
"base": _snapshot(base_info),
|
|
"objdiff_percent": functions[0].get("fuzzy_match_percent", 0.0),
|
|
}
|
|
expected = entry.get("measurements", {})
|
|
differences = []
|
|
differences.extend(_differences(expected.get("target", {}), current["target"], "target"))
|
|
differences.extend(_differences(expected.get("base", {}), current["base"], "base"))
|
|
if expected.get("objdiff_percent") != current["objdiff_percent"]:
|
|
differences.append({
|
|
"measurement": "objdiff_percent",
|
|
"expected": expected.get("objdiff_percent"),
|
|
"current": current["objdiff_percent"],
|
|
})
|
|
|
|
item = {
|
|
"key": key,
|
|
"unit": unit_name,
|
|
"function": function_name,
|
|
"class": entry["class"],
|
|
"evidence": entry["evidence"],
|
|
"measurements": current,
|
|
}
|
|
if differences:
|
|
item["differences"] = differences
|
|
stale.append(item)
|
|
else:
|
|
active.append(item)
|
|
|
|
return {
|
|
"summary": {
|
|
"active": len(active),
|
|
"stale": len(stale),
|
|
"invalid": len(invalid),
|
|
},
|
|
"active": active,
|
|
"active_keys": [item["key"] for item in active],
|
|
"stale": stale,
|
|
"invalid": invalid,
|
|
}
|
|
|
|
|
|
def require_valid_parked_functions(project_root, report_path, config_path, manifest_path):
|
|
result = validate_parked_functions(project_root, report_path, config_path, manifest_path)
|
|
if result["stale"] or result["invalid"]:
|
|
details = [item["key"] for item in result["stale"] + result["invalid"]]
|
|
raise ParkedFunctionsError(
|
|
"parked-function evidence is stale or invalid: " + ", ".join(details)
|
|
)
|
|
return result
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--project", type=Path, default=Path.cwd())
|
|
parser.add_argument("--report", type=Path, default=Path("build/report.json"))
|
|
parser.add_argument("--config", type=Path, default=Path("objdiff.json"))
|
|
parser.add_argument("--manifest", type=Path, default=Path("config/parked.json"))
|
|
parser.add_argument("--output", type=Path)
|
|
parser.add_argument(
|
|
"--allow-stale",
|
|
action="store_true",
|
|
help="report stale/invalid entries without returning a failure status",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
root = args.project.resolve()
|
|
result = validate_parked_functions(
|
|
root,
|
|
root / args.report,
|
|
root / args.config,
|
|
root / args.manifest,
|
|
)
|
|
serialized = json.dumps(result, indent=2) + "\n"
|
|
if args.output:
|
|
output = root / args.output
|
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
output.write_text(serialized, encoding="utf-8")
|
|
print(serialized, end="")
|
|
if not args.allow_stale and (result["stale"] or result["invalid"]):
|
|
raise SystemExit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|