1
0
mirror of https://github.com/dhil/phd-dissertation synced 2026-03-13 11:08:25 +00:00

Compare commits

..

3 Commits

Author SHA1 Message Date
9ae478047d runNext 2021-02-04 16:28:32 +00:00
9fdcf19ee5 Parameterised semantics 2021-02-04 15:04:41 +00:00
86269f6de2 Process synchronisation code. 2021-02-04 12:14:06 +00:00
2 changed files with 288 additions and 71 deletions

View File

@@ -455,6 +455,10 @@
\newcommand{\ufork}{\dec{ufork}} \newcommand{\ufork}{\dec{ufork}}
\newcommand{\Wait}{\dec{Wait}} \newcommand{\Wait}{\dec{Wait}}
\newcommand{\scheduler}{\dec{scheduler}} \newcommand{\scheduler}{\dec{scheduler}}
\newcommand{\Sstate}{\dec{Sstate}}
\newcommand{\Ready}{\dec{Ready}}
\newcommand{\Blocked}{\dec{Blocked}}
\newcommand{\init}{\dec{init}}
%% %%
%% Some control operators %% Some control operators

View File

@@ -7498,7 +7498,10 @@ being handled. Semantically, parameterised handlers are defined as
folds with state threading over computation trees. folds with state threading over computation trees.
We take the deep handler calculus $\HCalc$ as our starting point and We take the deep handler calculus $\HCalc$ as our starting point and
extend it with parameterised handlers to yield the calculus $\HPCalc$. extend it with parameterised handlers to yield the calculus
$\HPCalc$. The parameterised handler extension interacts nicely with
shallow handlers, and as such it can be added to $\SCalc$ with low
effort.
\subsection{Syntax and static semantics} \subsection{Syntax and static semantics}
In addition to a computation, a parameterised handler also take a In addition to a computation, a parameterised handler also take a
@@ -7506,21 +7509,31 @@ value as argument. This argument is the initial value of the state
cell embedded inside the handler. cell embedded inside the handler.
% %
\begin{syntax} \begin{syntax}
\slab{Handler\textrm{ }types} & F &::=& \cdots \mid \Record{C; A} \Rightarrow^\param D\\ \slab{Handler\textrm{ }types} & F &::=& \cdots \mid \Record{C; A} \Rightarrow^\param D\\
\slab{Computations} & M,N &::=& \cdots \mid \ParamHandle\; M \;\With\; H^\param(W)\\ \slab{Computations} & M,N &::=& \cdots \mid \ParamHandle\; M \;\With\; H^\param(W)\\
\slab{Parameterised\textrm{ }definitions} & H^\param &::=& q^A.~H \slab{Parameterised\textrm{ }definitions} & H^\param &::=& q^A.~H
\end{syntax} \end{syntax}
% %
Figure~\ref{fig:param-syntax} extends the syntax of The syntactic category of handler types $F$ is extended with a new
$\HCalc$ with parameterised handlers. Syntactically a parameterised kind of handler arrow for parameterised handlers. The left hand side
handler is new binding form $(q^A.~H)$, where $q$ is the name of the of the arrow is a pair, whose first component denotes the type of the
parameter, whose type is $A$. The name is bound in the ordinary input computation and the second component denotes the type of the
handler definition $H$. The elimination form handler parameter. The right hand side denotes the return type of the
($\ParamHandle\; M\;\With\;H^\param(W)$) is similar to the form for handler.
ordinary deep handlers, except that the parameterised handler %
definition is applied to a value $W$, which is the initial value of The computations category is extended with a new application form for
the parameter $q$. handlers, which runs a computation $M$ under a parameterised handler
$H$ applied to the value $W$.
%
Finally, a new category is added for parameterised handler
definitions. A parameterised handler definition is a new binding form
$(q^A.~H)$, where $q$ is the name of the parameter, whose type is $A$,
and $H$ is an ordinary handler definition $H$. The parameter $q$ is
accessible in the $\Return$ and operation clauses of $H$.
As with ordinary deep handlers and shallow handlers, there are two
typing rules: one for handler application and another for handler
definitions.
% %
\begin{mathpar} \begin{mathpar}
% Handle % Handle
@@ -7532,7 +7545,18 @@ the parameter $q$.
\typ{\Gamma}{H^\param : \Record{C; A} \Harrow^\param D} \typ{\Gamma}{H^\param : \Record{C; A} \Harrow^\param D}
} }
{\Gamma \vdash \ParamHandle \; M \; \With\; H^\param(W) : D} {\Gamma \vdash \ParamHandle \; M \; \With\; H^\param(W) : D}
\end{mathpar}
%
The $\tylab{Handle^\param}$ rule is similar to the $\tylab{Handle}$
and $\tylab{Handle^\dagger}$ rules, except that it has to account for
the parameter $W$, whose type has to be compatible with the second
component of the domain type of the handler definition $H^\param$.
%
The typing rule for parameterised handler definitions adapts the
corresponding typing rule $\tylab{Handler}$ for ordinary deep handlers
with the addition of a parameter.
%
\begin{mathpar}
% Parameterised handler % Parameterised handler
\inferrule*[Lab=\tylab{Handler^\param}] \inferrule*[Lab=\tylab{Handler^\param}]
{{\bl {{\bl
@@ -7546,101 +7570,290 @@ the parameter $q$.
{\typ{\Delta;\Gamma}{(q^{A'} . H) : \Record{C;A'} \Harrow^\param D}} {\typ{\Delta;\Gamma}{(q^{A'} . H) : \Record{C;A'} \Harrow^\param D}}
\end{mathpar} \end{mathpar}
%% %%
We require two additional rules to type parameterised handlers. The The key differences between the \tylab{Handler} and
rules are given in Figure~\ref{fig:param-static-semantics}. The main \tylab{Handler^\param} rules are that in the latter the return and
differences between the \tylab{Handler} and \tylab{Handler^\param} are operation cases are typed with respect to the parameter $q$, and that
that in the latter the return and operation cases are typed with resumptions $r_i$ have type $\Record{B_\ell;A'} \to D$, that is a
respect to the parameter $q$, and that resumptions $r$ have type parameterised resumption is a binary function, where the first
$\Record{B_\ell;A'} \to D$, that is they accept a pair as argument is the interpretation of an operation and the second argument
input. is the (updated) handler state. The return type of $r_i$ is the same
as the return type of the handler, meaning that an invocation of $r_i$
is guarded in the same way as an invocation of an ordinary deep
resumption.
\subsection{Dynamic semantics} \subsection{Dynamic semantics}
Operationally, a parameterised resumption uses the first component as The two reduction rules for parameterised handlers adapt the reduction
the return value of the operation, and the second component as the rules for ordinary deep handlers with a parameter.
updated value of the handler parameter $q$.
% %
This operational behaviour is formalised by following reduction rule \begin{reductions}
$\semlab{Op^\param}$. \semlab{Ret^\param} &
\ParamHandle \; (\Return \; V) \; \With \; (q.H)(W) &\reducesto& N[V/x,W/q],\\
\multicolumn{4}{@{}r@{}}{
\hfill\text{where } \hret = \{ \Return \; x \mapsto N \}} \\
\semlab{Op^\param} &
\ParamHandle \; \EC[\Do \; \ell \, V] \; \With \; (q.H)(W)
&\reducesto& N[V/p,W/q,R/r],\\
\multicolumn{4}{@{}r@{}}{
\hfill\ba[t]{@{~}r@{~}l}
\text{where}& R = \lambda\Record{y;q'}.\ParamHandle\;\EC[\Return \; y]\;\With\;(q.H)(q')\\
\text{and} &\hell = \{ \OpCase{\ell}{p}{r} \mapsto N \}\\
\text{and} &\ell \notin \BL(\EC)
\ea
}
\end{reductions}
%
The rule $\semlab{Ret^\param}$ handles the return value of a
computation. Just like the rule $\semlab{Ret}$ the return value $V$ is
substituted for the binder $x$ in the return case body
$N$. Furthermore the value $W$ is substituted for the handler
parameter $q$ in $N$, meaning the handler parameter is accessible in
the return case.
The $\semlab{Op^\param}$ handles an operation invocation. Both the
operation payload $V$ and handler argument $W$ are accessible inside
the case body $N$. As with ordinary deep handlers, the resumption
rewraps its handler, but with the slight twist that the parameterised
handler definition is applied to the updated parameter value $q'$
rather than the original value $W$. This achieves the effect of state
passing as the value of $q'$ becomes available upon the next
activation of the handler.
\subsection{Process synchronisation}
%
In Section~\ref{sec:tiny-unix-time} we implemented a time-sharing
system on top of a simple process model. However, the model lacks a
process synchronisation facility. It is somewhat difficult to cleanly
add support for synchronisation to the implementation as it is in
Section~\ref{sec:tiny-unix-time}. Firstly, because the interface of
$\Fork : \UnitType \to \Bool$ only gives us two possible process
identifiers: $\True$ and $\False$, meaning at any point we can only
identify two processes. Secondly, and more importantly, some state is
necessary to implement synchronisation, but the current implementation
of process scheduling is split amongst two handlers and one auxiliary
function, all of which need to coordinate their access and
manipulation of the state cell. One option is to use some global state
via the interface from Section~\ref{sec:tiny-unix-io}, which has the
advantage of making the state manipulation within the scheduler
modular, but it also has the disadvantage of exposing the state as an
implementation detail --- and it comes with all the caveats of
programming with global state. A parameterised handler provides an
elegant solution, which lets us internalise the state within the
scheduler.
We will see how a parameterised handler enables us to implement a
richer process model supporting synchronisation with ease. The effect
signature of process concurrency is as follows.
% %
\[ \[
\ba{@{~}l@{~}l} \Co \defas \{\UFork : \UnitType \opto \Int; \Wait : \Int \opto \UnitType; \Interrupt : \UnitType \opto \UnitType\}
&\ParamHandle \; \EC[\Do \; \ell \; V] \; \With \; (q.~H)(W)\\
\reducesto&
N[V/p,W/q,\lambda \Record{y;q'}.\Handle \; \EC[\Return \; y] \; \With \; (q.~H)(q')/r]\\
& \text{ where } \ell \notin BL(\EC) \text{ and } \hell = \{ \OpCase{\ell}{p}{r} \mapsto N \}
\ea
\] \]
% %
The parameter value $W$ is substituted for the parameter name $q$ into The operation $\UFork$ models \UNIX{}
the operation case body $N$. As with ordinary deep handlers, the \emph{fork}~\cite{RitchieT74}. It is generalisation of the $\Fork$
resumption rewraps its handler, but with the slight twist that the operation from Section~\ref{sec:tiny-unix-time}. The operation is
parameterised handler definition is applied to the updated parameter intended to return twice: once to the parent process with a unique
value $q'$ rather than the original value $W$. The reduction rule for process identifier for the child process, and a second time to the
handling the return of a computation is as follows. child process with the zero identifier. The $\Wait$ operation takes a
process identifier as argument and then blocks the invoking process
until the process associated with the provided identifier has
completed. The $\Interrupt$ operation is the same as in
Section~\ref{sec:tiny-unix-time}; it temporarily suspends the invoking
process in order to let another process run.
The main idea is to use the state cell of a parameterised handler to
manage the process queue and to keep track of the return values of
completed processes. The scheduler will return the list of values when
there are no more processes to be run. The process queue will consist
of reified processes, which we will represent using parameterised
resumptions. To make the type signatures understandable we will make
use of three mutually recursive type aliases.
% %
\[ \[
\ParamHandle \, (\Return \; V) \; \With \; (q.~H)(W) \reducesto N[V/x,W/q], \text{where } \hret = \{ \Return \; x \mapsto N \} \ba{@{~}l@{~}l@{~}c@{~}l}
\Proc &\alpha~\varepsilon &\defas& \Sstate~\alpha~\varepsilon \to \List~\alpha \eff \varepsilon\\
\Pstate &\alpha~\varepsilon &\defas& [\Ready:\Proc~\alpha~\varepsilon;\Blocked:\Record{\Int;\Proc~\alpha~\varepsilon}]\\
\Sstate &\alpha~\varepsilon &\defas& \Record{q:\List\,\Record{\Int;\Pstate~\alpha~\varepsilon};done:\List~\alpha;pid:\Int;pnext:\Int}\\
\ea
\] \]
% %
Both the return value $V$ and the parameter value $W$ are substituted The $\Proc$ alias is the type of reified processes. It is defined as a
into the return case body $N$ for their respective binders. function that takes the current scheduler state and returns a list of
$\alpha$s. This is almost the type of a parameterised resumption as
\subsection{Process scheduling} the only thing missing is the a component for the interpretation of an
operation.
\[ %
\Co \defas \{\UFork : \UnitType \opto \Int; \Interrupt : \UnitType \opto \UnitType; \Wait : \Int \opto \UnitType\} The second alias $\Pstate$ enumerates the possible process
\] states. Either a process is \emph{ready} to be run or it is
\emph{blocked} on some other process. The payload of the $\Ready$ tag
is the process to run. The $\Blocked$ tag is parameterised by a pair,
where the first component is the identifier of the process that is
being waited on and the second component is the process to be
continued when the other process has completed.
%
The third alias $\Sstate$ is the type of scheduler state. It is a
quadruple, where the label $q$ is the process queue. It is implemented
as an association list indexed by process identifiers. The label
$done$ is used to store the return values of completed processes. The
label $pid$ is used to remember the identifier of currently executing
process, and finally $pnext$ is used to compute a unique identifier
for new processes.
We will abstract some of the scheduling logic into an auxiliary
function $\runNext$, which is responsible for dequeuing and running
the next process from the queue.
%
\[ \[
\bl \bl
\dec{SchedState}~\alpha~\varepsilon \defas \Record{\List\,(\dec{Process}~\alpha~\varepsilon);\List~\alpha;\Int;\Int}\\ \runNext : \Sstate~\alpha~\varepsilon \to \List~\alpha \eff \varepsilon\\
\dec{Process}~\alpha~\varepsilon \defas \dec{SchedState} \to \alpha \eff \varepsilon \runNext~st \defas
\bl
\Case\;st.q\;\{\\
~\bl
\nil \mapsto st.done\\
\Record{pid;\Blocked\,\Record{pid';resume}} \cons q' \mapsto\\
\quad\bl
\Let\;st' \revto \Record{st \;\With\; q = q' \concat [\Record{pid;\Blocked\,\Record{pid';resume}}]}\;\In\\
\runNext~st'
\el\\
\Record{pid;\Ready~resume} \cons q' \mapsto\\
\quad\bl
\Let\;st' \revto \Record{st \;\With\; q = q'; pid = pid}\;\In\\
resume~st'\,\}
\el
\el
\el
\el \el
\] \]
%
The function operates on the scheduler state. It first performs a case
split on the process queue. There are three cases to consider.
\begin{enumerate}
\item The queue is empty. Then the function returns the list $done$,
which is the list of process return values.
\item The next process is blocked. Then the process is moved to the
end of the queue, and the function is applied recursively to the
scheduler state with the updated queue.
\item The next process is ready. Then the $q$ and $pid$ fields
within the scheduler state are updated accordingly. The updated
state is supplied as argument to the reified process.
\end{enumerate}
%
\[ \[
\bl \bl
\scheduler : \Record{\alpha \eff \Co;\dec{SchedState}~\alpha~\Co} \Harrow^\param \List~\alpha\\ \scheduler : \Record{\alpha \eff \{\Co;\varepsilon\};\Sstate~\alpha~\varepsilon} \Harrow^\param \List~\alpha \eff \varepsilon\\
\scheduler \defas \scheduler \defas
\bl \bl
\Record{ps;qs;done;pid;nextpid}.\\ st.\\
\bl \bl
\Return\;x \mapsto \Case\;qs \concat ps\;\{\\ \Return\;x \mapsto \\
\quad\bl \quad\bl
\nil \mapsto x \cons done\\ \Let\;done' \revto x \cons st.done\;\In\\
\Record{pid';qs';resume} \cons ps' \mapsto resume\,\Record{ps';qs';done;pid;nextpid}\} \runNext\,\Record{st\;\With\;done = done'}
\el\\ \el\\
\OpCase{\UFork}{\Unit}{resume} \mapsto\\ \OpCase{\UFork}{\Unit}{resume} \mapsto\\
\quad\bl \quad\bl
\Let\;ps' \revto \Record{nextpid;\nil;\lambda s.resume~\Record{0;s}} \cons ps\;\In\\ \Let\;resume' \revto \lambda st.resume\,\Record{0;st}\;\In\\
resume\,\Record{nextpid;\Record{ps';qs;done;pid;nextpid+1}} \Let\;pid \revto st.pnext \;\In\\
\el\\ \Let\;q' \revto st.q \concat [\Record{pid;\Ready~resume'}]\;\In\\
\OpCase{\Interrupt}{\Unit}{resume} \mapsto\\ \Let\;st' \revto \Record{st\;\With\;q = q'; pnext = pid + 1}\;\In\\
\quad\bl\Let\;\Record{pid';qs';resume'} \cons ps' \revto ps \concat [\Record{pid;qs;\lambda s.resume\,\Record{\Unit;s}}]\;\In\\ resume\,\Record{pid;st'}
resume'\,\Record{ps';qs';done;pid';nextpid} \el\\
\OpCase{\Wait}{pid}{resume} \mapsto\\
\quad\bl
\Let\;resume' \revto \lambda st.resume~\Record{\Unit;st}\;\In\\
\Let\;q' \revto
\bl
\If\;\dec{has}\,\Record{pid;st.q}\\
\Then\;st.q \concat [\Record{st.pid;\Blocked\,\Record{pid;resume'}}]\\
\Else\;st.q \concat [\Record{st.pid;\Ready~resume'}]
\el\\ \el\\
\OpCase{\Wait}{pid'}{resume} \mapsto\\ \In\;\runNext\,\Record{st\;\With\;q = q'}
\quad\bl \el\\
\Let\; q \revto \Record{qs;\lambda s.resume\,\Record{\Unit;s}}\;\In\\ \OpCase{\Interrupt}{\Unit}{resume} \mapsto\\
\Let\;\Record{qs';resume'} \revto \lookup\,\Record{pid';ps}\;\In\\ \quad\bl
\Let\;p \revto \Record{\Record{pid;q} \cons qs;resume'}\;\In\\ \Let\;resume' \revto \lambda st.resume\,\Record{\Unit;st}\;\In\\
\Let\;s \revto \modify\,\Record{pid';p;ps}\;\In\\ \Let\;q' \revto st.q \concat [\Record{st.pid;\Ready~resume'}]\;\In\\
\runNext~\Record{st \;\With\; q = q'}
\el \el
\el \el
\el \el
\el \el
\] \]
%
%
\[ \[
\bl \bl
\timesharee : (\UnitType \to \alpha \eff \Co) \to \List~\alpha\\ \timesharee : (\UnitType \to \alpha \eff \Co) \to \List~\alpha\\
\timesharee~m \defas \timesharee~m \defas
\bl \bl
\ParamHandle\;m\,\Unit\;\With\; \scheduler~\Record{\nil;1;2} \Let\;st_0 \revto \Record{q=\nil;done=\nil;pid=1;pnext=2}\;\In\\
\ParamHandle\;m\,\Unit\;\With\; \scheduler~st_0
\el \el
\el \el
\] \]
%
%
\[
\bl
\init : (\Unit \to \alpha \eff \varepsilon) \to \alpha \eff \{\Co;\Exit : \Int \opto \ZeroType;\varepsilon\}\\
\init~main \defas
\bl
\Let\;pid \revto \Do\;\UFork~\Unit\;\In\\
\If\;pid = 0\\
\Then\;main\,\Unit\\
\Else\;\Do\;\Wait~pid; \exit~1
\el
\el
\]
%
%
\[
\ba{@{~}l@{~}l}
&\bl
\runState\,\Record{\dec{fs}_0;\fileIO\,(\lambda\Unit.\\
\quad\timesharee\,(\lambda\Unit.\\
\qquad\dec{interruptWrite}\,(\lambda\Unit.\\
\qquad\quad\sessionmgr\,\Record{\Root;\lambda\Unit.\\
\qquad\qquad\status\,(\lambda\Unit.
\init\,(\lambda\Unit.
\ba[t]{@{}l}
\Let\;pid \revto \Do\;\UFork\,\Unit\;\In\\
\If\;pid = 0\\
\Then\;\bl
\su~\Alice; \Do\;\Wait~pid;
\quoteRitchie\,\Unit; \exit~2
\el\\
\Else\; \su~\Bob; \quoteHamlet\,\Unit;\exit~3))})))}
\ea
\el \smallskip\\
\reducesto^+&
\bl
\Record{
\ba[t]{@{}l}
[1;2;3];\\
\Record{
\ba[t]{@{}l}
dir=[\Record{\strlit{stdout};0}];\\
ilist=[\Record{0;\Record{lno=1;loc=0}}];\\
dreg=[
\ba[t]{@{}l}
\Record{0;
\ba[t]{@{}l@{}l}
\texttt{"}&\texttt{To be, or not to be,\nl{}that is the question:\nl{}}\\
&\texttt{Whether 'tis nobler in the mind to suffer\nl{}}\\
&\texttt{UNIX is basically a simple operating system, but }\\
&\texttt{you have to be a genius to understand the simplicity.\nl"}}];
\ea\\
lnext=1; inext=1}}\\
\ea
\ea
\ea\\
: \Record{\List~\Int; \FileSystem}
\el
\ea
\]
%
\section{Related work} \section{Related work}
\label{sec:unix-related-work} \label{sec:unix-related-work}