xref: /xnu-10002.41.9/tools/lldbmacros/core/collections.py (revision 699cd48037512bf4380799317ca44ca453c82f57)
1"""
2XNU Collection iterators
3"""
4from __future__ import absolute_import
5
6from builtins import object
7from .cvalue import gettype
8from .standard import xnu_format
9
10from six import add_metaclass
11from abc import ABCMeta, abstractmethod, abstractproperty
12
13
14#
15# Note to implementers
16# ~~~~~~~~~~~~~~~~~~~~
17#
18# Iterators must be named iter_* in accordance with typical python API naming
19#
20# The "root" or "head" of the collection must be passed by reference
21# and not by pointer.
22#
23# Returned elements must be references to the element type,
24# and not pointers.
25#
26# Intrusive data types should ask for an "element type" (as an SBType),
27# and a field name or path, and use SBType.xContainerOfTransform
28# to cache the transform to apply for the entire iteration.
29#
30
31
32def iter_linked_list(head_value, next_field_or_path,
33        first_field_or_path = None):
34    """
35    Iterates a NULL-terminated linked list.
36
37        Assuming these C types:
38
39            struct container {
40                struct node *list1_head;
41                struct node *list2_head;
42            }
43
44            struct node {
45                struct node *next;
46            }
47
48        and "v" is an SBValue to a `struct container` type,
49        enumerating list1 is:
50
51            iter_linked_list(v, 'next', 'list1_head')
52
53        if "v" is a `struct node *` directly, then the enumeration
54        becomes:
55
56            iter_linked_list(v, 'next')
57
58
59    @param head_value (SBValue)
60        a reference to the list head.
61
62    @param next_field_or_path (str)
63        The name of (or path to if starting with '.')
64        the field linking to the next element.
65
66    @param first_field_or_path (str or None)
67        The name of (or path to if starting with '.')
68        the field from @c head_value holding the pointer
69        to the first element of the list.
70    """
71
72    if first_field_or_path is None:
73        elt = head_value
74    elif first_field_or_path[0] == '.':
75        elt = head_value.chkGetValueForExpressionPath(first_field_or_path)
76    else:
77        elt = head_value.chkGetChildMemberWithName(first_field_or_path)
78
79    if next_field_or_path[0] == '.':
80        while elt.GetValueAsAddress():
81            elt = elt.Dereference()
82            yield elt
83            elt = elt.chkGetValueForExpressionPath(next_field_or_path)
84    else:
85        while elt.GetValueAsAddress():
86            elt = elt.Dereference()
87            yield elt
88            elt = elt.chkGetChildMemberWithName(next_field_or_path)
89
90
91def iter_queue_entries(head_value, elt_type, field_name_or_path, backwards=False):
92    """
93    Iterate over a queue of entries (<kern/queue.h> method 1)
94
95    @param head_value (SBValue)
96        a reference to the list head (queue_head_t)
97
98    @param elt_type (SBType)
99        The type of the elements on the chain
100
101    @param field_name_or_path (str)
102        The name of (or path to if starting with '.') the field
103        containing the struct mpsc_queue_chain linkage.
104
105    @param backwards (bool)
106         Whether the walk is forward or backwards
107    """
108
109    stop = head_value.GetLoadAddress()
110    elt  = head_value
111    key  = 'prev' if backwards else 'next'
112
113    transform = elt_type.xContainerOfTransform(field_name_or_path)
114
115    while True:
116        elt  = elt.chkGetChildMemberWithName(key)
117        addr = elt.GetValueAsAddress()
118        if addr == 0 or addr == stop:
119            break
120        elt = elt.Dereference()
121        yield transform(elt)
122
123
124def iter_queue(head_value, elt_type, field_name_or_path, backwards=False, unpack=None):
125    """
126    Iterate over queue of elements (<kern/queue.h> method 2)
127
128    @param head_value (SBValue)
129        A reference to the list head (queue_head_t)
130
131    @param elt_type (SBType)
132        The type of the elements on the chain
133
134    @param field_name_or_path (str)
135        The name of (or path to if starting with '.') the field
136        containing the struct mpsc_queue_chain linkage.
137
138    @param backwards (bool)
139         Whether the walk is forward or backwards
140
141    @param unpack (Function or None)
142        A function to unpack the pointer or None.
143    """
144
145    stop = head_value.GetLoadAddress()
146    elt  = head_value
147    key  = '.prev' if backwards else '.next'
148    addr = elt.xGetScalarByPath(key)
149    if field_name_or_path[0] == '.':
150        path = field_name_or_path + key
151    else:
152        path = "." + field_name_or_path + key
153
154    while True:
155        if unpack is not None:
156            addr = unpack(addr)
157        if addr == 0 or addr == stop:
158            break
159
160        elt  = elt.xCreateValueFromAddress('element', addr, elt_type)
161        addr = elt.xGetScalarByPath(path)
162        yield elt
163
164
165def iter_circle_queue(head_value, elt_type, field_name_or_path, backwards=False):
166    """
167    Iterate over a queue of entries (<kern/circle_queue.h>)
168
169    @param head_value (SBValue)
170        a reference to the list head (circle_queue_head_t)
171
172    @param elt_type (SBType)
173        The type of the elements on the chain
174
175    @param field_name_or_path (str)
176        The name of (or path to if starting with '.') the field
177        containing the struct mpsc_queue_chain linkage.
178
179    @param backwards (bool)
180         Whether the walk is forward or backwards
181    """
182
183    elt  = head_value.chkGetChildMemberWithName('head')
184    stop = elt.GetValueAsAddress()
185    key  = 'prev' if backwards else 'next'
186
187    transform = elt_type.xContainerOfTransform(field_name_or_path)
188
189    if stop:
190        if backwards:
191            elt  = elt.chkGetValueForExpressionPath('->prev')
192            stop = elt.GetValueAsAddress()
193
194        while True:
195            elt = elt.Dereference()
196            yield transform(elt)
197
198            elt  = elt.chkGetChildMemberWithName(key)
199            addr = elt.GetValueAsAddress()
200            if addr == 0 or addr == stop:
201                break
202
203
204def iter_mpsc_queue(head_value, elt_type, field_name_or_path):
205    """
206    Iterates a struct mpsc_queue_head
207
208    @param head_value (SBValue)
209        A struct mpsc_queue_head value.
210
211    @param elt_type (SBType)
212        The type of the elements on the chain.
213
214    @param field_name_or_path (str)
215        The name of (or path to if starting with '.') the field
216        containing the struct mpsc_queue_chain linkage.
217
218    @returns (generator)
219        An iterator for the MPSC queue.
220    """
221
222    transform = elt_type.xContainerOfTransform(field_name_or_path)
223
224    return (transform(e) for e in iter_linked_list(
225        head_value, 'mpqc_next', '.mpqh_head.mpqc_next'
226    ))
227
228
229class iter_priority_queue(object):
230    """
231    Iterates any of the priority queues from <kern/priority_queue.h>
232
233    @param head_value (SBValue)
234        A struct priority_queue* value.
235
236    @param elt_type (SBType)
237        The type of the elements on the chain.
238
239    @param field_name_or_path (str)
240        The name of (or path to if starting with '.') the field
241        containing the struct priority_queue_entry* linkage.
242
243    @returns (generator)
244        An iterator for the priority queue.
245    """
246
247    def __init__(self, head_value, elt_type, field_name_or_path):
248        self.transform = elt_type.xContainerOfTransform(field_name_or_path)
249
250        self.gen   = iter_linked_list(head_value, 'next', 'pq_root')
251        self.queue = []
252
253    def __iter__(self):
254        return self
255
256    def __next__(self):
257        queue = self.queue
258        elt   = next(self.gen, None)
259
260        if elt is None:
261            if not len(queue):
262                raise StopIteration
263            elt = queue.pop()
264            self.gen = iter_linked_list(elt, 'next', 'next')
265
266        addr = elt.xGetScalarByName('child')
267        if addr:
268            queue.append(elt.xCreateValueFromAddress(
269                None, addr & 0xffffffffffffffff, elt.GetType()))
270
271        return self.transform(elt)
272
273def iter_SLIST_HEAD(head_value, link_field):
274    """ Specialized version of iter_linked_list() for <sys/queue.h> SLIST_HEAD """
275
276    next_path = ".{}.sle_next".format(link_field)
277    return (e for e in iter_linked_list(head_value, next_path, "slh_first"))
278
279def iter_LIST_HEAD(head_value, link_field):
280    """ Specialized version of iter_linked_list() for <sys/queue.h> LIST_HEAD """
281
282    next_path = ".{}.le_next".format(link_field)
283    return (e for e in iter_linked_list(head_value, next_path, "lh_first"))
284
285def iter_STAILQ_HEAD(head_value, link_field):
286    """ Specialized version of iter_linked_list() for <sys/queue.h> STAILQ_HEAD """
287
288    next_path = ".{}.stqe_next".format(link_field)
289    return (e for e in iter_linked_list(head_value, next_path, "stqh_first"))
290
291def iter_TAILQ_HEAD(head_value, link_field):
292    """ Specialized version of iter_linked_list() for <sys/queue.h> TAILQ_HEAD """
293
294    next_path = ".{}.tqe_next".format(link_field)
295    return (e for e in iter_linked_list(head_value, next_path, "tqh_first"))
296
297def iter_SYS_QUEUE_HEAD(head_value, link_field):
298    """ Specialized version of iter_linked_list() for any <sys/queue> *_HEAD """
299
300    field  = head_value.rawGetType().GetFieldAtIndex(0).GetName()
301    next_path = ".{}.{}e_next".format(link_field, field.partition('_')[0])
302    return (e for e in iter_linked_list(head_value, next_path, field))
303
304
305class RB_HEAD(object):
306    """
307    Class providing utilities to manipulate a collection made with RB_HEAD()
308    """
309
310    def __init__(self, rbh_root_value, link_field, cmp):
311        """
312        Create an rb-tree wrapper
313
314        @param rbh_root_value (SBValue)
315            A value to an RB_HEAD() field
316
317        @param link_field (str)
318            The name of the RB_ENTRY() field in the elements
319
320        @param cmp (function)
321            The comparison function between a linked element,
322            and a key
323        """
324
325        self.root_sbv = rbh_root_value.chkGetChildMemberWithName('rbh_root')
326        self.field    = link_field
327        self.cmp      = cmp
328        self.etype    = self.root_sbv.GetType().GetPointeeType()
329
330    def _parent(self, elt):
331        RB_COLOR_MASK = 0x1
332
333        path   = "." + self.field + ".rbe_parent"
334        parent = elt.chkGetValueForExpressionPath(path)
335        paddr  = parent.GetValueAsAddress()
336
337        if paddr == 0:
338            return None, 0
339
340        if paddr & RB_COLOR_MASK == 0:
341            return parent.Dereference(), paddr
342
343        paddr &= ~RB_COLOR_MASK
344        return parent.xCreateValueFromAddress(None, paddr, self.etype), paddr
345
346    def _sibling(self, elt, rbe_left, rbe_right):
347
348        field = self.field
349        lpath = "." + field + rbe_left
350        rpath = "." + field + rbe_right
351
352        e_r = elt.chkGetValueForExpressionPath(rpath)
353        if e_r.GetValueAsAddress():
354            #
355            # IF (RB_RIGHT(elm, field)) {
356            #     elm = RB_RIGHT(elm, field);
357            #     while (RB_LEFT(elm, field))
358            #         elm = RB_LEFT(elm, field);
359            #
360
361            path = "->" + field + rbe_left
362            res = e_r
363            e_l = res.chkGetValueForExpressionPath(path)
364            while e_l.GetValueAsAddress():
365                res = e_l
366                e_l = res.chkGetValueForExpressionPath(path)
367
368            return res.Dereference()
369
370        eaddr = elt.GetLoadAddress()
371        e_p, paddr = self._parent(elt)
372
373        if paddr:
374            #
375            # IF (RB_GETPARENT(elm) &&
376            #     (elm == RB_LEFT(RB_GETPARENT(elm), field)))
377            #         elm = RB_GETPARENT(elm)
378            #
379
380            if e_p.xGetScalarByPath(lpath) == eaddr:
381                return e_p
382
383            #
384            # WHILE (RB_GETPARENT(elm) &&
385            #     (elm == RB_RIGHT(RB_GETPARENT(elm), field)))
386            #         elm = RB_GETPARENT(elm);
387            # elm = RB_GETPARENT(elm);
388            #
389
390            while paddr:
391                if e_p.xGetScalarByPath(rpath) != eaddr:
392                    return e_p
393
394                eaddr = paddr
395                e_p, paddr = self._parent(e_p)
396
397        return None
398
399    def _find(self, key):
400        elt = self.root_sbv
401        f   = self.field
402        le  = None
403        ge  = None
404
405        while elt.GetValueAsAddress():
406            elt = elt.Dereference()
407            rc  = self.cmp(elt, key)
408            if rc == 0:
409                return elt, elt, elt
410
411            if rc < 0:
412                ge  = elt
413                elt = elt.chkGetValueForExpressionPath("->" + f + ".rbe_left")
414            else:
415                le  = elt
416                elt = elt.chkGetValueForExpressionPath("->" + f + ".rbe_right")
417
418        return le, None, ge
419
420    def _minmax(self, direction):
421        """ Returns the first element in the tree """
422
423        elt  = self.root_sbv
424        res  = None
425        path = "->" + self.field + direction
426
427        while elt.GetValueAsAddress():
428            res = elt
429            elt = elt.chkGetValueForExpressionPath(path)
430
431        return res.Dereference() if res is not None else None
432
433    def find_lt(self, key):
434        """ Find the element smaller than the specified key """
435
436        elt, _, _ = self._find(key)
437        return self.prev(elt) if elt is not None else None
438
439    def find_le(self, key):
440        """ Find the element smaller or equal to the specified key """
441
442        elt, _, _ = self._find(key)
443        return elt
444
445    def find(self, key):
446        """ Find the element with the specified key """
447
448        _, elt, _ = self._find(key)
449        return elt
450
451    def find_ge(self, key):
452        """ Find the element greater or equal to the specified key """
453
454        _, _, elt = self._find(key)
455        return elt
456
457    def find_gt(self, key):
458        """ Find the element greater than the specified key """
459
460        _, _, elt = self._find(key)
461        return self.next(elt.Dereference()) if elt is not None else None
462
463    def first(self):
464        """ Returns the first element in the tree """
465
466        return self._minmax(".rbe_left")
467
468    def last(self):
469        """ Returns the last element in the tree """
470
471        return self._minmax(".rbe_right")
472
473    def next(self, elt):
474        """ Returns the next element in rbtree order or None """
475
476        return self._sibling(elt, ".rbe_left", ".rbe_right")
477
478    def prev(self, elt):
479        """ Returns the next element in rbtree order or None """
480
481        return self._sibling(elt, ".rbe_right", ".rbe_left")
482
483    def iter(self, min_key=None, max_key=None):
484        """
485        Iterates all elements in this red-black tree
486        with min_key <= key < max_key
487        """
488
489        e = self.first() if min_key is None else self.find_ge(min_key)
490
491        if max_key is None:
492            while e is not None:
493                yield e
494                e = self.next(e)
495        else:
496            cmp = self.cmp
497            while e is not None and cmp(e, max_key) >= 0:
498                yield e
499                e = self.next(e)
500
501    def __iter__(self):
502        return self.iter()
503
504
505def iter_RB_HEAD(rbh_root_value, link_field):
506    """ Conveniency wrapper for RB_HEAD iteration """
507
508    return RB_HEAD(rbh_root_value, link_field, None).iter()
509
510
511def iter_smr_queue(head_value, elt_type, field_name_or_path):
512    """
513    Iterate over an SMR queue of entries (<kern/smr.h>)
514
515    @param head_value (SBValue)
516        a reference to the list head (struct smrq_*_head)
517
518    @param elt_type (SBType)
519        The type of the elements on the chain
520
521    @param field_name_or_path (str)
522        The name of (or path to if starting with '.') the field
523        containing the struct mpsc_queue_chain linkage.
524    """
525
526    transform = elt_type.xContainerOfTransform(field_name_or_path)
527
528    return (transform(e) for e in iter_linked_list(
529        head_value, '.next.__smr_ptr', '.first.__smr_ptr'
530    ))
531
532@add_metaclass(ABCMeta)
533class _Hash(object):
534    @abstractproperty
535    def buckets(self):
536        """
537        Returns the number of buckets in the hash table
538        """
539        pass
540
541    @abstractproperty
542    def count(self):
543        """
544        Returns the number of elements in the hash table
545        """
546        pass
547
548    @abstractproperty
549    def rehashing(self):
550        """
551        Returns whether the hash is currently rehashing
552        """
553        pass
554
555    @abstractmethod
556    def iter(self, detailed=False):
557        """
558        @param detailed (bool)
559            whether to enumerate just elements, or show bucket info too
560            when bucket info is requested, enumeration returns a tuple of:
561            (0, bucket, index_in_bucket, element)
562        """
563        pass
564
565    def __iter__(self):
566        return self.iter()
567
568    def describe(self):
569        fmt = (
570            "Hash table info\n"
571            " address              : {1:#x}\n"
572            " element count        : {0.count}\n"
573            " bucket count         : {0.buckets}\n"
574            " rehashing            : {0.rehashing}"
575        )
576        print(xnu_format(fmt, self, self.hash_value.GetLoadAddress()))
577
578        if self.rehashing:
579            print()
580            return
581
582        b_len = {}
583        for _, b_idx, e_idx, _ in self.iter(detailed=True):
584            b_len[b_idx] = e_idx + 1
585
586        stats = {i: 0 for i in range(max(b_len.values()) + 1) }
587        for v in b_len.values():
588            stats[v] += 1
589        stats[0] = self.buckets - len(b_len)
590
591        fmt = (
592            " histogram            :\n"
593            "  {:>4}  {:>6}  {:>6}  {:>6}  {:>5}"
594        )
595        print(xnu_format(fmt, "size", "count", "(cum)", "%", "(cum)"))
596
597        fmt = "  {:>4,d}  {:>6,d}  {:>6,d}  {:>6.1%}  {:>5.0%}"
598        tot = 0
599        for sz, n in stats.items():
600            tot += n
601            print(xnu_format(fmt, sz, n, tot, n / self.buckets, tot / self.buckets))
602
603
604class SMRHash(_Hash):
605    """
606    Class providing utilities to manipulate SMR hash tables
607    """
608
609    def __init__(self, hash_value, traits_value):
610        """
611        Create an smr hash table iterator
612
613        @param hash_value (SBValue)
614            a reference to a struct smr_hash instance.
615
616        @param traits_value (SBValue)
617            a reference to the traits for this hash table
618        """
619        super(SMRHash, self).__init__()
620        self.hash_value = hash_value
621        self.traits_value = traits_value
622
623    @property
624    def buckets(self):
625        hash_arr = self.hash_value.xGetScalarByName('smrh_array')
626        return 1 << (64 - ((hash_arr >> 48) & 0xffff))
627
628    @property
629    def count(self):
630        return self.hash_value.xGetScalarByName('smrh_count')
631
632    @property
633    def rehashing(self):
634        return self.hash_value.xGetScalarByName('smrh_resizing')
635
636    def iter(self, detailed=False):
637        obj_null = self.traits_value.chkGetChildMemberWithName('smrht_obj_type')
638        obj_ty   = obj_null.GetType().GetPointeeType()
639        lnk_offs = self.traits_value.xGetScalarByPath('.smrht.link_offset')
640        hash_arr = self.hash_value.xGetScalarByName('smrh_array')
641        hash_sz  = 1 << (64 - ((hash_arr >> 48) & 0xffff))
642        hash_arr = obj_null.xCreateValueFromAddress(None,
643            hash_arr | 0xffff000000000000, gettype('struct smrq_slist_head'));
644
645        if detailed:
646            return (
647                (0, head_idx, e_idx, e.xCreateValueFromAddress(None, e.GetLoadAddress() - lnk_offs, obj_ty))
648                for head_idx, head in enumerate(hash_arr.xIterSiblings(0, hash_sz))
649                for e_idx, e in enumerate(iter_linked_list(head, '.next.__smr_ptr', '.first.__smr_ptr'))
650            )
651
652        return (
653            e.xCreateValueFromAddress(None, e.GetLoadAddress() - lnk_offs, obj_ty)
654            for head in hash_arr.xIterSiblings(0, hash_sz)
655            for e in iter_linked_list(head, '.next.__smr_ptr', '.first.__smr_ptr')
656        )
657
658
659class SMRScalableHash(_Hash):
660
661    def __init__(self, hash_value, traits_value):
662        """
663        Create an smr hash table iterator
664
665        @param hash_value (SBValue)
666            a reference to a struct smr_shash instance.
667
668        @param traits_value (SBValue)
669            a reference to the traits for this hash table
670        """
671        super(SMRScalableHash, self).__init__()
672        self.hash_value = hash_value
673        self.traits_value = traits_value
674
675    @property
676    def buckets(self):
677        shift = self.hash_value.xGetScalarByPath('.smrsh_state.curshift')
678        return (0xffffffff >> shift) + 1;
679
680    @property
681    def count(self):
682        sbv      = self.hash_value.chkGetChildMemberWithName('smrsh_count')
683        addr     = sbv.GetValueAsAddress() | 0x8000000000000000
684        target   = sbv.GetTarget()
685        ncpus    = target.chkFindFirstGlobalVariable('zpercpu_early_count').xGetValueAsInteger()
686        pg_shift = target.chkFindFirstGlobalVariable('page_shift').xGetValueAsInteger()
687
688        return sum(
689            target.xReadInt64(addr + (cpu << pg_shift))
690            for cpu in range(ncpus)
691        )
692
693    @property
694    def rehashing(self):
695        curidx = self.hash_value.xGetScalarByPath('.smrsh_state.curidx');
696        newidx = self.hash_value.xGetScalarByPath('.smrsh_state.newidx');
697        return curidx != newidx
698
699    def iter(self, detailed=False):
700        """
701        @param detailed (bool)
702            whether to enumerate just elements, or show bucket info too
703            when bucket info is requested, enumeration returns a tuple of:
704            (table_index, bucket, index_in_bucket, element)
705        """
706
707        hash_value   = self.hash_value
708        traits_value = self.traits_value
709
710        obj_null = traits_value.chkGetChildMemberWithName('smrht_obj_type')
711        obj_ty   = obj_null.GetType().GetArrayElementType().GetPointeeType()
712        lnk_offs = traits_value.xGetScalarByPath('.smrht.link_offset')
713        hashes   = []
714
715        curidx   = hash_value.xGetScalarByPath('.smrsh_state.curidx');
716        newidx   = hash_value.xGetScalarByPath('.smrsh_state.newidx');
717        arrays   = hash_value.chkGetChildMemberWithName('smrsh_array')
718
719        array    = arrays.chkGetChildAtIndex(curidx)
720        shift    = hash_value.xGetScalarByPath('.smrsh_state.curshift')
721        hashes.append((curidx, array.Dereference(), shift))
722        if newidx != curidx:
723            array    = arrays.chkGetChildAtIndex(newidx)
724            shift    = hash_value.xGetScalarByPath('.smrsh_state.newshift')
725            hashes.append((newidx, array.Dereference(), shift))
726
727        seen = set()
728
729        def _iter_smr_shash_bucket(head):
730            addr  = head.xGetScalarByName('lck_ptr_bits')
731            tg    = head.GetTarget()
732
733            while addr & 1 == 0:
734                addr &= 0xfffffffffffffffe
735                e = head.xCreateValueFromAddress(None, addr - lnk_offs, obj_ty)
736                if addr not in seen:
737                    seen.add(addr)
738                    yield e
739                addr = tg.xReadULong(addr);
740
741        if detailed:
742            return (
743                (hash_idx, head_idx, e_idx, e)
744                for hash_idx, hash_arr, hash_shift in hashes
745                for head_idx, head in enumerate(hash_arr.xIterSiblings(
746                    0, 1 + (0xffffffff >> hash_shift)))
747                for e_idx, e in enumerate(_iter_smr_shash_bucket(head))
748            )
749
750        return (
751            e
752            for hash_idx, hash_arr, hash_shift in hashes
753            for head in hash_arr.xIterSiblings(
754                0, 1 + (0xffffffff >> hash_shift))
755            for e in _iter_smr_shash_bucket(head)
756        )
757
758
759__all__ = [
760    iter_linked_list.__name__,
761    iter_queue_entries.__name__,
762    iter_queue.__name__,
763    iter_circle_queue.__name__,
764
765    iter_mpsc_queue.__name__,
766
767    iter_priority_queue.__name__,
768
769    iter_SLIST_HEAD.__name__,
770    iter_LIST_HEAD.__name__,
771    iter_STAILQ_HEAD.__name__,
772    iter_TAILQ_HEAD.__name__,
773    iter_SYS_QUEUE_HEAD.__name__,
774    iter_RB_HEAD.__name__,
775    RB_HEAD.__name__,
776
777    iter_smr_queue.__name__,
778    SMRHash.__name__,
779    SMRScalableHash.__name__,
780]
781