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