feat(opcodes): data model + loader for opcodes.toml
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
79
tools/opcodes_model.py
Normal file
79
tools/opcodes_model.py
Normal file
@@ -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
|
||||||
67
tools/test_opcodes.py
Normal file
67
tools/test_opcodes.py
Normal file
@@ -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())
|
||||||
Reference in New Issue
Block a user