568542fb9f9f4d9cf2446e099978eeb0c7198a01
[openpower-isa.git] / src / openpower / decoder / power_insn.py
1 import collections as _collections
2 import csv as _csv
3 import dataclasses as _dataclasses
4 import enum as _enum
5 import functools as _functools
6 import os as _os
7 import pathlib as _pathlib
8 import re as _re
9
10 try:
11 from functools import cached_property
12 except ImportError:
13 from cached_property import cached_property
14
15 from openpower.decoder.power_enums import (
16 Function as _Function,
17 MicrOp as _MicrOp,
18 In1Sel as _In1Sel,
19 In2Sel as _In2Sel,
20 In3Sel as _In3Sel,
21 OutSel as _OutSel,
22 CRInSel as _CRInSel,
23 CROutSel as _CROutSel,
24 LDSTLen as _LDSTLen,
25 LDSTMode as _LDSTMode,
26 RCOE as _RCOE,
27 CryIn as _CryIn,
28 Form as _Form,
29 SVEtype as _SVEtype,
30 SVMode as _SVMode,
31 SVPtype as _SVPtype,
32 SVExtra as _SVExtra,
33 RegType as _RegType,
34 SVExtraRegType as _SVExtraRegType,
35 SVExtraReg as _SVExtraReg,
36 )
37 from openpower.decoder.selectable_int import (
38 SelectableInt as _SelectableInt,
39 selectconcat as _selectconcat,
40 )
41 from openpower.decoder.power_fields import (
42 Field as _Field,
43 Array as _Array,
44 Mapping as _Mapping,
45 DecodeFields as _DecodeFields,
46 )
47 from openpower.decoder.pseudo.pagereader import ISA as _ISA
48
49
50 def dataclass(cls, record, keymap=None, typemap=None):
51 if keymap is None:
52 keymap = {}
53 if typemap is None:
54 typemap = {field.name:field.type for field in _dataclasses.fields(cls)}
55
56 def transform(key_value):
57 (key, value) = key_value
58 key = keymap.get(key, key)
59 hook = typemap.get(key, lambda value: value)
60 if hook is bool and value in ("", "0"):
61 value = False
62 else:
63 value = hook(value)
64 return (key, value)
65
66 record = dict(map(transform, record.items()))
67 for key in frozenset(record.keys()):
68 if record[key] == "":
69 record.pop(key)
70
71 return cls(**record)
72
73
74 @_functools.total_ordering
75 @_dataclasses.dataclass(eq=True, frozen=True)
76 class Opcode:
77 class Value(int):
78 def __repr__(self):
79 if self.bit_length() <= 32:
80 return f"0x{self:08x}"
81 else:
82 return f"0x{self:016x}"
83
84 class Mask(int):
85 def __repr__(self):
86 if self.bit_length() <= 32:
87 return f"0x{self:08x}"
88 else:
89 return f"0x{self:016x}"
90
91 value: Value
92 mask: Mask = None
93
94 def __lt__(self, other):
95 if not isinstance(other, Opcode):
96 return NotImplemented
97 return ((self.value, self.mask) < (other.value, other.mask))
98
99 def __post_init__(self):
100 (value, mask) = (self.value, self.mask)
101
102 if isinstance(value, Opcode):
103 if mask is not None:
104 raise ValueError(mask)
105 (value, mask) = (value.value, value.mask)
106 elif isinstance(value, str):
107 if mask is not None:
108 raise ValueError(mask)
109 value = int(value, 0)
110
111 if not isinstance(value, int):
112 raise ValueError(value)
113 if mask is None:
114 mask = value
115 if not isinstance(mask, int):
116 raise ValueError(mask)
117
118 object.__setattr__(self, "value", self.__class__.Value(value))
119 object.__setattr__(self, "mask", self.__class__.Mask(mask))
120
121
122 class IntegerOpcode(Opcode):
123 def __init__(self, value):
124 if isinstance(value, str):
125 value = int(value, 0)
126 return super().__init__(value=value, mask=None)
127
128
129 class PatternOpcode(Opcode):
130 def __init__(self, value):
131 (pattern, value, mask) = (value, 0, 0)
132
133 for symbol in pattern:
134 if symbol not in {"0", "1", "-"}:
135 raise ValueError(pattern)
136 value |= (symbol == "1")
137 mask |= (symbol != "-")
138 value <<= 1
139 mask <<= 1
140 value >>= 1
141 mask >>= 1
142
143 return super().__init__(value=value, mask=mask)
144
145
146 class FieldsOpcode(Opcode):
147 def __init__(self, fields):
148 def field(opcode, field):
149 (value, mask) = opcode
150 (field, bits) = field
151 shifts = map(lambda bit: (31 - bit), reversed(tuple(bits)))
152 for (index, shift) in enumerate(shifts):
153 bit = ((field & (1 << index)) != 0)
154 value |= (bit << shift)
155 mask |= (1 << shift)
156 return (value, mask)
157
158 (value, mask) = _functools.reduce(field, fields, (0, 0))
159
160 return super().__init__(value=value, mask=mask)
161
162
163 @_dataclasses.dataclass(eq=True, frozen=True)
164 class PPCRecord:
165 class FlagsMeta(type):
166 def __iter__(cls):
167 yield from (
168 "inv A",
169 "inv out",
170 "cry out",
171 "BR",
172 "sgn ext",
173 "rsrv",
174 "32b",
175 "sgn",
176 "lk",
177 "sgl pipe",
178 )
179
180 class Flags(frozenset, metaclass=FlagsMeta):
181 def __new__(cls, flags=frozenset()):
182 flags = frozenset(flags)
183 diff = (flags - frozenset(cls))
184 if diff:
185 raise ValueError(flags)
186 return super().__new__(cls, flags)
187
188 opcode: Opcode
189 comment: str
190 flags: Flags = Flags()
191 comment2: str = ""
192 function: _Function = _Function.NONE
193 intop: _MicrOp = _MicrOp.OP_ILLEGAL
194 in1: _In1Sel = _In1Sel.RA
195 in2: _In2Sel = _In2Sel.NONE
196 in3: _In3Sel = _In3Sel.NONE
197 out: _OutSel = _OutSel.NONE
198 cr_in: _CRInSel = _CRInSel.NONE
199 cr_out: _CROutSel = _CROutSel.NONE
200 cry_in: _CryIn = _CryIn.ZERO
201 ldst_len: _LDSTLen = _LDSTLen.NONE
202 upd: _LDSTMode = _LDSTMode.NONE
203 Rc: _RCOE = _RCOE.NONE
204 form: _Form = _Form.NONE
205 conditions: str = ""
206 unofficial: bool = False
207
208 __KEYMAP = {
209 "unit": "function",
210 "internal op": "intop",
211 "CR in": "cr_in",
212 "CR out": "cr_out",
213 "cry in": "cry_in",
214 "ldst len": "ldst_len",
215 "rc": "Rc",
216 "CONDITIONS": "conditions",
217 }
218
219 @classmethod
220 def CSV(cls, record, opcode_cls=Opcode):
221 typemap = {field.name:field.type for field in _dataclasses.fields(cls)}
222 typemap["opcode"] = opcode_cls
223
224 flags = set()
225 for flag in frozenset(PPCRecord.Flags):
226 if bool(record.pop(flag, "")):
227 flags.add(flag)
228 record["flags"] = PPCRecord.Flags(flags)
229
230 return dataclass(cls, record,
231 keymap=PPCRecord.__KEYMAP,
232 typemap=typemap)
233
234 @cached_property
235 def names(self):
236 return frozenset(self.comment.split("=")[-1].split("/"))
237
238
239 class PPCMultiRecord(frozenset):
240 @cached_property
241 def unified(self):
242 def merge(lhs, rhs):
243 value = 0
244 mask = 0
245 lvalue = lhs.opcode.value
246 rvalue = rhs.opcode.value
247 lmask = lhs.opcode.mask
248 rmask = rhs.opcode.mask
249 bits = max(lmask.bit_length(), rmask.bit_length())
250 for bit in range(bits):
251 lvstate = ((lvalue & (1 << bit)) != 0)
252 rvstate = ((rvalue & (1 << bit)) != 0)
253 lmstate = ((lmask & (1 << bit)) != 0)
254 rmstate = ((rmask & (1 << bit)) != 0)
255 vstate = lvstate
256 mstate = True
257 if (not lmstate or not rmstate) or (lvstate != rvstate):
258 vstate = 0
259 mstate = 0
260 value |= (vstate << bit)
261 mask |= (mstate << bit)
262
263 opcode = opcode=Opcode(value=value, mask=mask)
264
265 return _dataclasses.replace(lhs, opcode=opcode)
266
267 return _functools.reduce(merge, self)
268
269 def __getattr__(self, attr):
270 return getattr(self.unified, attr)
271
272
273 @_dataclasses.dataclass(eq=True, frozen=True)
274 class SVP64Record:
275 class ExtraMap(tuple):
276 class Extra(tuple):
277 @_dataclasses.dataclass(eq=True, frozen=True)
278 class Entry:
279 regtype: _SVExtraRegType = _SVExtraRegType.NONE
280 reg: _SVExtraReg = _SVExtraReg.NONE
281
282 def __repr__(self):
283 return f"{self.regtype.value}:{self.reg.name}"
284
285 def __new__(cls, value="0"):
286 if isinstance(value, str):
287 def transform(value):
288 (regtype, reg) = value.split(":")
289 regtype = _SVExtraRegType(regtype)
290 reg = _SVExtraReg(reg)
291 return cls.Entry(regtype=regtype, reg=reg)
292
293 if value == "0":
294 value = tuple()
295 else:
296 value = map(transform, value.split(";"))
297
298 return super().__new__(cls, value)
299
300 def __repr__(self):
301 return repr(list(self))
302
303 def __new__(cls, value=tuple()):
304 value = tuple(value)
305 if len(value) == 0:
306 value = (("0",) * 4)
307 return super().__new__(cls, map(cls.Extra, value))
308
309 def __repr__(self):
310 return repr({index:self[index] for index in range(0, 4)})
311
312 name: str
313 ptype: _SVPtype = _SVPtype.NONE
314 etype: _SVEtype = _SVEtype.NONE
315 in1: _In1Sel = _In1Sel.NONE
316 in2: _In2Sel = _In2Sel.NONE
317 in3: _In3Sel = _In3Sel.NONE
318 out: _OutSel = _OutSel.NONE
319 out2: _OutSel = _OutSel.NONE
320 cr_in: _CRInSel = _CRInSel.NONE
321 cr_out: _CROutSel = _CROutSel.NONE
322 extra: ExtraMap = ExtraMap()
323 conditions: str = ""
324 mode: _SVMode = _SVMode.NORMAL
325
326 __KEYMAP = {
327 "insn": "name",
328 "CONDITIONS": "conditions",
329 "Ptype": "ptype",
330 "Etype": "etype",
331 "CR in": "cr_in",
332 "CR out": "cr_out",
333 }
334
335 @classmethod
336 def CSV(cls, record):
337 for key in ("in1", "in2", "in3", "out", "out2", "CR in", "CR out"):
338 value = record[key]
339 if value == "0":
340 record[key] = "NONE"
341
342 extra = []
343 for idx in range(0, 4):
344 extra.append(record.pop(f"{idx}"))
345
346 record["extra"] = cls.ExtraMap(extra)
347
348 return dataclass(cls, record, keymap=cls.__KEYMAP)
349
350 @_functools.lru_cache(maxsize=None)
351 def extra_idx(self, key):
352 extra_idx = (
353 _SVExtra.Idx0,
354 _SVExtra.Idx1,
355 _SVExtra.Idx2,
356 _SVExtra.Idx3,
357 )
358
359 if key not in frozenset({
360 "in1", "in2", "in3", "cr_in",
361 "out", "out2", "cr_out",
362 }):
363 raise KeyError(key)
364
365 sel = getattr(self, key)
366 if sel is _CRInSel.BA_BB:
367 return _SVExtra.Idx_1_2
368 reg = _SVExtraReg(sel)
369 if reg is _SVExtraReg.NONE:
370 return _SVExtra.NONE
371
372 extra_map = {
373 _SVExtraRegType.SRC: {},
374 _SVExtraRegType.DST: {},
375 }
376 for index in range(0, 4):
377 for entry in self.extra[index]:
378 extra_map[entry.regtype][entry.reg] = extra_idx[index]
379
380 for regtype in (_SVExtraRegType.SRC, _SVExtraRegType.DST):
381 extra = extra_map[regtype].get(reg, _SVExtra.NONE)
382 if extra is not _SVExtra.NONE:
383 return extra
384
385 return _SVExtra.NONE
386
387 extra_idx_in1 = property(_functools.partial(extra_idx, key="in1"))
388 extra_idx_in2 = property(_functools.partial(extra_idx, key="in2"))
389 extra_idx_in3 = property(_functools.partial(extra_idx, key="in3"))
390 extra_idx_out = property(_functools.partial(extra_idx, key="out"))
391 extra_idx_out2 = property(_functools.partial(extra_idx, key="out2"))
392 extra_idx_cr_in = property(_functools.partial(extra_idx, key="cr_in"))
393 extra_idx_cr_out = property(_functools.partial(extra_idx, key="cr_out"))
394
395 @_functools.lru_cache(maxsize=None)
396 def extra_reg(self, key):
397 return _SVExtraReg(getattr(self, key))
398
399 extra_reg_in1 = property(_functools.partial(extra_reg, key="in1"))
400 extra_reg_in2 = property(_functools.partial(extra_reg, key="in2"))
401 extra_reg_in3 = property(_functools.partial(extra_reg, key="in3"))
402 extra_reg_out = property(_functools.partial(extra_reg, key="out"))
403 extra_reg_out2 = property(_functools.partial(extra_reg, key="out2"))
404 extra_reg_cr_in = property(_functools.partial(extra_reg, key="cr_in"))
405 extra_reg_cr_out = property(_functools.partial(extra_reg, key="cr_out"))
406
407
408 class BitSel:
409 def __init__(self, value=(0, 32)):
410 if isinstance(value, str):
411 (start, end) = map(int, value.split(":"))
412 else:
413 (start, end) = value
414 if start < 0 or end < 0 or start >= end:
415 raise ValueError(value)
416
417 self.__start = start
418 self.__end = end
419
420 return super().__init__()
421
422 def __repr__(self):
423 return f"[{self.__start}:{self.__end}]"
424
425 def __iter__(self):
426 yield from range(self.start, (self.end + 1))
427
428 @property
429 def start(self):
430 return self.__start
431
432 @property
433 def end(self):
434 return self.__end
435
436
437 @_dataclasses.dataclass(eq=True, frozen=True)
438 class Section:
439 class Mode(_enum.Enum):
440 INTEGER = _enum.auto()
441 PATTERN = _enum.auto()
442
443 @classmethod
444 def _missing_(cls, value):
445 if isinstance(value, str):
446 return cls[value.upper()]
447 return super()._missing_(value)
448
449 class Suffix(int):
450 def __new__(cls, value=None):
451 if isinstance(value, str):
452 if value.upper() == "NONE":
453 value = None
454 else:
455 value = int(value, 0)
456 if value is None:
457 value = 0
458
459 return super().__new__(cls, value)
460
461 def __str__(self):
462 return repr(self)
463
464 def __repr__(self):
465 return (bin(self) if self else "None")
466
467 path: _pathlib.Path
468 opcode: Opcode
469 bitsel: BitSel
470 suffix: Suffix
471 mode: Mode
472
473 @classmethod
474 def CSV(cls, record):
475 return dataclass(cls, record)
476
477
478 class Fields:
479 def __init__(self, items):
480 if isinstance(items, dict):
481 items = items.items()
482
483 def transform(item):
484 (name, bitrange) = item
485 return (name, list(bitrange.values()))
486
487 self.__mapping = dict(map(transform, items))
488
489 return super().__init__()
490
491 def __repr__(self):
492 return repr(self.__mapping)
493
494 def __iter__(self):
495 yield from self.__mapping.items()
496
497 def __contains__(self, key):
498 return self.__mapping.__contains__(key)
499
500 def __getitem__(self, key):
501 return self.__mapping.get(key, None)
502
503
504 @_dataclasses.dataclass(eq=True, frozen=True)
505 class Operand:
506 name: str
507
508 def disassemble(self, value, record, verbose=False):
509 raise NotImplementedError
510
511
512 @_dataclasses.dataclass(eq=True, frozen=True)
513 class DynamicOperand(Operand):
514 def disassemble(self, value, record, verbose=False):
515 span = record.fields[self.name]
516 value = value[span]
517 if verbose:
518 yield f"{int(value):0{value.bits}b}"
519 yield repr(span)
520 else:
521 yield str(int(value))
522
523
524 @_dataclasses.dataclass(eq=True, frozen=True)
525 class DynamicOperandReg(DynamicOperand):
526 @property
527 def extra_reg(self):
528 return _SVExtraReg(self.name)
529
530 def extra_idx(self, record):
531 for key in frozenset({
532 "in1", "in2", "in3", "cr_in",
533 "out", "out2", "cr_out",
534 }):
535 if self.extra_reg == record.svp64.extra_reg(key):
536 return record.extra_idx(key)
537
538 return _SVExtra.NONE
539
540
541 @_dataclasses.dataclass(eq=True, frozen=True)
542 class ImmediateOperand(DynamicOperand):
543 pass
544
545
546 @_dataclasses.dataclass(eq=True, frozen=True)
547 class StaticOperand(Operand):
548 value: int
549
550 def disassemble(self, value, record, verbose=False):
551 span = record.fields[self.name]
552 value = value[span]
553 if verbose:
554 yield f"{int(value):0{value.bits}b}"
555 yield repr(span)
556 else:
557 yield str(int(value))
558
559
560 @_dataclasses.dataclass(eq=True, frozen=True)
561 class DynamicOperandTargetAddrLI(DynamicOperandReg):
562 @property
563 def name(self):
564 return "LI"
565
566 @name.setter
567 def name(self, _):
568 pass
569
570 def disassemble(self, value, record, verbose=False):
571 span = record.fields["LI"]
572 value = value[span]
573 if verbose:
574 yield f"{int(value):0{value.bits}b}"
575 yield repr(span)
576 yield "target_addr = EXTS(LI || 0b00))"
577 else:
578 yield hex(int(_selectconcat(value,
579 _SelectableInt(value=0b00, bits=2))))
580
581
582 class DynamicOperandTargetAddrBD(DynamicOperand):
583 @property
584 def name(self):
585 return "BD"
586
587 @name.setter
588 def name(self, _):
589 pass
590
591 def disassemble(self, value, record, verbose=False):
592 span = record.fields["BD"]
593 value = value[span]
594 if verbose:
595 yield f"{int(value):0{value.bits}b}"
596 yield repr(span)
597 yield "target_addr = EXTS(BD || 0b00))"
598 else:
599 yield hex(int(_selectconcat(value,
600 _SelectableInt(value=0b00, bits=2))))
601
602
603 @_dataclasses.dataclass(eq=True, frozen=True)
604 class DynamicOperandGPR(DynamicOperandReg):
605 def disassemble(self, value, record, verbose=False):
606 svp64 = isinstance(value, SVP64Instruction)
607 span = record.fields[self.name]
608 value = value[span]
609 if verbose:
610 yield f"{int(value):0{value.bits}b}"
611 yield repr(span)
612 if svp64:
613 extra_idx = self.extra_idx(record)
614 if record.etype is _SVEtype.NONE:
615 yield f"extra[none]"
616 else:
617 etype = repr(record.etype).lower()
618 yield f"{etype}{extra_idx!r}"
619 else:
620 yield f"r{str(int(value))}"
621
622
623 @_dataclasses.dataclass(eq=True, frozen=True)
624 class DynamicOperandFPR(DynamicOperandReg):
625 def disassemble(self, value, record, verbose=False):
626 svp64 = isinstance(value, SVP64Instruction)
627 span = record.fields[self.name]
628 value = value[span]
629 if verbose:
630 yield f"{int(value):0{value.bits}b}"
631 yield repr(span)
632 if svp64:
633 extra_idx = self.extra_idx(record)
634 if record.etype is _SVEtype.NONE:
635 yield f"extra[none]"
636 else:
637 etype = repr(record.etype).lower()
638 yield f"{etype}{extra_idx!r}"
639 else:
640 yield f"f{str(int(value))}"
641
642
643 class Operands(tuple):
644 def __new__(cls, insn, iterable):
645 branches = {
646 "b": {"target_addr": DynamicOperandTargetAddrLI},
647 "ba": {"target_addr": DynamicOperandTargetAddrLI},
648 "bl": {"target_addr": DynamicOperandTargetAddrLI},
649 "bla": {"target_addr": DynamicOperandTargetAddrLI},
650 "bc": {"target_addr": DynamicOperandTargetAddrBD},
651 "bca": {"target_addr": DynamicOperandTargetAddrBD},
652 "bcl": {"target_addr": DynamicOperandTargetAddrBD},
653 "bcla": {"target_addr": DynamicOperandTargetAddrBD},
654 }
655
656 operands = []
657 for operand in iterable:
658 dynamic_cls = DynamicOperand
659 static_cls = StaticOperand
660
661 if "=" in operand:
662 (name, value) = operand.split("=")
663 operand = static_cls(name=name, value=int(value))
664 operands.append(operand)
665 else:
666 if operand.endswith(")"):
667 operand = operand.replace("(", " ").replace(")", "")
668 (immediate, _, operand) = operand.partition(" ")
669 else:
670 immediate = None
671
672 if immediate is not None:
673 operands.append(ImmediateOperand(name=immediate))
674
675 if insn in branches and operand in branches[insn]:
676 dynamic_cls = branches[insn][operand]
677
678 if operand in _RegType.__members__:
679 regtype = _RegType[operand]
680 if regtype is _RegType.GPR:
681 dynamic_cls = DynamicOperandGPR
682 elif regtype is _RegType.FPR:
683 dynamic_cls = DynamicOperandFPR
684
685 operand = dynamic_cls(name=operand)
686 operands.append(operand)
687
688 return super().__new__(cls, operands)
689
690 def __contains__(self, key):
691 return self.__getitem__(key) is not None
692
693 def __getitem__(self, key):
694 for operand in self:
695 if operand.name == key:
696 return operand
697
698 return None
699
700 @property
701 def dynamic(self):
702 for operand in self:
703 if isinstance(operand, DynamicOperand):
704 yield operand
705
706 @property
707 def static(self):
708 for operand in self:
709 if isinstance(operand, StaticOperand):
710 yield operand
711
712
713 @_functools.total_ordering
714 @_dataclasses.dataclass(eq=True, frozen=True)
715 class Record:
716 name: str
717 section: Section
718 ppc: PPCRecord
719 fields: Fields
720 operands: Operands
721 svp64: SVP64Record = None
722
723 def __lt__(self, other):
724 if not isinstance(other, Record):
725 return NotImplemented
726 return (self.opcode < other.opcode)
727
728 @cached_property
729 def opcode(self):
730 fields = []
731 if self.section.opcode:
732 fields += [(self.section.opcode.value, BitSel((0, 5)))]
733 fields += [(self.ppc.opcode.value, self.section.bitsel)]
734 else:
735 fields += [(self.ppc.opcode.value, self.section.bitsel)]
736
737 for operand in self.operands.static:
738 fields += [(operand.value, self.fields[operand.name])]
739
740 return FieldsOpcode(fields)
741
742 @property
743 def function(self):
744 return self.ppc.function
745
746 @property
747 def in1(self):
748 return self.ppc.in1
749
750 @property
751 def in2(self):
752 return self.ppc.in2
753
754 @property
755 def in3(self):
756 return self.ppc.in3
757
758 @property
759 def out(self):
760 return self.ppc.out
761
762 @property
763 def out2(self):
764 if self.svp64 is None:
765 return _OutSel.NONE
766 return self.ppc.out
767
768 @property
769 def cr_in(self):
770 return self.ppc.cr_in
771
772 @property
773 def cr_out(self):
774 return self.ppc.cr_out
775
776 def extra_idx(self, key):
777 return self.svp64.extra_idx(key)
778
779 ptype = property(lambda self: self.svp64.ptype)
780 etype = property(lambda self: self.svp64.etype)
781 extra_in1 = property(lambda self: self.svp64.extra_in1)
782 extra_in2 = property(lambda self: self.svp64.extra_in2)
783 extra_in3 = property(lambda self: self.svp64.extra_in3)
784 extra_out = property(lambda self: self.svp64.extra_out)
785 extra_out2 = property(lambda self: self.svp64.extra_out2)
786 extra_cr_in = property(lambda self: self.svp64.extra_cr_in)
787 extra_cr_out = property(lambda self: self.svp64.extra_cr_out)
788
789
790 class Instruction(_Mapping):
791 @classmethod
792 def integer(cls, value=0, bits=None, byteorder="little"):
793 if isinstance(value, (int, bytes)) and not isinstance(bits, int):
794 raise ValueError(bits)
795
796 if isinstance(value, bytes):
797 if ((len(value) * 8) != bits):
798 raise ValueError(f"bit length mismatch")
799 value = int.from_bytes(value, byteorder=byteorder)
800
801 if isinstance(value, int):
802 value = _SelectableInt(value=value, bits=bits)
803 elif isinstance(value, Instruction):
804 value = value.storage
805
806 if not isinstance(value, _SelectableInt):
807 raise ValueError(value)
808 if bits is None:
809 bits = len(cls)
810 if len(value) != bits:
811 raise ValueError(value)
812
813 value = _SelectableInt(value=value, bits=bits)
814
815 return cls(storage=value)
816
817 def __hash__(self):
818 return hash(int(self))
819
820 def record(self, db):
821 record = db[self]
822 if record is None:
823 raise KeyError(self)
824 return record
825
826 def disassemble(self, db, byteorder="little", verbose=False):
827 raise NotImplementedError
828
829
830 class WordInstruction(Instruction):
831 _: _Field = range(0, 32)
832 po: _Field = range(0, 6)
833
834 @classmethod
835 def integer(cls, value, byteorder="little"):
836 return super().integer(bits=32, value=value, byteorder=byteorder)
837
838 @property
839 def binary(self):
840 bits = []
841 for idx in range(32):
842 bit = int(self[idx])
843 bits.append(bit)
844 return "".join(map(str, bits))
845
846 def spec(self, db):
847 record = self.record(db=db)
848
849 immediate = ""
850 dynamic_operands = []
851 for operand in record.operands.dynamic:
852 name = operand.name
853 if immediate:
854 name = f"{immediate}({name})"
855 immediate = ""
856 if isinstance(operand, ImmediateOperand):
857 immediate = operand.name
858 if not immediate:
859 dynamic_operands.append(name)
860
861 static_operands = []
862 for operand in record.operands.static:
863 static_operands.append(f"{operand.name}={operand.value}")
864
865 operands = ""
866 if dynamic_operands:
867 operands += f" {','.join(dynamic_operands)}"
868 if static_operands:
869 operands += f" ({' '.join(static_operands)})"
870
871 return f"{record.name}{operands}"
872
873 def opcode(self, db):
874 record = self.record(db=db)
875 return f"0x{record.opcode.value:08x}"
876
877 def mask(self, db):
878 record = self.record(db=db)
879 return f"0x{record.opcode.mask:08x}"
880
881 def disassemble(self, db, byteorder="little", verbose=False):
882 integer = int(self)
883 blob = integer.to_bytes(length=4, byteorder=byteorder)
884 blob = " ".join(map(lambda byte: f"{byte:02x}", blob))
885
886 record = self.record(db=db)
887 if record is None:
888 yield f"{blob} .long 0x{integer:08x}"
889 return
890
891 operands = []
892 for operand in record.operands.dynamic:
893 operand = " ".join(operand.disassemble(value=self,
894 record=record, verbose=False))
895 operands.append(operand)
896 if operands:
897 operands = ",".join(operands)
898 operands = f" {operands}"
899 else:
900 operands = ""
901
902 yield f"{blob} {record.name}{operands}"
903
904 if verbose:
905 indent = (" " * 4)
906 binary = self.binary
907 spec = self.spec(db=db)
908 opcode = self.opcode(db=db)
909 mask = self.mask(db=db)
910 yield f"{indent}spec"
911 yield f"{indent}{indent}{spec}"
912 yield f"{indent}binary"
913 yield f"{indent}{indent}[0:8] {binary[0:8]}"
914 yield f"{indent}{indent}[8:16] {binary[8:16]}"
915 yield f"{indent}{indent}[16:24] {binary[16:24]}"
916 yield f"{indent}{indent}[24:32] {binary[24:32]}"
917 yield f"{indent}opcode"
918 yield f"{indent}{indent}{opcode}"
919 yield f"{indent}mask"
920 yield f"{indent}{indent}{mask}"
921 for operand in record.operands:
922 name = operand.name
923 yield f"{indent}{name}"
924 parts = operand.disassemble(value=self,
925 record=record, verbose=True)
926 for part in parts:
927 yield f"{indent}{indent}{part}"
928 yield ""
929
930
931 class PrefixedInstruction(Instruction):
932 class Prefix(WordInstruction.remap(range(0, 32))):
933 pass
934
935 class Suffix(WordInstruction.remap(range(32, 64))):
936 pass
937
938 _: _Field = range(64)
939 prefix: Prefix
940 suffix: Suffix
941 po: Suffix.po
942
943 @classmethod
944 def integer(cls, value, byteorder="little"):
945 return super().integer(bits=64, value=value, byteorder=byteorder)
946
947 @classmethod
948 def pair(cls, prefix=0, suffix=0, byteorder="little"):
949 def transform(value):
950 return WordInstruction.integer(value=value,
951 byteorder=byteorder)[0:32]
952
953 (prefix, suffix) = map(transform, (prefix, suffix))
954 value = _selectconcat(prefix, suffix)
955
956 return super().integer(value=value)
957
958
959 class Mode(_Mapping):
960 _: _Field = range(0, 5)
961 sel: _Field = range(0, 2)
962
963
964 class NormalMode(Mode):
965 class simple(Mode):
966 """simple mode"""
967 dz: Mode[3]
968 sz: Mode[4]
969
970 class smr(Mode):
971 """scalar reduce mode (mapreduce), SUBVL=1"""
972 RG: Mode[4]
973
974 class pmr(Mode):
975 """parallel reduce mode (mapreduce), SUBVL=1"""
976 pass
977
978 class svmr(Mode):
979 """subvector reduce mode, SUBVL>1"""
980 SVM: Mode[3]
981
982 class pu(Mode):
983 """Pack/Unpack mode, SUBVL>1"""
984 SVM: Mode[3]
985
986 class ffrc1(Mode):
987 """Rc=1: ffirst CR sel"""
988 inv: Mode[2]
989 CRbit: Mode[3, 4]
990
991 class ffrc0(Mode):
992 """Rc=0: ffirst z/nonz"""
993 inv: Mode[2]
994 VLi: Mode[3]
995 RC1: Mode[4]
996
997 class sat(Mode):
998 """sat mode: N=0/1 u/s, SUBVL=1"""
999 N: Mode[2]
1000 dz: Mode[3]
1001 sz: Mode[4]
1002
1003 class satx(Mode):
1004 """sat mode: N=0/1 u/s, SUBVL>1"""
1005 N: Mode[2]
1006 zz: Mode[3]
1007 dz: Mode[3]
1008 sz: Mode[3]
1009
1010 class satpu(Mode):
1011 """Pack/Unpack sat mode: N=0/1 u/s, SUBVL>1"""
1012 N: Mode[2]
1013 zz: Mode[3]
1014 dz: Mode[3]
1015 sz: Mode[3]
1016
1017 class prrc1(Mode):
1018 """Rc=1: pred-result CR sel"""
1019 inv: Mode[2]
1020 CRbit: Mode[3, 4]
1021
1022 class prrc0(Mode):
1023 """Rc=0: pred-result z/nonz"""
1024 inv: Mode[2]
1025 zz: Mode[3]
1026 RC1: Mode[4]
1027 dz: Mode[3]
1028 sz: Mode[3]
1029
1030 simple: simple
1031 smr: smr
1032 pmr: pmr
1033 svmr: svmr
1034 pu: pu
1035 ffrc1: ffrc1
1036 ffrc0: ffrc0
1037 sat: sat
1038 satx: satx
1039 satpu: satpu
1040 prrc1: prrc1
1041 prrc0: prrc0
1042
1043
1044 class LDSTImmMode(Mode):
1045 class simple(Mode):
1046 """simple mode"""
1047 zz: Mode[3]
1048 els: Mode[4]
1049 dz: Mode[3]
1050 sz: Mode[3]
1051
1052 class spu(Mode):
1053 """Structured Pack/Unpack"""
1054 zz: Mode[3]
1055 els: Mode[4]
1056 dz: Mode[3]
1057 sz: Mode[3]
1058
1059 class ffrc1(Mode):
1060 """Rc=1: ffirst CR sel"""
1061 inv: Mode[2]
1062 CRbit: Mode[3, 4]
1063
1064 class ffrc0(Mode):
1065 """Rc=0: ffirst z/nonz"""
1066 inv: Mode[2]
1067 els: Mode[3]
1068 RC1: Mode[4]
1069
1070 class sat(Mode):
1071 """sat mode: N=0/1 u/s"""
1072 N: Mode[2]
1073 zz: Mode[3]
1074 els: Mode[4]
1075 dz: Mode[3]
1076 sz: Mode[3]
1077
1078 class prrc1(Mode):
1079 """Rc=1: pred-result CR sel"""
1080 inv: Mode[2]
1081 CRbit: Mode[3, 4]
1082
1083 class prrc0(Mode):
1084 """Rc=0: pred-result z/nonz"""
1085 inv: Mode[2]
1086 els: Mode[3]
1087 RC1: Mode[4]
1088
1089 simple: simple
1090 spu: spu
1091 ffrc1: ffrc1
1092 ffrc0: ffrc0
1093 sat: sat
1094 prrc1: prrc1
1095 prrc0: prrc0
1096
1097
1098 class LDSTIdxMode(Mode):
1099 class simple(Mode):
1100 """simple mode"""
1101 SEA: Mode[2]
1102 sz: Mode[3]
1103 dz: Mode[3]
1104
1105 class stride(Mode):
1106 """strided (scalar only source)"""
1107 SEA: Mode[2]
1108 dz: Mode[3]
1109 sz: Mode[4]
1110
1111 class sat(Mode):
1112 """sat mode: N=0/1 u/s"""
1113 N: Mode[2]
1114 dz: Mode[3]
1115 sz: Mode[4]
1116
1117 class prrc1(Mode):
1118 """Rc=1: pred-result CR sel"""
1119 inv: Mode[2]
1120 CRbit: Mode[3, 4]
1121
1122 class prrc0(Mode):
1123 """Rc=0: pred-result z/nonz"""
1124 inv: Mode[2]
1125 zz: Mode[3]
1126 RC1: Mode[4]
1127 dz: Mode[3]
1128 sz: Mode[3]
1129
1130 simple: simple
1131 stride: stride
1132 sat: sat
1133 prrc1: prrc1
1134 prrc0: prrc0
1135
1136
1137 class RM(_Mapping):
1138 class Mode(Mode):
1139 normal: NormalMode
1140 ldst_imm: LDSTImmMode
1141 ldst_idx: LDSTIdxMode
1142
1143 _: _Field = range(24)
1144 mmode: _Field = (0,)
1145 mask: _Field = range(1, 4)
1146 elwidth: _Field = range(4, 6)
1147 ewsrc: _Field = range(6, 8)
1148 subvl: _Field = range(8, 10)
1149 extra: _Field = range(10, 19)
1150 mode: Mode.remap(range(19, 24))
1151 extra2: _Array[4] = (
1152 range(10, 12),
1153 range(12, 14),
1154 range(14, 16),
1155 range(16, 18),
1156 )
1157 smask: _Field = range(16, 19)
1158 extra3: _Array[3] = (
1159 range(10, 13),
1160 range(13, 16),
1161 range(16, 19),
1162 )
1163
1164
1165 class SVP64Instruction(PrefixedInstruction):
1166 """SVP64 instruction: https://libre-soc.org/openpower/sv/svp64/"""
1167 class Prefix(PrefixedInstruction.Prefix):
1168 id: _Field = (7, 9)
1169 rm: RM.remap((6, 8) + tuple(range(10, 32)))
1170
1171 prefix: Prefix
1172
1173 @property
1174 def binary(self):
1175 bits = []
1176 for idx in range(64):
1177 bit = int(self[idx])
1178 bits.append(bit)
1179 return "".join(map(str, bits))
1180
1181 def spec(self, db):
1182 return f"sv.{self.suffix.spec(db=db)}"
1183
1184 def opcode(self, db):
1185 return self.suffix.opcode(db=db)
1186
1187 def mask(self, db):
1188 return self.suffix.mask(db=db)
1189
1190 def mode(self, db):
1191 record = self.record(db=db)
1192
1193 Rc = False
1194 if record.operands["Rc"] is not None:
1195 Rc = bool(self[record.fields["Rc"]])
1196
1197 record = self.record(db=db)
1198 subvl = self.prefix.rm.subvl
1199 mode = self.prefix.rm.mode
1200 sel = mode.sel
1201
1202 if record.svp64.mode is _SVMode.NORMAL:
1203 mode = mode.normal
1204 if sel == 0b00:
1205 if mode[2] == 0b0:
1206 mode = mode.simple
1207 else:
1208 if subvl == 0b00:
1209 if mode[3] == 0b0:
1210 mode = mode.smr
1211 else:
1212 mode = mode.pmr
1213 else:
1214 if mode[4] == 0b0:
1215 mode = mode.svmr
1216 else:
1217 mode = mode.pu
1218 elif sel == 0b01:
1219 if Rc:
1220 mode = mode.ffrc1
1221 else:
1222 mode = mode.ffrc0
1223 elif sel == 0b10:
1224 if subvl == 0b00:
1225 mode = mode.sat
1226 else:
1227 if mode[4]:
1228 mode = mode.satx
1229 else:
1230 mode = mode.satpu
1231 elif sel == 0b11:
1232 if Rc:
1233 mode = mode.prrc1
1234 else:
1235 mode = mode.prrc0
1236 elif record.svp64.mode is _SVMode.LDST_IMM:
1237 mode = mode.ldst_imm
1238 if sel == 0b00:
1239 if mode[2] == 0b0:
1240 mode = mode.simple
1241 else:
1242 mode = mode.spu
1243 elif sel == 0b01:
1244 if Rc:
1245 mode = mode.ffrc1
1246 else:
1247 mode = mode.ffrc0
1248 elif sel == 0b10:
1249 mode = mode.sat
1250 elif sel == 0b11:
1251 if Rc:
1252 mode = mode.prrc1
1253 else:
1254 mode = mode.prrc0
1255 elif record.svp64.mode is _SVMode.LDST_IMM:
1256 mode = mode.ldst_idx
1257 if mode.sel == 0b00:
1258 mode = mode.simple
1259 elif mode.sel == 0b01:
1260 mode = mode.stride
1261 elif mode.sel == 0b10:
1262 mode = mode.sat
1263 elif mode.sel == 0b11:
1264 if Rc:
1265 mode = mode.prrc1
1266 else:
1267 mode = mode.prrc0
1268
1269 modes = {
1270 NormalMode.simple: "normal: simple",
1271 NormalMode.smr: "normal: smr",
1272 NormalMode.pmr: "normal: pmr",
1273 NormalMode.svmr: "normal: svmr",
1274 NormalMode.pu: "normal: pu",
1275 NormalMode.ffrc1: "normal: ffrc1",
1276 NormalMode.ffrc0: "normal: ffrc0",
1277 NormalMode.sat: "normal: sat",
1278 NormalMode.satx: "normal: satx",
1279 NormalMode.satpu: "normal: satpu",
1280 NormalMode.prrc1: "normal: prrc1",
1281 NormalMode.prrc0: "normal: prrc0",
1282 LDSTImmMode.simple: "ld/st imm: simple",
1283 LDSTImmMode.spu: "ld/st imm: spu",
1284 LDSTImmMode.ffrc1: "ld/st imm: ffrc1",
1285 LDSTImmMode.ffrc0: "ld/st imm: ffrc0",
1286 LDSTImmMode.sat: "ld/st imm: sat",
1287 LDSTImmMode.prrc1: "ld/st imm: prrc1",
1288 LDSTImmMode.prrc0: "ld/st imm: prrc0",
1289 LDSTIdxMode.simple: "ld/st idx simple",
1290 LDSTIdxMode.stride: "ld/st idx stride",
1291 LDSTIdxMode.sat: "ld/st idx sat",
1292 LDSTIdxMode.prrc1: "ld/st idx prrc1",
1293 LDSTIdxMode.prrc0: "ld/st idx prrc0",
1294 }
1295 for (cls, desc) in modes.items():
1296 if isinstance(mode, cls):
1297 return (mode, desc)
1298
1299 raise NotImplementedError
1300
1301 def disassemble(self, db, byteorder="little", verbose=False):
1302 integer_prefix = int(self.prefix)
1303 blob_prefix = integer_prefix.to_bytes(length=4, byteorder=byteorder)
1304 blob_prefix = " ".join(map(lambda byte: f"{byte:02x}", blob_prefix))
1305
1306 integer_suffix = int(self.suffix)
1307 blob_suffix = integer_suffix.to_bytes(length=4, byteorder=byteorder)
1308 blob_suffix = " ".join(map(lambda byte: f"{byte:02x}", blob_suffix))
1309
1310 record = self.record(db=db)
1311 if record is None or record.svp64 is None:
1312 yield f"{blob_prefix} .long 0x{int(self.prefix):08x}"
1313 yield f"{blob_suffix} .long 0x{int(self.suffix):08x}"
1314 return
1315
1316 yield f"{blob_prefix} sv.{record.name}"
1317 yield f"{blob_suffix}"
1318
1319 (mode, mode_desc) = self.mode(db=db)
1320
1321 if verbose:
1322 indent = (" " * 4)
1323 binary = self.binary
1324 spec = self.spec(db=db)
1325 opcode = self.opcode(db=db)
1326 mask = self.mask(db=db)
1327 yield f"{indent}spec"
1328 yield f"{indent}{indent}{spec}"
1329 yield f"{indent}binary"
1330 yield f"{indent}{indent}[0:8] {binary[0:8]}"
1331 yield f"{indent}{indent}[8:16] {binary[8:16]}"
1332 yield f"{indent}{indent}[16:24] {binary[16:24]}"
1333 yield f"{indent}{indent}[24:32] {binary[24:32]}"
1334 yield f"{indent}{indent}[32:40] {binary[32:40]}"
1335 yield f"{indent}{indent}[40:48] {binary[40:48]}"
1336 yield f"{indent}{indent}[48:56] {binary[48:56]}"
1337 yield f"{indent}{indent}[56:64] {binary[56:64]}"
1338 yield f"{indent}opcode"
1339 yield f"{indent}{indent}{opcode}"
1340 yield f"{indent}mask"
1341 yield f"{indent}{indent}{mask}"
1342 for operand in record.operands:
1343 name = operand.name
1344 yield f"{indent}{name}"
1345 parts = operand.disassemble(value=self,
1346 record=record, verbose=True)
1347 for part in parts:
1348 yield f"{indent}{indent}{part}"
1349
1350 yield f"{indent}mode"
1351 yield f"{indent}{indent}{mode_desc}"
1352 yield ""
1353
1354
1355 def parse(stream, factory):
1356 def match(entry):
1357 return ("TODO" not in frozenset(entry.values()))
1358
1359 lines = filter(lambda line: not line.strip().startswith("#"), stream)
1360 entries = _csv.DictReader(lines)
1361 entries = filter(match, entries)
1362 return tuple(map(factory, entries))
1363
1364
1365 class MarkdownDatabase:
1366 def __init__(self):
1367 db = {}
1368 for (name, desc) in _ISA():
1369 operands = []
1370 if desc.regs:
1371 (dynamic, *static) = desc.regs
1372 operands.extend(dynamic)
1373 operands.extend(static)
1374 db[name] = Operands(insn=name, iterable=operands)
1375 self.__db = db
1376 return super().__init__()
1377
1378 def __iter__(self):
1379 yield from self.__db.items()
1380
1381 def __getitem__(self, key):
1382 return self.__db.__getitem__(key)
1383
1384
1385 class FieldsDatabase:
1386 def __init__(self):
1387 db = {}
1388 df = _DecodeFields()
1389 df.create_specs()
1390 for (form, fields) in df.instrs.items():
1391 if form in {"DQE", "TX"}:
1392 continue
1393 if form == "all":
1394 form = "NONE"
1395 db[_Form[form]] = Fields(fields)
1396
1397 self.__db = db
1398
1399 return super().__init__()
1400
1401 def __getitem__(self, key):
1402 return self.__db.__getitem__(key)
1403
1404
1405 class PPCDatabase:
1406 def __init__(self, root, mdwndb):
1407 # The code below groups the instructions by section:identifier.
1408 # We use the comment as an identifier, there's nothing better.
1409 # The point is to capture different opcodes for the same instruction.
1410 dd = _collections.defaultdict
1411 records = dd(lambda: dd(set))
1412 path = (root / "insndb.csv")
1413 with open(path, "r", encoding="UTF-8") as stream:
1414 for section in parse(stream, Section.CSV):
1415 path = (root / section.path)
1416 opcode_cls = {
1417 section.Mode.INTEGER: IntegerOpcode,
1418 section.Mode.PATTERN: PatternOpcode,
1419 }[section.mode]
1420 factory = _functools.partial(
1421 PPCRecord.CSV, opcode_cls=opcode_cls)
1422 with open(path, "r", encoding="UTF-8") as stream:
1423 for insn in parse(stream, factory):
1424 records[section][insn.comment].add(insn)
1425
1426 db = dd(set)
1427 for (section, group) in records.items():
1428 for records in group.values():
1429 db[section].add(PPCMultiRecord(records))
1430
1431 self.__db = db
1432 self.__mdwndb = mdwndb
1433
1434 return super().__init__()
1435
1436 def __getitem__(self, key):
1437 def exact_match(key, record):
1438 for name in record.names:
1439 if name == key:
1440 return True
1441
1442 return False
1443
1444 def Rc_match(key, record):
1445 if not key.endswith("."):
1446 return False
1447
1448 if not record.Rc is _RCOE.RC:
1449 return False
1450
1451 return exact_match(key[:-1], record)
1452
1453 def LK_match(key, record):
1454 if not key.endswith("l"):
1455 return False
1456
1457 if "lk" not in record.flags:
1458 return False
1459
1460 return exact_match(key[:-1], record)
1461
1462 def AA_match(key, record):
1463 if not key.endswith("a"):
1464 return False
1465
1466 if record.intop not in {_MicrOp.OP_B, _MicrOp.OP_BC}:
1467 return False
1468
1469 if self.__mdwndb[key]["AA"] is None:
1470 return False
1471
1472 return (exact_match(key[:-1], record) or
1473 LK_match(key[:-1], record))
1474
1475 for (section, records) in self.__db.items():
1476 for record in records:
1477 if exact_match(key, record):
1478 return (section, record)
1479
1480 for record in records:
1481 if (Rc_match(key, record) or
1482 LK_match(key, record) or
1483 AA_match(key, record)):
1484 return (section, record)
1485
1486 return (None, None)
1487
1488
1489 class SVP64Database:
1490 def __init__(self, root, ppcdb):
1491 db = set()
1492 pattern = _re.compile(r"^(?:LDST)?RM-(1P|2P)-.*?\.csv$")
1493 for (prefix, _, names) in _os.walk(root):
1494 prefix = _pathlib.Path(prefix)
1495 for name in filter(lambda name: pattern.match(name), names):
1496 path = (prefix / _pathlib.Path(name))
1497 with open(path, "r", encoding="UTF-8") as stream:
1498 db.update(parse(stream, SVP64Record.CSV))
1499
1500 self.__db = {record.name:record for record in db}
1501 self.__ppcdb = ppcdb
1502
1503 return super().__init__()
1504
1505 def __getitem__(self, key):
1506 (_, record) = self.__ppcdb[key]
1507 if record is None:
1508 return None
1509
1510 for name in record.names:
1511 record = self.__db.get(name, None)
1512 if record is not None:
1513 return record
1514
1515 return None
1516
1517
1518 class Database:
1519 def __init__(self, root):
1520 root = _pathlib.Path(root)
1521
1522 mdwndb = MarkdownDatabase()
1523 fieldsdb = FieldsDatabase()
1524 ppcdb = PPCDatabase(root=root, mdwndb=mdwndb)
1525 svp64db = SVP64Database(root=root, ppcdb=ppcdb)
1526
1527 db = set()
1528 for (name, operands) in mdwndb:
1529 (section, ppc) = ppcdb[name]
1530 if ppc is None:
1531 continue
1532 svp64 = svp64db[name]
1533 fields = fieldsdb[ppc.form]
1534 record = Record(name=name,
1535 section=section, ppc=ppc, svp64=svp64,
1536 operands=operands, fields=fields)
1537 db.add(record)
1538
1539 self.__db = tuple(sorted(db))
1540
1541 return super().__init__()
1542
1543 def __repr__(self):
1544 return repr(self.__db)
1545
1546 def __iter__(self):
1547 yield from self.__db
1548
1549 @_functools.lru_cache(maxsize=None)
1550 def __contains__(self, key):
1551 return self.__getitem__(key) is not None
1552
1553 @_functools.lru_cache(maxsize=None)
1554 def __getitem__(self, key):
1555 if isinstance(key, (int, Instruction)):
1556 key = int(key)
1557 for record in self:
1558 opcode = record.opcode
1559 if ((opcode.value & opcode.mask) ==
1560 (key & opcode.mask)):
1561 return record
1562 return None
1563 elif isinstance(key, Opcode):
1564 for record in self:
1565 if record.opcode == key:
1566 return record
1567 elif isinstance(key, str):
1568 for record in self:
1569 if record.name == key:
1570 return record
1571 return None