power_insn.py: introduce instruction database
authorDmitry Selyutin <ghostmansd@gmail.com>
Thu, 4 Aug 2022 21:02:11 +0000 (00:02 +0300)
committerDmitry Selyutin <ghostmansd@gmail.com>
Sun, 14 Aug 2022 19:07:43 +0000 (22:07 +0300)
src/openpower/decoder/power_insn.py [new file with mode: 0644]

diff --git a/src/openpower/decoder/power_insn.py b/src/openpower/decoder/power_insn.py
new file mode 100644 (file)
index 0000000..cdd7ef9
--- /dev/null
@@ -0,0 +1,628 @@
+import collections as _collections
+import copy as _copy
+import csv as _csv
+import dataclasses as _dataclasses
+import enum as _enum
+import functools as _functools
+import os as _os
+import operator as _operator
+import pathlib as _pathlib
+import re as _re
+
+try:
+    from functools import cached_property
+except ImportError:
+    from cached_property import cached_property
+
+from openpower.decoder.power_enums import (
+    Function as _Function,
+    MicrOp as _MicrOp,
+    In1Sel as _In1Sel,
+    In2Sel as _In2Sel,
+    In3Sel as _In3Sel,
+    OutSel as _OutSel,
+    CRInSel as _CRInSel,
+    CROutSel as _CROutSel,
+    LDSTLen as _LDSTLen,
+    LDSTMode as _LDSTMode,
+    RC as _RC,
+    CryIn as _CryIn,
+    Form as _Form,
+    SVEtype as _SVEtype,
+    SVMode as _SVMode,
+    SVPtype as _SVPtype,
+    SVExtra as _SVExtra,
+    SVExtraRegType as _SVExtraRegType,
+    SVExtraReg as _SVExtraReg,
+)
+
+
+def dataclass(cls, record, keymap=None, typemap=None):
+    if keymap is None:
+        keymap = {}
+    if typemap is None:
+        typemap = {field.name:field.type for field in _dataclasses.fields(cls)}
+
+    def transform(key_value):
+        (key, value) = key_value
+        key = keymap.get(key, key)
+        hook = typemap.get(key, lambda value: value)
+        if hook is bool and value in ("", "0"):
+            value = False
+        else:
+            value = hook(value)
+        return (key, value)
+
+    record = dict(map(transform, record.items()))
+    for key in frozenset(record.keys()):
+        if record[key] == "":
+            record.pop(key)
+
+    return cls(**record)
+
+
+@_functools.total_ordering
+@_dataclasses.dataclass(eq=True, frozen=True)
+class Opcode:
+    class Value(int):
+        def __repr__(self):
+            if self.bit_length() <= 32:
+                return f"0x{self:08x}"
+            else:
+                return f"0x{self:016x}"
+
+    class Mask(int):
+        def __repr__(self):
+            if self.bit_length() <= 32:
+                return f"0x{self:08x}"
+            else:
+                return f"0x{self:016x}"
+
+    value: Value
+    mask: Mask = None
+
+    def __lt__(self, other):
+        if not isinstance(other, Opcode):
+            return NotImplemented
+        return ((self.value, self.mask) < (other.value, other.mask))
+
+    def __post_init__(self):
+        (value, mask) = (self.value, self.mask)
+
+        if isinstance(value, Opcode):
+            if mask is not None:
+                raise ValueError(mask)
+            (value, mask) = (value.value, value.mask)
+        elif isinstance(value, str):
+            if mask is not None:
+                raise ValueError(mask)
+            value = int(value, 0)
+
+        if not isinstance(value, int):
+            raise ValueError(value)
+        if mask is None:
+            mask = value
+        if not isinstance(mask, int):
+            raise ValueError(mask)
+
+        object.__setattr__(self, "value", self.__class__.Value(value))
+        object.__setattr__(self, "mask", self.__class__.Mask(mask))
+
+
+class IntegerOpcode(Opcode):
+    def __init__(self, value):
+        if isinstance(value, str):
+            value = int(value, 0)
+        return super().__init__(value=value, mask=None)
+
+
+class PatternOpcode(Opcode):
+    def __init__(self, value):
+        (pattern, value, mask) = (value, 0, 0)
+
+        for symbol in pattern:
+            if symbol not in {"0", "1", "-"}:
+                raise ValueError(pattern)
+            value |= (symbol == "1")
+            mask |= (symbol != "-")
+            value <<= 1
+            mask <<= 1
+        value >>= 1
+        mask >>= 1
+
+        return super().__init__(value=value, mask=mask)
+
+
+class FieldsOpcode(Opcode):
+    def __init__(self, fields):
+        def field(opcode, field):
+            (value, mask) = opcode
+            (field, bits) = field
+            shifts = map(lambda bit: (31 - bit), reversed(tuple(bits)))
+            for (index, shift) in enumerate(shifts):
+                bit = ((field & (1 << index)) != 0)
+                value |= (bit << shift)
+                mask |= (1 << shift)
+            return (value, mask)
+
+        (value, mask) = _functools.reduce(field, fields, (0, 0))
+
+        return super().__init__(value=value, mask=mask)
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class PPCRecord:
+    class FlagsMeta(type):
+        def __iter__(cls):
+            yield from (
+                "inv A",
+                "inv out",
+                "cry out",
+                "BR",
+                "sgn ext",
+                "rsrv",
+                "32b",
+                "sgn",
+                "lk",
+                "sgl pipe",
+            )
+
+    class Flags(frozenset, metaclass=FlagsMeta):
+        def __new__(cls, flags=frozenset()):
+            flags = frozenset(flags)
+            diff = (flags - frozenset(cls))
+            if diff:
+                raise ValueError(flags)
+            return super().__new__(cls, flags)
+
+    opcode: Opcode
+    comment: str
+    flags: Flags = Flags()
+    comment2: str = ""
+    unit: _Function = _Function.NONE
+    intop: _MicrOp = _MicrOp.OP_ILLEGAL
+    in1: _In1Sel = _In1Sel.RA
+    in2: _In2Sel = _In2Sel.NONE
+    in3: _In3Sel = _In3Sel.NONE
+    out: _OutSel = _OutSel.NONE
+    cr_in: _CRInSel = _CRInSel.NONE
+    cr_out: _CROutSel = _CROutSel.NONE
+    cry_in: _CryIn = _CryIn.ZERO
+    ldst_len: _LDSTLen = _LDSTLen.NONE
+    upd: _LDSTMode = _LDSTMode.NONE
+    rc: _RC = _RC.NONE
+    form: _Form = _Form.NONE
+    conditions: str = ""
+    unofficial: bool = False
+
+    __KEYMAP = {
+        "internal op": "intop",
+        "CR in": "cr_in",
+        "CR out": "cr_out",
+        "cry in": "cry_in",
+        "ldst len": "ldst_len",
+        "CONDITIONS": "conditions",
+    }
+
+    @classmethod
+    def CSV(cls, record, opcode_cls=Opcode):
+        typemap = {field.name:field.type for field in _dataclasses.fields(cls)}
+        typemap["opcode"] = opcode_cls
+
+        flags = set()
+        for flag in frozenset(PPCRecord.Flags):
+            if bool(record.pop(flag, "")):
+                flags.add(flag)
+        record["flags"] = PPCRecord.Flags(flags)
+
+        return dataclass(cls, record, keymap=PPCRecord.__KEYMAP, typemap=typemap)
+
+    @property
+    def identifier(self):
+        return self.comment
+
+    @cached_property
+    def names(self):
+        return frozenset(self.comment.split("=")[-1].split("/"))
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class SVP64Record:
+    class ExtraMap(tuple):
+        class Extra(tuple):
+            @_dataclasses.dataclass(eq=True, frozen=True)
+            class Entry:
+                regtype: _SVExtraRegType = _SVExtraRegType.NONE
+                reg: _SVExtraReg = _SVExtraReg.NONE
+
+                def __repr__(self):
+                    return f"{self.regtype.value}:{self.reg.name}"
+
+            def __new__(cls, value="0"):
+                if isinstance(value, str):
+                    def transform(value):
+                        (regtype, reg) = value.split(":")
+                        regtype = _SVExtraRegType(regtype)
+                        reg = _SVExtraReg(reg)
+                        return cls.Entry(regtype=regtype, reg=reg)
+
+                    if value == "0":
+                        value = tuple()
+                    else:
+                        value = map(transform, value.split(";"))
+
+                return super().__new__(cls, value)
+
+            def __repr__(self):
+                return repr(list(self))
+
+        def __new__(cls, value=tuple()):
+            value = tuple(value)
+            if len(value) == 0:
+                value = (("0",) * 4)
+            return super().__new__(cls, map(cls.Extra, value))
+
+        def __repr__(self):
+            return repr({index:self[index] for index in range(0, 4)})
+
+    identifier: str
+    ptype: _SVPtype = _SVPtype.NONE
+    etype: _SVEtype = _SVEtype.NONE
+    in1: _In1Sel = _In1Sel.NONE
+    in2: _In2Sel = _In2Sel.NONE
+    in3: _In3Sel = _In3Sel.NONE
+    out: _OutSel = _OutSel.NONE
+    out2: _OutSel = _OutSel.NONE
+    cr_in: _CRInSel = _CRInSel.NONE
+    cr_out: _CROutSel = _CROutSel.NONE
+    extra: ExtraMap = ExtraMap()
+    pu: bool = False
+    conditions: str = ""
+    mode: _SVMode = _SVMode.NORMAL
+
+    __KEYMAP = {
+        "insn": "identifier",
+        "CONDITIONS": "conditions",
+        "Ptype": "ptype",
+        "Etype": "etype",
+        "CR in": "cr_in",
+        "CR out": "cr_out",
+        "PU": "pu",
+    }
+
+    @classmethod
+    def CSV(cls, record):
+        for key in ("in1", "in2", "in3", "out", "out2", "CR in", "CR out"):
+            value = record[key]
+            if value == "0":
+                record[key] = "NONE"
+
+        record["extra"] = cls.ExtraMap(record.pop(f"{index}") for index in range(0, 4))
+
+        return dataclass(cls, record, keymap=cls.__KEYMAP)
+
+
+class BitSel:
+    def __init__(self, value=(0, 32)):
+        if isinstance(value, str):
+            (start, end) = map(int, value.split(":"))
+        else:
+            (start, end) = value
+        if start < 0 or end < 0 or start >= end:
+            raise ValueError(value)
+        
+        self.__start = start
+        self.__end = end
+
+        return super().__init__()
+
+    def __repr__(self):
+        return f"[{self.__start}:{self.__end}]"
+
+    def __iter__(self):
+        yield from range(self.start, (self.end + 1))
+
+    @property
+    def start(self):
+        return self.__start
+
+    @property
+    def end(self):
+        return self.__end
+
+
+@_dataclasses.dataclass(eq=True, frozen=True)
+class Section:
+    class Mode(_enum.Enum):
+        INTEGER = _enum.auto()
+        PATTERN = _enum.auto()
+
+        @classmethod
+        def _missing_(cls, value):
+            if isinstance(value, str):
+                return cls[value.upper()]
+            return super()._missing_(value)
+
+    class Suffix(int):
+        def __new__(cls, value=None):
+            if isinstance(value, str):
+                if value.upper() == "NONE":
+                    value = None
+                else:
+                    value = int(value, 0)
+            if value is None:
+                value = 0
+
+            return super().__new__(cls, value)
+
+        def __str__(self):
+            return repr(self)
+
+        def __repr__(self):
+            return (bin(self) if self else "None")
+
+    path: _pathlib.Path
+    opcode: Opcode
+    bitsel: BitSel
+    suffix: Suffix
+    mode: Mode
+
+    @classmethod
+    def CSV(cls, record):
+        return dataclass(cls, record)
+
+
+class Fields:
+    def __init__(self, items):
+        if isinstance(items, dict):
+            items = items.items()
+
+        def transform(item):
+            (name, bitrange) = item
+            return (name, tuple(bitrange.values()))
+
+        self.__mapping = dict(map(transform, items))
+
+        return super().__init__()
+
+    def __repr__(self):
+        return repr(self.__mapping)
+
+    def __contains__(self, key):
+        return self.__mapping.__contains__(key)
+
+    def __getitem__(self, key):
+        return self.__mapping.__getitem__(key)
+
+    def get(self, key, default):
+        return self.__mapping.get(key, default)
+
+
+@_functools.total_ordering
+@_dataclasses.dataclass(eq=True, frozen=True)
+class Instruction:
+    name: str
+    rc: bool
+    section: Section
+    ppc: PPCRecord
+    fields: Fields
+    svp64: SVP64Record = None
+
+    __EXTRA = (
+        _SVExtra.Idx0,
+        _SVExtra.Idx1,
+        _SVExtra.Idx2,
+        _SVExtra.Idx3,
+    )
+
+    def __lt__(self, other):
+        if not isinstance(other, Instruction):
+            return NotImplemented
+        return (self.opcode < other.opcode)
+
+    def __repr__(self):
+        return f"{self.__class__.__name__}(name={self.name!r}, opcode={self.opcode})"
+
+    @cached_property
+    def opcode(self):
+        fields = []
+        if self.section.opcode:
+            fields += [(self.section.opcode.value, BitSel((0, 5)))]
+            fields += [(self.ppc.opcode.value, self.section.bitsel)]
+        else:
+            fields += [(self.ppc.opcode.value, self.section.bitsel)]
+
+        # Some instructions are special regarding Rc handling.
+        # They are marked with Rc.ONE, but don't have Rc field.
+        # At least addic., andi., andis. belong to this list.
+        if self.rc and "Rc" in self.fields:
+            fields += [(1, self.fields["Rc"])]
+
+        return FieldsOpcode(fields)
+
+    @property
+    def in1(self):
+        return self.ppc.in1
+
+    @property
+    def in2(self):
+        return self.ppc.in2
+
+    @property
+    def in3(self):
+        return self.ppc.in3
+
+    @property
+    def out(self):
+        return self.ppc.out
+
+    @property
+    def out2(self):
+        if self.svp64 is None:
+            return _OutSel.NONE
+        return self.ppc.out
+
+    @property
+    def cr_in(self):
+        return self.ppc.cr_in
+
+    @property
+    def cr_out(self):
+        return self.ppc.cr_out
+
+    def sv_extra(self, key):
+        if key not in frozenset({
+                    "in1", "in2", "in3", "cr_in",
+                    "out", "out2", "cr_out",
+                }):
+            raise KeyError(key)
+
+        sel = getattr(self.svp64, key)
+        if sel is _CRInSel.BA_BB:
+            return _SVExtra.Idx_1_2
+
+        extra_map = {
+            _SVExtraRegType.SRC: {},
+            _SVExtraRegType.DST: {},
+        }
+        for index in range(0, 4):
+            for entry in self.svp64.extra[index]:
+                extra_map[entry.regtype][entry.reg] = Instruction.__EXTRA[index]
+
+        for regtype in (_SVExtraRegType.SRC, _SVExtraRegType.DST):
+            extra = extra_map[regtype][sel]
+            if extra is not _SVExtra.NONE:
+                return extra
+
+        return _SVExtra.NONE
+
+    sv_in1 = property(_functools.partial(sv_extra, key="in1"))
+    sv_in2 = property(_functools.partial(sv_extra, key="in2"))
+    sv_in3 = property(_functools.partial(sv_extra, key="in3"))
+    sv_out = property(_functools.partial(sv_extra, key="out"))
+    sv_out2 = property(_functools.partial(sv_extra, key="out2"))
+    sv_cr_in = property(_functools.partial(sv_extra, key="cr_in"))
+    sv_cr_out = property(_functools.partial(sv_extra, key="cr_out"))
+
+    @property
+    def sv_ptype(self):
+        if self.svp64 is None:
+            return _SVPtype.NONE
+        return self.svp64.ptype
+
+    @property
+    def sv_etype(self):
+        if self.svp64 is None:
+            return _SVEtype.NONE
+        return self.svp64.etype
+
+
+class Database:
+    def __init__(self, root):
+        root = _pathlib.Path(root)
+
+        def parse(stream, factory):
+            lines = filter(lambda line: not line.strip().startswith("#"), stream)
+            entries = _csv.DictReader(lines)
+            entries = filter(lambda entry: "TODO" not in frozenset(entry.values()), entries)
+            return tuple(map(factory, entries))
+
+        def database_ppc(root):
+            db = _collections.defaultdict(set)
+            path = (root / "insndb.csv")
+            with open(path, "r", encoding="UTF-8") as stream:
+                for section in parse(stream, Section.CSV):
+                    path = (root / section.path)
+                    opcode_cls = {
+                        section.Mode.INTEGER: IntegerOpcode,
+                        section.Mode.PATTERN: PatternOpcode,
+                    }[section.mode]
+                    factory = _functools.partial(PPCRecord.CSV, opcode_cls=opcode_cls)
+                    with open(path, "r", encoding="UTF-8") as stream:
+                        db[section].update(parse(stream, factory))
+            for (section, records) in db.items():
+                db[section] = {record.identifier:record for record in records}
+            return db
+
+        def database_svp64(root):
+            db = set()
+            pattern = _re.compile(r"^(?:LDST)?RM-(1P|2P)-.*?\.csv$")
+            for (prefix, _, names) in _os.walk(root):
+                prefix = _pathlib.Path(prefix)
+                for name in filter(lambda name: pattern.match(name), names):
+                    path = (prefix / _pathlib.Path(name))
+                    with open(path, "r", encoding="UTF-8") as stream:
+                        db.update(parse(stream, SVP64Record.CSV))
+            db = {record.identifier:record for record in db}
+            return db
+
+        def database_forms(root):
+            # This is hack. The whole code there should be moved here.
+            # The fields.text parser should take care of the validation.
+            from openpower.decoder.power_fields import DecodeFields as _DecodeFields
+            db = {}
+            df = _DecodeFields()
+            df.create_specs()
+            for (form, fields) in df.instrs.items():
+                if form in {"DQE", "TX"}:
+                    continue
+                if form == "all":
+                    form = "NONE"
+                db[_Form[form]] = Fields(fields)
+            return db
+
+        def database(ppcdb, svp64db, formsdb):
+            items = set()
+            for section in ppcdb:
+                for (identifier, ppc) in ppcdb[section].items():
+                    fields = formsdb[ppc.form]
+                    svp64 = svp64db.get(identifier)
+                    if ppc.rc is _RC.ONE:
+                        variants = {name:True for name in ppc.names}
+                    elif ppc.rc is _RC.RC:
+                        variants = {name:False for name in ppc.names}
+                        variants.update({f"{name}.":True for name in ppc.names})
+                    else:
+                        variants = {name:False for name in ppc.names}
+                    for (name, rc) in variants.items():
+                        items.add(Instruction(name=name, rc=rc,
+                            section=section, ppc=ppc, fields=fields, svp64=svp64))
+
+            items = tuple(sorted(items, key=_operator.attrgetter("opcode")))
+            opcodes = {item.opcode:item for item in items}
+            names = {item.name:item for item in sorted(items, key=_operator.attrgetter("name"))}
+
+            return (items, opcodes, names)
+
+        ppcdb = database_ppc(root)
+        svp64db = database_svp64(root)
+        formsdb = database_forms(root)
+
+        (items, opcodes, names) = database(ppcdb, svp64db, formsdb)
+        self.__items = items
+        self.__opcodes = opcodes
+        self.__names = names
+
+        return super().__init__()
+
+    def __repr__(self):
+        return repr(self.__items)
+
+    def __iter__(self):
+        yield from self.__items
+
+    def __contains__(self, key):
+        if isinstance(key, int):
+            return self.__opcodes.__contains__(key)
+        elif isinstance(key, str):
+            return self.__names.__contains__(key)
+        else:
+            raise KeyError(key)
+
+    def __getitem__(self, key):
+        if isinstance(key, int):
+            return self.__opcodes.__getitem__(key)
+        elif isinstance(key, str):
+            return self.__names.__getitem__(key)
+        else:
+            raise KeyError(key)