c8ff4f4d5eda46be53061ee2e1793d3483c72adb
[phd-thesis.git] / top / imp.tex
1 \documentclass[../thesis.tex]{subfiles}
2
3 \input{subfilepreamble}
4
5 \setcounter{chapter}{6}
6
7 \begin{document}
8 \input{subfileprefix}
9 \chapter{The implementation of mTask}%
10 \label{chp:implementation}
11 \begin{chapterabstract}
12 This chapter shows the implementation of the \gls{MTASK} system by:
13 \begin{itemize}
14 \item showing the compilation and execution toolchain;
15 \item showing the implementation of the byte code compiler for the \gls{MTASK} language;
16 \item elaborating on the implementation and architecture of the \gls{RTS} of \gls{MTASK};
17 \item and explaining the machinery used to automatically serialise and deserialise data to and fro the device.
18 \end{itemize}
19 \end{chapterabstract}
20
21 The \gls{MTASK} system targets resource-constrained edge devices that have little memory, processor speed, and communication.
22 Such edge devices are often powered by microcontrollers, tiny computers specifically designed for embedded applications.
23 The microcontrollers usually have flash-based program memory which wears out fairly quickly.
24 For example, the flash memory of the popular atmega328p powering the \gls{ARDUINO} UNO is rated for \num{10000} write cycles.
25 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.
26 Hence, for dynamic applications, storing the program in the \gls{RAM} of the device and thus interpreting this code is necessary in order to save precious write cycles of the program memory.
27 In the \gls{MTASK} system, the \gls{MTASK} \gls{RTS}, a domain-specific \gls{OS}, is responsible for interpreting the programs.
28
29 Programs in \gls{MTASK} are \gls{DSL} terms constructed at run time in an \gls{ITASK} system.
30 \Cref{fig:toolchain} shows the compilation and execution toolchain of such programs.
31 First, the source code is compiled to a byte code specification, this specification contains the compiled main expression, the functions, and the \gls{SDS} and peripheral configuration.
32 How an \gls{MTASK} task is compiled to this specification is shown in \cref{sec:compiler_imp}.
33 This package is then sent to the \gls{RTS} of the device for execution.
34 In order to execute a task, first the main expression is evaluated in the interpreter, resulting in a task tree.
35 Then, using small-step reduction, the task tree is continuously rewritten by the rewrite engine of the \gls{RTS}.
36 At times, the reduction requires the evaluation of expressions, using the interpreter.
37 During every rewrite step, a task value is produced.
38 On the device, the \gls{RTS} may have multiple tasks at the same time active.
39 By interleaving the rewrite steps, parallel operation is achieved.
40 The design, architecture and implementation of the \gls{RTS} is shown in \cref{sec:compiler_rts}.
41
42 \begin{figure}
43 \centering
44 \centerline{\includestandalone{toolchain}}
45 \caption{Compilation and execution toolchain of \gls{MTASK} programs.}%
46 \label{fig:toolchain}
47 \end{figure}
48
49 \section{Compiler}\label{sec:compiler_imp}
50 %The byte code compiler for \gls{MTASK} is an interpretation of the \gls{MTASK} language.
51 %In order to compile terms, instances for all \gls{MTASK} type classes are required for the \cleaninline{:: BCInterpret a} type.
52 %Terms in \gls{MTASK} are constructed and compiled at run time but type checked at compile time in the host language \gls{CLEAN}.
53 %The compiled tasks are sent to the device for interpretation, a detailed overview of the execution process is found in \cref{sec:compiler_rts}.
54 %The result of compilation is the byte code, and some metadata regarding the used peripherals and \glspl{SDS}.
55 %Interpreting the byte code only uses the stack, hence, all data types are unboxed.
56 %
57 %The heap is only used to store the task trees that
58 %
59 %The byte code is interpreted by the interpreter
60 %In order to make it work on resource-constrained microcontrollers, some properties of the language are strictly enforced.
61 %is designed to generate code that runs on resource-constrained edge devices.
62 %There is no heap avaliable for expressions, only for tasks
63 \todo[inline]{Zou je hier niet een prargraafje schrijven over dat dit een beetje speciale compiler is. Alle type checks worden al door Clean gedaan. Dat is voordat deze compiler ooit uitgevoerd gaat worden. Bovendien kan het Clean programma de te compileren byte code dynamisch maken. Dat staat natuurlijk al eerder een keer, maar je make niet aannemen dat iedereen alles leest (en nu nog weet)
64 Dit gaat wel hard de diepte in. Zou je niet een kort stukje schrijven over hoe je bytecode machine er uit ziet?
65 Heap: voor de huidige task tree die herschreven wordt.
66 Function code: sequence of bytecode instructie.
67 SDSs + Objects
68 Stack om expressies te evelaueren en function calls te doen.
69 Plaatje a la Figure 7.5.
70
71 Om de code te maken heb je een intsantie van alle classen in mTask nodig voor BCInterpret a.
72
73 Voor veel lezers zou het genoeg zijn om alleen dat te snappen, maak het ze eenvoudig.}
74
75 \subsection{Compiler infrastructure}
76 The byte code compiler interpretation for the \gls{MTASK} language is implemented as a monad stack containing a writer monad and a state monad.
77 The writer monad is used to generate code snippets locally without having to store them in the monadic values.
78 The state monad accumulates the code, and stores the state the compiler requires.
79 \Cref{lst:compiler_state} shows the data type for the state, storing:
80 function the compiler currently is in;
81 code of the main expression;
82 context (see \cref{ssec:step});
83 code for the functions;
84 next fresh label;
85 a list of all the used \glspl{SDS}, either local \glspl{SDS} containing the initial value (\cleaninline{Left}) or lowered \glspl{SDS} (see \cref{sec:liftsds}) containing a reference to the associated \gls{ITASK} \gls{SDS};
86 and finally there is a list of peripherals used.
87
88 \begin{lstClean}[label={lst:compiler_state},caption={The type for the \gls{MTASK} byte code compiler.}]
89 :: BCInterpret a :== StateT BCState (WriterT [BCInstr] Identity) a
90 :: BCState =
91 { bcs_infun :: JumpLabel
92 , bcs_mainexpr :: [BCInstr]
93 , bcs_context :: [BCInstr]
94 , bcs_functions :: Map JumpLabel BCFunction
95 , bcs_freshlabel :: JumpLabel
96 , bcs_sdses :: [Either String255 MTLens]
97 , bcs_hardware :: [BCPeripheral]
98 }
99 :: BCFunction =
100 { bcf_instructions :: [BCInstr]
101 , bcf_argwidth :: UInt8
102 , bcf_returnwidth :: UInt8
103 }
104 \end{lstClean}
105
106 Executing the compiler is done by providing an initial state and running the monad.
107 After compilation, several post-processing steps are applied to make the code suitable for the microprocessor.
108 First, in all tail call \cleaninline{BCReturn} instructions are replaced by \cleaninline{BCTailCall} instructions to optimise the tail calls.
109 Furthermore, all byte code is concatenated, resulting in one big program.
110 Many instructions have commonly used arguments, so shorthands are introduced to reduce the program size.
111 For example, the \cleaninline{BCArg} instruction is often called with argument \numrange{0}{2} and can be replaced by the \cleaninline{BCArg0} to \cleaninline{BCArg2} shorthands.
112 Furthermore, redundant instructions such as pop directly after push are removed as well in order not to burden the code generation with these intricacies.
113 Finally, the labels are resolved to represent actual program addresses instead of the freshly generated identifiers.
114 After the byte code is ready, the lowered \glspl{SDS} are resolved to provide an initial value for them.
115 The byte code, \gls{SDS} specification and peripheral specifications are the result of the process, ready to be sent to the device.
116
117 \subsection{Instruction set}
118 The instruction set is a fairly standard stack machine instruction set extended with special \gls{TOP} instructions for creating task tree nodes.
119 All instructions are housed in a \gls{CLEAN} \gls{ADT} and serialised to the byte representation using generic functions (see \cref{sec:ccodegen}).
120 Type synonyms and newtypes are used to provide insight on the arguments of the instructions (\cref{lst:type_synonyms}).
121 Labels are always two bytes long, all other arguments are one byte long.
122
123 \begin{lstClean}[caption={Type synonyms for instructions arguments.},label={lst:type_synonyms}]
124 :: ArgWidth :== UInt8 :: ReturnWidth :== UInt8
125 :: Depth :== UInt8 :: Num :== UInt8
126 :: SdsId :== UInt8 :: JumpLabel =: JL UInt16
127 \end{lstClean}
128
129 \Cref{lst:instruction_type} shows an excerpt of the \gls{CLEAN} type that represents the instruction set.
130 Shorthand instructions such as instructions with inlined arguments are omitted for brevity.
131 Detailed semantics for the instructions are given in \cref{chp:bytecode_instruction_set}.
132 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.
133
134 \begin{lstClean}[caption={The type housing the instruction set in \gls{MTASK}.},label={lst:instruction_type}]
135 :: BCInstr
136 //Jumps
137 = BCJumpF JumpLabel | BCLabel JumpLabel | BCJumpSR ArgWidth JumpLabel
138 | BCReturn ReturnWidth ArgWidth
139 | BCTailcall ArgWidth ArgWidth JumpLabel
140 //Arguments
141 | BCArgs ArgWidth ArgWidth
142 //Task node creation and refinement
143 | BCMkTask BCTaskType | BCTuneRateMs | BCTuneRateSec
144 //Stack ops
145 | BCPush String255 | BCPop Num | BCRot Depth Num | BCDup | BCPushPtrs
146 //Casting
147 | BCItoR | BCItoL | BCRtoI | ...
148 // arith
149 | BCAddI | BCSubI | ...
150 ...
151
152 :: BCTaskType
153 = BCStableNode ArgWidth | BCUnstableNode ArgWidth
154 // Pin io
155 | BCReadD | BCWriteD | BCReadA | BCWriteA | BCPinMode
156 // Interrupts
157 | BCInterrupt
158 // Repeat
159 | BCRepeat
160 // Delay
161 | BCDelay | BCDelayUntil
162 // Parallel
163 | BCTAnd | BCTOr
164 //Step
165 | BCStep ArgWidth JumpLabel
166 //Sds ops
167 | BCSdsGet SdsId | BCSdsSet SdsId | BCSdsUpd SdsId JumpLabel
168 // Rate limiter
169 | BCRateLimit
170 ////Peripherals
171 //DHT
172 | BCDHTTemp UInt8 | BCDHTHumid UInt8
173 ...
174 \end{lstClean}
175
176 \section{Compilation rules}
177 This section describes the compilation rules, the translation from \gls{AST} to byte code.
178 The compilation scheme consists of three schemes\slash{}functions.
179 Double vertical bars, e.g.\ $\stacksize{a_i}$, denote the number of stack cells required to store the argument.
180
181 Some schemes have a context $r$ as an argument which contains information about the location of the arguments in scope.
182 More information is given in the schemes requiring such arguments.
183
184 \begin{table}
185 \centering
186 \caption{An overview of the compilation schemes.}
187 \begin{tabularx}{\linewidth}{l X}
188 \toprule
189 Scheme & Description\\
190 \midrule
191 $\cschemeE{e}{r}$ & Produces the value of expression $e$ given the context $r$ and pushes it on the stack.
192 The result can be a basic value or a pointer to a task.\\
193 $\cschemeF{e}$ & Generates the bytecode for functions.\\
194 $\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.\\
195 \bottomrule
196 \end{tabularx}
197 \end{table}
198
199 \subsection{Expressions}
200 Almost all expression constructions are compiled using $\mathcal{E}$.
201 The argument of $\mathcal{E}$ is the context (see \cref{ssec:functions}).
202 Values are always placed on the stack; tuples and other compound data types are unpacked.
203 Function calls, function arguments and tasks are also compiled using $\mathcal{E}$ but their compilations is explained later.
204
205 \begin{align*}
206 \cschemeE{\text{\cleaninline{lit}}~e}{r} & = \text{\cleaninline{BCPush (bytecode e)}};\\
207 \cschemeE{e_1\mathbin{\text{\cleaninline{+.}}}e_2}{r} & = \cschemeE{e_1}{r};
208 \cschemeE{e_2}{r};
209 \text{\cleaninline{BCAdd}};\\
210 {} & \text{\emph{Similar for other binary operators}}\\
211 \cschemeE{\text{\cleaninline{Not}}~e}{r} & =
212 \cschemeE{e}{r};
213 \text{\cleaninline{BCNot}};\\
214 {} & \text{\emph{Similar for other unary operators}}\\
215 \cschemeE{\text{\cleaninline{If}}~e_1~e_2~e_3}{r} & =
216 \cschemeE{e_1}{r};
217 \text{\cleaninline{BCJmpF}}\enskip l_{else}; \mathbin{\phantom{=}} \cschemeE{e_2}{r}; \text{\cleaninline{BCJmp}}\enskip l_{endif};\\
218 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCLabel}}\enskip l_{else}; \cschemeE{e_3}{r}; \mathbin{\phantom{=}} \text{\cleaninline{BCLabel}}\enskip l_{endif};\\
219 {} & \text{\emph{Where $l_{else}$ and $l_{endif}$ are fresh labels}}\\
220 \cschemeE{\text{\cleaninline{tupl}}~e_1~e_2}{r} & =
221 \cschemeE{e_1}{r};
222 \cschemeE{e_2}{r};\\
223 {} & \text{\emph{Similar for other unboxed compound data types}}\\
224 \cschemeE{\text{\cleaninline{first}}~e}{r} & =
225 \cschemeE{e}{r};
226 \text{\cleaninline{BCPop}}\enskip w;\\
227 {} & \text{\emph{Where $w$ is the width of the right value and}}\\
228 {} & \text{\emph{similar for other unboxed compound data types}}\\
229 \cschemeE{\text{\cleaninline{second}}\enskip e}{r} & =
230 \cschemeE{e}{r};
231 \text{\cleaninline{BCRot}}\enskip (w_l+w_r)\enskip w_r;
232 \text{\cleaninline{BCPop}}\enskip w_l;\\
233 {} & \text{\emph{Where $w_l$ is the width of the left and, $w_r$ of the right value}}\\
234 {} & \text{\emph{similar for other unboxed compound data types}}\\
235 \end{align*}
236
237 Translating $\mathcal{E}$ to \gls{CLEAN} code is very straightforward, it basically means writing the instructions to the writer monad.
238 Almost always, the type of the interpretation is not used, i.e.\ it is a phantom type.
239 To still have the functions return the correct type, the \cleaninline{tell`}\footnote{\cleaninline{tell` :: [BCInstr] -> BCInterpret a}} helper is used.
240 This function is similar to the writer monad's \cleaninline{tell} function but is cast to the correct type.
241 \Cref{lst:imp_arith} shows the implementation for the arithmetic and conditional expressions.
242 Note that $r$, the context, is not an explicit argument here but stored in the state.
243
244 \begin{lstClean}[caption={Interpretation implementation for the arithmetic and conditional functions.},label={lst:imp_arith}]
245 instance expr BCInterpret where
246 lit t = tell` [BCPush (toByteCode{|*|} t)]
247 (+.) a b = a >>| b >>| tell` [BCAdd]
248 ...
249 If c t e = freshlabel >>= \elselabel->freshlabel >>= \endiflabel->
250 c >>| tell` [BCJumpF elselabel] >>|
251 t >>| tell` [BCJump endiflabel,BCLabel elselabel] >>|
252 e >>| tell` [BCLabel endiflabel]
253 \end{lstClean}
254
255 \subsection{Functions}\label{ssec:functions}
256 Compiling functions and other top-level definitions is done using in $\mathcal{F}$, which generates bytecode for the complete program by iterating over the functions and ending with the main expression.
257 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.
258 The main expression is a special case of $\mathcal{F}$ since it neither has arguments nor something to continue.
259 Therefore, it is just compiled using $\mathcal{E}$ with an empty context.
260
261 \begin{align*}
262 \cschemeF{main=m} & =
263 \cschemeE{m}{[]};\\
264 \cschemeF{f~a_0 \ldots a_n = b~\text{\cleaninline{In}}~m} & =
265 \text{\cleaninline{BCLabel}}~f; \cschemeE{b}{[\langle f, i\rangle, i\in \{(\Sigma^n_{i=0}\stacksize{a_i})..0\}]};\\
266 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCReturn}}~\stacksize{b}~n; \cschemeF{m};\\
267 \end{align*}
268
269 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}).
270 On executing \cleaninline{BCJumpSR}, the program counter is set, and the interpreter jumps to the function (\cref{lst:funcall_jumpsr}).
271 When the function returns, the return value overwrites the old pointers and the arguments.
272 This occurs right after a \cleaninline{BCReturn} (\cref{lst:funcall_ret}).
273 Putting the arguments on top of pointers and not reserving space for the return value uses little space and facilitates tail call optimisation.
274
275 \begin{figure}
276 \begin{subfigure}{.24\linewidth}
277 \centering
278 \includestandalone{memory1}
279 \caption{\cleaninline{BCPushPtrs}.}\label{lst:funcall_pushptrs}
280 \end{subfigure}
281 \begin{subfigure}{.24\linewidth}
282 \centering
283 \includestandalone{memory2}
284 \caption{Arguments.}\label{lst:funcall_args}
285 \end{subfigure}
286 \begin{subfigure}{.24\linewidth}
287 \centering
288 \includestandalone{memory3}
289 \caption{\cleaninline{BCJumpSR}.}\label{lst:funcall_jumpsr}
290 \end{subfigure}
291 \begin{subfigure}{.24\linewidth}
292 \centering
293 \includestandalone{memory4}
294 \caption{\cleaninline{BCReturn}.}\label{lst:funcall_ret}
295 \end{subfigure}
296 \caption{The stack layout during function calls.}%
297 \end{figure}
298
299 Calling a function and referencing function arguments are an extension to $\mathcal{E}$ as shown below.
300 Arguments may be at different places on the stack at different times (see \cref{ssec:step}) and therefore the exact location is always is determined from the context using \cleaninline{findarg}\footnote{\cleaninline{findarg [l`:r] l = if (l == l`) 0 (1 + findarg r l)}}.
301 Compiling argument $a_{f^i}$, the $i$th argument in function $f$, consists of traversing all positions in the current context.
302 Arguments wider than one stack cell are fetched in reverse to reconstruct the original order.
303
304 \begin{align*}
305 \cschemeE{f(a_0, \ldots, a_n)}{r} & =
306 \text{\cleaninline{BCPushPtrs}}; \cschemeE{a_i}{r}~\text{for all}~i\in\{n\ldots 0\}; \text{\cleaninline{BCJumpSR}}~n~f;\\
307 \cschemeE{a_{f^i}}{r} & =
308 \text{\cleaninline{BCArg}~findarg}(r, f, i)~\text{for all}~i\in\{w\ldots v\};\\
309 {} & v = \Sigma^{i-1}_{j=0}\stacksize{a_{f^j}}~\text{ and }~ w = v + \stacksize{a_{f^i}}\\
310 \end{align*}
311
312 Translating the compilation schemes for functions to \gls{CLEAN} is not as straightforward as other schemes due to the nature of shallow embedding in combination with the use of state.
313 The \cleaninline{fun} class has a single function with a single argument.
314 This argument is a \gls{CLEAN} function that---when given a callable \gls{CLEAN} function representing the \gls{MTASK} function---produces the \cleaninline{main} expression and a callable function.
315 To compile this, the argument must be called with a function representing a function call in \gls{MTASK}.
316 \Cref{lst:fun_imp} shows the implementation for this as \gls{CLEAN} code.
317 To uniquely identify the function, a fresh label is generated.
318 The function is then called with the \cleaninline{callFunction} helper function that generates the instructions that correspond to calling the function.
319 That is, it pushes the pointers, compiles the arguments, and writes the \cleaninline{JumpSR} instruction.
320 The resulting structure (\cleaninline{g In m}) contains a function representing the mTask function (\cleaninline{g}) and the \cleaninline{main} structure to continue with.
321 To get the actual function, \cleaninline{g} must be called with representations for the argument, i.e.\ using \cleaninline{findarg} for all arguments.
322 The arguments are added to the context using \cleaninline{infun} and \cleaninline{liftFunction} is called with the label, the argument width and the compiler.
323 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.
324 After lifting the function, the context is cleared again and compilation continues with the rest of the program.
325
326 \begin{lstClean}[label={lst:fun_imp},caption={The interpretation implementation for functions.}]
327 instance fun (BCInterpret a) BCInterpret | type a where
328 fun def = {main=freshlabel >>= \funlabel->
329 let (g In m) = def \a->callFunction funlabel (toByteWidth a) [a]
330 argwidth = toByteWidth (argOf g)
331 in addToCtx funlabel zero argwidth
332 >>| infun funlabel
333 (liftFunction funlabel argwidth
334 (g (retrieveArgs funlabel zero argwidth)
335 ) ?None)
336 >>| clearCtx >>| m.main
337 }
338
339 argOf :: ((m a) -> b) a -> UInt8 | toByteWidth a
340 callFunction :: JumpLabel UInt8 [BCInterpret b] -> BCInterpret c | ...
341 liftFunction :: JumpLabel UInt8 (BCInterpret a) (?UInt8) -> BCInterpret ()
342 infun :: JumpLabel (BCInterpret a) -> BCInterpret a
343 \end{lstClean}
344
345 \subsection{Tasks}\label{ssec:scheme_tasks}
346 Task trees are created with the \cleaninline{BCMkTask} instruction that allocates a node and pushes a pointer to it on the stack.
347 It pops arguments from the stack according to the given task type.
348 The following extension of $\mathcal{E}$ shows this compilation scheme (except for the step combinator, explained in \cref{ssec:step}).
349
350 \begin{align*}
351 \cschemeE{\text{\cleaninline{rtrn}}~e}{r} & =
352 \cschemeE{e}{r};
353 \text{\cleaninline{BCMkTask BCStable}}_{\stacksize{e}};\\
354 \cschemeE{\text{\cleaninline{unstable}}~e}{r} & =
355 \cschemeE{e}{r};
356 \text{\cleaninline{BCMkTask BCUnstable}}_{\stacksize{e}};\\
357 \cschemeE{\text{\cleaninline{readA}}~e}{r} & =
358 \cschemeE{e}{r};
359 \text{\cleaninline{BCMkTask BCReadA}};\\
360 \cschemeE{\text{\cleaninline{writeA}}~e_1~e_2}{r} & =
361 \cschemeE{e_1}{r};
362 \cschemeE{e_2}{r};
363 \text{\cleaninline{BCMkTask BCWriteA}};\\
364 \cschemeE{\text{\cleaninline{readD}}~e}{r} & =
365 \cschemeE{e}{r};
366 \text{\cleaninline{BCMkTask BCReadD}};\\
367 \cschemeE{\text{\cleaninline{writeD}}~e_1~e_2}{r} & =
368 \cschemeE{e_1}{r};
369 \cschemeE{e_2}{r};
370 \text{\cleaninline{BCMkTask BCWriteD}};\\
371 \cschemeE{\text{\cleaninline{delay}}~e}{r} & =
372 \cschemeE{e}{r};
373 \text{\cleaninline{BCMkTask BCDelay}};\\
374 \cschemeE{\text{\cleaninline{rpeat}}~e}{r} & =
375 \cschemeE{e}{r};
376 \text{\cleaninline{BCMkTask BCRepeat}};\\
377 \cschemeE{e_1\text{\cleaninline{.\|\|.}}e_2}{r} & =
378 \cschemeE{e_1}{r};
379 \cschemeE{e_2}{r};
380 \text{\cleaninline{BCMkTask BCOr}};\\
381 \cschemeE{e_1\text{\cleaninline{.&&.}}e_2}{r} & =
382 \cschemeE{e_1}{r};
383 \cschemeE{e_2}{r};
384 \text{\cleaninline{BCMkTask BCAnd}};\\
385 \end{align*}
386
387 This translates to Clean code by writing the correct \cleaninline{BCMkTask} instruction as exemplified in \cref{lst:imp_ret}.
388
389 \begin{lstClean}[caption={The byte code interpretation implementation for \cleaninline{rtrn}.},label={lst:imp_ret}]
390 instance rtrn BCInterpret
391 where
392 rtrn m = m >>| tell` [BCMkTask (bcstable m)]
393 \end{lstClean}
394
395 \subsection{Sequential combinator}\label{ssec:step}
396 The \cleaninline{step} construct is a special type of task because the task value of the left-hand side changes over time.
397 Therefore, the task continuations on the right-hand side are \emph{observing} this task value and acting upon it.
398 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.
399 This function either returns a pointer to a task tree or fails (denoted by $\bot$).
400 It is special because in the generated function, the task value of a task is inspected.
401 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.
402 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.
403 The resulting function is basically a list of if-then-else constructions to check all predicates one by one.
404 Some optimisation is possible here but has currently not been implemented.
405
406 \begin{align*}
407 \cschemeE{t_1\text{\cleaninline{>>*.}}e_2}{r} & =
408 \cschemeE{a_{f^i}}{r}, \langle f, i\rangle\in r;
409 \text{\cleaninline{BCMkTask}}~\text{\cleaninline{BCStable}}_{\stacksize{r}}; \cschemeE{t_1}{r};\\
410 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCMkTask}}~\text{\cleaninline{BCAnd}}; \text{\cleaninline{BCMkTask}}~(\text{\cleaninline{BCStep}}~(\cschemeS{e_2}{(r + [\langle l_s, i\rangle])}{\stacksize{t_1}}));\\
411 \end{align*}
412
413 \begin{align*}
414 \cschemeS{[]}{r}{w} & =
415 \text{\cleaninline{BCPush}}~\bot;\\
416 \cschemeS{\text{\cleaninline{IfValue}}~f~t:cs}{r}{w} & =
417 \text{\cleaninline{BCArg}} (\stacksize{r} + w);
418 \text{\cleaninline{BCIsNoValue}};\\
419 {} & \mathbin{\phantom{=}} \cschemeE{f}{r};
420 \text{\cleaninline{BCAnd}};\\
421 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCJmpF}}~l_1;\\
422 {} & \mathbin{\phantom{=}} \cschemeE{t}{r};
423 \text{\cleaninline{BCJmp}}~l_2;\\
424 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCLabel}}~l_1;
425 \cschemeS{cs}{r}{w};\\
426 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCLabel}}~l_2;\\
427 {} & \text{\emph{Where $l_1$ and $l_2$ are fresh labels}}\\
428 {} & \text{\emph{Similar for \cleaninline{IfStable} and \cleaninline{IfUnstable}}}\\
429 \end{align*}
430
431 The step combinator has a task as the left-hand side and a list of continuations at the right-hand side.
432 First the context is evaluated ($\cschemeE{a_{f^i}}{r}$).
433 The context contains arguments from functions and steps that need to be preserved after rewriting.
434 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 step.
435 This means that the task tree is transformed as seen in \cref{lst:context_tree}.
436 In this figure, the expression \cleaninline{t1 >>=. \\v1->t2 >>=. \\v2->...} is shown\footnote{%
437 \cleaninline{t >>=. e} is a shorthand combinator for \cleaninline{t >>* [OnStable (\\_->true) e].}}.
438 Then, the right-hand side list of continuations is converted to an expression using $\mathcal{S}$.
439
440 \begin{figure}
441 \begin{subfigure}{.5\textwidth}
442 \includestandalone{contexttree1}
443 \caption{Without the embedded context.}
444 \end{subfigure}%
445 \begin{subfigure}{.5\textwidth}
446 \includestandalone{contexttree2}
447 \caption{With the embedded context.}
448 \end{subfigure}
449 \caption{Context embedded in a virtual task tree.}%
450 \label{lst:context_tree}
451 \end{figure}
452
453 The translation to \gls{CLEAN} is given in \cref{lst:imp_seq}.
454
455 \begin{lstClean}[caption={Byte code compilation interpretation implementation for the step class.},label={lst:imp_seq}]
456 instance step BCInterpret where
457 (>>*.) lhs cont
458 //Fetch a fresh label and fetch the context
459 = freshlabel >>= \funlab->gets (\s->s.bcs_context)
460 //Generate code for lhs
461 >>= \ctx->lhs
462 //Possibly add the context
463 >>| tell` (if (ctx =: []) []
464 //The context is just the arguments up till now in reverse
465 ( [BCArg (UInt8 i)\\i<-reverse (indexList ctx)]
466 ++ map BCMkTask (bcstable (UInt8 (length ctx)))
467 ++ [BCMkTask BCTAnd]
468 ))
469 //Increase the context
470 >>| addToCtx funlab zero lhswidth
471 //Lift the step function
472 >>| liftFunction funlab
473 //Width of the arguments is the width of the lhs plus the
474 //stability plus the context
475 (one + lhswidth + (UInt8 (length ctx)))
476 //Body label ctx width continuations
477 (contfun funlab (UInt8 (length ctx)))
478 //Return width (always 1, a task pointer)
479 (Just one)
480 >>| modify (\s->{s & bcs_context=ctx})
481 >>| tell` [BCMkTask (instr rhswidth funlab)]
482
483 toContFun :: JumpLabel UInt8 -> BCInterpret a
484 toContFun steplabel contextwidth
485 = foldr tcf (tell` [BCPush fail]) cont
486 where
487 tcf (IfStable f t)
488 = If ((stability >>| tell` [BCIsStable]) &. f val)
489 (t val >>| tell` [])
490 ...
491 stability = tell` [BCArg (lhswidth + contextwidth)]
492 val = retrieveArgs steplabel zero lhswidth
493 \end{lstClean}
494
495 \subsection{Shared data sources}\label{lst:imp_sds}
496 The compilation scheme for \gls{SDS} definitions is a trivial extension to $\mathcal{F}$.
497 While there is no code generated in the definition, the byte code compiler is storing all \gls{SDS} data in the \cleaninline{bcs_sdses} field in the compilation state.
498 Regular \glspl{SDS} are stored as \cleaninline{Right String255} values.
499 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.
500 For this, an \cleaninline{BCInterpret} is created that emits this identifier.
501 When passing it to the function, the initial value of the \gls{SDS} is returned.
502 In the case of a local \gls{SDS}, this initial value is stored as a byte code encoded value in the state and the compiler continues with the rest of the program.
503
504 \begin{align*}
505 \cschemeF{\text{\cleaninline{sds}}~x=i~\text{\cleaninline{In}}~m} & =
506 \cschemeF{m};\\
507 \end{align*}
508
509 The \gls{SDS} access tasks have a compilation scheme similar to other tasks (see \cref{ssec:scheme_tasks}).
510 The \cleaninline{getSds} task just pushes a task tree node with the \gls{SDS} identifier embedded.
511 The \cleaninline{setSds} task evaluates the value, lifts that value to a task tree node and creates \pgls{SDS} set node.
512
513 \begin{align*}
514 \cschemeE{\text{\cleaninline{getSds}}~s}{r} & =
515 \text{\cleaninline{BCMkTask}} (\text{\cleaninline{BCSdsGet}} s);\\
516 \cschemeE{\text{\cleaninline{setSds}}~s~e}{r} & =
517 \cschemeE{e}{r};
518 \text{\cleaninline{BCMkTask BCStable}}_{\stacksize{e}};\\
519 {} & \mathbin{\phantom{=}} \text{\cleaninline{BCMkTask}} (\text{\cleaninline{BCSdsSet}} s);\\
520 \end{align*}
521
522 \Cref{lst:comp_sds} shows the implementation of the \cleaninline{sds} type class.
523 First, the initial \gls{SDS} value is extracted from the expression by bootstrapping the fixed point with a dummy value.
524 This is safe because the expression on the right-hand side of the \cleaninline{In} is never evaluated.
525 Then, using \cleaninline{addSdsIfNotExist}, the identifier for this particular \gls{SDS} is either retrieved from the compiler state or generated freshly.
526 This identifier is then used to provide a reference to the \cleaninline{def} definition to evaluate the main expression.
527 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.
528 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.
529
530 % VimTeX: SynIgnore on
531 \begin{lstClean}[caption={Backend implementation for the \gls{SDS} classes.},label={lst:comp_sds}]
532 :: Sds a = Sds Int
533 instance sds BCInterpret where
534 sds def = {main =
535 let (t In e) = def (abort "sds: expression too strict")
536 in addSdsIfNotExist (Left $ String255 (toByteCode{|*|} t))
537 >>= \sdsi-> let (t In e) = def (pure (Sds sdsi))
538 in e.main
539 }
540 getSds f = f >>= \(Sds i)-> tell` [BCMkTask (BCSdsGet (fromInt i))]
541 setSds f v = f >>= \(Sds i)->v >>| tell`
542 ( map BCMkTask (bcstable (byteWidth v))
543 ++ [BCMkTask (BCSdsSet (fromInt i))])
544 \end{lstClean}
545 % VimTeX: SynIgnore off
546
547 Lowered \glspl{SDS} are stored in the compiler state as \cleaninline{Right MTLens} values.
548 The compilation of the code and the serialisation of the data throws away all typing information.
549 The \cleaninline{MTLens} is a type synonym for \pgls{SDS} that represents the typeless serialised value of the underlying \gls{SDS}.
550 This is done so that the \cleaninline{withDevice} task can write the received \gls{SDS} updates to the according \gls{SDS} while the \gls{SDS} is not in scope.
551 The \gls{ITASK} notification mechanism then takes care of the rest.
552 Such \pgls{SDS} is created by using the \cleaninline{mapReadWriteError} which, given a pair of read and write functions with error handling, produces \pgls{SDS} with the lens embedded.
553 The read function transforms converts the typed value to a typeless serialised value.
554 The write function will, given a new serialised value and the old typed value, produce a new typed value.
555 It tries to decode the serialised value, if that succeeds, it is written to the underlying \gls{SDS}, an error is thrown otherwise.
556 \Cref{lst:mtask_itasksds_lens} shows the implementation for this.
557
558 % VimTeX: SynIgnore on
559 \begin{lstClean}[label={lst:mtask_itasksds_lens},caption={Lens applied to lowered \gls{ITASK} \glspl{SDS} in \gls{MTASK}.}]
560 lens :: (Shared sds a) -> MTLens | type a & RWShared sds
561 lens sds = mapReadWriteError
562 ( \r-> Ok (fromString (toByteCode{|*|} r)
563 , \w r-> ?Just <$> iTasksDecode (toString w)
564 ) ?None sds
565 \end{lstClean}
566 % VimTeX: SynIgnore off
567
568 \Cref{lst:mtask_itasksds_lift} shows the code for the implementation of \cleaninline{lowerSds} that uses the \cleaninline{lens} function shown earlier.
569 It is very similar to the \cleaninline{sds} constructor in \cref{lst:comp_sds}, only now a \cleaninline{Right} value is inserted in the \gls{SDS} administration.
570
571 % VimTeX: SynIgnore on
572 \begin{lstClean}[label={lst:mtask_itasksds_lift},caption={The implementation for lowering \glspl{SDS} in \gls{MTASK}.}]
573 instance lowerSds BCInterpret where
574 lowerSds def = {main =
575 let (t In _) = def (abort "lowerSds: expression too strict")
576 in addSdsIfNotExist (Right $ lens t)
577 >>= \sdsi->let (_ In e) = def (pure (Sds sdsi)) in e.main
578 }\end{lstClean}
579 % VimTeX: SynIgnore off
580
581 \section{Run-time system}\label{sec:compiler_rts}
582 The \gls{RTS} is a customisable domain-specific \gls{OS} that takes care of the execution of tasks.
583 Furthermore, it also takes care of low-level mechanisms such as the communication, multitasking, and memory management.
584 Once a device is programmed with the \gls{MTASK} \gls{RTS}, it can continuously receive new tasks without the need for reprogramming.
585 The \gls{OS} is written in portable \ccpp{} and only contains a small device-specific portion.
586 In order to keep the abstraction level high and the hardware requirements low, much of the high-level functionality of the \gls{MTASK} language is implemented not in terms of lower-level constructs from \gls{MTASK} language but in terms of \ccpp{} code.
587
588 Most microcontrollers software consists of a cyclic executive instead of an \gls{OS}, this one loop function is continuously executed and all work is performed there.
589 In the \gls{RTS} of the \gls{MTASK} system, there is also such an event loop function.
590 It is a function with a relatively short execution time that gets called repeatedly.
591 The event loop consists of three distinct phases.
592 After doing the three phases, the devices goes to sleep for as long as possible (see \cref{chp:green_computing_mtask} for more details on task scheduling).
593
594 \subsection{Communication phase}
595 In the first phase, the communication channels are processed.
596 The exact communication method is a customisable device-specific option baked into the \gls{RTS}.
597 The interface is kept deliberately simple and consists of two layers: a link interface and a communication interface.
598 Besides opening, closing and cleaning up, the link interface has three functions that are shown in \cref{lst:link_interface}.
599 Consequently, implementing this link interface is very simple, but it is still possible to implement more advanced link features such as buffering.
600 There are implementations for this interface for serial or \gls{WIFI} connections using \gls{ARDUINO}, and \gls{TCP} connections for Linux.
601
602 \begin{lstArduino}[caption={Link interface of the \gls{MTASK} \gls{RTS}.},label={lst:link_interface}]
603 bool link_input_available(void);
604 uint8_t link_read_byte(void);
605 void link_write_byte(uint8_t b);
606 \end{lstArduino}
607
608 The communication interface abstracts away from this link interface and is typed instead.
609 It contains only two functions as seen in \cref{lst:comm_interface}.
610 There are implementations for direct communication, or communication using an \gls{MQTT} broker.
611 Both use the automatic serialisation and deserialisation shown in \cref{sec:ccodegen}.
612
613 \begin{lstArduino}[caption={Communication interface of the \gls{MTASK} \gls{RTS}.},label={lst:comm_interface}]
614 struct MTMessageTo receive_message(void);
615 void send_message(struct MTMessageFro msg);
616 \end{lstArduino}
617
618 Processing the received messages from the communication channels happens synchronously and the channels are exhausted completely before moving on to the next phase.
619 There are several possible messages that can be received from the server:
620
621 \begin{description}
622 \item[SpecRequest]
623 is a message instructing the device to send its specification.
624 It is received immediately after connecting.
625 The \gls{RTS} responds with a \texttt{Spec} answer containing the specification.
626 \item[TaskPrep]
627 tells the device a task is on its way.
628 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.
629 This message allows the \gls{RTS} to postpone execution for a while, until the larger task has been received.
630 The server sends the task only after the device acknowledged the preparation by sending a \texttt{TaskPrepAck} message.
631 \item[Task]
632 contains a new task, its peripheral configuration, the \glspl{SDS}, and the byte code.
633 The new task is immediately copied to the task storage but is only initialised during the next phase.
634 The device acknowledges the task by sending a \texttt{TaskAck} message.
635 \item[SdsUpdate]
636 notifies the device of the new value for a lowered \gls{SDS}.
637 The old value of the lowered \gls{SDS} is immediately replaced with the new one.
638 There is no acknowledgement required.
639 \item[TaskDel]
640 instructs the device to delete a running task.
641 Tasks are automatically deleted when they become stable.
642 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.
643 The device acknowledges the deletion by sending a \texttt{TaskDelAck}.
644 \item[Shutdown]
645 tells the device to reset.
646 \end{description}
647
648 \subsection{Execution phase}
649 The second phase performs one execution step for all tasks that wish for it.
650 Tasks are ordered in a priority queue ordered by the time a task needs to execute, the \gls{RTS} selects all tasks that can be scheduled, see \cref{sec:scheduling} for more details.
651 Execution of a task is always an interplay between the interpreter and the rewriter.
652 The rewriter scans the current task tree and tries to rewrite it using small-step reduction.
653 Expressions in the tree are always strictly evaluated by the interpreter.
654
655 When a new task is received, the main expression is evaluated to produce a task tree.
656 A task tree is a tree structure in which each node represents a task combinator and the leaves are basic tasks.
657 If a task is not initialised yet, i.e.\ the pointer to the current task tree is still null, the byte code of the main function is interpreted.
658 The main expression of \gls{MTASK} programs sent to the device fore execution always produces a task tree.
659 Execution of a task consists of continuously rewriting the task until its value is stable.
660
661 Rewriting is a destructive process, i.e.\ the rewriting is done in place.
662 The rewriting engine uses the interpreter when needed, e.g.\ to calculate the step continuations.
663 The rewriter and the interpreter use the same stack to store intermediate values.
664 Rewriting steps are small so that interleaving results in seemingly parallel execution.
665 In this phase new task tree nodes may be allocated.
666 Both rewriting and initialization are atomic operations in the sense that no processing on \glspl{SDS} is done other than \gls{SDS} operations from the task itself.
667 The host is notified if a task value is changed after a rewrite step by sending a \texttt{TaskReturn} message.
668
669 Take for example a blink task for which the code is shown in \cref{lst:blink_code}.
670
671 \begin{lstClean}[caption={Code for a blink program.},label={lst:blink_code}]
672 declarePin D13 PMOutput \ledPin->
673 fun \blink=(\st->delay (lit 500) >>|. writeD ledPin st >>=. blink o Not)
674 In {main = blink true}
675 \end{lstClean}
676
677 On receiving this task, the task tree is still null and the initial expression \cleaninline{blink true} is evaluated by the interpreter.
678 This results in the task tree shown in \cref{fig:blink_tree1}.
679 Rewriting always starts at the top of the tree and traverses to the leaves, the basic tasks that do the actual work.
680 The first basic task encountered is the \cleaninline{delay} task, that yields no value until the time, \qty{500}{\ms} in this case, has passed.
681 When the \cleaninline{delay} task yielded a stable value after a number of rewrites, the task continues with the right-hand side of the \cleaninline{>>\|.} combinator by evaluating the expression (see \cref{fig:blink_tree2})\footnotemark.
682 \footnotetext{\cleaninline{t1 >>\|. t2} is a shorthand for \cleaninline{t1 >>*. [IfStable id \\_->t2]}.}
683 This combinator has a \cleaninline{writeD} task at the left-hand side that becomes stable after one rewrite step in which it writes the value to the given pin.
684 When \cleaninline{writeD} becomes stable, the written value is the task value that is observed by the right-hand side of the \cleaninline{>>=.} combinator.
685 Then the interpreter is used again to evaluate the expression, now that the argument of the function is known.
686 The result of the call to the function is again a task tree, but now with different arguments to the tasks, e.g.\ the state in \cleaninline{writeD} is inversed.
687
688 \begin{figure}
689 \centering
690 \begin{subfigure}[t]{.5\textwidth}
691 \includestandalone{blinktree1}
692 \caption{Initial task tree}%
693 \label{fig:blink_tree1}
694 \end{subfigure}%
695 \begin{subfigure}[t]{.5\textwidth}
696 \includestandalone{blinktree2}
697 \caption{Task tree after the delay passed}%
698 \label{fig:blink_tree2}
699 \end{subfigure}
700 \caption{The task trees during reduction for a blink task in \gls{MTASK}.}%
701 \label{fig:blink_tree}
702 \end{figure}
703
704 \subsection{Memory management}
705 The third and final phase is memory management.
706 The \gls{MTASK} \gls{RTS} is designed to run on systems with as little as \qty{2}{\kibi\byte} of \gls{RAM}.
707 Aggressive memory management is therefore vital.
708 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.
709 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}.
710 The size of this block can be changed in the configuration of the \gls{RTS} if necessary.
711 On an \gls{ARDUINO} UNO---equipped with \qty{2}{\kibi\byte} of \gls{RAM}---the maximum viable size is about \qty{1500}{\byte}.
712 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}).
713
714 \begin{figure}
715 \centering
716 \includestandalone{memorylayout}
717 \caption{Memory layout in the \gls{MTASK} \gls{RTS}.}\label{fig:memory_layout}
718 \end{figure}
719
720 A task is stored below the stack and it consists of 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.
721
722 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.
723 As a consequence, the stack moves when a new task is received.
724 This never happens within execution because communication is always processed before execution.
725 Values in the interpreter are always stored on the stack.
726 Compound data types are stored unboxed and flattened.
727 Task trees grow from the top down as in a heap.
728 This approach allows for flexible ratios, i.e.\ many tasks and small trees or few tasks and big trees.
729
730 Stable tasks, and unreachable task tree nodes are removed.
731 If a task is to be removed, tasks with higher memory addresses are moved down.
732 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.
733 This is possible because there is no sharing or cycles in task trees and nodes contain pointers to their parent.
734
735
736 \section{C code generation for communication}\label{sec:ccodegen}
737 All communication between the \gls{ITASK} server and the \gls{MTASK} server is type parametrised and automated.
738 From the structural representation of the type, a \gls{CLEAN} parser and printer is constructed using generic programming.
739 Furthermore, a \ccpp{} parser and printer is generated for use on the \gls{MTASK} device.
740 The technique for generating the \ccpp{} parser and printer is very similar to template metaprogramming and requires a rich generic programming library or compiler support that includes a lot of metadata in the record and constructor nodes.
741 Using generic programming in the \gls{MTASK} system, both serialisation and deserialisation on the microcontroller and the server is automatically generated.
742
743 \subsection{Server}
744 On the server, off-the-shelve generic programming techniques are used to make the serialisation and deserialisation functions (see \cref{lst:ser_deser_server}).
745 Serialisation is a simple conversion from a value of the type to a string.
746 Deserialisation is a bit different in order to support streaming\footnotemark.
747 \footnotetext{%
748 Here the \cleaninline{*!} variant of the generic interface is chosen that has less uniqueness constraints for the compiler-generated adaptors \citep{alimarine_generic_2005,hinze_derivable_2001}.%
749 }
750 Given a list of available characters, a tuple is always returned.
751 The right-hand side of the tuple contains the remaining characters, the unparsed input.
752 The left-hand side contains either an error or a maybe value.
753 If the value is a \cleaninline{?None}, there was no full value to parse.
754 If the value is a \cleaninline{?Just}, the data field contains a value of the requested type.
755
756 \begin{lstClean}[caption={Serialisation and deserialisation functions in \gls{CLEAN}.},label={lst:ser_deser_server}]
757 generic toByteCode a :: a -> String
758 generic fromByteCode a *! :: [Char] -> (Either String (? a), [Char])
759 \end{lstClean}
760
761 \subsection{Client}
762 The \gls{RTS} of the \gls{MTASK} system runs on resource-constrained microcontrollers and is implemented in portable \ccpp{}.
763 In order to achieve more interoperation safety, the communication between the server and the client is automated, i.e.\ the serialisation and deserialisation code in the \gls{RTS} is generated.
764 The technique used for this is very similar to the technique shown in \cref{chp:first-class_datatypes}.
765 However, instead of using template metaprogramming, a feature \gls{CLEAN} lacks, generic programming is used also as a two-stage rocket.
766 In contrast to many other generic programming systems, \gls{CLEAN} allows for access to much of the metadata of the compiler.
767 For example, \cleaninline{Cons}, \cleaninline{Object}, \cleaninline{Field}, and \cleaninline{Record} generic constructors are enriched with their arity, names, types, \etc.
768 Furthermore, constructors can access the metadata of the objects and fields of their parent records.
769 Using this metadata, generic functions are created that generate \ccpp{} type definitions, parsers and printers for any first-order \gls{CLEAN} type.
770 The exact details of this technique can be found in the future in a paper that is in preparation.
771
772 \Glspl{ADT} are converted to tagged unions, newtypes to typedefs, records to structs, and arrays to dynamic size-parametrised allocated arrays.
773 For example, the \gls{CLEAN} types in \cref{lst:ser_clean} are translated to the \ccpp{} types seen in \cref{lst:ser_c}
774
775 \begin{lstClean}[caption={Simple \glspl{ADT} in \gls{CLEAN}.},label={lst:ser_clean}]
776 :: T a = A a | B NT {#Char}
777 :: NT =: NT Real
778 \end{lstClean}
779
780 \begin{lstArduino}[caption={Generated \ccpp{} type definitions for the simple \glspl{ADT}.},label={lst:ser_c}]
781 typedef double Real;
782 typedef char Char;
783
784 typedef Real NT;
785 enum T_c {A_c, B_c};
786
787 struct Char_HshArray { uint32_t size; Char *elements; };
788 struct T {
789 enum T_c cons;
790 struct { void *A;
791 struct { NT f0; struct Char_HshArray f1; } B;
792 } data;
793 };
794 \end{lstArduino}
795
796 For each of these generated types, two functions are created, a typed printer, and a typed parser (see \cref{lst:ser_pp}).
797 The parser functions are parametrised by a read function, an allocation function and parse functions for all type variables.
798 This allows for the use of these functions in environments where the communication is parametrised and the memory management is self-managed such as in the \gls{MTASK} \gls{RTS}.
799
800 \begin{lstArduino}[caption={Printer and parser for the \glspl{ADT} in \ccpp{}.},label={lst:ser_pp}]
801 struct T parse_T(uint8_t (*get)(), void *(*alloc)(size_t),
802 void *(*parse_0)(uint8_t (*)(), void *(*)(size_t)));
803
804 void print_T(void (*put)(uint8_t), struct T r,
805 void (*print_0)(void (*)(uint8_t), void *));
806 \end{lstArduino}
807
808 \section{Conclusion}
809 This chapter showed the implementation of the \gls{MTASK} byte code compiler, the \gls{RTS}, and the internals of their communication.
810 It is not straightforward to execute \gls{MTASK} tasks on resources-constrained \gls{IOT} edge devices.
811 To achieve this, the terms in the \gls{DSL} are compiled to compact domain-specific byte code.
812 This byte code is sent for interpretation to the light-weight \gls{RTS} of the edge device.
813 The \gls{RTS} first evaluates the main expression in the interpreter.
814 The result of this evaluation, a run time representation of the task, is a task tree.
815 This task tree is rewritten according to small-step reduction rules until a stable value is observed.
816 Rewriting multiple tasks at the same time is achieved by interleaving the rewrite steps, resulting in seemingly parallel execution of the tasks.
817 All communication, including the serialisation and deserialisation, between the server and the \gls{RTS} is automated.
818 From the structural representation of the types, printers and parsers are generated for the server and the client.
819
820 \input{subfilepostamble}
821 \end{document}