fb0a597a84761112e78a194f5a66e5d30b970498
[phd-thesis.git] / top / imp.tex
1 \documentclass[../thesis.tex]{subfiles}
2
3 \input{subfilepreamble}
4
5 \setcounter{chapter}{5}
6
7 \begin{document}
8 \input{subfileprefix}
9 \chapter{Implementation}%
10 \label{chp:implementation}
11 \begin{chapterabstract}
12 \noindent This chapter shows the implementation of the \gls{MTASK} system by:
13 \begin{itemize}
14 \item gives details of the implementation of \gls{MTASK}'s \gls{TOP} engine that executes the \gls{MTASK} tasks on the microcontroller;
15 \item shows the implementation of the byte code compiler for \gls{MTASK}'s \gls{TOP} language;
16 \item explains the machinery used to automatically serialise and deserialise data to-and-fro the device.
17 \end{itemize}
18 \end{chapterabstract}
19
20 Microcontrollers usually have flash-based program memory which wears out fairly quick.
21 For example, the atmega328p in the \gls{ARDUINO} UNO is rated for 10000 write cycles.
22 While this sounds like a lot, if new tasks are sent to the device every minute or so, a lifetime of only seven days is guaranteed.
23 Hence, for dynamic applications, interpretation on the device is necessary, saving precious write cycles of the program memory.
24
25 In order to provide the device with the tools to interpret the byte code, it is programmed with a \gls{RTS}, a customisable domain-specific \gls{OS} that takes care of the execution of tasks but also low-level mechanisms such as the communication, multi tasking, and memory management.
26 Once the device is programmed with the \gls{MTASK} \gls{RTS}, it can continuously receive new tasks without the need for reprogramming.
27 The \gls{OS} is written in portable \ccpp{} and only contains a small device-specific portion.
28
29 \section{\texorpdfstring{\Glsxtrlong{RTS}}{Run time system}}
30 The event loop of the \gls{RTS} is executed repeatedly and consists of three distinct phases.
31 After doing the three phases, the devices goes to sleep for as long as possible (see \cref{chp:green_computing_mtask}).
32
33 \subsection{Communication}
34 In the first phase, the communication channels are processed.
35 The exact communication method is a customisable device-specific option baked into the \gls{RTS}.
36 The interface is deliberately kept simple and consists of a two layer interface: a link interface and a communication interface.
37 Besides opening, closing and cleaning up, the link interface has only three functions that are shown in \cref{lst:link_interface}.
38 Consequently, implementing this link interface is very simple but allows for many more advanced link settings such as buffering.
39 There are implementations for this interface for serial or Wi-Fi connections using \gls{ARDUINO} and \gls{TCP} connections for Linux.
40
41 \begin{lstArduino}[caption={Link interface of the \gls{MTASK} \gls{RTS}.},label={lst:link_interface}]
42 bool link_input_available(void);
43 uint8_t link_read_byte(void);
44 void link_write_byte(uint8_t b);
45 \end{lstArduino}
46
47 The communication interface abstracts away from this link interface and is typed instead.
48 It contains only two functions as seen in \cref{lst:comm_interface}.
49 There are implementations for direct communication, or communication using an \gls{MQTT} broker.
50 Both use the automatic serialisation and deserialisation shown in \cref{sec:ccodegen}.
51
52 \begin{lstArduino}[caption={Communication interface of the \gls{MTASK} \gls{RTS}.},label={lst:comm_interface}]
53 struct MTMessageTo receive_message(void);
54 void send_message(struct MTMessageFro msg);
55 \end{lstArduino}
56
57 Processing the received messages from the communication channels happens synchronously and the channels are exhausted completely before moving on to the next phase.
58 There are several possible messages that can be received from the server:
59
60 \begin{description}
61 \item[SpecRequest]
62 is a message instructing the device to send its specification and is sent usually immediately after connecting.
63 The \gls{RTS} responds with a \texttt{Spec} answer containing the specification.
64 \item[TaskPrep]
65 tells the device a (big) task is on its way.
66 Especially on faster connections, it may be the case that the communication buffers overflow because a big message is sent while the \gls{RTS} is busy executing tasks.
67 This allows the \gls{RTS} to postpone execution for a while, until the big task has been received.
68 The server sends the big task when the device acknowledges (by sending a \texttt{TaskPrepAck} message) the preparation.
69 \item[Task]
70 contains a new task, its peripheral configuration, the \glspl{SDS}, and the bytecode.
71 The new task is immediately copied to the task storage but is only initialised during the next phase after which a \texttt{TaskAck} is sent.
72 Tasks are stored below the stack, but since the stack is only used in the middle phase, execution, it is no problem that it moves.
73 \item[SdsUpdate]
74 notifies the device of the new value for a lowered \gls{SDS}.
75 The old value of the lowered \gls{SDS} is immediately replaced with the new one.
76 There is no acknowledgement required.
77 \item[TaskDel]
78 instructs the device to delete a running task.
79 Tasks are automatically deleted when they become stable.
80 However, a task may also be deleted when the surrounding task on the server is deleted, for example when the task is on the left-hand side of a step combinator and the condition to step holds.
81 The device acknowledges by sending a \texttt{TaskDelAck}.
82 \item[Shutdown]
83 tells the device to reset.
84 \end{description}
85
86 \subsection{Execution}
87 The second phase consists of performing one execution step for all tasks that wish for it.
88 Tasks are ordered in a priority queue ordered by the time a task needs to be executed, the \gls{RTS} selects all tasks that can be scheduled, see \cref{sec:scheduling} for more details.
89 Execution of a task is always an interplay between the interpreter and the \emph{rewriter}.
90
91 If a task is not initialized yet, i.e.\ the pointer to the current task tree is still null, the byte code of the main function is interpreted.
92 The main expression always produces a task tree.
93 Execution of a task consists of continuously rewriting the task until its value is stable.
94 Rewriting is a destructive process, i.e.\ the rewriting is done in place.
95 The rewriting engine uses the interpreter when needed, e.g.\ to calculate the step continuations.
96 The rewriter and the interpreter use the same stack to store intermediate values.
97 Rewriting steps are small so that interleaving results in seemingly parallel execution.
98 In this phase new task tree nodes may be allocated.
99 Both rewriting and initialization are atomic operations in the sense that no processing on SDSs is done other than SDS operations from the task itself.
100 The host is notified if a task value is changed after a rewrite step.
101
102 \subsection{Memory management}
103 The third and final phase is memory management.
104 The \gls{MTASK} \gls{RTS} is designed to run on systems with as little as \qty{2}{\kibi\byte} of \gls{RAM}.
105 Aggressive memory management is therefore vital.
106 Not all firmwares for microprocessors support heaps and---when they do---allocation often leaves holes when not used in a \emph{last in first out} strategy.
107 The \gls{RTS} uses a chunk of memory in the global data segment with its own memory manager tailored to the needs of \gls{MTASK}.
108 The size of this block can be changed in the configuration of the \gls{RTS} if necessary.
109 On an \gls{ARDUINO} UNO---equipped with \qty{2}{\kibi\byte} of \gls{RAM}---the maximum viable size is about \qty{1500}{\byte}.
110 The self-managed memory uses a similar layout as the memory layout for \gls{C} programs only the heap and the stack are switched (see \cref{fig:memory_layout}).
111
112 \begin{figure}
113 \centering
114 \includestandalone{memorylayout}
115 \caption{Memory layout}\label{fig:memory_layout}
116 \end{figure}
117
118 A task is stored below the stack and its complete state is a \gls{CLEAN} record contain most importantly the task id, a pointer to the task tree in the heap (null if not initialised yet), the current task value, the configuration of \glspl{SDS}, the configuration of peripherals, the byte code and some scheduling information.
119
120 In memory, task data grows from the bottom up and the interpreter stack is located directly on top of it growing in the same direction.
121 As a consequence, the stack moves when a new task is received.
122 This never happens within execution because communication is always processed before execution.
123 Values in the interpreter are always stored on the stack.
124 Compound data types are stored unboxed and flattened.
125 Task trees grow from the top down as in a heap.
126 This approach allows for flexible ratios, i.e.\ many tasks and small trees or few tasks and big trees.
127
128 Stable tasks, and unreachable task tree nodes are removed.
129 If a task is to be removed, tasks with higher memory addresses are moved down.
130 For task trees---stored in the heap---the \gls{RTS} already marks tasks and task trees as trash during rewriting so the heap can be compacted in a single pass.
131 This is possible because there is no sharing or cycles in task trees and nodes contain pointers pointers to their parent.
132
133 \todo{plaa\-tje van me\-mo\-ry hier uitbreiden}
134
135 \section{Compiler}
136 \subsection{Instruction set}
137 The instruction set is a fairly standard stack machine instruction set extended with special \gls{TOP} instructions.
138
139 \Cref{lst:instruction_type} shows the \gls{CLEAN} type representing the instruction set of which \cref{tbl:instr_task} gives detailed semantics.
140 Type synonyms are used to provide insight on the arguments of the instructions.
141 One notable instruction is the \cleaninline{MkTask} instruction, it allocates and initialises a task tree node and pushes a pointer to it on the stack.
142
143 \begin{lstClean}[caption={The type housing the instruction set.},label={lst:instruction_type}]
144 :: ArgWidth :== UInt8 :: ReturnWidth :== UInt8
145 :: Depth :== UInt8 :: Num :== UInt8
146 :: SdsId :== UInt8 :: JumpLabel =: JL UInt16
147
148 //** Datatype housing all instructions
149 :: BCInstr
150 //Return instructions
151 //Jumps
152 = BCJumpF JumpLabel | BCJump JumpLabel | BCLabel JumpLabel | BCJumpSR ArgWidth JumpLabel
153 | BCReturn ReturnWidth ArgWidth | BCTailcall ArgWidth ArgWidth JumpLabel
154 //Arguments
155 | BCArgs ArgWidth ArgWidth
156 //Task node creation and refinement
157 | BCMkTask BCTaskType | BCTuneRateMs | BCTuneRateSec
158 //Task value ops
159 | BCIsStable | BCIsUnstable | BCIsNoValue | BCIsValue
160 //Stack ops
161 | BCPush String255 | BCPop Num | BCRot Depth Num | BCDup | BCPushPtrs
162 //Casting
163 | BCItoR | BCItoL | BCRtoI | ...
164 // arith
165 | BCAddI | BCSubI | ...
166 ...
167
168 //** Datatype housing all task types
169 :: BCTaskType
170 = BCStableNode ArgWidth | ArgWidth
171 // Pin io
172 | BCReadD | BCWriteD | BCReadA | BCWriteA | BCPinMode
173 // Interrupts
174 | BCInterrupt
175 // Repeat
176 | BCRepeat
177 // Delay
178 | BCDelay | BCDelayUntil //* Only for internal use
179 // Parallel
180 | BCTAnd | BCTOr
181 //Step
182 | BCStep ArgWidth JumpLabel
183 //Sds ops
184 | BCSdsGet SdsId | BCSdsSet SdsId | BCSdsUpd SdsId JumpLabel
185 // Rate limiter
186 | BCRateLimit
187 ////Peripherals
188 //DHT
189 | BCDHTTemp UInt8 | BCDHTHumid UInt8
190 ...
191 \end{lstClean}
192
193 \subsection{Compiler}
194 The bytecode compiler interpretation for the \gls{MTASK} language is implemented as a monad stack containing a writer monad and a state monad.
195 The writer monad is used to generate code snippets locally without having to store them in the monadic values.
196 The state monad accumulates the code, and stores the stateful data the compiler requires.
197 \Cref{lst:compiler_state} shows the data type for the state, storing:
198 function the compiler currently is in;
199 code of the main expression;
200 context (see \todo{insert ref to compilation rules step here});
201 code for the functions;
202 next fresh label;
203 a list of all the used \glspl{SDS}, either local \glspl{SDS} containing the initial value (\cleaninline{Left}) or lifted \glspl{SDS} (see \cref{sec:liftsds}) containing a reference to the associated \gls{ITASK} \gls{SDS};
204 and finally there is a list of peripherals used.
205
206 \begin{lstClean}[label={lst:compiler_state},caption={\Gls{MTASK}'s byte code compiler type}]
207 :: BCInterpret a :== StateT BCState (WriterT [BCInstr] Identity) a
208 :: BCState =
209 { bcs_infun :: JumpLabel
210 , bcs_mainexpr :: [BCInstr]
211 , bcs_context :: [BCInstr]
212 , bcs_functions :: Map JumpLabel BCFunction
213 , bcs_freshlabel :: JumpLabel
214 , bcs_sdses :: [Either String255 MTLens]
215 , bcs_hardware :: [BCPeripheral]
216 }
217 :: BCFunction =
218 { bcf_instructions :: [BCInstr]
219 , bcf_argwidth :: UInt8
220 , bcf_returnwidth :: UInt8
221 }
222 \end{lstClean}
223
224 Executing the compiler is done by providing an initial state.
225 After compilation, several post-processing steps are applied to make the code suitable for the microprocessor.
226 First, in all tail call \cleaninline{BCReturn}'s are replaced by \cleaninline{BCTailCall} to implement tail call elimination.
227 Furthermore, all byte code is concatenated, resulting in one big program.
228 Many instructions have commonly used arguments so shorthands are introduced to reduce the program size.
229 For example, the \cleaninline{BCArg} instruction is often called with argument \qtyrange{0}{2} and can be replaced by the \cleaninline{BCArg0}--\cleaninline{BCArg2} shorthands.
230 Furthermore, redundant instructions (e.g.\ pop directly after push) are removed as well in order not to burden the code generation with these intricacies.
231 Finally the labels are resolved to represent actual program addresses instead of freshly generated identifiers.
232 After the byte code is ready, the lifted \glspl{SDS} are resolved to provide an initial value for them.
233 The result---byte code, \gls{SDS} specification and perpipheral specifications---are the result of the process, ready to be sent to the device.
234
235 \section{Compilation rules}
236 This section describes the compilation rules, the translation from abstract syntax to byte code.
237 The compilation scheme consists of three schemes\slash{}functions.
238 When something is surrounded by double vertical bars, e.g.\ $\stacksize{a_i}$, it denotes the number of stack cells required to store it.
239
240 Some schemes have a \emph{context} $r$ as an argument which contains information about the location of the arguments in scope.
241 More information is given in the schemes requiring such arguments.
242
243 \newcommand{\cschemeE}[2]{\mathcal{E}\llbracket#1\rrbracket~#2}
244 \newcommand{\cschemeF}[1]{\mathcal{F}\llbracket#1\rrbracket}
245 \newcommand{\cschemeS}[3]{\mathcal{S}\llbracket#1\rrbracket~#2~#3}
246 \begin{table}
247 \centering
248 \begin{tabularx}{\linewidth}{l X}
249 \toprule
250 Scheme & Description\\
251 \midrule
252 $\cschemeE{e}{r}$ & Produces the value of expression $e$ given the context $r$ and pushes it on the stack.
253 The result can be a basic value or a pointer to a task.\\
254 $\cschemeF{e}$ & Generates the bytecode for functions.\\
255 $\cschemeS{e}{r}{w} $ & Generates the function for the step continuation given the context $r$ and the width $w$ of the left-hand side task value.\\
256 \bottomrule
257 \end{tabularx}
258 \end{table}
259
260 \subsection{Expressions}
261 Almost all expression constructions are compiled using $\mathcal{E}$.
262 The argument of $\mathcal{E}$ is the context (see \cref{ssec:functions}).
263 Values are always placed on the stack; tuples and other compound data types are unpacked.
264 Function calls, function arguments and tasks are also compiled using $\mathcal{E}$ but their compilations is explained later.
265
266 \begin{align*}
267 \cschemeE{\text{\cleaninline{lit}}~e}{r} & = \text{\cleaninline{BCPush (bytecode e)}};\\
268 \cschemeE{e_1\mathbin{\text{\cleaninline{+.}}}e_2}{r} & = \cschemeE{e_1}{r};
269 \cschemeE{e_2}{r};
270 \text{\cleaninline{BCAdd}};\\
271 {} & \text{\emph{Similar for other binary operators}}\\
272 \cschemeE{\text{\cleaninline{Not}}~e}{r} & =
273 \cschemeE{e}{r};
274 \text{\cleaninline{BCNot}};\\
275 {} & \text{\emph{Similar for other unary operators}}\\
276 \cschemeE{\text{\cleaninline{If}}~e_1~e_2~e_3}{r} & =
277 \cschemeE{e_1}{r};
278 \text{\cleaninline{BCJmpF}}\enskip l_{else}; \mathbin{\phantom{=}} \cschemeE{e_2}{r}; \text{\cleaninline{BCJmp}}\enskip l_{endif};\\
279 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCLabel}}\enskip l_{else}; \cschemeE{e_3}{r}; \mathbin{\phantom{=}} \text{\cleaninline{BCLabel}}\enskip l_{endif};\\
280 {} & \text{\emph{Where $l_{else}$ and $l_{endif}$ are fresh labels}}\\
281 \cschemeE{\text{\cleaninline{tupl}}~e_1~e_2}{r} & =
282 \cschemeE{e_1}{r};
283 \cschemeE{e_2}{r};\\
284 {} & \text{\emph{Similar for other unboxed compound data types}}\\
285 \cschemeE{\text{\cleaninline{first}}~e}{r} & =
286 \cschemeE{e}{r};
287 \text{\cleaninline{BCPop}}\enskip w;\\
288 {} & \text{\emph{Where $w$ is the width of the left value and}}\\
289 {} & \text{\emph{similar for other unboxed compound data types}}\\
290 \cschemeE{\text{\cleaninline{second}}\enskip e}{r} & =
291 \cschemeE{e}{r};
292 \text{\cleaninline{BCRot}}\enskip w_1\enskip (w_1+w_2);
293 \text{\cleaninline{BCPop}}\enskip w_2;\\
294 {} & \text{\emph{Where $w_1$ is the width of the left and, $w_2$ of the right value}}\\
295 {} & \text{\emph{similar for other unboxed compound data types}}\\
296 \end{align*}
297
298 Translating $\mathcal{E}$ to \gls{CLEAN} code is very straightforward, it basically means executing the monad.
299 Almost always, the type of the interpretation is not used, i.e.\ it is a phantom type.
300 To still have the functions return the correct type, the \cleaninline{tell`}\footnote{\cleaninline{tell` :: [BCInstr] -> BCInterpret a}} helper is used.
301 This function is similar to the writer monad's \cleaninline{tell} function but is casted to the correct type.
302 \Cref{lst:imp_arith} shows the implementation for the arithmetic and conditional expressions.
303 Note that $r$, the context, is not an explicit argument but stored in the state.
304
305 \begin{lstClean}[caption={Interpretation implementation for the arithmetic and conditional classes.},label={lst:imp_arith}]
306 instance expr BCInterpret where
307 lit t = tell` [BCPush (toByteCode{|*|} t)]
308 (+.) a b = a >>| b >>| tell` [BCAdd]
309 ...
310 If c t e = freshlabel >>= \elselabel->freshlabel >>= \endiflabel->
311 c >>| tell` [BCJumpF elselabel] >>|
312 t >>| tell` [BCJump endiflabel,BCLabel elselabel] >>|
313 e >>| tell` [BCLabel endiflabel]
314 \end{lstClean}
315
316 \subsection{Functions}\label{ssec:functions}
317 Compiling functions occurs in $\mathcal{F}$, which generates bytecode for the complete program by iterating over the functions and ending with the main expression.
318 When compiling the body of the function, the arguments of the function are added to the context so that the addresses can be determined when referencing arguments.
319 The main expression is a special case of $\mathcal{F}$ since it neither has arguments nor something to continue.
320 Therefore, it is just compiled using $\mathcal{E}$.
321
322 \begin{align*}
323 \cschemeF{main=m} & =
324 \cschemeE{m}{[]};\\
325 \cschemeF{f~a_0 \ldots a_n = b~\text{\cleaninline{In}}~m} & =
326 \text{\cleaninline{BCLabel}}~f; \cschemeE{b}{[\langle f, i\rangle, i\in \{(\Sigma^n_{i=0}\stacksize{a_i})..0\}]};\\
327 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCReturn}}~\stacksize{b}~n; \cschemeF{m};\\
328 \end{align*}
329
330 A function call starts by pushing the stack and frame pointer, and making space for the program counter (\cref{lst:funcall_pushptrs}) followed by evaluating the arguments in reverse order (\cref{lst:funcall_args}).
331 On executing \cleaninline{BCJumpSR}, the program counter is set and the interpreter jumps to the function (\cref{lst:funcall_jumpsr}).
332 When the function returns, the return value overwrites the old pointers and the arguments.
333 This occurs right after a \cleaninline{BCReturn} (\cref{lst:funcall_ret}).
334 Putting the arguments on top of pointers and not reserving space for the return value uses little space and facilitates tail call optimization.
335
336 \begin{figure}
337 \begin{subfigure}{.24\linewidth}
338 \centering
339 \includestandalone{memory1}
340 \caption{\cleaninline{BCPushPtrs}}\label{lst:funcall_pushptrs}
341 \end{subfigure}
342 \begin{subfigure}{.24\linewidth}
343 \centering
344 \includestandalone{memory2}
345 \caption{Arguments}\label{lst:funcall_args}
346 \end{subfigure}
347 \begin{subfigure}{.24\linewidth}
348 \centering
349 \includestandalone{memory3}
350 \caption{\cleaninline{BCJumpSR}}\label{lst:funcall_jumpsr}
351 \end{subfigure}
352 \begin{subfigure}{.24\linewidth}
353 \centering
354 \includestandalone{memory4}
355 \caption{\cleaninline{BCReturn}}\label{lst:funcall_ret}
356 \end{subfigure}
357 \caption{The stack layout during function calls.}%
358 \end{figure}
359
360 Calling a function and referencing function arguments are an extension to $\mathcal{E}$ as shown below.
361 Arguments may be at different places on the stack at different times (see \cref{ssec:step}) and therefore the exact location always has to be determined from the context using \cleaninline{findarg}\footnote{\cleaninline{findarg [l`:r] l = if (l == l`) 0 (1 + findarg r l)}}.
362 Compiling argument $a_{f^i}$, the $i$th argument in function $f$, consists of traversing all positions in the current context.
363 Arguments wider than one stack cell are fetched in reverse to preserve the order.
364
365 \begin{align*}
366 \cschemeE{f(a_0, \ldots, a_n)}{r} & =
367 \text{\cleaninline{BCPushPtrs}}; \cschemeE{a_n}{r}; \cschemeE{a_{\ldots}}{r}; \cschemeE{a_0}{r}; \text{\cleaninline{BCJumpSR}}~n~f;\\
368 \cschemeE{a_{f^i}}{r} & =
369 \text{\cleaninline{BCArg}~findarg}(r, f, i)~\text{for all}~i\in\{w\ldots v\};\\
370 {} & v = \Sigma^{i-1}_{j=0}\stacksize{a_{f^j}}~\text{ and }~ w = v + \stacksize{a_{f^i}}\\
371 \end{align*}
372
373 Translating the compilation schemes for functions to Clean is not as straightforward as other schemes due to the nature of shallow embedding.\todo{deze \P{} moet ge\-\"up\-da\-ted worden}
374 The \cleaninline{fun} class has a single function with a single argument.
375 This argument is a Clean function that---when given a callable Clean function representing the mTask function---will produce \cleaninline{main} and a callable function.
376 To compile this, the argument must be called with a function representing a function call in mTask.
377 \Cref{lst:fun_imp} shows the implementation for this as Clean code.
378 To uniquely identify the function, a fresh label is generated.
379 The function is then called with the \cleaninline{callFunction} helper function that generates the instructions that correspond to calling the function.
380 That is, it pushes the pointers, compiles the arguments, and writes the \cleaninline{JumpSR} instruction.
381 The resulting structure (\cleaninline{g In m}) contains a function representing the mTask function (\cleaninline{g}) and the \cleaninline{main} structure to continue with.
382 To get the actual function, \cleaninline{g} must be called with representations for the argument, i.e.\ using \cleaninline{findarg} for all arguments.
383 The arguments are added to the context and \cleaninline{liftFunction} is called with the label, the argument width and the compiler.
384 This function executes the compiler, decorates the instructions with a label and places them in the function dictionary together with the metadata such as the argument width.
385 After lifting the function, the context is cleared again and compilation continues with the rest of the program.
386
387 \begin{lstClean}[label={lst:fun_imp},caption={The backend implementation for functions.}]
388 instance fun (BCInterpret a) BCInterpret | type a where
389 fun def = {main=freshlabel >>= \funlabel->
390 let (g In m) = def \a->callFunction funlabel (toByteWidth a) [a]
391 argwidth = toByteWidth (argOf g)
392 in addToCtx funlabel zero argwidth
393 >>| infun funlabel
394 (liftFunction funlabel argwidth
395 (g (retrieveArgs funlabel zero argwidth)
396 ) ?None)
397 >>| clearCtx >>| m.main
398 }
399
400 argOf :: ((m a) -> b) a -> UInt8 | toByteWidth a
401 callFunction :: JumpLabel UInt8 [BCInterpret b] -> BCInterpret c | ...
402 liftFunction :: JumpLabel UInt8 (BCInterpret a) (?UInt8) -> BCInterpret ()
403 \end{lstClean}
404
405 \subsection{Tasks}\label{ssec:scheme_tasks}
406 Task trees are created with the \cleaninline{BCMkTask} instruction that allocates a node and pushes it to the stack.
407 It pops arguments from the stack according to the given task type.
408 The following extension of $\mathcal{E}$ shows this compilation scheme (except for the step combinator, explained in \cref{ssec:step}).
409
410 \begin{align*}
411 \cschemeE{\text{\cleaninline{rtrn}}~e}{r} & =
412 \cschemeE{e}{r};
413 \text{\cleaninline{BCMkTask BCStable}}_{\stacksize{e}};\\
414 \cschemeE{\text{\cleaninline{unstable}}~e}{r} & =
415 \cschemeE{e}{r};
416 \text{\cleaninline{BCMkTask BCUnstable}}_{\stacksize{e}};\\
417 \cschemeE{\text{\cleaninline{readA}}~e}{r} & =
418 \cschemeE{e}{r};
419 \text{\cleaninline{BCMkTask BCReadA}};\\
420 \cschemeE{\text{\cleaninline{writeA}}~e_1~e_2}{r} & =
421 \cschemeE{e_1}{r};
422 \cschemeE{e_2}{r};
423 \text{\cleaninline{BCMkTask BCWriteA}};\\
424 \cschemeE{\text{\cleaninline{readD}}~e}{r} & =
425 \cschemeE{e}{r};
426 \text{\cleaninline{BCMkTask BCReadD}};\\
427 \cschemeE{\text{\cleaninline{writeD}}~e_1~e_2}{r} & =
428 \cschemeE{e_1}{r};
429 \cschemeE{e_2}{r};
430 \text{\cleaninline{BCMkTask BCWriteD}};\\
431 \cschemeE{\text{\cleaninline{delay}}~e}{r} & =
432 \cschemeE{e}{r};
433 \text{\cleaninline{BCMkTask BCDelay}};\\
434 \cschemeE{\text{\cleaninline{rpeat}}~e}{r} & =
435 \cschemeE{e}{r};
436 \text{\cleaninline{BCMkTask BCRepeat}};\\
437 \cschemeE{e_1\text{\cleaninline{.\|\|.}}e_2}{r} & =
438 \cschemeE{e_1}{r};
439 \cschemeE{e_2}{r};
440 \text{\cleaninline{BCMkTask BCOr}};\\
441 \cschemeE{e_1\text{\cleaninline{.&&.}}e_2}{r} & =
442 \cschemeE{e_1}{r};
443 \cschemeE{e_2}{r};
444 \text{\cleaninline{BCMkTask BCAnd}};\\
445 \end{align*}
446
447 This simply translates to Clean code by writing the correct \cleaninline{BCMkTask} instruction as exemplified in \cref{lst:imp_ret}.
448
449 \begin{lstClean}[caption={The backend implementation for \cleaninline{rtrn}.},label={lst:imp_ret}]
450 instance rtrn BCInterpret
451 where
452 rtrn m = m >>| tell` [BCMkTask (bcstable m)]
453 \end{lstClean}
454
455 \subsection{Step combinator}\label{ssec:step}
456 The \cleaninline{step} construct is a special type of task because the task value of the left-hand side may change over time.
457 Therefore, the continuation tasks on the right-hand side are \emph{observing} this task value and acting upon it.
458 In the compilation scheme, all continuations are first converted to a single function that has two arguments: the stability of the task and its value.
459 This function either returns a pointer to a task tree or fails (denoted by $\bot$).
460 It is special because in the generated function, the task value of a task can actually be inspected.
461 Furthermore, it is a lazy node in the task tree: the right-hand side may yield a new task tree after several rewrite steps (i.e.\ it is allowed to create infinite task trees using step combinators).
462 The function is generated using the $\mathcal{S}$ scheme that requires two arguments: the context $r$ and the width of the left-hand side so that it can determine the position of the stability which is added as an argument to the function.
463 The resulting function is basically a list of if-then-else constructions to check all predicates one by one.
464 Some optimization is possible here but has currently not been implemented.
465
466 \begin{align*}
467 \cschemeE{t_1\text{\cleaninline{>>*.}}t_2}{r} & =
468 \cschemeE{a_{f^i}}{r}, \langle f, i\rangle\in r;
469 \text{\cleaninline{BCMkTask}}~\text{\cleaninline{BCStable}}_{\stacksize{r}}; \cschemeE{t_1}{r};\\
470 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCMkTask}}~\text{\cleaninline{BCAnd}}; \text{\cleaninline{BCMkTask}}~(\text{\cleaninline{BCStep}}~(\cschemeS{t_2}{(r + [\langle l_s, i\rangle])}{\stacksize{t_1}}));\\
471 \end{align*}
472
473 \begin{align*}
474 \cschemeS{[]}{r}{w} & =
475 \text{\cleaninline{BCPush}}~\bot;\\
476 \cschemeS{\text{\cleaninline{IfValue}}~f~t:cs}{r}{w} & =
477 \text{\cleaninline{BCArg}} (\stacksize{r} + w);
478 \text{\cleaninline{BCIsNoValue}};\\
479 {} & \mathbin{\phantom{=}} \cschemeE{f}{r};
480 \text{\cleaninline{BCAnd}};\\
481 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCJmpF}}~l_1;\\
482 {} & \mathbin{\phantom{=}} \cschemeE{t}{r};
483 \text{\cleaninline{BCJmp}}~l_2;\\
484 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCLabel}}~l_1;
485 \cschemeS{cs}{r}{w};\\
486 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCLabel}}~l_2;\\
487 {} & \text{\emph{Where $l_1$ and $l_2$ are fresh labels}}\\
488 {} & \text{\emph{Similar for \cleaninline{IfStable} and \cleaninline{IfUnstable}}}\\
489 \end{align*}
490
491 First the context is evaluated.
492 The context contains arguments from functions and steps that need to be preserved after rewriting.
493 The evaluated context is combined with the left-hand side task value by means of a \cleaninline{.&&.} combinator to store it in the task tree so that it is available after a rewrite.
494 This means that the task tree is be transformed as follows:
495
496 \begin{lstClean}
497 t1 >>= \v1->t2 >>= \v2->t3 >>= ...
498 //is transformed to
499 t1 >>= \v1->rtrn v1 .&&. t2 >>= \v2->rtrn (v1, v2) .&&. t3 >>= ...
500 \end{lstClean}
501
502 The translation to \gls{CLEAN} is given in \cref{lst:imp_seq}.
503
504 \begin{lstClean}[caption={Backend implementation for the step class.},label={lst:imp_seq}]
505 instance step BCInterpret where
506 (>>*.) lhs cont
507 //Fetch a fresh label and fetch the context
508 = freshlabel >>= \funlab->gets (\s->s.bcs_context)
509 //Generate code for lhs
510 >>= \ctx->lhs
511 //Possibly add the context
512 >>| tell` (if (ctx =: []) []
513 //The context is just the arguments up till now in reverse
514 ( [BCArg (UInt8 i)\\i<-reverse (indexList ctx)]
515 ++ map BCMkTask (bcstable (UInt8 (length ctx)))
516 ++ [BCMkTask BCTAnd]
517 ))
518 //Increase the context
519 >>| addToCtx funlab zero lhswidth
520 //Lift the step function
521 >>| liftFunction funlab
522 //Width of the arguments is the width of the lhs plus the
523 //stability plus the context
524 (one + lhswidth + (UInt8 (length ctx)))
525 //Body label ctx width continuations
526 (contfun funlab (UInt8 (length ctx)))
527 //Return width (always 1, a task pointer)
528 (Just one)
529 >>| modify (\s->{s & bcs_context=ctx})
530 >>| tell` [BCMkTask (instr rhswidth funlab)]
531
532 toContFun :: JumpLabel UInt8 -> BCInterpret a
533 toContFun steplabel contextwidth
534 = foldr tcf (tell` [BCPush fail]) cont
535 where
536 tcf (IfStable f t)
537 = If ((stability >>| tell` [BCIsStable]) &. f val)
538 (t val >>| tell` [])
539 ...
540 stability = tell` [BCArg (lhswidth + contextwidth)]
541 val = retrieveArgs steplabel zero lhswidth
542 \end{lstClean}
543
544 \subsection{\texorpdfstring{\Glspl{SDS}}{Shared data sources}}
545 The compilation scheme for \gls{SDS} definitions is a trivial extension to $\mathcal{F}$ since there is no code generated as seen below.
546
547 \begin{align*}
548 \cschemeF{\text{\cleaninline{sds}}~x=i~\text{\cleaninline{In}}~m} & =
549 \cschemeF{m};\\
550 \end{align*}
551
552 The \gls{SDS} access tasks have a compilation scheme similar to other tasks (see \cref{ssec:scheme_tasks}).
553 The \cleaninline{getSds} task just pushes a task tree node with the \gls{SDS} identifier embedded.
554 The \cleaninline{setSds} task evaluates the value, lifts that value to a task tree node and creates an \gls{SDS} set node.
555
556 \begin{align*}
557 \cschemeE{\text{\cleaninline{getSds}}~s}{r} & =
558 \text{\cleaninline{BCMkTask}} (\text{\cleaninline{BCSdsGet}} s);\\
559 \cschemeE{\text{\cleaninline{setSds}}~s~e}{r} & =
560 \cschemeE{e}{r};
561 \text{\cleaninline{BCMkTask BCStable}}_{\stacksize{e}};\\
562 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCMkTask}} (\text{\cleaninline{BCSdsSet}} s);\\
563 \end{align*}
564
565 While there is no code generated in the definition, the byte code compiler is storing the \gls{SDS} data in the \cleaninline{bcs_sdses} field in the compilation state.
566 The \glspl{SDS} are typed as functions in the host language so an argument for this function must be created that represents the \gls{SDS} on evaluation.
567 For this, an \cleaninline{BCInterpret} is created that emits this identifier.
568 When passing it to the function, the initial value of the \gls{SDS} is returned.
569 This initial value is stored as a byte code encoded value in the state and the compiler continues with the rest of the program.
570
571 Compiling \cleaninline{getSds} is a matter of executing the \cleaninline{BCInterpret} representing the \gls{SDS}, which yields the identifier that can be embedded in the instruction.
572 Setting the \gls{SDS} is similar: the identifier is retrieved and the value is written to put in a task tree so that the resulting task can remember the value it has written.
573 Lifted SDSs are compiled in a very similar way.\todo{deze \P{} moet naar integration?}
574 The only difference is that there is no initial value but an iTasks SDS when executing the Clean function.
575 A lens on this SDS converting \cleaninline{a} from the \cleaninline{Shared a} to a \cleaninline{String255}---a bytecode encoded version---is stored in the state.
576 The encoding and decoding itself is unsafe when used directly but the type system of the language and the abstractions make it safe.
577 Upon sending the mTask task to the device, the initial values of the lifted SDSs are fetched to complete the SDS specification.
578
579 % VimTeX: SynIgnore on
580 \begin{lstClean}[caption={Backend implementation for the SDS classes.},label={lst:comp_sds}]
581 :: Sds a = Sds Int
582 instance sds BCInterpret where
583 sds def = {main = freshsds >>= \sdsi->
584 let sds = modify (\s->{s & bcs_sdses=put sdsi
585 (Left (toByteCode t)) s.bcs_sdses})
586 >>| pure (Sds sdsi)
587 (t In e) = def sds
588 in e.main}
589 getSds f = f >>= \(Sds i)-> tell` [BCMkTask (BCSdsGet (fromInt i))]
590 setSds f v = f >>= \(Sds i)->v >>| tell`
591 ( map BCMkTask (bcstable (byteWidth v))
592 ++ [BCMkTask (BCSdsSet (fromInt i))])
593 \end{lstClean}
594 % VimTeX: SynIgnore off
595
596 \section{C code generation}\label{sec:ccodegen}
597 All communication between the \gls{ITASK} server and the \gls{MTASK} server is type-parametrised.
598 From the structural representation of the type, a \gls{CLEAN} parser and printer is constructed using generic programming.
599 Furthermore, a \ccpp{} parser and printer is generated for use on the \gls{MTASK} device.
600 The technique for generating the \ccpp{} parser and printer is very similar to template metaprogramming and requires a generic programming library or compiler support that includes a lot of metadata in the record and constructor nodes.
601
602 \section{Conclusion}
603 %\todo[inline]{conclusion}
604
605 \input{subfilepostamble}
606 \end{document}