• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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"""
24Python package containing common tools for manipulating register JSON.
25"""
26
27from __future__ import absolute_import, division, print_function, unicode_literals
28
29import itertools
30import json
31import re
32import sys
33
34from collections import defaultdict
35from contextlib import contextmanager
36
37class 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
87class 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
134class MergeError(Exception):
135    def __init__(self, msg):
136        super(MergeError, self).__init__(msg)
137
138class RegisterDatabaseError(Exception):
139    def __init__(self, msg):
140        super(RegisterDatabaseError, self).__init__(msg)
141
142@contextmanager
143def 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
153def 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
188def 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
194class 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
838def 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
850def 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;
902