Build iserv (external interpreter) program on demand
We're in the process of making boot libraries reinstallable. E.g. GHC was built with ghc-bignum
using the GMP backend, but the user wants to use ghc-bignum
with its native
(haskell) backend. To make this possible, we have to rebuild ghc-bignum
with the native
backend enabled and every package depending on it: ghc-internal
, base
, etc.
By doing this, we end up in a state where GHC can't use its internal interpreter for Template Haskell as it would require linking conflicting versions of the boot libraries. This is akin to cross-compilation and indeed we can similarly use an external-interpreter program to allow GHC to evaluate TH splices.
Currently, GHC comes with some prebuilt ghc-iserv-*
programs to support RTS ways: profiling, debug, etc. (A vanilla GHC can't use its internal interpreter to load a library built with the profiling/debug way because of the ABI incompatibility). Distributing a set of prebuilt iserv
programs was (quite) fine as there was only a closed number of iserv
programs to distribute (and advanced users could build their own).
Now, however, if we support reinstallable boot libraries, it's no longer a closed world. Users will expect that changing the Cabal flag for ghc-bignum
in their cabal.project
will just work. So I believe GHC should build ghc-iserv
programs on demand.
Currently an iserv
program is composed of just:
-- utils/iserv/src/Main.hs
-- |
-- The Remote GHCi server.
--
-- For details on Remote GHCi, see Note [Remote GHCi] in
-- compiler/GHC/Runtime/Interpreter.hs.
--
module Main (main) where
import GHCi.Server (defaultServer)
main :: IO ()
main = defaultServer
// utils/iserv/cbits/iservmain.c
#include <ghcversion.h>
#include <rts/PosixSource.h>
#include <Rts.h>
#include <HsFFI.h>
int main (int argc, char *argv[])
{
RtsConfig conf = defaultRtsConfig;
// We never know what symbols GHC will look up in the future, so
// we must retain CAFs for running interpreted code.
conf.keep_cafs = 1;
conf.rts_opts_enabled = RtsOptsAll;
extern StgClosure ZCMain_main_closure;
hs_main(argc, argv, &ZCMain_main_closure, conf);
}
We could reduce it to just the following C file:
#include <ghcversion.h>
#include <rts/PosixSource.h>
#include <Rts.h>
#include <HsFFI.h>
int main (int argc, char *argv[])
{
RtsConfig conf = defaultRtsConfig;
// We never know what symbols GHC will look up in the future, so
// we must retain CAFs for running interpreted code.
conf.keep_cafs = 1;
conf.rts_opts_enabled = RtsOptsAll;
extern StgClosure ghci_unit_idZCGHCiziServerzidefaultServer; // ghci_unit_id depends on the ghci unit we link
hs_main(argc, argv, &ghci_unit_idZCGHCiziServerzidefaultServer, conf);
}
By only having a C file, we wouldn't have to build another Haskell program in a current GHC session which could be difficult (messing with HscEnv, etc.). If we only have to build a C program, it's fine: we just need to pass the appropriate flags to link with the GHCi unit.
A proof that it works is that it is already what we do in the JS backend to spawn an external interpreter on demand (see here), with the difference that it's done in JS so there is nothing to compile compared to C.
Cons:
- a bit slower than just running a pre-existing program. But we would cache these programs for the duration of a
ghc --make
session, and it's only a C program to compile/link, not Haskell.
Pros:
- no need to prebuild
ghc-iserv-*
programs: smaller bindists, smaller bug surface - support for arbitrary RTS ways, dependencies, etc.
- one step closer to #12218 (where we also statically link the dependencies required by a splice)
- (advanced users can still provide their own
ghc-iserv
via-pgmi
, so no change here)