hdcstorage / hdc /debugger.py
AEUPH's picture
HDCSTORAGE: hyperdimensional computer, webOS and agentic MCP layer (stdlib-only Python)
ad474c9 verified
Raw History Blame Contribute Delete
9.14 kB
"""The debugger.
A debugger for this machine has an unusual job: the instruction stream is not
readable by construction, so inspection *is* decoding. Every view here goes
through cleanup and carries its confidence, which means a low-confidence
decode is visible as a fact about the program rather than hidden behind a
plausible-looking mnemonic.
What it offers:
* breakpoints by cell index or by label;
* step / step-over, with the PC held at a BREAK trap;
* a trace ring of executed instructions with decode confidence and the
registers each one wrote;
* symbolic register inspection — for each register, the nearest named symbol
and its similarity, which is the HDC equivalent of a type readout;
* raw bit-plane views of registers and storage cells.
"""
from __future__ import annotations
from dataclasses import dataclass
from .isa import REG_NAMES, format_operands
from .interpreter import Machine, TraceEntry
@dataclass
class StopReason:
"""Why execution stopped."""
reason: str # 'break' | 'halt' | 'budget' | 'step' | 'error'
pc: int
detail: str = ""
def __str__(self) -> str:
return "%s at pc=%d%s" % (self.reason, self.pc, (" (%s)" % self.detail) if self.detail else "")
class Debugger:
"""Interactive control of a :class:`~hdc.interpreter.Machine`."""
def __init__(self, machine: Machine):
self.m = machine
self.breakpoints: set[int] = set()
self.labels: dict[str, int] = {}
self._trap_pc: int | None = None
# ---------------------------------------------------------- breakpoints
def attach_program(self, program) -> "Debugger":
"""Take label -> cell mapping from an assembled program."""
self.labels = dict(program.labels)
self.breakpoints.clear()
self._trap_pc = None
return self
def add_break(self, where: int | str) -> int:
"""Set a breakpoint at a cell index or a label name."""
pc = self._where(where)
self.breakpoints.add(pc)
return pc
def remove_break(self, where: int | str) -> bool:
pc = self._where(where)
if pc in self.breakpoints:
self.breakpoints.discard(pc)
return True
return False
def _where(self, where: int | str) -> int:
if isinstance(where, int):
return where
if where in self.labels:
return self.labels[where]
if where.startswith("@"):
return int(where[1:])
raise KeyError("no such label: %r" % (where,))
def check(self) -> StopReason | None:
"""Does the current PC sit on a fresh breakpoint?"""
pc = self.m.pc
if pc == self._trap_pc:
return None
if pc in self.breakpoints:
self._trap_pc = pc
return StopReason("break", pc, "breakpoint")
return None
# -------------------------------------------------------------- running
def step(self) -> TraceEntry | None:
"""Execute exactly one instruction, ignoring breakpoints."""
entry = self.m.step()
if self.m.status == "break":
self._trap_pc = self.m.pc
else:
self._trap_pc = None
return entry
def step_over(self) -> TraceEntry | None:
"""Step one instruction; a CALL runs to completion first."""
entry = self.m.step()
if entry is not None and entry.op == "CALL":
depth = len(self.m.stack)
guard = 0
while len(self.m.stack) >= depth and not self.m.halted and guard < 100_000:
self.m.step()
guard += 1
self._trap_pc = None
return entry
def continue_(self, max_steps: int = 100_000) -> StopReason:
"""Run until the next breakpoint, HALT, fault or budget."""
m = self.m
if m.halted:
return StopReason("halt", m.pc, "already halted")
if m.status == "ready":
m.status = "running"
# If we are parked on a trap, allow that cell to execute once.
if m.pc == self._trap_pc:
m.skip_break = True
self._trap_pc = None
while m.steps < max_steps:
stop = self.check()
if stop is not None:
m.status = "break"
return stop
if m.halted:
return StopReason("halt", m.pc)
if m.pc >= m.prog_len:
m.halted = True
m.status = "halted"
return StopReason("halt", m.pc, "past end of program")
before = m.pc
entry = m.step()
if entry is None:
return StopReason("halt", m.pc)
if m.status == "error":
return StopReason("error", m.pc, self.trace_tail(1))
if entry.op == "BREAK" and m.status == "break":
self._trap_pc = before
return StopReason("break", before, "BREAK trap")
m.status = "budget"
return StopReason("budget", m.pc, "step budget exhausted")
# ---------------------------------------------------------------- views
def trace_tail(self, n: int = 10) -> str:
rows = self.m.trace[-n:]
if not rows:
return "<no steps yet>"
return "\n".join(e.render() for e in rows)
def disasm(self, start: int | None = None, count: int = 12) -> str:
"""Disassemble cells, marking the PC and any breakpoints."""
m = self.m
if start is None:
start = max(0, m.pc - 2)
rows = []
for pc in range(start, min(start + count, m.prog_len)):
dec = m.decode_at(pc)
marks = []
if pc == m.pc:
marks.append("=>")
else:
marks.append(" ")
if pc in self.breakpoints:
marks.append("B")
else:
marks.append(" ")
rows.append(
"%s%s %04d %-8s %-26s conf=%.2f"
% ("".join(marks), "", pc, dec.op, format_operands(dec), dec.confidence)
)
return "\n".join(rows) if rows else "<empty program>"
def regs(self) -> str:
"""Registers: integer payload, and nearest named symbol (cleanup)."""
m = self.m
rows = [
"REG VALUE POP SYMBOL SIM HV(head)"
,
"-" * 78,
]
for i in range(len(m.regs)):
val = m.get_int(i)
hits = m.storage.nearest_symbol(m.regs[i], k=1)
sym, s = (hits[0] if hits else ("", 0.0))
if s < 0.15:
# Below the noise band there is no nearest symbol worth
# naming; printing the argmax anyway would imply a match.
sym, s = "", 0.0
from .hypervec import bits
rows.append(
"%-5s %-10d %-6d %-25s %-5.2f %s"
% (
REG_NAMES[i],
val,
m.regs[i].bit_count(),
sym[:25] or "-",
s,
bits(m.regs[i], m.d)[:24],
)
)
rows.append(
"PC=%d ZF=%d STACK=%d STEPS=%d STATUS=%s"
% (m.pc, int(m.flag_z), len(m.stack), m.steps, m.status)
)
return "\n".join(rows)
def sim_table(self, regs: list[int] | None = None) -> str:
"""Pairwise register similarity — the 'how HDC is my state' view."""
m = self.m
idx = regs if regs is not None else list(range(len(m.regs)))
head = " " + " ".join("%6s" % REG_NAMES[i] for i in idx)
rows = [head]
for i in idx:
cells = []
for j in idx:
cells.append("%6.3f" % m.get_sim(i, j))
rows.append("%5s " % REG_NAMES[i] + " ".join(cells))
return "\n".join(rows)
def bits(self, reg: int) -> str:
"""Bit planes of one register."""
from .hypervec import bitplanes
return "\n".join(bitplanes(self.m.regs[reg], self.m.d))
def cell_bits(self, name: str) -> str:
"""Bit planes of a storage cell."""
return "\n".join(self.m.storage.bitplanes(name))
def summary(self) -> str:
m = self.m
return (
"status=%-8s pc=%-4d steps=%-5d halted=%-5s zf=%d bps=%s\n%s"
% (
m.status,
m.pc,
m.steps,
m.halted,
int(m.flag_z),
sorted(self.breakpoints) or "[]",
self.disasm(count=5),
)
)
def debug_program(machine: Machine, program, breakpoints: list[int | str] = ()) -> str:
"""Convenience: attach, set breaks, run, and return a session transcript."""
dbg = Debugger(machine).attach_program(program)
for b in breakpoints:
dbg.add_break(b)
lines = [dbg.summary()]
stop = dbg.continue_()
lines.append("stopped: %s" % stop)
lines.append(dbg.regs())
lines.append("trace:\n" + dbg.trace_tail(8))
return "\n".join(lines)