Version 2 (modified by simonpj, 8 years ago) (diff)


Maintaining an explicit call stack

There has been a vigorous thread on error attribution ("I get a head [] error; but who called head?")

This page summarises some half baked ideas that Simon and I have been discussing. See also

The basic idea

  1. 'assert' magically injects the current file location. One could imagine generalising this a bit so that you could sya
    	...(f $currentLocation)...

to pass a string describing the current location to f.

  1. But that doesn't help with 'head'. We want to pass head's site to head. That's what jhc does when you give 'head' the a magic SRCLOC_ANNOTATE pragma:
    • every call to head gets replaced with head_check $currentLocation
    • you get to write head_check yourself, with type
      		head_check :: String -> [a] -> a
  1. But what about the caller of the function that calls head? Obviously we'd like to pass that on too!
    	foo :: [Int] -> Int
    	{-# SRCLOC_ANNOTATE foo #-}
    	foo xs = head (filter odd xs)
    	foo_check :: String -> [Int] -> Int
    	foo_check s xs = head_check ("line 5 in Bar.hs" ++ s) xs

Now in effect, we build up a call stack.

  1. In fact, it's very similar to the "cost-centre stack" that GHC builds for profiling, except that it's explicit rather than implicit. (Which is good. Of course the stack should be a proper data type, not a String.)

However, unlike GHC's profiling stuff, it is *selective*. You can choose to annotate just one function, or 10, or all. If call an annotated function from an unannotated one, you get only the information that it was called from the unannotated one:

	foo :: [Int] -> Int   -- No SRCLOC_ANNOTATE
	foo xs = head (filter odd xs)
	foo:: [Int] -> Int
	foo xs = head_check ("line 5 in Bar.hs") xs

This selectiveness makes it much less heavyweight than GHC's currrent "recompile everything" story.

Open questions

Lots of open questions

  • It would be great to use the exact same stack value for profiling. Not so easy...for exmaple, time profiling uses sampling based on timer interrupts that expect to find the current cost centre stack in a particular register. But a big pay-off; instead of having magic rules in GHC to handle SCC annotations, we could throw the full might of the Simplifier at it.

  • CAFs are a nightmare. Here's a nasty case:
      foo :: Int -> Int -> Int
      foo = \x. if fac x > 111 then \y. stuff else \y. other-stuff
      bad :: Int -> Int
      bad = foo 77
    How would you like to transform this?