From: Dmitry Selyutin Date: Wed, 6 Sep 2023 19:22:30 +0000 (+0300) Subject: libsvp64: introduce code generator X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=ba7703509ec242d5ef3a54da7ceca42171ca0485;p=openpower-isa.git libsvp64: introduce code generator --- diff --git a/src/libsvp64/codegen.py b/src/libsvp64/codegen.py new file mode 100644 index 00000000..ccd4ef1c --- /dev/null +++ b/src/libsvp64/codegen.py @@ -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 ") + self.emit("#include ") + self.emit("#include ") + 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 ") + self.emit("#include ") + 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()