Stack Annotations are a new feature in GHC-9.14 which allow you to push arbitrary Haskell values to the RTS callstack.
The annotations are rendered in backtraces in order to provide additional context for exceptions.
We started by exposing an experimental user interface for this feature in ghc-experimental-9.1401.0.
As we continue to learn more about the user requirements for the interface, we hope to stabilise over the next few GHC versions.
There is also a compatibility library ghc-stack-annotations for older GHC versions.
For example, the backtrace for the following program will contain the callstack added by annotateCallStackIO, a special list and a string annotation.
import GHC.Stack.Annotation.Experimental
import Control.Exception.Backtrace
import Control.Exception
main :: IO ()
main = do
setBacktraceMechanismState IPEBacktrace True
annotateCallStackIO $ do
annotateStackShowIO ([1..4] :: [Int]) $ do
annotateStackStringIO "Lovely annotation" $ do
foo 500
foo :: Int -> IO ()
foo arg =
throwIO $ ErrorCall $ "Exception: " <> show arg
This example assumes GHC 9.14.1 (and ghc-experimental GHC.Stack.Annotation.Experimental), and results in the following stacktrace:
T159: Uncaught exception ghc-internal:GHC.Internal.Exception.ErrorCall:
Exception: 500
IPE backtrace:
Lovely annotation
[1,2,3,4]
annotateCallStackIO, called at app/Main.hs:12:3 in T159-0.1.0.0-inplace-T159:Main
HasCallStack backtrace:
throwIO, called at app/Main.hs:19:3 in T159-0.1.0.0-inplace-T159:Main
The stack annotations are now visible in the backtrace, giving us backtraces which include user-specific context.
The interface of StackAnnotation was initially trying to be minimal, exposing a single method:
-- | 'StackAnnotation's are types which can be pushed onto the call stack
-- as the payload of 'AnnFrame' stack frames.
--
class StackAnnotation a where
-- | Display a human readable string for the 'StackAnnotation'.
displayStackAnnotation :: a -> String
This is flexible, but it turns out, the lack of structure makes it difficult for consumers to extract important information, for example source locations.
A better backtrace should always include source locations if possible, so we want to have a backtrace like this:
T159: Uncaught exception ghc-internal:GHC.Internal.Exception.ErrorCall:
Exception: 500
IPE backtrace:
Lovely annotation, called at app/Main.hs:14:7 in T159-0.1.0.0-inplace-T159:Main
[1,2,3,4], called at app/Main.hs:13:5 in T159-0.1.0.0-inplace-T159:Main
annotateCallStackIO, called at app/Main.hs:12:3 in T159-0.1.0.0-inplace-T159:Main
HasCallStack backtrace:
throwIO, called at app/Main.hs:19:3 in T159-0.1.0.0-inplace-T159:Main
Turns out, other downstream consumers directly benefit from source locations as well, for example ghc-debugger:

Note the rendered “Call stack” (on the left of the image) shows the rendered StackAnnotations including the source location.
Further, ghc-stack-profiler can use them to provide easier to read flamegraphs.
Thus, we propose to extend the interface of StackAnnotation to:
-- | 'StackAnnotation's are types which can be pushed onto the call stack
-- as the payload of 'AnnFrame' stack frames.
--
class StackAnnotation a where
-- | Display a human readable string for the 'StackAnnotation'.
--
displayStackAnnotation :: a -> String
-- | Get the 'SrcLoc' of the given 'StackAnnotation'.
--
-- This is optional, 'SrcLoc' are not strictly required for 'StackAnnotation', but
-- it is still heavily encouarged to provide a 'SrcLoc' for better IPE backtraces.
sourceLocationOfStackAnnotation :: a -> Maybe SrcLoc
-- | The description of the StackAnnotation without any metadata such as source locations.
displayStackAnnotationShort :: a -> String
This allows users to provide customised source locations, but still tries to be as flexible and non-restrictive as possible.
Now, my questions:
- Do people agree that extending the
StackAnnotationinterface makes sense and is worthwhile to provide better backtraces? - If yes, what do you think about the hardest problem here, the naming? In particular
sourceLocationOfStackAnnotationsounds really unwieldy to me. - Are there other important things
StackAnnotationshould expose in a structured manner?