Template Haskell
We added
hooks
to these iserv-related functions:
startIServstopIServiservCallreadIServwriteIServ
The hook of hscCompileCoreExpr is also used. The implementation of the hooks
are in
Asterius.GHCi.Internals
Normally, startIServ and stopIServ starts/stops the current iserv process.
We don't use the normal iserv library for iserv though; we use
inline-js-core to start a node process. inline-js-core has its own mechanism
of message passing between host/node, which is used for sending JavaScript code
to node for execution and getting results. In the case of TH, the linked
JavaScript and WebAssembly code is sent. Additionally, we create POSIX pipes and
pass the file descriptors as environment variables to the sent code; so most TH
messages are still passed via the pipes, like normal iserv processes.
The iservCall function is used for sending a Message to iserv and
synchronously getting the result. The sent messages are related to linking, like
loading archives and objects. Normally, linking is handled by the iserv
process, since it's linked with GHC's own runtime linker. In our case, porting
GHC's runtime linker to WebAssembly is going to be a huge project, so we still
perform TH linking in the host ahc process. The linking messages aren't sent
to node at all; using the hooked iservCall, we maintain our own in-memory
linker state which records information like the loaded archives and objects.
When splices are executed, GHC first emits a RunTH message, then repeatedly
queries the response message from iserv; if it's a RunTHDone, then the dust
settles and GHC reads the execution result. The response message may also be a
query to GHC, then GHC sends back the query result and repeat the loop. In our
case, we don't send the RunTH message itself to node; RunTH indicates
execution has begun, so we perform linking, and use inline-js-core to load the
linked JavaScript and WebAssembly code, then create and initialize the Asterius
instance object. The splice's closure address is known at link time, so we can
apply the TH runner's function closure to the splice closure, and kick off
evaluation from there. The TH runner function creates a fresh IORef QState, a
Pipe from the passed in pipe file descriptors, and uses ghci library's own
runTH function to run the splice. During execution, the Quasi class methods
may be called, and on the node side, they are turned to THMessages sent back
to the host via the Pipe, and the responses are then fetched.
Our function signatures of readIServ and writeIServ are modified. Normal GHC
simply uses Get and Put in the binary library for reading/writing via the
Pipe, but we simply read/write a polymorphic type variable a, with Binary
and Typeable constraints. Having Binary constraint allows fetching the
needed get and put functions, and Typeable allows us to inspect the
message pre-serialization. This is important, since we need to catch RunTH or
RunModFinalizer messages. As mentioned before, these messages aren't sent to
node, and we have special logic to handle them.
As for hscCompileCoreExpr: it's used for compiling the CoreExpr of a splice
and getting the resulting RemoteHValue. We don't support GHC bytecode, so we
overload it and go through the regular pipeline, compile it down to Cmm, then
WebAssembly, finally performing linking, using the closures of the TH runner
function and the splice as "root symbols". The resulting RemoteHValue is not
"remote" though; it's simply the static address of the splice's closure, and the
TH runner function will need to encapsulate it as a RemoteRef before feeding
to runTH.
TH WIP branch:
asterius-TH
GitHub Project with relevant issues