Template Haskell

We added hooks to these iserv-related functions:

  • startIServ
  • stopIServ
  • iservCall
  • readIServ
  • writeIServ

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