Tip
QuickNotes are short, quick, informal notes. Often to order my thoughts and analyse the options before I write the
“real” design (or other docs).
They are unofficially published. You can read them but do not expect that they are well written. Or till actually, as
maintaining them is not a goal.
QuickNote: The Sieve in RPython
Before implementing an RPython backend, a short study of RPython is made, including implementing The Sieve (basic variant) in RPython. It’s like a hand-compiled RPython variant, following the approach as described in the Jinja rendering study), for the CC2CPy backend.
Writing RPython isn’t too complicated for someone with a C-background. But making the code pass the RPython compiler
can be tricky, however. Both, as just added statements can interfere with existing code – when that isn’t “static
enough”, new code can trigger compile errors in old, tested code! And, because the RPython compiler isn’t that great
in giving helpful info.
It feels like old-style debugging: try-and-error, remove-and-enable-lines, one-at-a-time until one
understands and it becomes easy to fix…
This blog is both to share some RPython experience and to study the patterns that are needed to generate RPython code to implement the Castle-Compiler.
Code
Below we show only a part of the (manually written) code. It roughly follows the “handCompiled-C” version (see
CC2Cpy: CCastle to C compiler in Python). Again, mostly the structure to get the (Castle) Components working, like sending events between Elements
is presented.
All other code is quite straightforward, and can be found in the code-archive
For this study, I had two objectives: (a) make it (functional) work in RPython, and (b) find the patterns to automate. So, do not expect greatly crafted (maintainable) code.
Definitions
In Castle, you use Events, Protocols and Components (to name a few), all will result in some data structures
[1] that define them: build-in classes that are (statically) instantiated.
This part is easy to automate.
CC_P_StartSieve_runTo = 7 # XXX Now arbitrary number, simulating 7 [0:6] inherited events
CC_P_StartSieve_NoEvents = 8 # XXX This number is ``CC_P_StartSieve_runTo+1``
cc_P_StartSieve = buildin.CC_B_Protocol(name = "SimpleSieve",
kind = buildin.CC_B_ProtocolKindIs_Event,
inherit_from = None,
events = [])
cc_P_StartSieve.events.append(
buildin.CC_B_P_EventID(name="runTo",
seqNo=CC_P_StartSieve_runTo,
part_of=cc_P_StartSieve))
cc_P_StartSieve.events.append(
buildin.CC_B_P_EventID(name="newMax",
seqNo=CC_P_StartSieve_NoEvents,
part_of=cc_P_StartSieve))
CC_P_SimpleSieve_input = 6 # XXX
cc_P_SimpleSieve = buildin.CC_B_Protocol(name = "SimpleSieve",
kind = None, # Not given ==> use inherit_from.kind
inherit_from = SlowStart.cc_P_SlowStart,
base_arguments = (1,),
events = [])
cc_P_SimpleSieve.events.append(
buildin.CC_B_P_EventID(name="input",
seqNo=CC_P_SimpleSieve_input,
part_of=cc_P_SimpleSieve))
cc_P_SlowStart = buildin.CC_B_Protocol(name = "SlowStart",
parameters = [('queue_max', int)],
kind = buildin.CC_B_ProtocolKindIs_Event,
inherit_from = None,
events = [])
cc_P_SlowStart.events.append(
buildin.CC_B_P_EventID(name="setMax",
seqNo=-1, #XXX
parameters = [('queue_max', int)],
part_of=cc_P_SlowStart))
Hint
Here,the basic cc_P_<<ProtoName>>
structure is defined first, without events.
Then, the events are added, one by one; by appending to the (internal) list.
As those CC_B_P_EventID
(s) refer the Protocol-structure, there is no syntax to combine that (now).
You will see this approach also in other definition.
cc_CI_Generator = buildin.CC_B_ComponentInterface(name = "Generator",
inherit_from = base.cc_CI_Component,
ports = []) # GAM:SEE Note in FinderMoat
cc_CI_Generator.ports.append(
buildin.CC_B_C_PortID(name="controll",
portNo=2, # GAM: why start at 2??
protocol=StartSieve.cc_P_StartSieve,
direction=buildin.CC_B_PortDirectionIs_in,
part_of=cc_CI_Generator))
cc_CI_Generator.ports.append(
buildin.CC_B_C_PortID(name="outlet",
portNo=3,
protocol=SimpleSieve.cc_P_SimpleSieve,
direction=buildin.CC_B_PortDirectionIs_out,
part_of=cc_CI_Generator))
cc_CI_Generator.ports.append(
buildin.CC_B_C_PortID(name="collect",
portNo=3,
protocol=SimpleSieve.cc_P_SimpleSieve,
direction=buildin.CC_B_PortDirectionIs_in,
part_of=cc_CI_Generator))
cc_CI_Sieve = buildin.CC_B_ComponentInterface(name = "Sieve",
inherit_from = base.cc_CI_Component,
ports = []) # GAM:SEE Note in FinderMoat
cc_CI_Sieve.ports.append(
buildin.CC_B_C_PortID(name="try",
portNo=2, # GAM: why start at 2??
protocol=SimpleSieve.cc_P_SimpleSieve,
direction=buildin.CC_B_PortDirectionIs_in,
part_of=cc_CI_Sieve))
cc_CI_Sieve.ports.append(
buildin.CC_B_C_PortID(name="coprime",
portNo=3,
protocol=SimpleSieve.cc_P_SimpleSieve,
direction=buildin.CC_B_PortDirectionIs_out,
part_of=cc_CI_Sieve))
cc_CI_Finder = buildin.CC_B_ComponentInterface(name = "Finder",
inherit_from = base.cc_CI_Component,
ports = [])
cc_CI_Finder.ports.append(
buildin.CC_B_C_PortID(name="newPrime",
portNo=2,
protocol=SimpleSieve.cc_P_SimpleSieve,
direction=buildin.CC_B_PortDirectionIs_in,
part_of=cc_CI_Finder))
cc_CI_Finder.ports.append(
buildin.CC_B_C_PortID(name = "found",
portNo = 3,
protocol = SimpleSieve.cc_P_SimpleSieve,
direction = buildin.CC_B_PortDirectionIs_out,
part_of = cc_CI_Finder))
cc_CI_Board = buildin.CC_B_ComponentInterface(name = "Board",
inherit_from = base.cc_CI_Component,
ports = [])
# XXX ToDo: add a port to start?
cc_CI_Main = buildin.CC_B_ComponentInterface(name = "Main",
inherit_from = cc_CI_Board,
ports = []) # No port (for now)
Warning
Each port as a (sequence) number, which isn’t really used (yet). For now a small, but non-zero, number is used.
As ports can inherit, each component-interface starts with a portNo that is one higher as the number of ports in
the base-port.
With start of ‘2’, we assumes the base-port has 2 port of it own.
cc_C_Generator = buildin.CC_B_ComponentClass(
interface = GeneratorMoat.cc_CI_Generator,
methods = [], # XXX Is this needed?
isa = None, # Later: use for meta-classes; For now isa is optional
#instance_size = sys.getsizeof(CC_Generator), Not needed for rPython
)
cc_C_Sieve = buildin.CC_B_ComponentClass(
interface = SieveMoat.cc_CI_Sieve,
methods = [], # XXX Is this needed? As the are 'in" CC_Sieve"
isa = None, # Later: use for meta-classes; For now isa is optional
#instance_size = sys.getsizeof(CC_Sieve), Not needed for rPython
)
cc_C_Finder = buildin.CC_B_ComponentClass(
interface = FinderMoat.cc_CI_Finder,
methods = [], # XXX Is this needed? As the are 'in" CC_Finder"
isa = None, # Later: use for meta-classes; For now isa is optional
#instance_size = sys.getsizeof(CC_Finder), Not needed for rPython
)
cc_C_Main = buildin.CC_B_ComponentClass(
interface = MainMoat.cc_CI_Main,
methods = [], # XXX Is this needed?
isa = None, # Later: use for meta-classes; For now isa is optional
#instance_size = sys.getsizeof(CC_Main), Not needed for rPython
)
Note
It’s possible to improve this code in many ways. For example, we can use smart initialisers to get define a
data-structure in ones.
However, as it is (a) an experiment and (b, more relevant) it’s (ultimately) generated code, that isn’t needed, nor
foreseen.
Data structures
Each (Castle) component results in a (RPython) class containing all code for that component. As (R)Python classes are also namespaces, the names of (e.g.) the even-handlers become shorter; there is no need to put the component-name in the method-name. But in general, we follow the structure as in the CC2Cpy approach.
Components
A (Castle) Component becomes a subclass of CC_B_Component
, an (abstract) base class providing (currently mostly)
debugging support. Subclasses should override the _debug_attr_()
method, which returns (but not print) a string
with name=value
pairs for debug-printing.
The initialiser is split in __init__()
and _castle_init()
; the latter implements (generates code for) the
Castle-init function, and is called by __init__()
[2].
Both initialisers will have the parameters to the class, and/or the elements. A default value (Nil) is set in
__init__
, whereas _castle_init
can set it – as specified in the castle-code. See the SieveClass.py code for
an example (not shown).
Static variables in Components –which are defined in e.g. EventHandlers– are also handled in __init__
. As they
have a scope of one function, their name is prefixed with that function-name. You can find an example in the
GeneratorClass.py (not shown).
Roughly each protocol-event for each port (aka an EventHandler) results in a (R)Python method, as do data- (and other) handlers, and internal functions.
DispatchTables
Every (input) port can trigger several EventHandlers (or data-/stream-/… handlers), as an (event) protocol can have multiple events. Therefore, a component has an (event) DispatchTable per port [3].
Every DispatchTable is an array (python: List) with the same size as the number of events in the corresponding Protocol (which is static at compile-time) – including the inherited events [4].
Pins
A (Castle) component can have sub-components. They can be interconnected using the typical syntax and (RPython)
structure.
But a component-element can also receive and send messages to its sub-elements. The (SieveDemo) Main component uses
this to handle a newly found prime (by the Finder
’s output port found
). In Castle-code the EventHandler names
both the (sub)element and its port (instead of only a port). See the sidebar for an example.
Such an “incoming event” of a sub-element needs a DispatchTable also, we call them “pin(s)”.
Note: No extra DispatchTables are needed to send an event to a sub-element; the normal in-port of that component (aka it
DispatchTable) handles that. A bit of extra gear is needed to fire that event, however (see below).
Examples
class CC_Generator(buildin.CC_B_Component): # generated class
# A few standard method
def __init__(self, *args): ...
def _castle_init(self): ...
def _debug_attr_(self, name_only=True): ...
# One method for each event-port combi
def StartSieve_newMax__controll(self, max_): ...
def StartSieve_runTo__controll(self, max_): ...
def SimpleSieve_input__collect(self, foundPrime): ...
# other
def assertCorrectPrime(self, foundPrime): # internal, test (manually written)
class CC_Sieve(buildin.CC_B_Component): # generated class
# A few standard method
def __init__(self, *args): ...
def _castle_init(self, onPrime): ... # parameters depend on Moat definition
def _debug_attr_(self, name_only=True): ...
# One method for each event-port combi
def SimpleSieve_input__try(self, try_): ...
class CC_Finder(buildin.CC_B_Component): # generated class
# A few standard method
def __init__(self, *args): ...
def _castle_init(self, onPrime): ... # parameters depend on Moat definition
def _debug_attr_(self, name_only=True): ...
# One method for each event-port combi
def SimpleSieve_input__newPrime(self, foundPrime): # handCompiledC: CC_E_Finder__SimpleSieve_input__newPrime
class CC_Main(CC_Board): # generated class
# A few standard method
def __init__(self, *args): ...
def _castle_init(self, onPrime): ... # parameters depend on Moat definition
def _debug_attr_(self, name_only=True): ...
# One method for each event-port combi
def SimpleSieve_input__generator_found(self, newPrime): ...
def powerOn__power(self, max): ...
# other
def insert_sieve(self, s):
class CC_Board: # default class
"A bad name, now used for historical reasons. Rename before R1.0"
# A few standard method
def __init__(self, *args): ...
cc_S_Generator_controll = [
None, None, None, None, None, None, None, # 0-6: not relevant here
CC_Generator.StartSieve_runTo__controll, # 7
CC_Generator.StartSieve_newMax__controll, # 8
]
cc_S_Generator_collect = [
None, None, None, None, None, None, # 0-5: not relevant here
CC_Generator.SimpleSieve_input__collect, # 6/CC_P_SimpleSieve_input
]
cc_S_Sieve_try = [
None, None, None, None, None, None, # 0-5: not relevant here
CC_Sieve.SimpleSieve_input__try, # 6/CC_P_SimpleSieve_input
]
cc_S_Finder_newPrime = [
None, None, None, None, None, None, # 0-5: not relevant here
CC_Finder.SimpleSieve_input__newPrime # 6/CC_P_SimpleSieve_input
]
cc_S_Main_power = [
None, # 0: not relevant here
CC_Main.powerOn__power,
]
cc_S_Main__pin1 = [ # internal pin
None, None, None, None, None, None, # 0-5: not relevant here
CC_Main.SimpleSieve_input__finder_found, # 6/CC_P_SimpleSieve_input
]
Code fragments
To connect two components (via ports), and to send events (or other protocols) over them, some code is needed. That is studied below.
Connecting
Castle supports multiple Machineries, with all will generate a bit different code. Here we use the DirectCall one [6].
We use (one of the ) same examples as in QuickNote: Connecting two ports (see: CC_Mi_Main__init).
The Castle Code:
.generator.outlet = .finder.newPrime;
This will result in two lines of RPython. They are very similar to the C-backend: the outlet field holds the other component (element) and the handlers denote the DispatchTable of the incoming port.
self.generator.outlet.connection = self.finder
self.generator.outlet.handlers = FinderClass.cc_S_Finder_newPrime
Sending
Again, we use an earlier example (in QuickNote: Sending Events & Machinery). The Generator is sending numbers to the sieves
self.outlet.input(i); // Part of Generator:: StartSieve.runTo(max) on self.controll
This translates to a few lines of Python.
The manual crafted version adds some print/debug and assert statements. It also handles the case where a None
is set
in a DispatchTable. See the real code for the details. Here you see the essence.
outport = self.outlet
receiver = outport.connection
handlers = outport.handlers
handler = handlers[SimpleSieve.CC_P_SimpleSieve_input]
handler(receiver, i)
Or simply:
outport = self.outlet
index = SimpleSieve.CC_P_SimpleSieve_input
handler = outport.handlers[index]
handler(outport.connection, i)
Note
This code assumes there is always a valid method in all entries of the DispatchTable. See the generic error function footnote.
Trigger sub-elements
To send an event to an (in)-port of a (Castle) sub-element, we need to select the correct (RPython) DispatchTable, index it with the event number, and call that method, passing the sub-element’s (RPython) instance – and parameters.
GeneratorClass.cc_S_Generator_controll[StartSieve.CC_P_StartSieve_runTo](self.generator, max)
Warning
The Castle Code for this feature isn’t 100% clear. Several variants of the (Castle) Sieve do it differently, wrong or simply lack that line [7].
See also
https://osdn.net/users/albertmietus/pf/TryOut_PyPy-and-Rpython (SLOW!)
My collection of small rpython demos, and typical Python-code that is not RPython ..https://osdn.net/users/albertmietus/pf/CC_Sieve_in_rPython (SLOW)
The code used for in this study.
Footnotes
Comments
comments powered by Disqus