speed up ==, hash, <, >, <=, and >= for plain_data
[nmutil.git] / src / nmutil / dynamicpipe.py
1 # SPDX-License-Identifier: LGPL-2.1-or-later
2 # See Notices.txt for copyright information
3
4 """ Meta-class that allows a dynamic runtime parameter-selectable "mixin"
5
6 This work is funded through NLnet under Grant 2019-02-012
7
8 The reasons why this technique is being deployed is because SimpleHandshake
9 needs to be dynamically replaced at the end-users' choice, without having
10 to duplicate dozens of classes using multiple-inheritanc "Mix-in" techniques.
11
12 It is however extremely unusual, and has been explicitly limited to this *one*
13 module. DO NOT try to use this technique elsewhere, it is extremely hard to
14 understand (meta-class programming).
15
16 """
17
18 from abc import ABCMeta
19
20 from nmutil.singlepipe import SimpleHandshake
21 from nmutil.singlepipe import MaskCancellable
22
23 import threading
24
25 # with many thanks to jsbueno on stackexchange for this one
26 # https://stackoverflow.com/questions/57273070/
27 # list post:
28 # http://lists.libre-riscv.org/pipermail/libre-riscv-dev/2019-July/002259.html
29
30
31 class Meta(ABCMeta):
32 registry = {}
33 recursing = threading.local()
34 recursing.check = False
35 mlock = threading.Lock()
36
37 def __call__(cls, *args, **kw):
38 mcls = cls.__class__
39 if mcls.recursing.check:
40 return super().__call__(*args, **kw)
41 spec = args[0]
42 base = spec.pipekls # pick up the dynamic class from PipelineSpec, HERE
43
44 if (cls, base) not in mcls.registry:
45 print("__call__", args, kw, cls, base,
46 base.__bases__, cls.__bases__)
47 mcls.registry[cls, base] = type(
48 cls.__name__,
49 (cls, base) + cls.__bases__[1:],
50 {}
51 )
52 real_cls = mcls.registry[cls, base]
53
54 with mcls.mlock:
55 mcls.recursing.check = True
56 instance = real_cls.__class__.__call__(real_cls, *args, **kw)
57 mcls.recursing.check = False
58 return instance
59
60
61 # Inherit from this class instead of SimpleHandshake (or other ControlBase
62 # derivative), and the metaclass will instead *replace* DynamicPipe -
63 # *at runtime* - with the class that is specified *as a parameter*
64 # in PipelineSpec.
65 #
66 # as explained in the list posting and in the stackexchange post, this is
67 # needed to avoid a MASSIVE suite of duplicated multiple-inheritance classes
68 # that "Mix in" SimpleHandshake (or other).
69 #
70 # unfortunately, composition does not work in this instance
71 # (make an *instance* of SimpleHandshake or other class and pass it in)
72 # due to the multiple level inheritance, and in several places
73 # the inheriting class needs to do some setup that the deriving class
74 # needs in order to function correctly.
75
76 class DynamicPipe(metaclass=Meta):
77 def __init__(self, *args):
78 print("DynamicPipe init", super(), args)
79 super().__init__(self, *args)
80
81
82 # bad hack: the DynamicPipe metaclass ends up creating an __init__ signature
83 # for the dynamically-derived class. luckily, SimpleHandshake only needs
84 # "self" as the 1st argument (it is its own "Stage"). anything else
85 # could hypothetically be passed through the pspec.
86 class SimpleHandshakeRedir(SimpleHandshake):
87 def __init__(self, mod, *args):
88 print("redir", mod, args)
89 stage = self
90 if args and args[0].stage:
91 stage = args[0].stage
92 SimpleHandshake.__init__(self, stage)
93
94
95 class MaskCancellableRedir(MaskCancellable):
96 def __init__(self, mod, *args):
97 stage = self
98 maskwid = args[0].maskwid
99 if args[0].stage:
100 stage = args[0].stage
101 print("redir mask", mod, args, maskwid)
102 MaskCancellable.__init__(self, stage, maskwid)