This document describes a proposal for a hooks interface to customise certain phases of GHC's source transformation facilities. A hook is GHC API-user-supplied callback that overrides the built-in functionality when installed.
Much of GHC is exposed as a library, making it possible to use the compiler in various new ways, like extracting type information from Haskell code or generating code from one of the intermediate representations. Often, these use cases require some change from the default behaviour at certain points during compilation. For example someone writing a custom code generator would want reuse as much as possible of the GHC pipeline, to get dependency chasing and recompilation avoidance, but replace the part where normally the native code generator would be called.
Unfortunately the GHC API offers no configuration options for this, and it is hard to customize the compiler manually: For example once load LoadAllTargets is called, GHC runs its own pipeline, replacing specific functionality would require the user to copy large parts of GhcMake and DriverPipeline.
One option is to split GHC's stages into lots of small function calls and allow the user to wire these stages together as needed. Unfortunately, this is very difficult to do and wouldn't necessarily be very flexible since there are a number of expected invariants not encoded in the types.
Instead we propose hooks: Hooks are an extensible way to replace specific parts of the GHC pipeline by user-supplied callbacks. The goal is to add them to strategic points in the library, to cover most use cases without sprinkling hooks carelessly throughout the GHC code. The API should be considered in flux, since it could require some experimentation to find the best hook locations.
An example that uses all currently implemented hooks, along with who uses them can be found here:
Each hook has a potentially different type from all the other hooks. Additionally, we need to be able to communicate hooks to all the locations where they may be invoked. We can achieve this by storing the hooks in the DynFlags.
Unfortunately, it is impossible to store them directly, as a record, since that would lead to huge cyclic imports (the data types used by the hooks would depend on DynFlags, but DynFlags would depend on the modules defining the types in the hooks record)
Instead we implement Hooks as a heterogeneous map. The public API allows one to insert and lookup hooks safely, correctness of the types is guaranteed by the Hook type family. Internally, the TypeRep of a is used as the actual key for Hook a:
newtype Hooks = Hooks (M.Map TypeRep Any)type family Hook a :: *insertHook :: Typeable a => a -> Hook a -> Hooks -> HooksinsertHook tag hook (Hooks m) = Hooks (M.insert (typeOf tag) (unsafeCoerce hook) m)lookupHook :: Typeable a => a -> Hooks -> Maybe (Hook a)lookupHook tag (Hooks m) = fmap unsafeCoerce (M.lookup (typeOf tag) m)
Installing a hook
To use a hook, just add it to the DynFlags for your session, using the Hook's key:
Keep in mind that Hooks is a low level API, it's easy to break things. Also in some cases, inserting one hook may require inserting another. For example if you use TcForeignsHook to accept extra types for your foreign imports, you'll need DsForeignsHook to desugar them, otherwise GHC will not know what to do with them.
Making a new hook
Every hook requires a key, a data type that has to implement Typeable, since we use its TypeRep as a globally unique key for the Hooks map. Additionally, a type family instance of Hook is required, to map the key to the actual hook type. You might want to export the original unhooked function, or extra types and functions that users of the hook will need.
If you're in a monad with a HasDynFlags instance, you can use the getHooked function from DynFlags:
data HscFrontendHook = HscFrontendHook deriving Typeabletype instance Hook HscFrontendHook = ModSummary -> Hsc TcGblEnvgenericHscFrontend :: ModSummary -> Hsc TcGblEnvgenericHscFrontend mod_summary = getHooked HscFrontendHook genericHscFrontend' >>= ($ mod_summary)-- original functiongenericHscFrontend' :: ModSummary -> Hsc TcGblEnvgenericHscFrontend' mod_summary | ExtCoreFile <- ms_hsc_src mod_summary = panic "GHC does not currently support reading External Core files" | otherwise = do hscFileFrontEnd mod_summary
Otherwise, use the hooks field from DynFlags directly: