libsvp64: introduce code generator
authorDmitry Selyutin <ghostmansd@gmail.com>
Wed, 6 Sep 2023 19:22:30 +0000 (22:22 +0300)
committerDmitry Selyutin <ghostmansd@gmail.com>
Sun, 10 Sep 2023 20:29:53 +0000 (23:29 +0300)
src/libsvp64/codegen.py [new file with mode: 0644]

diff --git a/src/libsvp64/codegen.py b/src/libsvp64/codegen.py
new file mode 100644 (file)
index 0000000..ccd4ef1
--- /dev/null
@@ -0,0 +1,482 @@
+import argparse
+import collections
+import contextlib
+import dataclasses
+import enum
+import itertools
+import pathlib
+import sys
+
+import mdis.dispatcher
+import mdis.visitor
+import mdis.walker
+
+from openpower.decoder.power_enums import (
+    find_wiki_dir,
+)
+
+import openpower.insndb.core as insndb
+
+
+def traverse(root, visitor, walker, **kwargs):
+    with visitor(root, **kwargs):
+        for (node, *_, path, pathcls) in walker(root):
+            traverse(node, visitor, walker, path=path, pathcls=pathcls)
+
+
+def fetch(span):
+    bits = len(span)
+    one = "UINT64_C(1)"
+    for (dst, origin) in enumerate(span):
+        src = (32 - (origin + 1))
+        dst = (bits - (dst + 1))
+        dst = f"UINT64_C({dst})"
+        src = f"UINT64_C({src})"
+        yield f"/* {origin:<2} */ (((insn >> {src}) & {one}) << {dst}) |"
+    yield f"UINT64_C(0)"
+
+
+class Mode(enum.Enum):
+    PPC_DIS_GEN_C = "svp64-dis-gen.c"
+    PPC_OPC_GEN_C = "svp64-opc-gen.c"
+
+    def __call__(self, db, **arguments):
+        def pairwise(iterable):
+            (a, b) = itertools.tee(iterable)
+            next(b, None)
+            return zip(a, b)
+
+        cache = Cache()
+        codegen = {
+            Mode.PPC_DIS_GEN_C: DisGenSource,
+            Mode.PPC_OPC_GEN_C: OpcGenSource,
+        }[self](cache=cache, **arguments)
+        for (root, visitor) in pairwise((db, cache, codegen),):
+            walker_cls = getattr(visitor, "Walker", Walker)
+            traverse(root=root, visitor=visitor, walker=walker_cls())
+
+
+class StructMeta(type):
+    def __new__(metacls, name, bases, ns):
+        cls = super().__new__(metacls, name, bases, ns)
+        return dataclasses.dataclass(cls, eq=True, frozen=True)
+
+
+class Struct(metaclass=StructMeta):
+    pass
+
+
+class DynamicOperandIds(tuple): pass
+class StaticOperands(tuple): pass
+class DynamicOperands(tuple): pass
+class POTable(tuple): pass
+class Records(tuple): pass
+
+
+class DynamicOperandId(Struct):
+    name: str = "NIL"
+    index: int = 0
+
+
+class DynamicOperand(Struct):
+    cls: type
+    span: tuple
+    names: tuple
+
+
+class Record(Struct):
+    name: str
+    opcode: insndb.Record.Opcode
+    dynamic_operand_ids: DynamicOperandIds
+    static_operands: StaticOperands
+
+
+class Cache(mdis.visitor.ContextVisitor):
+    def __init__(self):
+        self.__PO = ([0] * (1 << 6))
+        self.__records = collections.defaultdict(list)
+        self.__static_operand = collections.defaultdict(list)
+        self.__dynamic_operand = collections.defaultdict(set)
+        self.__dynamic_operand_id = collections.defaultdict(list)
+
+        return super().__init__()
+
+    def __iter__(self):
+        table = tuple(self.__dynamic_operand.keys())
+        nil = DynamicOperandId()
+
+        def dynamic_operand_id(item):
+            (name, cls, span) = item
+            index = (table.index((cls, span),) + 1)
+            return DynamicOperandId(name=name, index=index)
+
+        def dynamic_operand(item):
+            ((cls, span), names) = item
+            return DynamicOperand(cls=cls, span=span, names=tuple(sorted(names)))
+
+        def record(item):
+            (opcode, name) = item
+            dynamic_operand_ids = map(dynamic_operand_id, self.__dynamic_operand_id[name])
+            dynamic_operand_ids = DynamicOperandIds(tuple(dynamic_operand_ids) + (nil,))
+            static_operands = StaticOperands(self.__static_operand[name])
+
+            return Record(opcode=opcode, name=name,
+                dynamic_operand_ids=dynamic_operand_ids,
+                static_operands=static_operands)
+
+        yield DynamicOperands(map(dynamic_operand, self.__dynamic_operand.items()))
+        yield Records(map(record, sorted(self.__records.items())))
+        yield POTable(self.__PO)
+
+    @mdis.dispatcher.Hook(insndb.Record)
+    @contextlib.contextmanager
+    def dispatch_record(self, node):
+        self.__record = node
+        yield node
+
+    @mdis.dispatcher.Hook(insndb.Record.Opcode)
+    @contextlib.contextmanager
+    def dispatch_record_opcode(self, node):
+        self.__records[node] = self.__record.name
+        self.__PO[self.__record.PO] += 1
+        yield node
+
+    @mdis.dispatcher.Hook(insndb.StaticOperand)
+    @contextlib.contextmanager
+    def dispatch_static_operand(self, node):
+        self.__static_operand[self.__record.name].append(node)
+        yield node
+
+    @mdis.dispatcher.Hook(insndb.DynamicOperand)
+    @contextlib.contextmanager
+    def dispatch_dynamic_operand(self, node):
+        (cls, span) = (node.__class__, node.span)
+        self.__dynamic_operand[cls, span].add(node.name)
+        self.__dynamic_operand_id[self.__record.name].append((node.name, cls, span),)
+        yield node
+
+
+class Walker(insndb.Walker):
+    @mdis.dispatcher.Hook(Cache)
+    def dispatch_cache(self, node):
+        yield from ()
+
+
+class Codegen(mdis.visitor.ContextVisitor):
+    def __init__(self, cache, **arguments):
+        self.__level = 0
+
+        return super().__init__()
+
+    def __enter__(self):
+        self.__level += 1
+        return self
+
+    def __exit__(self, exc_type, exc_value, exc_traceback):
+        self.__level -= 1
+
+    def emit(self, message=""):
+        indent = ((" " * 4 * self.__level) if message else "")
+        print(f"{indent}{message}")
+
+    @mdis.dispatcher.Hook(Cache)
+    @contextlib.contextmanager
+    def dispatch_cache(self, node):
+        self.emit("/*")
+        self.emit(" * Autogenerated by libresoc codegen script")
+        self.emit(" * DO NOT EDIT: all changes will be lost")
+        self.emit(" */")
+        self.emit("")
+        yield node
+
+
+class Header(Codegen):
+    pass
+
+
+class Source(Codegen):
+    @mdis.dispatcher.Hook(str)
+    @contextlib.contextmanager
+    def dispatch_str(self, node, *, path, pathcls):
+        self.emit(f"{pathcls(path)} = \"{node}\",")
+        with self: yield node
+
+    @mdis.dispatcher.Hook(object)
+    @contextlib.contextmanager
+    def dispatch_object(self, node, *, path, pathcls):
+        self.emit(f"{pathcls(path)} = {{")
+        with self: yield node
+        self.emit("},")
+
+
+class Record(Struct):
+    static_operands: StaticOperands
+    name: str
+    opcode: insndb.Record.Opcode
+    dynamic_operand_ids: DynamicOperandIds
+
+
+class DisGenSource(Source):
+    class Walker(Walker):
+        @mdis.dispatcher.Hook(DynamicOperand, Records)
+        def dispatch_ignore(self, node):
+            yield from ()
+
+        @mdis.dispatcher.Hook(Cache)
+        def dispatch_cache(self, node):
+            (operands, _, _) = node
+            yield from self([operands])
+
+    @mdis.dispatcher.Hook(DynamicOperands)
+    @contextlib.contextmanager
+    def dispatch_operands(self, node):
+        self.emit("static inline enum svp64_state")
+        self.emit("svp64_disassemble_operand(struct svp64_ctx *ctx, uint32_t insn, size_t id) {")
+        with self:
+            self.emit("int64_t value;")
+            self.emit("")
+            self.emit(f"switch (ctx->record->operands[id]) {{")
+            yield node
+            self.emit("default:")
+            with self:
+                self.emit("return (enum svp64_state)((size_t)SVP64_ERROR_OPERAND_0 + id);")
+            self.emit("}")
+        self.emit("")
+        with self:
+            self.emit("ctx->operands[id] = value;")
+            self.emit("")
+            self.emit("return SVP64_SUCCESS;")
+        self.emit("}")
+        self.emit("")
+
+        self.emit("static inline enum svp64_state")
+        self.emit("svp64_disassemble_operands(struct svp64_ctx *ctx, uint32_t insn) {")
+        with self:
+            condition = " && ".join((
+                "(id < (sizeof(ctx->record->operands) / sizeof(*ctx->record->operands)))",
+                "ctx->record->operands[id]",
+            ))
+            self.emit(f"for (size_t id = 0; ({condition}); ++id) {{")
+            with self:
+                self.emit("enum svp64_state state;")
+                self.emit("")
+                self.emit("state = svp64_disassemble_operand(ctx, insn, id);")
+                self.emit("if (state != SVP64_SUCCESS)")
+                with self:
+                    self.emit("return state;")
+            self.emit("}")
+            self.emit("")
+            self.emit("return SVP64_SUCCESS;")
+        self.emit("}")
+
+    @mdis.dispatcher.Hook(DynamicOperand)
+    @contextlib.contextmanager
+    def dispatch_operand(self, node, *, path, pathcls):
+        def generic_handler(span):
+            yield f"value = (int64_t)("
+            with self:
+                yield from fetch(span)
+            yield f");"
+            self.emit("break;")
+
+        def signed_handler(span):
+            mask = f"(UINT64_C(1) << (UINT64_C({len(span)}) - 1))"
+            yield "value = (int64_t)("
+            with self:
+                yield "("
+                with self:
+                    yield "("
+                    with self:
+                        yield from fetch(span)
+                    yield ")"
+                    yield "^"
+                    yield f"{mask}"
+                yield ")"
+                yield "-"
+                yield f"{mask}"
+            yield ");"
+            self.emit("break;")
+
+        handlers = {
+            insndb.SignedOperand: signed_handler,
+        }
+        self.emit(f"case 0x{(path + 1):02x}: /* {', '.join(node.names)} */")
+        with self:
+            handler = handlers.get(node.cls, generic_handler)
+            for line in handler(span=node.span):
+                self.emit(line)
+            self.emit("")
+            yield node
+
+    @mdis.dispatcher.Hook(Cache)
+    @contextlib.contextmanager
+    def dispatch_cache(self, node):
+        self.emit("/*")
+        self.emit(" * Autogenerated by libresoc codegen script")
+        self.emit(" * DO NOT EDIT: all changes will be lost")
+        self.emit(" */")
+        self.emit("")
+        self.emit("#include <stdbool.h>")
+        self.emit("#include <stddef.h>")
+        self.emit("#include <stdint.h>")
+        self.emit("")
+        self.emit("#include \"svp64.h\"")
+        self.emit("")
+        yield node
+        self.emit("enum svp64_state")
+        self.emit("svp64_disassemble(struct svp64_ctx *ctx, uint32_t insn) {")
+        with self:
+            self.emit("ctx->record = svp64_lookup_insn(insn);")
+            self.emit("")
+            self.emit("if (ctx->record == NULL)")
+            with self:
+                self.emit("return SVP64_ERROR_LOOKUP;")
+            self.emit("")
+            self.emit("return svp64_disassemble_operands(ctx, insn);")
+        self.emit("}")
+
+
+class OpcGenSource(Source):
+    class Walker(Walker):
+        @mdis.dispatcher.Hook(DynamicOperandId, DynamicOperands, insndb.StaticOperand, POTable)
+        def dispatch_ignore(self, node):
+            yield from ()
+
+        @mdis.dispatcher.Hook(Record)
+        def dispatch_record(self, node):
+            keys = {
+                "dynamic_operand_ids": "operands",
+            }
+
+            for field in dataclasses.fields(node):
+                key = field.name
+                value = getattr(node, key)
+                key = keys.get(key, key)
+                yield (value, node, key, mdis.walker.AttributePath)
+
+        @mdis.dispatcher.Hook(Cache)
+        def dispatch_cache(self, node):
+            (_, records, potable) = node
+            yield from self([records, potable])
+
+    @mdis.dispatcher.Hook(DynamicOperandId)
+    @contextlib.contextmanager
+    def dispatch_dynamic_operand_id(self, node, *, path, pathcls):
+        index = f"UINT8_C(0x{node.index:02x})"
+        self.emit(f"{pathcls(path)} = {index}, /* {node.name} */")
+        with self: yield node
+
+    @mdis.dispatcher.Hook(StaticOperands)
+    @contextlib.contextmanager
+    def dispatch_static_operands(self, node):
+        if node:
+            self.emit("/*")
+            yield node
+            self.emit(" */")
+        else:
+            yield node
+
+    @mdis.dispatcher.Hook(insndb.StaticOperand)
+    @contextlib.contextmanager
+    def dispatch_static_operand(self, node):
+        self.emit(f" * {node.name}={node.value} [{', '.join(map(str, node.span))}]")
+        yield node
+
+    @mdis.dispatcher.Hook(insndb.Record.Opcode.Value, insndb.Record.Opcode.Mask)
+    @contextlib.contextmanager
+    def dispatch_opcode_parts(self, node, *, path, pathcls):
+        self.emit(f"{pathcls(path)} = UINT64_C(0x{node:016x}),")
+        with self: yield node
+
+    @mdis.dispatcher.Hook(insndb.Record.Opcode)
+    @contextlib.contextmanager
+    def dispatch_opcode(self, node):
+        self.emit(".opcode = {")
+        with self: yield node
+        self.emit("},")
+
+    @mdis.dispatcher.Hook(POTable)
+    @contextlib.contextmanager
+    def dispatch_potable(self, node):
+        heads = ([0] * (1 << 6))
+        tails = ([0] * (1 << 6))
+        for (index, counter) in enumerate(itertools.accumulate(node)):
+            heads[index] = (counter - node[index])
+            tails[index] = counter
+        heads = [(tail - node[index]) for (index, tail) in enumerate(tails)]
+        self.emit("static uint16_t const svp64_opcode_hash[64][2] = {")
+        with self:
+            for index in range(64):
+                head = heads[index]
+                tail = tails[index]
+                self.emit(f"[0x{index:02x}] = {{{head}, {tail}}},")
+        self.emit("};")
+        self.emit("")
+        yield node
+
+    @mdis.dispatcher.Hook(Records)
+    @contextlib.contextmanager
+    def dispatch_records(self, node):
+        self.emit("static struct svp64_record const svp64_records[] = {")
+        with self: yield node
+        self.emit("};")
+        self.emit("")
+
+    @mdis.dispatcher.Hook(Cache)
+    @contextlib.contextmanager
+    def dispatch_cache(self, node):
+        self.emit("/*")
+        self.emit(" * Autogenerated by libresoc codegen script")
+        self.emit(" * DO NOT EDIT: all changes will be lost")
+        self.emit(" */")
+        self.emit("")
+        self.emit("#include <stddef.h>")
+        self.emit("#include <stdint.h>")
+        self.emit("")
+        self.emit("#include \"svp64.h\"")
+        self.emit("")
+        yield node
+        self.emit("struct svp64_record const *")
+        self.emit("svp64_lookup_insn(uint32_t insn) {")
+        with self:
+            self.emit("uint32_t PO = (")
+            with self:
+                for line in fetch(range(6)):
+                    self.emit(line)
+            self.emit(");")
+            self.emit("struct svp64_record const *iter = &svp64_records[svp64_opcode_hash[PO][0]];")
+            self.emit("struct svp64_record const *tail = &svp64_records[svp64_opcode_hash[PO][1]];")
+            self.emit("")
+            self.emit("for (; iter != tail; ++iter) {")
+            with self:
+                self.emit("struct svp64_opcode const *opcode = &iter->opcode;")
+                self.emit("")
+                self.emit("if ((opcode->value & opcode->mask) == (insn & opcode->mask))")
+                with self:
+                    self.emit("return iter;")
+            self.emit("}")
+            self.emit("")
+            self.emit("return NULL;")
+        self.emit("}")
+
+
+def main():
+    table = {mode:{} for mode in Mode}
+    main_parser = argparse.ArgumentParser("codegen",
+        description="C code generator")
+    main_parser.add_argument("-d", "--database",
+        type=pathlib.Path,
+        default=pathlib.Path(find_wiki_dir()))
+    main_subprarsers = main_parser.add_subparsers(dest="mode", required=True)
+    for (mode, _) in table.items():
+        parser = main_subprarsers.add_parser(mode.value)
+
+    arguments = dict(vars(main_parser.parse_args()))
+    mode = Mode(arguments.pop("mode"))
+    db = insndb.Database(root=arguments.pop("database"))
+
+    return mode(db=db, **arguments)
+
+
+if __name__ == "__main__":
+    main()