Draft semantics of concurrency and foreign calls.
Note: This document assumes that every function takes exactly one argument. Just imagine that it's the last argument in a fully saturated call.
Foreign export asynchronous
Suppose that a Haskell function f
is exported to JavaScript
asynchronously (which might be the default). When JavaScript calls the
exported function with argument v
, it has the effect of performing
the IO action ⟦f⟧ v
, where the translation ⟦f⟧
is defined as
follows:
⟦f⟧ v = do
p <- allocate new promise
let run_f = case try (return $ f $ jsToHaskell v) of
Left exn -> p.fails (exnToJS exn)
Right a -> p.succeeds (haskellToJS a)
forkIO run_f
return p -- returned to JavaScript
Not specified here is whether the scheduler is allowed to steal a few cycles to run previously forked threads.
N.B. This is just a semantics. We certainly have the option of implementing the entire action completely in the runtime system.
Not yet specified: What is the API by which JavaScript would call an asynchronously exported Haskell function? Would it, for example, use API functions to construct a Haskell closure, then evaluate it?
Foreign import asynchronous
Suppose that a JavaScript function g
is imported asynchronously
(which might be the default). Let types a
and b
stand for two
unknown but fixed types. The JavaScript function expects an argument
of type a
and returns a Promise
that (if successful) eventually
delivers a value of type b
. When a Haskell thunk of the form g e
is forced (evaluated), the machine performs the following monadic
action, the result of which is (eventually) written into the thunk.
do let v = haskellToJS e -- evaluates e, converts result to JavaScript
p <- g v -- call returns a `Promise`, "immediately"
m <- newEmptyMVar
... juju to associate m with p ... -- RTS primitive?
result <- takeMVar m
case result of Left fails -> ... raise asynchronous exception ...
Right b -> return $ jsToHaskell v
CPU sharing
Suppose GHC wishes to say politely to the JavaScript engine, "every so
often I would like to use the CPU for a bounded time." It looks like
Haskell would need to add a message to the JavaScript message queue,
such that the function associated with that messages is "run Haskell
for N ticks." Is the right API to call setTimeout
with a delay of 0
seconds?
Concurrency sketch
Let's suppose the state of a Haskell machine has these components:
-
F
("fuel") is the number of ticks a Haskell thread can execute before returning control to JavaScript. This component is present only when Haskell code is running. -
R
("running") is either the currently running Haskell thread, or if no thread is currently running, it is • ("nothing") -
Q
("run queue") is a collection of runnable threads. -
H
("heap") is the Haskell heap, which may containMVar
s and threads that are blocked on them.
Components R
and H
are used linearly, so they can be stored in
global mutable state.
The machine will enjoy a set of labeled transitions such as are
described in Simon PJ's paper on the "Awkward Squad." Call these the
"standard transitions." (The awkward-squad machine state is a single
term, formed by the parallel composition of R
with all the threads
of Q
and all the MVars of H
. The awkward squad doesn't care about
order, but we do.) To specify the standard transitions, we could add
an additional clock that tells the machine when to switch the running
thread R
out for a new thread from the queue. Or we could leave the
context switch nondeterministic, as it is in the awkward-squad paper.
Whatever seems useful.
Every state transition has the potential use to fuel. Fuel might
actually be implemented using an allocation clock, but for semantics
purposes, we can simply decrement fuel at each state transition, then
gate the standard transitions on the condition F > 0
.
At a high level, every invocation of Haskell looks the same:
JavaScript starts the Haskell machine in a state ⟨F, •, Q, H⟩
, and
the Haskell machine makes repeated state transitions until it reaches
one of two stopping states:
-
⟨F',•, [], H'⟩
: no Haskell threads are left to run -
⟨0, ̧R', Q', H'⟩
: fuel is exhausted, in which case the machine moves the currently running thread onto the run queue, reaching state⟨0, ̧•, R':Q', H'⟩
Once one of these states is reached, GHC's runtime system takes two actions:
-
It allocates a polite request for the CPU and puts that request on the JavaScript message queue, probably using
setTimeout
with a delay of 0 seconds. -
It returns control to JavaScript.