On Sat, Mar 5, 2016 at 12:23 AM, Michael Sloan <mgsloan@gmail.com> wrote:
On Fri, Mar 4, 2016 at 8:30 AM, Eric Seidel <eric@seidel.io> wrote:
On Fri, Mar 4, 2016, at 06:53, Johannes Waldmann wrote:
> Dear Cafe,
>
> the new (8.*) call stack feature
> https://downloads.haskell.org/~ghc/8.0.1-rc2/docs/html/users_guide/glasgow_exts.html#hascallstack
> is certainly nice for debugging during development.
>
> But how costly is it at runtime? I notice a 5 percent slowdown.

HasCallStack is really just a type class with a couple special rules for
building dictionaries in GHC's constraint solver. So at runtime each
function with a HasCallStack constraint takes an extra CallStack
argument. I don't know how to quantify the performance implications
beyond that, but you're right that HasCallStack is not free.

Hmm, is it no longer an implicit parameter?  I thought the new
HasCallStack stuff was just a constraint synonym like

type HasCallStack = (?callStack :: CallStack)

For the most part the distinction doesn't matter, but it does when it
comes to constraints in instance heads.  The issue is that constraints
are not solved at their callsites.  Do we want `HasCallStack` to be
able to be present in an instance head, and potentially get a location
quite different from the actual use site?  I would find this confusing,
though sometimes helpful.

I realized I could answer my own questions simply by trying it out on GHC 8 RC2!

    instance HasCallStack => Read Void where
      read _ = prettyStack callStack ++ "\nCan't possibly read Void!" 

does indeed produce the error

    • Illegal implicit parameter ‘?callStack::CallStack’
    • In the context: HasCallStack
      While checking an instance declaration
      In the instance declaration for ‘Read Void’

Implicit parameters are a good match for this callstack stuff, because
they have the restriction that they cannot be used as supeclass
constraints.

Does your work on the callstack stuff (thanks!) introduce backwards
compatibility issues?  I'm working on a library that will make extensive
use of 7.10's implementation of callstacks.

I tried it out, and it does have compatibility with the implicit parameter
still!  Good stuff.

However, I was making use of a potentially unintentional aspect
of the 7.10 API, which is that `getCallStack` can be used in a record
update of `CallStack`.  This is useful for annotating callstacks with
helpful debugging info, like the arguments used for an invocation.
The existing format for the message works quite well for this, since
it's just the function name.  So, appending parameters results in
"fn a1 a2 a3", etc.

Is it too late in the GHC release cycle to have add an additional
GHC.Stack.Internal module which enables modification of CallStack?

I realize that it might be quite intentional that CallStack can't be modified,
as that could make it less useful as a source of trustable diagnostic info.
How about only exposing access to the top:

getCallStackHeadMessage :: CallStack -> Maybe String
setCallStackHeadMessage :: String -> CallStack
modifyCallStackHeadMessage :: (String -> String) -> CallStack -> CallStack

Or, if we're feeling lensy

callStackHeadMessage :: Functor f => (String -> f String) -> (CallStack -> f CallStack)

Since only the top can be modified, we can trust that the entries of
the CallStack are accurate.

 
> That's not a problem if there's an easy way to switch
> this off for production (without changing the code).

Since HasCallStack is not a feature of the RTS, but actually part of the
generated code, there's not really an easy way to disable it without
changing the code. As much as I dislike CPP, I think it's the best
solution for a toggleable HasCallStack, something like

#if DEBUG
#define HASCALLSTACK (HasCallStack)
#else
#define HASCALLSTACK ()
#endif

foo :: HASCALLSTACK => a -> b

ought to work.

> Related: how to make code that uses it,
> compile with older ghcs that don't have it.
>
> I made this hack: do not import GHC.Stack.Types, but instead
>
> {-# language CPP, MultiParamTypeClasses #-}
>
> #if (__GLASGOW_HASKELL__ < 710)
> {-# language NullaryTypeClasses #-}
> #endif
>
> module Stack
> ( HasCallStack )
> where
>
> #if (__GLASGOW_HASKELL__ >= 800)
> import GHC.Stack.Types
> #else
> class HasCallStack
> instance HasCallStack
> #endif

This might be a nice addition to the base-compat package.

> When I compile with 8.rc2, and change ">= 800" to ">= 900",
> I am getting the 5 percent speedup mentioned above.
>
> But does it really do what I hope it does
> (remove all runtime overhead that call stacks may have)?

It should remove all the overhead of call stacks for calling functions
you wrote. If you import a function with a HasCallStack constraint
there's no way to disable the overhead for that function (for good
reason, it might use the CallStack!).

> When I compile with 7.10.3, I am getting 5 .. 10 percent faster again.
>
> My code does nothing fancy (w.r.t. types and libraries),
> it just uses Data.IntMap heavily. And it has some
>
> class Semiring s where
>   zero :: s
>   one  :: s
>   plus :: HasCallStack => s -> s -> s
>   times :: HasCallStack => s -> s -> s

I'm curious, why do plus and times take a CallStack? I wouldn't expect
them to be partial, so it seems like unnecessary overhead.

Eric
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://mail.haskell.org/cgi-bin/mailman/listinfo/haskell-cafe