libopid: refactor operands disassembly
[openpower-isa.git] / src / libopid / codegen.py
1 import argparse
2 import collections
3 import contextlib
4 import dataclasses
5 import enum
6 import itertools
7 import pathlib
8 import sys
9
10 import mdis.dispatcher
11 import mdis.visitor
12 import mdis.walker
13
14 from openpower.decoder.power_enums import (
15 find_wiki_dir,
16 )
17
18 import openpower.insndb.core as insndb
19
20
21 def traverse(root, visitor, walker, **kwargs):
22 with visitor(root, **kwargs):
23 for (node, *_, path, pathcls) in walker(root):
24 traverse(node, visitor, walker, path=path, pathcls=pathcls)
25
26
27 def fetch(span):
28 bits = len(span)
29 one = "UINT32_C(1)"
30 for (dst, origin) in enumerate(span):
31 src = (32 - (origin + 1))
32 dst = (bits - (dst + 1))
33 dst = f"UINT32_C({dst})"
34 src = f"UINT32_C({src})"
35 yield f"/* {origin:<2} */ (((insn >> {src}) & {one}) << {dst}) |"
36 yield f"UINT32_C(0)"
37
38
39 class Mode(enum.Enum):
40 PPC_DIS_GEN_C = "opid-dis-gen.c"
41 PPC_OPC_GEN_C = "opid-opc-gen.c"
42
43 def __call__(self, db, **arguments):
44 def pairwise(iterable):
45 (a, b) = itertools.tee(iterable)
46 next(b, None)
47 return zip(a, b)
48
49 cache = Cache()
50 codegen = {
51 Mode.PPC_DIS_GEN_C: DisGenSource,
52 Mode.PPC_OPC_GEN_C: OpcGenSource,
53 }[self](cache=cache, **arguments)
54 for (root, visitor) in pairwise((db, cache, codegen),):
55 walker_cls = getattr(visitor, "Walker", Walker)
56 traverse(root=root, visitor=visitor, walker=walker_cls())
57
58
59 class StructMeta(type):
60 def __new__(metacls, name, bases, ns):
61 cls = super().__new__(metacls, name, bases, ns)
62 return dataclasses.dataclass(cls, eq=True, frozen=True)
63
64
65 class Struct(metaclass=StructMeta):
66 pass
67
68
69 class DynamicOperandIds(tuple): pass
70 class StaticOperands(tuple): pass
71 class DynamicOperands(tuple): pass
72 class POTable(tuple): pass
73 class RecordTable(tuple): pass
74
75
76 class DynamicOperandId(Struct):
77 name: str = "NIL"
78 index: int = 0
79
80
81 class DynamicOperand(Struct):
82 cls: type
83 span: tuple
84 names: tuple
85
86
87 class Record(Struct):
88 name: str
89 opcode: insndb.Record.Opcode
90 dynamic_operand_ids: DynamicOperandIds
91 static_operands: StaticOperands
92
93
94 class Cache(mdis.visitor.ContextVisitor):
95 def __init__(self):
96 self.__PO = ([0] * (1 << 6))
97 self.__records = collections.defaultdict(list)
98 self.__static_operand = collections.defaultdict(list)
99 self.__dynamic_operand = collections.defaultdict(set)
100 self.__dynamic_operand_id = collections.defaultdict(list)
101
102 return super().__init__()
103
104 def __iter__(self):
105 table = tuple(self.__dynamic_operand.keys())
106 nil = DynamicOperandId()
107
108 def dynamic_operand_id(item):
109 (name, cls, span) = item
110 index = (table.index((cls, span),) + 1)
111 return DynamicOperandId(name=name, index=index)
112
113 def dynamic_operand(item):
114 ((cls, span), names) = item
115 return DynamicOperand(cls=cls, span=span, names=tuple(sorted(names)))
116
117 def record(item):
118 (opcode, name) = item
119 dynamic_operand_ids = map(dynamic_operand_id, self.__dynamic_operand_id[name])
120 dynamic_operand_ids = DynamicOperandIds(tuple(dynamic_operand_ids) + (nil,))
121 static_operands = StaticOperands(self.__static_operand[name])
122
123 return Record(opcode=opcode, name=name,
124 dynamic_operand_ids=dynamic_operand_ids,
125 static_operands=static_operands)
126
127 yield DynamicOperands(map(dynamic_operand, self.__dynamic_operand.items()))
128 yield RecordTable(map(record, sorted(self.__records.items())))
129 yield POTable(self.__PO)
130
131 @mdis.dispatcher.Hook(insndb.Record)
132 @contextlib.contextmanager
133 def dispatch_record(self, node):
134 self.__record = node
135 yield node
136
137 @mdis.dispatcher.Hook(insndb.Record.Opcode)
138 @contextlib.contextmanager
139 def dispatch_record_opcode(self, node):
140 self.__records[node] = self.__record.name
141 self.__PO[self.__record.PO] += 1
142 yield node
143
144 @mdis.dispatcher.Hook(insndb.StaticOperand)
145 @contextlib.contextmanager
146 def dispatch_static_operand(self, node):
147 self.__static_operand[self.__record.name].append(node)
148 yield node
149
150 @mdis.dispatcher.Hook(insndb.DynamicOperand)
151 @contextlib.contextmanager
152 def dispatch_dynamic_operand(self, node):
153 (cls, span) = (node.__class__, node.span)
154 self.__dynamic_operand[cls, span].add(node.name)
155 self.__dynamic_operand_id[self.__record.name].append((node.name, cls, span),)
156 yield node
157
158
159 class Walker(insndb.Walker):
160 @mdis.dispatcher.Hook(Cache)
161 def dispatch_cache(self, node):
162 yield from ()
163
164
165 class Codegen(mdis.visitor.ContextVisitor):
166 def __init__(self, cache, **arguments):
167 self.__level = 0
168
169 return super().__init__()
170
171 def __enter__(self):
172 self.__level += 1
173 return self
174
175 def __exit__(self, exc_type, exc_value, exc_traceback):
176 self.__level -= 1
177
178 def emit(self, message=""):
179 indent = ((" " * 4 * self.__level) if message else "")
180 print(f"{indent}{message}")
181
182 @mdis.dispatcher.Hook(Cache)
183 @contextlib.contextmanager
184 def dispatch_cache(self, node):
185 self.emit("/*")
186 self.emit(" * Autogenerated by libresoc codegen script")
187 self.emit(" * DO NOT EDIT: all changes will be lost")
188 self.emit(" */")
189 self.emit("")
190 yield node
191
192
193 class Header(Codegen):
194 pass
195
196
197 class Source(Codegen):
198 @mdis.dispatcher.Hook(str)
199 @contextlib.contextmanager
200 def dispatch_str(self, node, *, path, pathcls):
201 self.emit(f"{pathcls(path)} = \"{node}\",")
202 with self: yield node
203
204 @mdis.dispatcher.Hook(object)
205 @contextlib.contextmanager
206 def dispatch_object(self, node, *, path, pathcls):
207 self.emit(f"{pathcls(path)} = {{")
208 with self: yield node
209 self.emit("},")
210
211 @mdis.dispatcher.Hook(Cache)
212 @contextlib.contextmanager
213 def dispatch_cache(self, node):
214 self.emit("/*")
215 self.emit(" * Autogenerated by libresoc codegen script")
216 self.emit(" * DO NOT EDIT: all changes will be lost")
217 self.emit(" */")
218 self.emit("")
219 self.emit("#include <stddef.h>")
220 self.emit("#include <stdint.h>")
221 self.emit("")
222 self.emit("#include \"opid.h\"")
223 self.emit("")
224 yield node
225
226
227 class Record(Struct):
228 static_operands: StaticOperands
229 name: str
230 opcode: insndb.Record.Opcode
231 dynamic_operand_ids: DynamicOperandIds
232
233
234 class DisGenSource(Source):
235 class Walker(Walker):
236 @mdis.dispatcher.Hook(DynamicOperand, RecordTable)
237 def dispatch_ignore(self, node):
238 yield from ()
239
240 @mdis.dispatcher.Hook(Cache)
241 def dispatch_cache(self, node):
242 (operands, _, _) = node
243 yield from self([operands])
244
245 @mdis.dispatcher.Hook(DynamicOperands)
246 @contextlib.contextmanager
247 def dispatch_operands(self, node):
248 self.emit("static inline enum opid_state")
249 self.emit("opid_disassemble_operand(uint32_t insn,")
250 with self:
251 with self:
252 self.emit("size_t category,")
253 self.emit("struct opid_operand *operand) {")
254 with self:
255 self.emit(f"switch (category) {{")
256 yield node
257 self.emit("default:")
258 with self:
259 self.emit("return OPID_ERROR_OPERAND_0;")
260 self.emit("}")
261 self.emit("")
262 with self:
263 self.emit("return OPID_SUCCESS;")
264 self.emit("}")
265
266 @mdis.dispatcher.Hook(DynamicOperand)
267 @contextlib.contextmanager
268 def dispatch_operand(self, node, *, path, pathcls):
269 def generic_handler(span, flags="UINT32_C(0)"):
270 yield f"operand->value = ("
271 with self:
272 yield from fetch(span)
273 yield f");"
274 yield f"operand->flags = {flags};"
275 self.emit("break;")
276
277 def nonzero_handler(span):
278 yield f"operand->value = (UINT32_C(1) + ("
279 with self:
280 yield from fetch(span)
281 yield f"));"
282 yield f"operand->flags = OPID_OPERAND_NONZERO;"
283 self.emit("break;")
284
285 def signed_handler(span, flags="OPID_OPERAND_SIGNED"):
286 mask = f"(UINT32_C(1) << (UINT32_C({len(span)}) - 1))"
287 yield "operand->value = ("
288 with self:
289 yield "("
290 with self:
291 yield "("
292 with self:
293 yield from fetch(span)
294 yield ")"
295 yield "^"
296 yield f"{mask}"
297 yield ")"
298 yield "-"
299 yield f"{mask}"
300 yield ");"
301 yield f"operand->flags = {flags};"
302 self.emit("break;")
303
304 def address_handler(span):
305 yield from signed_handler(span, "(OPID_OPERAND_ADDRESS | OPID_OPERAND_SIGNED)")
306
307 def gpr_handler(span, pair=False):
308 if not pair:
309 yield from generic_handler(span, "OPID_OPERAND_GPR")
310 else:
311 yield from generic_handler(span, "(OPID_OPERAND_GPR | OPID_OPERAND_PAIR)")
312
313 def fpr_handler(span, pair=False):
314 if not pair:
315 yield from generic_handler(span, "OPID_OPERAND_FPR")
316 else:
317 yield from generic_handler(span, "(OPID_OPERAND_FPR | OPID_OPERAND_PAIR)")
318
319 def cr3_handler(span):
320 yield from generic_handler(span, "OPID_OPERAND_CR3")
321
322 def cr5_handler(span):
323 yield from generic_handler(span, "OPID_OPERAND_CR5")
324
325 handlers = {
326 insndb.GPRPairOperand: lambda span: gpr_handler(span, True),
327 insndb.FPRPairOperand: lambda span: fpr_handler(span, True),
328 insndb.GPROperand: gpr_handler,
329 insndb.FPROperand: fpr_handler,
330 insndb.CR3Operand: cr3_handler,
331 insndb.CR5Operand: cr5_handler,
332 insndb.TargetAddrOperand: address_handler,
333 insndb.SignedOperand: signed_handler,
334 insndb.NonZeroOperand: nonzero_handler,
335 insndb.DynamicOperand: generic_handler,
336 object: None,
337 }
338 self.emit(f"case 0x{(path + 1):02x}: /* {', '.join(node.names)} */")
339 with self:
340 for (cls, handler) in handlers.items():
341 if issubclass(node.cls, cls):
342 break
343 if handler is None:
344 raise ValueError("unknown handler")
345 for line in handler(span=node.span):
346 self.emit(line)
347 self.emit("")
348 yield node
349
350
351 class OpcGenSource(Source):
352 class Walker(Walker):
353 @mdis.dispatcher.Hook(DynamicOperandId, DynamicOperands, insndb.StaticOperand, POTable)
354 def dispatch_ignore(self, node):
355 yield from ()
356
357 @mdis.dispatcher.Hook(Record)
358 def dispatch_record(self, node):
359 keys = {
360 "dynamic_operand_ids": "operands",
361 }
362
363 for field in dataclasses.fields(node):
364 key = field.name
365 value = getattr(node, key)
366 key = keys.get(key, key)
367 yield (value, node, key, mdis.walker.AttributePath)
368
369 @mdis.dispatcher.Hook(Cache)
370 def dispatch_cache(self, node):
371 (_, records, potable) = node
372 yield from self([records, potable])
373
374 @mdis.dispatcher.Hook(DynamicOperandId)
375 @contextlib.contextmanager
376 def dispatch_dynamic_operand_id(self, node, *, path, pathcls):
377 index = f"UINT8_C(0x{node.index:02x})"
378 self.emit(f"{pathcls(path)} = {index}, /* {node.name} */")
379 with self: yield node
380
381 @mdis.dispatcher.Hook(StaticOperands)
382 @contextlib.contextmanager
383 def dispatch_static_operands(self, node):
384 if node:
385 self.emit("/*")
386 yield node
387 self.emit(" */")
388 else:
389 yield node
390
391 @mdis.dispatcher.Hook(insndb.StaticOperand)
392 @contextlib.contextmanager
393 def dispatch_static_operand(self, node):
394 self.emit(f" * {node.name}={node.value} [{', '.join(map(str, node.span))}]")
395 yield node
396
397 @mdis.dispatcher.Hook(insndb.Record.Opcode.Value, insndb.Record.Opcode.Mask)
398 @contextlib.contextmanager
399 def dispatch_opcode_parts(self, node, *, path, pathcls):
400 self.emit(f"{pathcls(path)} = UINT32_C(0x{node:016x}),")
401 with self: yield node
402
403 @mdis.dispatcher.Hook(insndb.Record.Opcode)
404 @contextlib.contextmanager
405 def dispatch_opcode(self, node):
406 self.emit(".opcode = {")
407 with self: yield node
408 self.emit("},")
409
410 @mdis.dispatcher.Hook(POTable)
411 @contextlib.contextmanager
412 def dispatch_potable(self, node):
413 heads = ([0] * (1 << 6))
414 tails = ([0] * (1 << 6))
415 for (index, counter) in enumerate(itertools.accumulate(node)):
416 heads[index] = (counter - node[index])
417 tails[index] = counter
418 heads = [(tail - node[index]) for (index, tail) in enumerate(tails)]
419 self.emit("static uint16_t const opid_opcode_table[64][2] = {")
420 with self:
421 for index in range(64):
422 head = heads[index]
423 tail = tails[index]
424 self.emit(f"[0x{index:02x}] = {{{head}, {tail}}},")
425 self.emit("};")
426 yield node
427
428 @mdis.dispatcher.Hook(RecordTable)
429 @contextlib.contextmanager
430 def dispatch_records(self, node):
431 self.emit("static struct opid_record const opid_record_table[] = {")
432 with self: yield node
433 self.emit("};")
434 self.emit("")
435
436
437 def main():
438 table = {mode:{} for mode in Mode}
439 main_parser = argparse.ArgumentParser("codegen",
440 description="C code generator")
441 main_parser.add_argument("-d", "--database",
442 type=pathlib.Path,
443 default=pathlib.Path(find_wiki_dir()))
444 main_subprarsers = main_parser.add_subparsers(dest="mode", required=True)
445 for (mode, _) in table.items():
446 parser = main_subprarsers.add_parser(mode.value)
447
448 arguments = dict(vars(main_parser.parse_args()))
449 mode = Mode(arguments.pop("mode"))
450 db = insndb.Database(root=arguments.pop("database"))
451
452 return mode(db=db, **arguments)
453
454
455 if __name__ == "__main__":
456 main()