Skip to content
Ryan Scott edited this page May 22, 2015 · 5 revisions

HERMIT is a toolkit for interacting with GHC's intermediate language, Core. Why would someone want to do this?

  • Programmers concerned with performance may wish to understand how their code is being optimized. HERMIT provides a compact pretty printer for Core, along with the ability to pause compilation and "look around".
  • Library writers may want to provide customized optimization passes with their libraries.
  • GHC developers may wish to prototype their optimization ideas without modifying GHC directly, or use HERMIT to debug existing passes.

Ostensibly, all of these things can be accomplished with GHC's existing plugin mechanism and knowledge of some of GHC's internals. The goal of HERMIT is to lower the barrier to entry, and provide a higher level of abstraction.

Installing

HERMIT is provided as a cabal package, so it can be installed like any other package. We currently support both GHC 7.6 and 7.8 (HEAD).

cabal update
cabal install hermit

We rarely break the build (I swear!), so those wishing for the absolute latest are welcome to install from this github repository.

Running HERMIT

We'll use the following simple program (in Fib.hs) as our target for the next few examples.

module Main where

main = print $ fib 35

fib :: Int -> Int
fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

To examine this program with HERMIT, simply run the hermit command on the file.

> hermit Fib.hs
[starting HERMIT v0.4.0.0 on Fib.hs]
% ghc Fib.hs -fforce-recomp -O2 -dcore-lint -fsimple-list-literals -fexpose-all-unfoldings -fplugin=HERMIT -fplugin-opt=HERMIT:*:
[1 of 1] Compiling Main             ( Fib.hs, Fib.o )
...
===================== Welcome to HERMIT ======================
HERMIT is a toolkit for the interactive transformation of GHC
core language programs. Documentation on HERMIT can be found
on the HERMIT web page at:
http://www.ittc.ku.edu/csdl/fpg/software/hermit.html

You have just loaded the interactive shell. To exit, type 
"abort" or "resume" to abort or resume GHC compilation.

Type "help" for instructions on how to list or search the
available HERMIT commands.

To get started, you could try the following:
  - type "binding-of 'foo", where "foo" is a function
    defined in the module;
  - type "set-pp-type Show" to display full type information;
  - type "info" for more information about the current node;
  - to descend into a child node, type the name of the child
    ("info" includes a list of children of the current node);
  - to ascend, use the "up" command;
  - type "log" to display an activity log.
==============================================================

module main:Main where
  fib ∷ Int → Int
  main ∷ IO ()
  main ∷ IO ()
hermit<0>

Lets explain some things, from the top:

  • The hermit command simply calls ghc with some flags set. The important ones are -fplugin and -fplugin-opt. We provide the hermit command because its more convenient than setting the GHC options directly once invocations become more interesting. More on that soon.
  • By default, HERMIT runs on ALL modules being compiled. So if our Main module depended on other modules that are also being recompiled, HERMIT will present an interactive prompt for each one.
  • Also by default, HERMIT runs at the front of the optimization pipeline. The code you're about to see is the result of the desugarer. GHC's normal optimizations will run after this interactive session.
  • The 0 in the prompt is the AST number you are working on. More on that later.

The initial view just shows the module signature. You can step down into a list of top-level bindings using the prog command.

hermit<0> prog
rec fib = λ ds →
      case ds of wild ▲
        I# ds →
          case ds of ds ▲
            _ → (λ ds → (+) ▲ $fNumInt (fib ((-) ▲ $fNumInt ds (I# 1))) (fib ((-) ▲ $fNumInt ds (I# 2)))) void#
            0 → I# 1
            1 → I# 1
main = ($) ▲ ▲ (print ▲ $fShowInt) (fib (I# 35))
main = runMainIO ▲ main
hermit<1>

We now can see the right-hand sides of each of the three top-level bindings. (The two bindings for main are actually distinct. We'll ignore this for now.) If you're following along, you'll notice those triangles are green. Those triangles are placeholders for types, which are explicit in Core. HERMIT's pretty-printer follows the following color conventions:

  • White/Black: value-level identifiers (depends on terminal background color)
  • Red: syntax such as lambdas, arrows, and parentheses
  • Blue: keywords such as let and case
  • Green: types
  • Yellow: coercions

By default, the pretty-printer elides both types and coercions for readability. You can change this behavior with the set-pp-* commands. For instance, to see types:

hermit<1> set-pp-type Show
rec fib ∷ Int → Int
    fib = λ ds →
      case ds of wild Int
        I# ds →
          case ds of ds Int
            _ → (λ ds → (+) Int $fNumInt (fib ((-) Int $fNumInt ds (I# 1))) (fib ((-) Int $fNumInt ds (I# 2)))) void#
            0 → I# 1
            1 → I# 1
main ∷ IO ()
main = ($) Int (IO ()) (print Int $fShowInt) (fib (I# 35))
main ∷ IO ()
main = runMainIO () main
hermit<1>

You'll notice there are lots of ds running around, plus some other syntactic noise in the form of applications of $ and void#. Lets clean things up a bit:

hermit<1> simplify ; unshadow
rec fib ∷ Int → Int
    fib = λ ds →
      case ds of wild Int
        I# ds →
          case ds of ds Int
            _ → (+) Int $fNumInt (fib ((-) Int $fNumInt ds (I# 1))) (fib ((-) Int $fNumInt ds (I# 2)))
            0 → I# 1
            1 → I# 1
main ∷ IO ()
main = print Int $fShowInt (fib (I# 35))
main ∷ IO ()
main = runMainIO () main
rec fib ∷ Int → Int
    fib = λ ds →
      case ds of wild Int
        I# ds0 →
          case ds0 of ds1 Int
            _ → (+) Int $fNumInt (fib ((-) Int $fNumInt ds (I# 1))) (fib ((-) Int $fNumInt ds (I# 2)))
            0 → I# 1
            1 → I# 1
main ∷ IO ()
main = print Int $fShowInt (fib (I# 35))
main ∷ IO ()
main = runMainIO () main
hermit<3>

We ran two commands: simplify and unshadow. The former does some basic dead code and inlining things, while the latter makes the human readable names of variables distinct (they were already distinct via their internal uniques). HERMIT prints the result after each command, so this looks like a lot of output. To make it more clear, lets view the difference between AST 1 (which we started on) and AST 3 (which is where we are now).

hermit<3> diff 1 3
Commands:
=========
simplify
unshadow

Diff:
=====
 rec fib ∷ Int → Int
     fib = λ ds →
       case ds of wild Int
-        I# ds →
-          case ds of ds Int
-            _ → (λ ds → (+) Int $fNumInt (fib ((-) Int $fNumInt ds (I# 1))) (fib ((-) Int $fNumInt ds (I# 2)))) void#
+        I# ds0 →
+          case ds0 of ds1 Int
+            _ → (+) Int $fNumInt (fib ((-) Int $fNumInt ds (I# 1))) (fib ((-) Int $fNumInt ds (I# 2)))
             0 → I# 1
             1 → I# 1
 main ∷ IO ()
-main = ($) Int (IO ()) (print Int $fShowInt) (fib (I# 35))
+main = print Int $fShowInt (fib (I# 35))
 main ∷ IO ()
 main = runMainIO () main
hermit<3>

To demonstrate a simple change, we'll inline the recursive calls to fib. We only want to work on the right-hand side of fib, so we use the rhs-of navigation command to get there.

hermit<3> rhs-of 'fib
λ ds →
  case ds of wild Int
    I# ds0 →
      case ds0 of ds1 Int
        _ → (+) Int $fNumInt (fib ((-) Int $fNumInt ds (I# 1))) (fib ((-) Int $fNumInt ds (I# 2)))
        0 → I# 1
        1 → I# 1
hermit<4>

Note the apostrophe before fib in the command we just typed. By convention, names in the target code are prefixed with an apostrophe to distinguish them from commands/strings. Some identifiers generated by GHC (notably for dictionaries) are not valid Haskell identifiers, and may need to be enclosed in double quotes. For example: occurence-of '"$fTypeableks[]".

Clone this wiki locally