Version 6 (modified by AndyGill, 11 years ago) (diff)


Haskell Program Coverage

This page describes the Haskell Program Coverage implementation inside GHC.

The basic idea is this

  • For each (sub)expression in the Haskell Syntax, write the (sub)expression in a HsTick
  • Each HsTick has a module local index number.
  • There is a table (The Mix data structure) that maps this index number to original source location.
  • Each HsTick is mapped in the Desugar pass with:
      dsExpr (HsTick n e) = case tick<modname,n> of DEFAULT -> e
  • This tick is a special type of Id, a TickOpId which takes no core-level argument, but has two pre-applied arguments; the module name and the module-local tick number.
    • We store both module name and tick number to allow this Id to be passed (inlined) inside other modules.
    • This Id has type State# World#
  • The core simplifier must not remove this case, but it can move it.
    • The do-not-remove is enforced via the ... function in ....
    • The semantics are tick if-and-when-and-as you enter the DEFAULT case. But a chain of consecutive ticks can be executed in any order.
  • The CoreToStg Pass translates the ticks into StgTick
      coreToStgExpr (case tick<m,n> of DEFAULT -> e) = StgTick m n (coreToStgExpr e)
  • The Cmm code generator translates StgTick to a 64 bit increment.

Other details

  • A executable startup time, we perform a depth first traversal some module specific code, gathering a list of all Hpc registered modules, and the module specific tick table.
  • There is one table per module, so we can link the increment statically, without needing to know the global tick number.
  • The module Hpc.c in the RTS handles all the reading of these table.
  • At startup, if a .tix file is found, Hpc.c checks that this is the same binary as generated the .tix file, and if so, pre-loads all the tick counts in the module specific locations.
  • (I am looking for a good way of checking the binaries for sameness)
  • At shutdown, we write back out the .tix files, from the module-local tables.

Binary Tick Boxes

There is also the concept of a binary tick box. This is a syntactical boolean, like a guard or conditional for an if. We use tick boxes to record the result of the boolean, to check for coverage over True and False.

  • Each HsBinaryTick is mapped in the Desugar pass with:
      dsExpr (HsBinaryTick t f e) = case e of 
                                     { True -> case tick<modname,t> of DEFAULT -> True
                                     ; False -> case tick<modname,f> of DEFAULT -> False }
  • After desugaring, there is no longer any special code for binary tick box.

Tracer Mode

There is a mode '-fhpc-tracer', which compiles code which outputs .rix files; a record of everywhere the program goes.

  • by default, the -fhpc-tracer program does exactly the same as a -fhpc compiled program.
  • setting the env var HPCRIX causes an additional action, at each tick (and a few other important events), the global tick number is written into the file named in HPCRIX.
  • Typically, HPCRIX would point to a named pipe.

There is a Hpc tracer which sets up both the named pipe, and the HPCRIX variable exactly for dynamically interacting with the tracer output.

Machine Generated Haskell

Sometimes, Haskell is the target language - for example, Happy and Alex. In this case, you want to be able to check for coverage of your original program. So we have a new pragma.

    {-# GENERATED "Parser" 100-2:101-4 #-} <expr>

This means that the expression was obtained from the given file and locations. This might be code included verbatim (for example the actions in Happy), or be generated from a specification from this location.