amd/registers: sort registers by offset in json
[mesa.git] / src / amd / registers / regdb.py
1 #
2 # Copyright 2017-2019 Advanced Micro Devices, Inc.
3 #
4 # Permission is hereby granted, free of charge, to any person obtaining a
5 # copy of this software and associated documentation files (the "Software"),
6 # to deal in the Software without restriction, including without limitation
7 # on the rights to use, copy, modify, merge, publish, distribute, sub
8 # license, and/or sell copies of the Software, and to permit persons to whom
9 # the Software is furnished to do so, subject to the following conditions:
10 #
11 # The above copyright notice and this permission notice (including the next
12 # paragraph) shall be included in all copies or substantial portions of the
13 # Software.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
18 # THE AUTHOR(S) AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM,
19 # DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20 # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21 # USE OR OTHER DEALINGS IN THE SOFTWARE.
22 #
23 """
24 Python package containing common tools for manipulating register JSON.
25 """
26
27 from __future__ import absolute_import, division, print_function, unicode_literals
28
29 import itertools
30 import json
31 import re
32 import sys
33
34 from collections import defaultdict
35 from contextlib import contextmanager
36
37 class UnionFind(object):
38 """
39 Simplistic implementation of a union-find data structure that also keeps
40 track of the sets that have been unified.
41
42 - add: add an element to the implied global set of elements
43 - union: unify the sets containing the two given elements
44 - find: return the representative element of the set containing the
45 given element
46 - get_set: get the set containing the given element
47 - sets: iterate over all sets (the sets form a partition of the set of all
48 elements that have ever been added)
49 """
50 def __init__(self):
51 self.d = {}
52
53 def add(self, k):
54 if k not in self.d:
55 self.d[k] = set([k])
56
57 def union(self, k1, k2):
58 k1 = self.find(k1)
59 k2 = self.find(k2)
60 if k1 == k2:
61 return
62 if len(k1) < len(k2):
63 k1, k2 = k2, k1
64 self.d[k1].update(self.d[k2])
65 self.d[k2] = (k1,)
66
67 def find(self, k):
68 e = self.d[k]
69 if isinstance(e, set):
70 return k
71 assert isinstance(e, tuple)
72 r = self.find(e[0])
73 self.d[k] = (r,)
74 return r
75
76 def get_set(self, k):
77 k = self.find(k)
78 assert isinstance(self.d[k], set)
79 return self.d[k]
80
81 def sets(self):
82 for v in self.d.values():
83 if isinstance(v, set):
84 yield v
85
86
87 class Object(object):
88 """
89 Convenience helper class that essentially acts as a dictionary for convenient
90 conversion from and to JSON while allowing the use of .field notation
91 instead of subscript notation for member access.
92 """
93 def __init__(self, **kwargs):
94 for k, v in kwargs.items():
95 setattr(self, k, v)
96
97 def update(self, **kwargs):
98 for key, value in kwargs.items():
99 setattr(self, key, value)
100 return self
101
102 def __str__(self):
103 return 'Object(' + ', '.join(
104 '{k}={v}'.format(**locals()) for k, v, in self.__dict__.items()
105 ) + ')'
106
107 @staticmethod
108 def from_json(json, keys=None):
109 if isinstance(json, list):
110 return [Object.from_json(v) for v in json]
111 elif isinstance(json, dict):
112 obj = Object()
113 for k, v in json.items():
114 if keys is not None and k in keys:
115 v = keys[k](v)
116 else:
117 v = Object.from_json(v)
118 setattr(obj, k, v)
119 return obj
120 else:
121 return json
122
123 @staticmethod
124 def to_json(obj):
125 if isinstance(obj, Object):
126 return dict((k, Object.to_json(v)) for k, v in obj.__dict__.items())
127 elif isinstance(obj, dict):
128 return dict((k, Object.to_json(v)) for k, v in obj.items())
129 elif isinstance(obj, list):
130 return [Object.to_json(v) for v in obj]
131 else:
132 return obj
133
134 class MergeError(Exception):
135 def __init__(self, msg):
136 super(MergeError, self).__init__(msg)
137
138 class RegisterDatabaseError(Exception):
139 def __init__(self, msg):
140 super(RegisterDatabaseError, self).__init__(msg)
141
142 @contextmanager
143 def merge_scope(name):
144 """
145 Wrap a merge handling function in a "scope" whose name will be added when
146 propagating MergeErrors.
147 """
148 try:
149 yield
150 except Exception as e:
151 raise MergeError('{name}: {e}'.format(**locals()))
152
153 def merge_dicts(dicts, keys=None, values=None):
154 """
155 Generic dictionary merging function.
156
157 dicts -- list of (origin, dictionary) pairs to merge
158 keys -- optional dictionary to provide a merge-strategy per key;
159 the merge strategy is a callable which will receive a list of
160 (origin, value) pairs
161 value -- optional function which provides a merge-strategy for values;
162 the merge strategy is a callable which will receive the name of
163 the key and a list of (origin, value) pairs
164
165 The default strategy is to allow merging keys if all origin dictionaries
166 that contain the key have the same value for it.
167 """
168 ks = set()
169 for _, d in dicts:
170 ks.update(d.keys())
171
172 result = {}
173 for k in ks:
174 vs = [(o, d[k]) for o, d in dicts if k in d]
175 with merge_scope('Key {k}'.format(**locals())):
176 if keys is not None and k in keys:
177 result[k] = keys[k](vs)
178 elif values is not None:
179 result[k] = values(k, vs)
180 else:
181 base_origin, base = vs[0]
182 for other_origin, other in vs[1:]:
183 if base != other:
184 raise MergeError('{base} (from {base_origin}) != {other} (from {other_origin})'.format(**locals()))
185 result[k] = base
186 return result
187
188 def merge_objects(objects, keys=None):
189 """
190 Like merge_dicts, but applied to instances of Object.
191 """
192 return Object(**merge_dicts([(origin, obj.__dict__) for origin, obj in objects], keys=keys))
193
194 class RegisterDatabase(object):
195 """
196 A register database containing:
197
198 - enums: these are lists of named values that can occur in a register field
199 - register types: description of a register type or template as a list of
200 fields
201 - register mappings: named and typed registers mapped at locations in an
202 address space
203 """
204 def __init__(self):
205 self.__enums = {}
206 self.__register_types = {}
207 self.__register_mappings = []
208 self.__regmap_by_addr = None
209 self.__chips = None
210
211 def __post_init(self):
212 """
213 Perform some basic canonicalization:
214 - enum entries are sorted by value
215 - register type fields are sorted by starting bit
216 - __register_mappings is sorted by offset
217 - the chips field of register mappings is sorted
218
219 Lazily computes the set of all chips mentioned by register mappings.
220 """
221 if self.__regmap_by_addr is not None:
222 return
223
224 for enum in self.__enums.values():
225 enum.entries.sort(key=lambda entry: entry.value)
226
227 for regtype in self.__register_types.values():
228 regtype.fields.sort(key=lambda field: field.bits[0])
229
230 self.__regmap_by_addr = defaultdict(list)
231 self.__chips = set()
232
233 # Merge register mappings using sort order and garbage collect enums
234 # and register types.
235 old_register_mappings = self.__register_mappings
236 old_register_mappings.sort(key=lambda regmap: regmap.map.at)
237
238 self.__register_mappings = []
239 for regmap in old_register_mappings:
240 addr = (regmap.map.to, regmap.map.at)
241 chips = set(getattr(regmap, 'chips', ['undef']))
242 type_ref = getattr(regmap, 'type_ref', None)
243
244 self.__chips.update(chips)
245
246 merged = False
247 for other in reversed(self.__register_mappings):
248 if other.name != regmap.name:
249 break
250
251 other_addr = (other.map.to, other.map.at)
252 other_chips = getattr(other, 'chips', ['undef'])
253 other_type_ref = getattr(other, 'type_ref', None)
254
255 if addr == other_addr and\
256 (type_ref is None or other_type_ref is None or type_ref == other_type_ref):
257 other.chips = sorted(list(chips.union(other_chips)))
258 if type_ref is not None:
259 other.type_ref = type_ref
260 merged = True
261 break
262
263 if merged:
264 continue
265
266 addrmappings = self.__regmap_by_addr[addr]
267
268 for other in addrmappings:
269 other_type_ref = getattr(other, 'type_ref', None)
270 other_chips = getattr(other, 'chips', ['undef'])
271 if type_ref is not None and other_type_ref is not None and \
272 type_ref != other_type_ref and chips.intersection(other_chips):
273 raise RegisterDatabaseError(
274 'Registers {0} and {1} overlap and have conflicting types'.format(
275 other.name, regmap.name))
276
277 addrmappings.append(regmap)
278 self.__register_mappings.append(regmap)
279
280 def garbage_collect(self):
281 """
282 Remove unreferenced enums and register types.
283 """
284 old_enums = self.__enums
285 old_register_types = self.__register_types
286
287 self.__enums = {}
288 self.__register_types = {}
289 for regmap in self.__register_mappings:
290 if hasattr(regmap, 'type_ref') and regmap.type_ref not in self.__register_types:
291 regtype = old_register_types[regmap.type_ref]
292 self.__register_types[regmap.type_ref] = regtype
293 for field in regtype.fields:
294 if hasattr(field, 'enum_ref') and field.enum_ref not in self.__enums:
295 self.__enums[field.enum_ref] = old_enums[field.enum_ref]
296
297 def __validate_register_type(self, regtype):
298 for field in regtype.fields:
299 if hasattr(field, 'enum_ref') and field.enum_ref not in self.__enums:
300 raise RegisterDatabaseError(
301 'Register type field {0} has unknown enum_ref {1}'.format(
302 field.name, field.enum_ref))
303
304 def __validate_register_mapping(self, regmap):
305 if hasattr(regmap, 'type_ref') and regmap.type_ref not in self.__register_types:
306 raise RegisterDatabaseError(
307 'Register mapping {0} has unknown type_ref {1}'.format(
308 regmap.name, regmap.type_ref))
309
310 def __validate(self):
311 for regtype in self.__register_types.values():
312 self.__validate_register_type(regtype)
313 for regmap in self.__register_mappings:
314 self.__validate_register_mapping(regmap)
315
316 @staticmethod
317 def enum_key(enum):
318 """
319 Return a key that uniquely describes the signature of the given
320 enum (assuming that it has been canonicalized). Two enums with the
321 same key can be merged.
322 """
323 return ''.join(
324 ':{0}:{1}'.format(entry.name, entry.value)
325 for entry in enum.entries
326 )
327
328 def add_enum(self, name, enum):
329 if name in self.__enums:
330 raise RegisterDatabaseError('Duplicate enum ' + name)
331 self.__enums[name] = enum
332
333 @staticmethod
334 def __merge_enums(enums, union=False):
335 def merge_entries(entries_lists):
336 values = defaultdict(list)
337 for origin, enum in entries_lists:
338 for entry in enum:
339 values[entry.value].append((origin, entry))
340
341 if not union:
342 if any(len(entries) != len(enums) for entries in values.values()):
343 raise RegisterDatabaseError(
344 'Attempting to merge enums with different values')
345
346 return [
347 merge_objects(entries)
348 for entries in values.values()
349 ]
350
351 return merge_objects(
352 enums,
353 keys={
354 'entries': merge_entries,
355 }
356 )
357
358 def merge_enums(self, names, newname, union=False):
359 """
360 Given a list of enum names, merge them all into one with a new name and
361 update all references.
362 """
363 if newname not in names and newname in self.__enums:
364 raise RegisterDatabaseError('Enum {0} already exists'.format(newname))
365
366 newenum = self.__merge_enums(
367 [(name, self.__enums[name]) for name in names],
368 union=union
369 )
370
371 for name in names:
372 del self.__enums[name]
373 self.__enums[newname] = newenum
374
375 for regtype in self.__register_types.values():
376 for field in regtype.fields:
377 if getattr(field, 'enum_ref', None) in names:
378 field.enum_ref = newname
379
380 self.__regmap_by_addr = None
381
382 def add_register_type(self, name, regtype):
383 if regtype in self.__register_types:
384 raise RegisterDatabaseError('Duplicate register type ' + name)
385 self.__register_types[name] = regtype
386 self.__validate_register_type(regtype)
387
388 def register_type(self, name):
389 self.__post_init()
390 return self.__register_types[name]
391
392 @staticmethod
393 def __merge_register_types(regtypes, union=False, field_keys={}):
394 def merge_fields(fields_lists):
395 fields = defaultdict(list)
396 for origin, fields_list in fields_lists:
397 for field in fields_list:
398 fields[field.bits[0]].append((origin, field))
399
400 if not union:
401 if any(len(entries) != len(regtypes) for entries in fields.values()):
402 raise RegisterDatabaseError(
403 'Attempting to merge register types with different fields')
404
405 return [
406 merge_objects(field, keys=field_keys)
407 for field in fields.values()
408 ]
409
410 with merge_scope('Register types {0}'.format(', '.join(name for name, _ in regtypes))):
411 return merge_objects(
412 regtypes,
413 keys={
414 'fields': merge_fields,
415 }
416 )
417
418 def merge_register_types(self, names, newname, union=False):
419 """
420 Given a list of register type names, merge them all into one with a
421 new name and update all references.
422 """
423 if newname not in names and newname in self.__register_types:
424 raise RegisterDatabaseError('Register type {0} already exists'.format(newname))
425
426 newregtype = self.__merge_register_types(
427 [(name, self.__register_types[name]) for name in names],
428 union=union
429 )
430
431 for name in names:
432 del self.__register_types[name]
433 self.__register_types[newname] = newregtype
434
435 for regmap in self.__register_mappings:
436 if getattr(regmap, 'type_ref', None) in names:
437 regmap.type_ref = newname
438
439 self.__regmap_by_addr = None
440
441 def add_register_mapping(self, regmap):
442 self.__regmap_by_addr = None
443 self.__register_mappings.append(regmap)
444 self.__validate_register_mapping(regmap)
445
446 def remove_register_mappings(self, regmaps_to_remove):
447 self.__post_init()
448
449 regmaps_to_remove = set(regmaps_to_remove)
450
451 regmaps = self.__register_mappings
452 self.__register_mappings = []
453 for regmap in regmaps:
454 if regmap not in regmaps_to_remove:
455 self.__register_mappings.append(regmap)
456
457 self.__regmap_by_addr = None
458
459 def enum(self, name):
460 """
461 Return the enum of the given name, if any.
462 """
463 self.__post_init()
464 return self.__enums.get(name, None)
465
466 def enums(self):
467 """
468 Yields all (name, enum) pairs.
469 """
470 self.__post_init()
471 for name, enum in self.__enums.items():
472 yield (name, enum)
473
474 def fields(self):
475 """
476 Yields all (register_type, fields) pairs.
477 """
478 self.__post_init()
479 for regtype in self.__register_types.values():
480 for field in regtype.fields:
481 yield (regtype, field)
482
483 def register_types(self):
484 """
485 Yields all (name, register_type) pairs.
486 """
487 self.__post_init()
488 for name, regtype in self.__register_types.items():
489 yield (name, regtype)
490
491 def register_mappings_by_name(self, name):
492 """
493 Return a list of register mappings with the given name.
494 """
495 self.__post_init()
496
497 begin = 0
498 end = len(self.__register_mappings)
499 while begin < end:
500 middle = (begin + end) // 2
501 if self.__register_mappings[middle].name < name:
502 begin = middle + 1
503 elif name < self.__register_mappings[middle].name:
504 end = middle
505 else:
506 break
507
508 if begin >= end:
509 return []
510
511 # We now have begin <= mid < end with begin.name <= name, mid.name == name, name < end.name
512 # Narrow down begin and end
513 hi = middle
514 while begin < hi:
515 mid = (begin + hi) // 2
516 if self.__register_mappings[mid].name < name:
517 begin = mid + 1
518 else:
519 hi = mid
520
521 lo = middle + 1
522 while lo < end:
523 mid = (lo + end) // 2
524 if self.__register_mappings[mid].name == name:
525 lo = mid + 1
526 else:
527 end = mid
528
529 return self.__register_mappings[begin:end]
530
531 def register_mappings(self):
532 """
533 Yields all register mappings.
534 """
535 self.__post_init()
536 for regmap in self.__register_mappings:
537 yield regmap
538
539 def chips(self):
540 """
541 Yields all chips.
542 """
543 self.__post_init()
544 return iter(self.__chips)
545
546 def merge_chips(self, chips, newchip):
547 """
548 Merge register mappings of the given chips into a single chip of the
549 given name. Recursively merges register types and enums when appropriate.
550 """
551 self.__post_init()
552
553 chips = set(chips)
554
555 regtypes_merge = UnionFind()
556 enums_merge = UnionFind()
557
558 # Walk register mappings to find register types that should be merged.
559 for idx, regmap in itertools.islice(enumerate(self.__register_mappings), 1, None):
560 if not hasattr(regmap, 'type_ref'):
561 continue
562 if chips.isdisjoint(regmap.chips):
563 continue
564
565 for other in self.__register_mappings[idx-1::-1]:
566 if regmap.name != other.name:
567 break
568 if chips.isdisjoint(other.chips):
569 continue
570 if regmap.map.to != other.map.to or regmap.map.at != other.map.at:
571 raise RegisterDatabaseError(
572 'Attempting to merge chips with incompatible addresses of {0}'.format(regmap.name))
573 if not hasattr(regmap, 'type_ref'):
574 continue
575
576 if regmap.type_ref != other.type_ref:
577 regtypes_merge.add(regmap.type_ref)
578 regtypes_merge.add(other.type_ref)
579 regtypes_merge.union(regmap.type_ref, other.type_ref)
580
581 # Walk over regtype sets that are to be merged and find enums that
582 # should be merged.
583 for type_refs in regtypes_merge.sets():
584 fields_merge = defaultdict(set)
585 for type_ref in type_refs:
586 regtype = self.__register_types[type_ref]
587 for field in regtype.fields:
588 if hasattr(field, 'enum_ref'):
589 fields_merge[field.name].add(field.enum_ref)
590
591 for enum_refs in fields_merge.values():
592 if len(enum_refs) > 1:
593 enum_refs = list(enum_refs)
594 enums_merge.add(enum_refs[0])
595 for enum_ref in enum_refs[1:]:
596 enums_merge.add(enum_ref)
597 enums_merge.union(enum_ref, enum_refs[0])
598
599 # Merge all mergeable enum sets
600 remap_enum_refs = {}
601 for enum_refs in enums_merge.sets():
602 enum_refs = sorted(enum_refs)
603 newname = enum_refs[0] + '_' + newchip
604 i = 0
605 while newname in self.__enums:
606 newname = enum_refs[0] + '_' + newchip + str(i)
607 i += 1
608
609 for enum_ref in enum_refs:
610 remap_enum_refs[enum_ref] = newname
611
612 # Don't use self.merge_enums, because we don't want to automatically
613 # update _all_ references to the merged enums (some may be from
614 # register types that aren't going to be merged).
615 self.add_enum(newname, self.__merge_enums(
616 [(enum_ref, self.__enums[enum_ref]) for enum_ref in enum_refs],
617 union=True
618 ))
619
620 # Merge all mergeable type refs
621 remap_type_refs = {}
622 for type_refs in regtypes_merge.sets():
623 type_refs = sorted(type_refs)
624 newname = type_refs[0] + '_' + newchip
625 i = 0
626 while newname in self.__enums:
627 newname = type_refs[0] + '_' + newchip + str(i)
628 i += 1
629
630 updated_regtypes = []
631 for type_ref in type_refs:
632 remap_type_refs[type_ref] = newname
633
634 regtype = Object.from_json(Object.to_json(self.__register_types[type_ref]))
635 for field in regtype.fields:
636 if hasattr(field, 'enum_ref'):
637 field.enum_ref = remap_enum_refs.get(enum_ref, enum_ref)
638
639 updated_regtypes.append(regtype)
640
641 def merge_enum_refs(enum_refs):
642 enum_refs = set(
643 remap_enum_refs.get(enum_ref, enum_ref)
644 for origin, enum_ref in enum_refs
645 )
646 assert len(enum_refs) == 1 # should be ensured by how we determine the enums to be merged
647 return enum_refs.pop()
648
649 self.add_register_type(newname, self.__merge_register_types(
650 [(type_ref, self.__register_types[type_ref]) for type_ref in type_refs],
651 field_keys={
652 'enum_ref': merge_enum_refs,
653 },
654 union=True
655 ))
656
657 # Merge register mappings
658 register_mappings = self.__register_mappings
659 self.__register_mappings = []
660
661 regmap_accum = None
662 for regmap in register_mappings:
663 if regmap_accum and regmap.name != regmap_accum.name:
664 regmap_accum.chips = [newchip]
665 self.__register_mappings.append(regmap_accum)
666 regmap_accum = None
667
668 joining_chips = chips.intersection(regmap.chips)
669 if not joining_chips:
670 self.__register_mappings.append(regmap)
671 continue
672 remaining_chips = set(regmap.chips).difference(chips)
673
674 type_ref = getattr(regmap, 'type_ref', None)
675 if type_ref is None:
676 regmap.chips = sorted(remaining_chips.union([newchip]))
677 self.__register_mappings.append(regmap)
678 continue
679
680 type_ref = remap_type_refs.get(type_ref, type_ref)
681 if remaining_chips:
682 regmap.chips = sorted(remaining_chips)
683 self.__register_mappings.append(regmap)
684 if not regmap_accum:
685 regmap = Object.from_json(Object.to_json(regmap))
686 if type_ref is not None:
687 regmap.type_ref = type_ref
688
689 if not regmap_accum:
690 regmap_accum = regmap
691 else:
692 if not hasattr(regmap_accum.type_ref, 'type_ref'):
693 if type_ref is not None:
694 regmap_accum.type_ref = type_ref
695 else:
696 assert type_ref is None or type_ref == regmap_accum.type_ref
697 if regmap_accum:
698 self.__register_mappings.append(regmap_accum)
699
700 def update(self, other):
701 """
702 Add the contents of the other database to self.
703
704 Doesn't de-duplicate entries.
705 """
706 self.__post_init()
707 other.__post_init()
708
709 enum_remap = {}
710 regtype_remap = {}
711
712 for regmap in other.__register_mappings:
713 regmap = Object.from_json(Object.to_json(regmap))
714
715 type_ref = getattr(regmap, 'type_ref', None)
716 if type_ref is not None and type_ref not in regtype_remap:
717 regtype = Object.from_json(Object.to_json(other.__register_types[type_ref]))
718
719 chips = getattr(regmap, 'chips', [])
720 suffix = '_' + chips[0] if chips else ''
721
722 for field in regtype.fields:
723 enum_ref = getattr(field, 'enum_ref', None)
724 if enum_ref is not None and enum_ref not in enum_remap:
725 enum = Object.from_json(Object.to_json(other.__enums[enum_ref]))
726
727 remapped = enum_ref + suffix if enum_ref in self.__enums else enum_ref
728 i = 0
729 while remapped in self.__enums:
730 remapped = enum_ref + suffix + str(i)
731 i += 1
732 self.add_enum(remapped, enum)
733 enum_remap[enum_ref] = remapped
734
735 if enum_ref is not None:
736 field.enum_ref = enum_remap[enum_ref]
737
738 remapped = type_ref + suffix if type_ref in self.__register_types else type_ref
739 i = 0
740 while remapped in self.__register_types:
741 remapped = type_ref + suffix + str(i)
742 i += 1
743 self.add_register_type(remapped, regtype)
744 regtype_remap[type_ref] = remapped
745
746 if type_ref is not None:
747 regmap.type_ref = regtype_remap[type_ref]
748
749 self.add_register_mapping(regmap)
750
751 def to_json(self):
752 self.__post_init()
753 return {
754 'enums': Object.to_json(self.__enums),
755 'register_types': Object.to_json(self.__register_types),
756 'register_mappings': Object.to_json(self.__register_mappings),
757 }
758
759 def encode_json_pretty(self):
760 """
761 Use a custom JSON encoder which pretty prints, but keeps inner structures compact
762 """
763 # Since the JSON module isn't very extensible, this ends up being
764 # really hacky.
765 obj = self.to_json()
766
767 replacements = []
768 def placeholder(s):
769 placeholder = "JSON-{key}-NOSJ".format(key=len(replacements))
770 replacements.append(json.dumps(s, sort_keys=True))
771 return placeholder
772
773 # Pre-create non-indented encodings for inner objects
774 for enum in obj['enums'].values():
775 enum['entries'] = [
776 placeholder(entry)
777 for entry in enum['entries']
778 ]
779
780 for regtype in obj['register_types'].values():
781 regtype['fields'] = [
782 placeholder(field)
783 for field in regtype['fields']
784 ]
785
786 for regmap in obj['register_mappings']:
787 regmap['map'] = placeholder(regmap['map'])
788 if 'chips' in regmap:
789 regmap['chips'] = placeholder(regmap['chips'])
790
791 # Now create the 'outer' encoding with indentation and search-and-replace
792 # placeholders
793 result = json.dumps(obj, indent=1, sort_keys=True)
794
795 result = re.sub(
796 '"JSON-([0-9]+)-NOSJ"',
797 lambda m: replacements[int(m.group(1))],
798 result
799 )
800
801 return result
802
803 @staticmethod
804 def from_json(json):
805 db = RegisterDatabase()
806
807 db.__enums = dict((k, Object.from_json(v)) for k, v in json['enums'].items())
808 if 'register_types' in json:
809 db.__register_types = dict(
810 (k, Object.from_json(v))
811 for k, v in json['register_types'].items()
812 )
813 if 'register_mappings' in json:
814 db.__register_mappings = Object.from_json(json['register_mappings'])
815
816 # Old format
817 if 'registers' in json:
818 for reg in json['registers']:
819 type_ref = None
820 if 'fields' in reg and reg['fields']:
821 type_ref = reg['names'][0]
822 db.add_register_type(type_ref, Object(
823 fields=Object.from_json(reg['fields'])
824 ))
825
826 for name in reg['names']:
827 regmap = Object(
828 name=name,
829 map=Object.from_json(reg['map'])
830 )
831 if type_ref is not None:
832 regmap.type_ref = type_ref
833 db.add_register_mapping(regmap)
834
835 db.__post_init()
836 return db
837
838 def deduplicate_enums(regdb):
839 """
840 Find enums that have the exact same entries and merge them.
841 """
842 buckets = defaultdict(list)
843 for name, enum in regdb.enums():
844 buckets[RegisterDatabase.enum_key(enum)].append(name)
845
846 for bucket in buckets.values():
847 if len(bucket) > 1:
848 regdb.merge_enums(bucket, bucket[0])
849
850 def deduplicate_register_types(regdb):
851 """
852 Find register types with the exact same fields (identified by name and
853 bit range) and merge them.
854
855 However, register types *aren't* merged if they have different enums for
856 the same field (as an exception, if one of them has an enum and the other
857 one doesn't, we assume that one is simply missing a bit of information and
858 merge the register types).
859 """
860 buckets = defaultdict(list)
861 for name, regtype in regdb.register_types():
862 key = ''.join(
863 ':{0}:{1}:{2}:'.format(
864 field.name, field.bits[0], field.bits[1],
865 )
866 for field in regtype.fields
867 )
868 buckets[key].append((name, regtype.fields))
869
870 for bucket in buckets.values():
871 # Register types in the same bucket have the same fields in the same
872 # places, but they may have different enum_refs. Allow merging when
873 # one has an enum_ref and another doesn't, but don't merge if they
874 # have enum_refs that differ.
875 bucket_enum_refs = [
876 [getattr(field, 'enum_ref', None) for field in fields]
877 for name, fields in bucket
878 ]
879 while bucket:
880 regtypes = [bucket[0][0]]
881 enum_refs = bucket_enum_refs[0]
882 del bucket[0]
883 del bucket_enum_refs[0]
884
885 idx = 0
886 while idx < len(bucket):
887 if all([
888 not lhs or not rhs or lhs == rhs
889 for lhs, rhs in zip(enum_refs, bucket_enum_refs[idx])
890 ]):
891 regtypes.append(bucket[idx][0])
892 enum_refs = [lhs or rhs for lhs, rhs in zip(enum_refs, bucket_enum_refs[idx])]
893 del bucket[idx]
894 del bucket_enum_refs[idx]
895 else:
896 idx += 1
897
898 if len(regtypes) > 1:
899 regdb.merge_register_types(regtypes, regtypes[0])
900
901 # kate: space-indent on; indent-width 4; replace-tabs on;