From 6fac7c5c87cb1e5055254a8e1fba28c20fab64e9 Mon Sep 17 00:00:00 2001 From: Dmitry Selyutin Date: Fri, 5 Aug 2022 00:02:11 +0300 Subject: [PATCH] power_insn.py: introduce instruction database --- src/openpower/decoder/power_insn.py | 628 ++++++++++++++++++++++++++++ 1 file changed, 628 insertions(+) create mode 100644 src/openpower/decoder/power_insn.py diff --git a/src/openpower/decoder/power_insn.py b/src/openpower/decoder/power_insn.py new file mode 100644 index 00000000..cdd7ef98 --- /dev/null +++ b/src/openpower/decoder/power_insn.py @@ -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) -- 2.30.2