diff --git a/tools/opcodes_model.py b/tools/opcodes_model.py new file mode 100644 index 0000000..26d18f4 --- /dev/null +++ b/tools/opcodes_model.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""In-memory model + loader + linter for vm-map/opcodes.toml (the canonical opcode reference). +Read-only: uses stdlib tomllib. See docs/superpowers/specs/2026-07-06-opcode-reference-design.md.""" +from __future__ import annotations +import tomllib +from dataclasses import dataclass, field +from pathlib import Path + +CATEGORIES = {"marker", "structural", "control", "adv", "draw", "audio", "input", "compute", "unknown"} +SOURCES = {"kelebek", "harness", "investigation", "frida", "unicorn", "inference"} +CONFIDENCE = {"low": 1, "med": 2, "high": 3} + +@dataclass +class Semantics: + name: str + category: str = "unknown" + summary: str = "" + noop_headless: bool = False + source: str = "kelebek" + confidence: str = "low" + depends_on: list[int] = field(default_factory=list) + evidence: str = "" + details: str = "" + confirm_by: str = "" + args: list[dict] = field(default_factory=list) + +@dataclass +class Opcode: + op: int + label: str + argc: int + code_target_args: list[int] = field(default_factory=list) + abi_source: str = "kelebek+decode-validated" + abi_note: str = "" + semantics: Semantics | None = None + +@dataclass +class Model: + meta: dict + opcodes: dict[int, Opcode] + +def load(path) -> Model: + data = tomllib.loads(Path(path).read_text(encoding="utf-8")) + ops: dict[int, Opcode] = {} + for e in data.get("opcode", []): + sem = None + s = e.get("semantics") + if s is not None: + sem = Semantics( + name=s.get("name", e.get("label", "")), + category=s.get("category", "unknown"), + summary=s.get("summary", ""), + noop_headless=bool(s.get("noop_headless", False)), + source=s.get("source", "kelebek"), + confidence=s.get("confidence", "low"), + depends_on=[int(x) for x in s.get("depends_on", [])], + evidence=s.get("evidence", ""), + details=s.get("details", ""), + confirm_by=s.get("confirm_by", ""), + args=list(s.get("args", [])), + ) + ops[int(e["op"])] = Opcode( + op=int(e["op"]), label=e.get("label", ""), argc=int(e["argc"]), + code_target_args=[int(x) for x in e.get("code_target_args", [])], + abi_source=e.get("abi_source", "kelebek+decode-validated"), + abi_note=e.get("abi_note", ""), semantics=sem, + ) + return Model(meta=data.get("meta", {}), opcodes=ops) + +def dependents(model: Model) -> dict[int, list[int]]: + """Reverse of depends_on: op -> [ops whose semantics depend on it].""" + rev: dict[int, list[int]] = {op: [] for op in model.opcodes} + for op, oc in model.opcodes.items(): + if oc.semantics: + for dep in oc.semantics.depends_on: + rev.setdefault(dep, []).append(op) + for k in rev: + rev[k].sort() + return rev diff --git a/tools/test_opcodes.py b/tools/test_opcodes.py new file mode 100644 index 0000000..b67befe --- /dev/null +++ b/tools/test_opcodes.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Standalone tests for the opcode reference tooling. Run: py -3.11 -X utf8 tools/test_opcodes.py""" +import os, sys, tempfile +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import opcodes_model as M + +FAILS = [] +def check(cond, msg): + print((" ok " if cond else " FAIL ") + msg) + if not cond: FAILS.append(msg) + +FIXTURE = ''' +[meta] +opcodes_used_by_himegari = 2 +[[opcode]] +op = 0x90 +label = "u0041BEB0" +argc = 7 +code_target_args = [5, 6, 7] +[opcode.semantics] +name = "hotspot-branch" +category = "input" +summary = "cursor hotspot hit-test" +noop_headless = true +source = "investigation" +confidence = "high" +depends_on = [0x1f4] +evidence = "301/301 uniform" +[[opcode.semantics.args]] +i = 1 +role = "x" +observed_types = ["imm"] +[[opcode]] +op = 0x1f4 +label = "u004160D0" +argc = 0 +[opcode.semantics] +name = "stmt-begin" +category = "marker" +source = "investigation" +confidence = "high" +''' + +def write_tmp(text): + fd, p = tempfile.mkstemp(suffix=".toml"); os.close(fd) + open(p, "w", encoding="utf-8").write(text) + return p + +def test_load(): + m = M.load(write_tmp(FIXTURE)) + check(set(m.opcodes) == {0x90, 0x1f4}, "loads both opcodes keyed by int") + o = m.opcodes[0x90] + check(o.argc == 7, "0x90 argc == 7") + check(o.code_target_args == [5, 6, 7], "0x90 code_target_args parsed") + check(o.semantics.name == "hotspot-branch", "0x90 semantics.name") + check(o.semantics.depends_on == [0x1f4], "depends_on parsed as int list") + check(o.semantics.args[0]["role"] == "x", "arg role parsed") + rev = M.dependents(m) + check(rev.get(0x1f4) == [0x90], "dependents: 0x1f4 depended on by 0x90") + +def main(): + test_load() + print("FAILURES:", len(FAILS)) + return 1 if FAILS else 0 + +if __name__ == "__main__": + sys.exit(main())