The basic task of the ghc driver is to run each input file through the right phases (compiling, linking, etc.).
The first phase to run is determined by the input-file suffix, and the last phase is determined by a flag. If no relevant flag is present, then go all the way through linking. This table summarises:
Phase of the compilation system | Suffix saying “start here” | Flag saying “stop after” | (suffix of) output file |
literate pre-processor | .lhs | - | - |
C pre-processor (opt.) | - | - | - |
Haskell compiler | .hs | -C, -S | .hc, .s |
C compiler (opt.) | .hc or .c | -S | .s |
assembler | .s | -c | .o |
linker | other | - | a.out |
Thus, a common invocation would be: ghc -c Foo.hs
Note: What the Haskell compiler proper produces depends on whether a native-code generator is used (producing assembly language) or not (producing C).
The option -cpp must be given for the C pre-processor phase to be run, that is, the pre-processor will be run over your Haskell source file before continuing.
The option -E runs just the pre-processing passes of the compiler, outputting the result on stdout before stopping. If used in conjunction with -cpp, the output is the code blocks of the original (literal) source after having put it through the grinder that is the C pre-processor. Sans -cpp, the output is the de-litted version of the original source.
The option -optcpp-E runs just the pre-processing stage of the C-compiling phase, sending the result to stdout. (For debugging or obfuscation contests, usually.)