"""Minimal i860 XR/XP disassembler driven by the binutils opcode table.

Usage: python i860dis.py <va_hex> [len_hex]
Disassembles VREND.MNG code (loads at 0xf0400000; file header 0x1C).

Rebuilt 2026-07-12 (the task-#55 original didn't survive); opcode truth =
scratchpad/i860_opcodes.h (binutils include/opcode/i860.h, haiku legacy
mirror).  Operand syntax per the header comment.
"""
import re, struct, sys, os

HERE = os.path.dirname(os.path.abspath(__file__))
MNG = os.path.join(HERE, '..', 'content', 'VREND.MNG')
HDR = 0x1C
CODEBASE = 0xf0400000
CODESZ = 0x3ac80
DATASZ = 0x1e9c0

def load_ops():
    src = open(os.path.join(HERE, 'i860_opcodes.h')).read()
    ops = []
    for m in re.finditer(r'\{\s*"([^"]+)",\s*(0x[0-9a-fA-F]+),\s*(0x[0-9a-fA-F]+),\s*"([^"]*)",\s*(\w+)\s*\}', src):
        name, match, lose, args, expand = m.groups()
        ops.append((name, int(match, 16), int(lose, 16), args))
    return ops

OPS = load_ops()

def fields(w):
    return {
        'src2': (w >> 21) & 0x1F,
        'dest': (w >> 16) & 0x1F,
        'src1': (w >> 11) & 0x1F,
        'imm16': w & 0xFFFF,
        'imm5': (w >> 11) & 0x1F,
        'lbroff': w & 0x03FFFFFF,
        'sbroff': ((w >> 5) & 0xF800) | (w & 0x07FF),   # split 16-bit branch offset
        'split16': ((w >> 5) & 0xF800) | (w & 0x07FF),  # split 16-bit (st) immediate
        'creg': (w >> 21) & 0x1F,
    }

def sext(v, bits):
    return v - (1 << bits) if v & (1 << (bits - 1)) else v

def decode(w, pc):
    for name, match, lose, args in OPS:
        if (w & match) == match and (w & lose) == 0:
            # binutils order guarantees no false positive only if we check
            # match as EXACT on the fixed bits: (w & ~varmask) == match.
            # Reconstruct: fixed bits = match | lose; variable = operands.
            f = fields(w)
            out = []
            for a in args:
                if a in '#,()': continue
                if a == '1': out.append('r%d' % f['src1'])
                elif a == '2': out.append('r%d' % f['src2'])
                elif a == 'd': out.append('r%d' % f['dest'])
                elif a in 'iIJKLM': out.append('0x%x' % f['imm16'])
                elif a == '5': out.append('%d' % f['imm5'])
                elif a == 'l':
                    tgt = pc + 4 + (sext(f['lbroff'], 26) << 2)
                    out.append('0x%08x' % tgt)
                elif a == 'r':
                    tgt = pc + 4 + (sext(f['sbroff'], 16) << 2)
                    out.append('0x%08x' % tgt)
                elif a in 'sSTU': out.append('0x%x' % f['split16'])
                elif a == 'e': out.append('f%d' % f['src1'])
                elif a == 'f': out.append('f%d' % f['src2'])
                elif a == 'g': out.append('f%d' % f['dest'])
                elif a == 'c': out.append('creg%d' % f['creg'])
                else: out.append('?%s' % a)
            return name, out
    return None, None

def candidates(w):
    """All table rows consistent with word w (the table has overlaps; the
    first (most specific) is printed, rest shown when ambiguous)."""
    res = []
    for name, match, lose, args in OPS:
        if (w & match) == match and (w & lose) == 0:
            res.append(name)
    return res

def main():
    data = open(MNG, 'rb').read()
    code = data[HDR:HDR + CODESZ]
    va = int(sys.argv[1], 16)
    ln = int(sys.argv[2], 16) if len(sys.argv) > 2 else 0x100
    off = va - CODEBASE
    for pc in range(va, va + ln, 4):
        w = struct.unpack_from('<I', code, pc - CODEBASE)[0]
        name, ops = decode(w, pc)
        if name is None:
            print('0x%08x: %08x  ???' % (pc, w))
        else:
            print('0x%08x: %08x  %-10s %s' % (pc, w, name, ','.join(ops)))

if __name__ == '__main__':
    main()
