Hi Alexis,
let me try to provide the high up view. I'm sorry if I'm going a bit overboard on details you already know. But let's
start with clearing up a misconception first. No, GHCi does not always require dynamic linking.
At the very abstract level we have a compiler that knows how to turn various inputs into object code. This includes, C, Cmm, Haskell, assembly (.c, .S, .cmm, .hs -> .o). Thus a complete haskell package ends up as a bunch of object code files. We know that for dynamic linkers, we may need slightly different arguments (e.g. PIC).
We next roll these up into archives (.a) and occasionally a pre-linked object file (e.g. link the whole set of object files into one object file, and resolve internal references); as well as a dynamic (shared object, dylib) file.
For GHCi's purposes (and TH), we ultimately want to call a haskell function to produce some AST to splice in. This haskell
function might not be defined in a different package, but the same, so we'll have to deal with some in-flight packages anyway.
We may habe some Byte Code Object (BCO) glue code to invoke the haskell function, which GHCi will interpret during evaluation. However that function can depend on a large dependency tree, and we don't have BCO for everything. I still think it would be nice to have an abstract machine and an Intermediate Representation/ByteCode, that's a much larger project though. Also until recently BCO's couldn't encode unboxed types/sums even.
So given the BCO glue code, we really want to call into object code (also for performance). You can instruct GHCi to prefer object code as well via -fobject-code.
This now leads us to the need of getting the object code somehow into memory and running it. The dynamic system linker approach would be to turn the object code with the function we want to call into a shared library, and just hand that over to the linker (e.g. dlopen).
However, GHC has for a long time grown it's own in-memory static linker. As such it has the capability to load object file (.o) and resolve them on the fly. There is no need for system shared libraries, a system linker, and to deal with potential bugs in that linker. It also means we can link on platforms that don't have a system linker or a severely restricted one (e.g. iOS).
So from a high level you can look at GHC's RTS linker as a special feature of GHC that allows us to not need a system
provided dynamic linker, if there is none available, or using it is undesirable.
Whether or not stuff is loaded through the internal or external interpreter has near no difference. You _can_ load different abi's through the external iserv (as that iserv can be built against a different abi).
Hope this helps a bit? Feel free to ask more questions.
Cheers,
Moritz