Run the compiler itself. This does not start a separate process; it's just a call to a Haskell function. This step always generates an interface fileFoo.hi, and depending on what flags you give, it also generates a compiled file:

Assembly code: flag -S, file Foo.s

C code: flag -fviaC, file Foo.hc

C-- mode: flag -fcmm, file Foo.cmm, believed not to work

Run the C compiler [followed by the evil mangler] or assembler, as appropriate, generating Foo.o

Interface files

An interface file supports separate compilation by recording the information gained by compiling M.hs in its interface file M.hi. Morally speaking, the interface file M.hi is part of the object file M.o; it's like a super symbol-table for M.o.

Interface files are kept in binary, GHC-specific format. The format of these files changes with each GHC release, but not with patch-level releases. You can see what's in an interface file (often very useful) thus:

ghc --show-iface M.hi

Here are some of the things stored in an interface file M.hi

A list of what M exports.

The types of exported functions, definition of exported types, and so on.

Version information, used to drive the smart recompilation checker.

The strictness, arity, and unfolding of exported functions. This is crucial for cross-module optimisation; but it is only included when you compile with -O.

HC files

GHC uses gcc as a code generator, in a very stylised way:

Generate Foo.hc

Compile it with gcc, using register declarations to nail a bunch of things into registers (e.g. the allocation pointer)