[GHC] #13360: Add a flag to enable inferring HasCallStack constraints

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature | Status: new request | Priority: normal | Milestone: Component: Compiler | Version: 8.0.1 Keywords: | Operating System: Unknown/Multiple Architecture: | Type of failure: None/Unknown Unknown/Multiple | Test Case: | Blocked By: Blocking: | Related Tickets: Differential Rev(s): | Wiki Page: -------------------------------------+------------------------------------- GHC is careful to not infer a HasCallStack constraint for a function, because the user didn't ask for one, it would complicate the type, and because it would incur some runtime overhead. I think this is the right default, but it can make debugging exceptions tedious as you have to manually add HasCallStack constraints to the entire chain of functions in the stack. I sometimes use CPP to give myself a flag to toggle the constraints, but I'd rather not have to resort to CPP. Instead, it would be nice if GHC supported a `-finfer-hascallstack` flag (off by default) that would simply generalize over the HasCallStack constraints rather than defaulting them. Then, if my program blows up I can simply recompile with `-finfer-hascallstack` and get a more informative trace. No manual editing, no preparation with CPP, easy! Furthermore, if we had such a flag, it might be nice to '''enable''' it by default in ghci. The two prime concerns are 1. Complicating type signatures. But `:t` solves the HasCallStack constraint before printing the type, so this is only a concern if you use `:i`. 2. Runtime overhead. I don't think this is a valid concern in ghci. I think this would be a nice and simple improvement to the debugging experience. The downside is that even with `-finfer-hascallstack` you would not get full call-stacks for functions in an imported module that was compiled normally. So this would not help with e.g. partial functions in `base`. (It would be really nice to find a solution to this that doesn't involve building modules in multiple ways..) -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by simonpj): Reasonable idea and easy to implement. What about functions that have a user-supplied type signature? It'd be a bit more awkward, perhaps not impossible, to add a `HasCallStack` constraint. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:1 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by gridaphobe): I think I would want GHC to inject `HasCallStack` into explicit type signatures. One possibility would be to just always add `HasCallStack` as a top-level given, and then silently drop it if it's found to be unused. I know GHC already has code to warn about unused constraints, but I don't know how awkward it would be to make use of that for this purpose. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:2 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by dfeuer): * milestone: => 8.4.1 -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:3 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda):
and because it would incur some runtime overhead
Is there any doc/benchmark that quantifies this runtime overhead? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:4 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by bgamari): Well, the overhead may (nearly) disappear entirely if the function with the constraint is inlined. If not then you will pay the cost of an additional pointer argument and possibly a constructor allocation. Also, to subscribe to future changes in a ticket add yourself to the CC field. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:5 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by saurabhnanda): * cc: saurabhnanda (added) -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:6 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

Well, the overhead may (nearly) disappear entirely if the function with
#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): the constraint is inlined. If not then you will pay the cost of an additional pointer argument and possibly a constructor allocation. What would be a sensible way to benchmark this runtime cost? I'm in the process of evaluating the impact of enabling `HasCallstack` all throughout my code and am putting together a benchmark. The perf impact of `HasCallstack` would be significantly lower than compiling with profiling enabled, right? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:7 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by gridaphobe):
What would be a sensible way to benchmark this runtime cost?
I did a couple [https://github.com/gridaphobe/located- base/blob/master/bench/Bench.hs microbenchmarks] a while ago. They only test the cost of constructing and passing the `CallStack` around, so it's not necessarily an accurate reflection of the cost of enabling `HasCallStack` in real-world code. Your benchmarks would be a valuable addition!
The perf impact of HasCallstack would be significantly lower than compiling with profiling enabled, right?
I suspect so, since the profiling machinery can also interfere with optimizations whereas the `CallStack`s do not, but I've never checked the difference. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:8 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda):
I did a couple microbenchmarks a while ago.
Thanks for sharing the benchmarks. They seem to be reporting a 50-250% slowdown with HasCallstack. My hunch is that the slowdown is linear wrt the depth of the call-stack. Will need to do some benchmarking to be sure of this. Do you know what the underlying data-structure for storing the call-stack is? IIUC, callstacks have been implemented as an extra implicit argument, and the slowdown shouldn't be more than adding a regular argument (say, a global configuration record) to a regular function. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:9 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by gridaphobe): The underlying data structure is a slight twist on a linked list. https://hackage.haskell.org/package/base-4.10.0.0/docs/src/GHC.Stack.Types.h... The `FreezeCallStack` constructor is an experimental addition to let library authors avoid exposing implementation details in `CallStack`s. You're right that the cost of adding a `CallStack` should be roughly equivalent to passing another argument around, plus the cost of allocating each source location in the stack. It would make sense for the cost to be linear in the depth of the stack, each addition needs to allocate a new `PushCallStack` constructor, but maybe the constant factors can be improved. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:10 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda):
You're right that the cost of adding a CallStack should be roughly equivalent to passing another argument around, plus the cost of allocating each source location in the stack.
I'm amazed that this operation ends up causing a 2-3x slowdown, although, to be fair, the scale is in the range of ~100ns. I think a more realistic benchmark would be wrt actual IO or parsing, where the time-scale of underlying operations will outweigh the minor performance hit added by HasCallstack. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:11 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by simonpj):
I'm amazed that this operation ends up causing a 2-3x slowdown
I'm amazed too. I'd suggest investigating a bit deeper. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:12 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by bgamari): Well, keep in mind that the call stacks will slightly increase the size of the core and therefore things that GHC previously decided to inline may no longer be inlined. However, the size of the affect is still surprising. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:13 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): I'm picking this up now. Any pointers on how to build a sensible benchmark for HasCallstack? Here's what I'm planning to do: * Benchmark small pieces of pure code which calls `error` or `throwIO` or `throwM` where every function where every function in the callstack has/omits the `HasCallstack` constraint. * Benchmark some real-world libraries that involve parsing and IO with/without the `HasCallstack` constraint, eg. opaleye, postgresql- simple, yaml, and aeson. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:14 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): Does this seem like a sensible benchmark for a pure function that throws an Exception: https://github.com/vacationlabs/hascallstack- benchmarks/blob/f055b16db7bf42e069daf25e126bd04a3c8b65bc/app/Main.hs Here are the numbers, which seem to be suggesting a 6-7x perf penalty for using `HasCallstack`, which seems to be getting worse as the depth of the callstack increases: * factorial/20: 4x penalty * factorial/30: 4.8x penalty * factorial/50: 5.9x penalty * factorial/100: 6.9x penalty * factorial/200: 7.7x penalty * factorial/300: 7.8x penalty Can someone confirm this pattern on their machine as well? {{{ benchmarking factorial/regular/20 time 1.757 μs (1.734 μs .. 1.782 μs) 0.999 R² (0.998 R² .. 1.000 R²) mean 1.732 μs (1.722 μs .. 1.753 μs) std dev 41.67 ns (26.25 ns .. 72.27 ns) variance introduced by outliers: 30% (moderately inflated) benchmarking factorial/callstack/20 time 7.080 μs (7.041 μs .. 7.138 μs) 1.000 R² (0.999 R² .. 1.000 R²) mean 7.087 μs (7.058 μs .. 7.135 μs) std dev 128.1 ns (88.70 ns .. 183.6 ns) variance introduced by outliers: 17% (moderately inflated) benchmarking factorial/regular/30 time 2.142 μs (2.129 μs .. 2.161 μs) 0.999 R² (0.998 R² .. 1.000 R²) mean 2.155 μs (2.140 μs .. 2.192 μs) std dev 76.73 ns (47.29 ns .. 132.5 ns) variance introduced by outliers: 48% (moderately inflated) benchmarking factorial/callstack/30 time 10.42 μs (10.34 μs .. 10.53 μs) 0.999 R² (0.998 R² .. 1.000 R²) mean 10.65 μs (10.53 μs .. 10.84 μs) std dev 527.4 ns (362.1 ns .. 827.1 ns) variance introduced by outliers: 60% (severely inflated) benchmarking factorial/regular/50 time 2.962 μs (2.933 μs .. 3.007 μs) 0.998 R² (0.997 R² .. 1.000 R²) mean 2.972 μs (2.947 μs .. 3.021 μs) std dev 119.0 ns (80.88 ns .. 181.1 ns) variance introduced by outliers: 53% (severely inflated) benchmarking factorial/callstack/50 time 17.56 μs (17.26 μs .. 18.13 μs) 0.993 R² (0.979 R² .. 0.999 R²) mean 17.62 μs (17.34 μs .. 18.34 μs) std dev 1.365 μs (588.7 ns .. 2.620 μs) variance introduced by outliers: 77% (severely inflated) benchmarking factorial/regular/100 time 4.920 μs (4.883 μs .. 4.973 μs) 0.999 R² (0.998 R² .. 1.000 R²) mean 4.969 μs (4.915 μs .. 5.037 μs) std dev 200.2 ns (144.0 ns .. 264.3 ns) variance introduced by outliers: 52% (severely inflated) benchmarking factorial/callstack/100 time 33.78 μs (33.63 μs .. 33.99 μs) 0.999 R² (0.999 R² .. 1.000 R²) mean 34.36 μs (34.04 μs .. 34.98 μs) std dev 1.493 μs (1.015 μs .. 2.390 μs) variance introduced by outliers: 50% (moderately inflated) benchmarking factorial/regular/200 time 9.002 μs (8.921 μs .. 9.131 μs) 0.999 R² (0.998 R² .. 1.000 R²) mean 9.132 μs (9.052 μs .. 9.229 μs) std dev 299.3 ns (238.2 ns .. 375.3 ns) variance introduced by outliers: 39% (moderately inflated) benchmarking factorial/callstack/200 time 69.26 μs (67.10 μs .. 71.77 μs) 0.995 R² (0.992 R² .. 1.000 R²) mean 68.59 μs (67.86 μs .. 69.60 μs) std dev 2.830 μs (1.919 μs .. 3.858 μs) variance introduced by outliers: 44% (moderately inflated) benchmarking factorial/regular/300 time 12.93 μs (12.90 μs .. 12.97 μs) 0.999 R² (0.999 R² .. 1.000 R²) mean 13.27 μs (13.11 μs .. 13.51 μs) std dev 597.3 ns (422.6 ns .. 842.9 ns) variance introduced by outliers: 54% (severely inflated) benchmarking factorial/callstack/300 time 101.2 μs (100.7 μs .. 101.7 μs) 0.999 R² (0.999 R² .. 1.000 R²) mean 103.9 μs (102.5 μs .. 105.9 μs) std dev 5.361 μs (3.981 μs .. 6.618 μs) variance introduced by outliers: 54% (severely inflated) }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:15 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): Does anyone know if this kind of perf penalty is a fact-of-life with other languages as well, except other languages don't have an optional callstack, so no one even knows? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:16 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by simonpj): factorial is a very particular kind of program. I really doubt that a factor of 7 is typical. And I'd want to dig a bit more into what is happening here. Factorial 300 builds a stack of depth 300 -- but it also computes some extremly large `Integers`. As the size goes up I'd expect eventually 99% of the time to be spent in GMP. (This is another reason why this particular benchmark might be considered misleading.) So where is that increasing factor coming from? I'm not denying it's there; but sometimes these corner cases show up some stupid infelicity in GHC that is easily fixed. There are lots of programs in `nofib` that are a bit less unrealistic than factorial. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:17 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda):
There are lots of programs in nofib that are a bit less unrealistic than factorial.
Are you referring to https://github.com/ghc/nofib/tree/master/real ? Which program from that directly would you suggest for the HasCallStack benchmark? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:18 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by simonpj): Yes, but there are also the `imaginary` and `spectral` suites. See [https://github.com/ghc/nofib] There's a [https://www.semanticscholar.org/paper/The-nofib-Benchmark- Suite-of-Haskell-Programs-Partain/60d73856a1b51913e9179377356bdd63e270a199 paper about nofib] I don't know which is most suitable. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:19 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): Here are some benchmarks for postgresql-simple, a project which is doing significant real-world IO (which is where most errors occur, in my experience): **No Callstack -- Benchmarking a query with/without error** {{{ benchmarking complicatedQuery/without error time 172.3 μs (169.0 μs .. 175.2 μs) 0.985 R² (0.971 R² .. 0.993 R²) mean 224.5 μs (203.3 μs .. 271.1 μs) std dev 110.9 μs (58.97 μs .. 200.8 μs) variance introduced by outliers: 99% (severely inflated) benchmarking complicatedQuery/with error time 171.4 μs (169.6 μs .. 173.5 μs) 0.996 R² (0.992 R² .. 0.999 R²) mean 174.4 μs (171.3 μs .. 179.8 μs) std dev 14.00 μs (8.934 μs .. 19.24 μs) variance introduced by outliers: 72% (severely inflated) }}} **HasCallStack -- Benchmarking same query but with [https://github.com/vacationlabs/postgresql- simple/commit/774d3eaa259c217ada3918e5c29f3dab792af38c: HasCallStack applied on almost every function of the library]** {{{ benchmarking complicatedQuery/without error time 177.9 μs (176.3 μs .. 180.6 μs) 0.996 R² (0.992 R² .. 0.998 R²) mean 186.8 μs (182.6 μs .. 192.6 μs) std dev 17.42 μs (13.92 μs .. 21.59 μs) variance introduced by outliers: 78% (severely inflated) benchmarking complicatedQuery/with error time 171.4 μs (168.7 μs .. 175.6 μs) 0.991 R² (0.981 R² .. 0.997 R²) mean 175.3 μs (171.4 μs .. 182.1 μs) std dev 17.27 μs (11.45 μs .. 25.30 μs) variance introduced by outliers: 80% (severely inflated) }}} The difference in perf is coming across as almost noise. **Is this an acceptable benchmark for measuring real-world impact of enabling HasCallstack for IO-centric functions? Does this strengthen the case for implementing this flag?** -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:20 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): **Question** -- if `error` is not there in the source code, does GHC optimise away `HasCallStack` completely? I'm seeing the same perf numbers for the two functions given below (the call to `throw` has been commented out): {{{ factorialCS :: (HasCallStack) => Integer -> Integer factorialCS 1 = 1 -- factorialCS 3 = throw $ CustomException "Error from CS version" callStack factorialCS n = n * factorialCS (n - 1) factorial :: Integer -> Integer factorial 1 = 1 -- factorial 3 = throw $ CustomException "Error from non-CS version" emptyCallStack factorial n = n * factorial (n - 1) }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:21 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): **UPDATE** It seems that GHC is optimising away all callstack related code if `callStack` is not actually being used (which was the case with `postgresql-simple`). Here are the same benchmarks as [https://ghc.haskell.org/trac/ghc/ticket/13360#comment:20: earlier], but with `callStack` [https://github.com/vacationlabs/postgresql- simple/commit/355f0bcefacfaa6dfb808b5bbbb4fa89956650ae: actually being used.] There seems to be a **1.6x perf penalty** in real-world IO-centric code. It seems to be quite a big perf penalty. How do I dig deeper? **HasCallStack, where the callStack is actually being used** {{{ benchmarking complicatedQuery/without error time 289.7 μs (250.6 μs .. 330.3 μs) 0.905 R² (0.850 R² .. 0.961 R²) mean 276.8 μs (263.2 μs .. 293.6 μs) std dev 49.36 μs (34.39 μs .. 70.50 μs) variance introduced by outliers: 92% (severely inflated) benchmarking complicatedQuery/with error time 269.1 μs (242.8 μs .. 294.2 μs) 0.947 R² (0.919 R² .. 0.979 R²) mean 274.3 μs (262.3 μs .. 286.1 μs) std dev 43.09 μs (33.99 μs .. 54.33 μs) variance introduced by outliers: 90% (severely inflated) }}} **Without HasCallStack** {{{ benchmarking complicatedQuery/without error time 183.9 μs (179.6 μs .. 189.9 μs) 0.992 R² (0.987 R² .. 0.997 R²) mean 186.6 μs (183.1 μs .. 193.1 μs) std dev 15.60 μs (11.45 μs .. 20.83 μs) variance introduced by outliers: 74% (severely inflated) benchmarking complicatedQuery/with error time 180.7 μs (176.3 μs .. 187.1 μs) 0.992 R² (0.984 R² .. 0.999 R²) mean 181.9 μs (179.4 μs .. 186.7 μs) std dev 11.55 μs (7.139 μs .. 19.47 μs) variance introduced by outliers: 61% (severely inflated) }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:22 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by gridaphobe): Thanks for digging in to this! Now that we can see there is an impact for real-world code, I'd suggest stepping back to the simpler examples like the `loop` benchmark I linked earlier, to determine what's causing the overhead. HasCallStack **should** be equivalent to an extra argument that builds up a list of SrcLocs (technically `(String, SrcLoc)` pairs). So my first experiment would probably be to write another benchmark that implements HasCallStack in user code. These two versions should perform the same, do they? If not, maybe GHC is doing something silly when it generates the HasCallStack code. Next I would try shrinking the contents of the user-level CallStack, instead of `(String, SrcLoc)` make it a list of `()`. This should measure the cost of pushing a new item onto the stack. Perhaps that cost is proportional to the size of the item (I wouldn't think so since the item should be a thunk, but it's worth checking). -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:23 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda):
These two versions should perform the same, do they?
They seem to be performing almost the same (benchmarks are called `explicit callstack` below) -- in fact, the explicitly managed callstack is performing slightly worse.
Next I would try shrinking the contents of the user-level CallStack
This seems to be the **biggest cost** (benchmarks are called `explicit callstack (mini)` below. In fact, with the "minified contents" the perf- penalty is **~1.2x vis-a-vis ~3-4x** in the regular callstack. [https://github.com/vacationlabs/hascallstack- benchmarks/blob/7e32c2d95f6fd82b0afbf0436a895f9edf1913e0/app/Main.hs: Benchmark code] {{{ benchmarking recur/no callstack/10 time 837.4 ns (826.7 ns .. 850.3 ns) 0.999 R² (0.998 R² .. 1.000 R²) mean 829.8 ns (824.7 ns .. 838.4 ns) std dev 20.68 ns (11.06 ns .. 33.00 ns) variance introduced by outliers: 33% (moderately inflated) benchmarking recur/HasCallStack/10 time 2.773 μs (2.763 μs .. 2.787 μs) 0.999 R² (0.999 R² .. 1.000 R²) mean 2.797 μs (2.775 μs .. 2.846 μs) std dev 105.8 ns (38.93 ns .. 174.7 ns) variance introduced by outliers: 50% (severely inflated) benchmarking recur/explicit callstack/10 time 4.103 μs (4.086 μs .. 4.131 μs) 1.000 R² (0.999 R² .. 1.000 R²) mean 4.147 μs (4.114 μs .. 4.210 μs) std dev 157.9 ns (107.7 ns .. 233.9 ns) variance introduced by outliers: 50% (moderately inflated) benchmarking recur/explicit callstack (mini)/10 time 1.027 μs (1.024 μs .. 1.031 μs) 1.000 R² (0.999 R² .. 1.000 R²) mean 1.048 μs (1.037 μs .. 1.068 μs) std dev 52.87 ns (35.62 ns .. 76.73 ns) variance introduced by outliers: 67% (severely inflated) benchmarking recur/no callstack/100 time 8.290 μs (7.961 μs .. 8.558 μs) 0.995 R² (0.993 R² .. 1.000 R²) mean 8.061 μs (7.992 μs .. 8.224 μs) std dev 312.9 ns (154.9 ns .. 482.3 ns) variance introduced by outliers: 48% (moderately inflated) benchmarking recur/HasCallStack/100 time 32.06 μs (31.89 μs .. 32.23 μs) 1.000 R² (0.999 R² .. 1.000 R²) mean 32.05 μs (31.96 μs .. 32.17 μs) std dev 368.3 ns (299.5 ns .. 449.9 ns) benchmarking recur/explicit callstack/100 time 48.52 μs (47.12 μs .. 49.92 μs) 0.997 R² (0.995 R² .. 1.000 R²) mean 47.70 μs (47.39 μs .. 48.28 μs) std dev 1.334 μs (750.2 ns .. 2.156 μs) variance introduced by outliers: 28% (moderately inflated) benchmarking recur/explicit callstack (mini)/100 time 9.225 μs (9.111 μs .. 9.423 μs) 0.998 R² (0.996 R² .. 1.000 R²) mean 9.188 μs (9.121 μs .. 9.306 μs) std dev 291.6 ns (160.9 ns .. 459.9 ns) variance introduced by outliers: 38% (moderately inflated) }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:24 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by gridaphobe): Interesting! So it appears that the overhead is coming from the contents of each entry in the call stack. I'm a bit surprised by this, the `String` and `SrcLoc` should both be floated out and shared between calls (like you did manually with `spuriousSrcLoc`). I wouldn't expect that to cause such an overhead. It would be good to isolate which part of the stack entries is causing the overhead, e.g. Is it the `String`s? (we could use `Addr#` like GHC does for compiler-generated errors) Is the number of fields in a `SrcLoc` causing one of GHC's size heuristics to act strangely? etc. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:25 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): **I think we are aren't approaching this pragmatically.** The 3.4x penalty, reported above, occurs only when every single computation throws an error and the error/callstack is forced (usually by printing it to a log-file). **However** in real life only a certain percentage of computations fail, and most of the times the callstacks aren't evaluated completely. (I tried a version where `e deepseq` wasn't being called and every version of the code had the same perf. Here are some benchmarks where **only 20% computations fail**. [https://github.com/vacationlabs/hascallstack- benchmarks/blob/84ab56c63eb96208c5193a187b431251037a2ce8/app/Main.hs: code is here] The perf penalty is now **~1.2x** for the `HasCallStack` version. I think there is still some opportunity to optimise the stack entries, but the perf penalty in real-world scenarios is not as alarming as I had earlier reported. **This is going to be 'Exhibit A' whenever I need to cite advantages of lazy evaluation** {{{ benchmarking recur/no callstack/1000 time 1.720 ms (1.648 ms .. 1.776 ms) 0.992 R² (0.987 R² .. 0.996 R²) mean 1.668 ms (1.638 ms .. 1.703 ms) std dev 117.6 μs (97.03 μs .. 144.4 μs) variance introduced by outliers: 53% (severely inflated) benchmarking recur/HasCallStack/1000 time 1.700 ms (1.660 ms .. 1.742 ms) 0.994 R² (0.990 R² .. 0.997 R²) mean 1.725 ms (1.695 ms .. 1.762 ms) std dev 117.1 μs (95.96 μs .. 150.7 μs) variance introduced by outliers: 52% (severely inflated) benchmarking recur/explicit callstack/1000 time 1.740 ms (1.701 ms .. 1.782 ms) 0.994 R² (0.991 R² .. 0.997 R²) mean 1.746 ms (1.710 ms .. 1.779 ms) std dev 126.3 μs (101.9 μs .. 158.5 μs) variance introduced by outliers: 53% (severely inflated) benchmarking recur/explicit callstack (mini)/1000 time 1.694 ms (1.639 ms .. 1.758 ms) 0.988 R² (0.976 R² .. 0.995 R²) mean 1.735 ms (1.701 ms .. 1.783 ms) std dev 135.1 μs (102.7 μs .. 180.1 μs) variance introduced by outliers: 58% (severely inflated) benchmarking recur/no callstack/10000 time 16.70 ms (14.62 ms .. 18.45 ms) 0.941 R² (0.888 R² .. 0.975 R²) mean 19.34 ms (18.32 ms .. 20.57 ms) std dev 2.575 ms (1.965 ms .. 3.431 ms) variance introduced by outliers: 62% (severely inflated) benchmarking recur/HasCallStack/10000 time 20.03 ms (18.44 ms .. 21.43 ms) 0.955 R² (0.911 R² .. 0.979 R²) mean 19.07 ms (17.62 ms .. 20.33 ms) std dev 3.299 ms (2.438 ms .. 4.141 ms) variance introduced by outliers: 71% (severely inflated) benchmarking recur/explicit callstack/10000 time 21.81 ms (20.15 ms .. 22.95 ms) 0.967 R² (0.919 R² .. 0.993 R²) mean 21.22 ms (19.91 ms .. 22.07 ms) std dev 2.337 ms (1.799 ms .. 3.171 ms) variance introduced by outliers: 51% (severely inflated) benchmarking recur/explicit callstack (mini)/10000 time 18.23 ms (15.22 ms .. 20.59 ms) 0.904 R² (0.825 R² .. 0.960 R²) mean 20.31 ms (18.68 ms .. 21.64 ms) std dev 3.227 ms (2.640 ms .. 3.903 ms) variance introduced by outliers: 71% (severely inflated) benchmarking recur/no callstack/100000 time 199.8 ms (119.6 ms .. 283.6 ms) 0.857 R² (0.493 R² .. 1.000 R²) mean 199.5 ms (152.1 ms .. 232.5 ms) std dev 49.70 ms (37.44 ms .. 59.33 ms) variance introduced by outliers: 65% (severely inflated) benchmarking recur/HasCallStack/100000 time 210.9 ms (166.5 ms .. 262.5 ms) 0.970 R² (0.924 R² .. 1.000 R²) mean 170.5 ms (152.4 ms .. 188.6 ms) std dev 21.57 ms (13.10 ms .. 33.13 ms) variance introduced by outliers: 32% (moderately inflated) benchmarking recur/explicit callstack/100000 time 288.3 ms (229.0 ms .. 398.6 ms) 0.965 R² (0.893 R² .. 1.000 R²) mean 242.8 ms (192.7 ms .. 270.7 ms) std dev 48.74 ms (11.69 ms .. 64.53 ms) variance introduced by outliers: 57% (severely inflated) benchmarking recur/explicit callstack (mini)/100000 time 215.5 ms (73.60 ms .. 322.8 ms) 0.804 R² (0.327 R² .. 1.000 R²) mean 212.8 ms (157.4 ms .. 242.9 ms) std dev 50.88 ms (24.98 ms .. 66.53 ms) variance introduced by outliers: 65% (severely inflated) }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:26 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): Here are the benchmarks of postgresql-simple with only 20% queries resulting in an error. There's **no perceptible difference in perf.** **With HasCallStack** {{{ benchmarking complicatedQuery/with error time 186.4 μs (178.2 μs .. 195.3 μs) 0.989 R² (0.986 R² .. 0.996 R²) mean 198.0 μs (190.5 μs .. 207.8 μs) std dev 29.62 μs (22.83 μs .. 37.34 μs) variance introduced by outliers: 90% (severely inflated) }}} **Without HasCallStack** {{{ benchmarking complicatedQuery/with error time 187.3 μs (180.5 μs .. 197.0 μs) 0.986 R² (0.970 R² .. 0.999 R²) mean 184.4 μs (181.3 μs .. 189.8 μs) std dev 14.19 μs (7.153 μs .. 23.78 μs) variance introduced by outliers: 70% (severely inflated) }}} -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:27 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by gridaphobe): Aha, I didn't realize that your benchmarks were always throwing the error. This makes a lot more sense now, using `HasCallStack` ought to be fairly cheap in the common case were the error does not occur. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:28 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): So, how do we take it forward from here? Is it possible to have HasCallStack automatically for functions in IO and/or above a certain cost threshold? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:29 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

So, how do we take it forward from here? Is it possible to have HasCallStack automatically for functions in IO and/or above a certain cost
#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by bgamari): threshold? I think we'll need to define the criterion here a bit more precisely. In particular, it's not clear that doing this in a completely type-driven way makes sense. For instance, what about a function in `MaybeT IO`? Using a size-threshold sounds plausible but feels a bit ad-hoc. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:30 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): The only reason I was suggesting a criterion was to reduce the perf impact in real-world code. However, if it is simple to implement this for every function, and we find the perf impact to be negligible, it is better than having an ad-hoc criterion. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:31 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by bgamari): I think the best way to approach this would just be to run an experiment. Hack your suggested change into the compiler and measure the effect on real-world code. My suspicion is that this will show that performance regresses by approximately the same amount as `-fprof-auto -prof` since operationally the two approaches are somewhat similar. However, this does raise a question: What prevents you from just using cost center stacks? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:32 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda):
My suspicion is that this will show that performance regresses by approximately the same amount as -fprof-auto -prof since operationally the two approaches are somewhat similar.
Where can I read more about what `-fprof-auto -prof` does?
However, this does raise a question: What prevents you from just using cost center stacks?
By cost-center stacks, do you mean whatever `-prof` does? Upon an exception, does it give a proper callstack? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:33 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

So, how do we take it forward from here? Is it possible to have HasCallStack automatically for functions in IO and/or above a certain cost
#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by gridaphobe): Replying to [comment:30 bgamari]: threshold?
I think we'll need to define the criterion here a bit more precisely. In
particular, it's not clear that doing this in a completely type-driven way makes sense. For instance, what about a function in `MaybeT IO`? Yeah, and it gets even worse when you think about things like `MonadIO`, `MonadBaseControl`, etc.
Using a size-threshold sounds plausible but feels a bit ad-hoc.
I agree. I'm pretty reluctant to tie a user-facing feature like `HasCallStack` to GHC's size heuristics, that would make it quite difficult for users to reason about which functions will get an automatic constraint. And worse, there doesn't seem to be any reason to expect a size heuristic to produce a proper **chain** of callstack-aware functions.
However, this does raise a question: What prevents you from just using cost center stacks?
Well, cost-center stacks require the entire world to have been compiled with `-prof`, which can be pretty time-consuming. In contrast, `HasCallStack` could be enabled on a per-module basis while maintaining interoperability with non-`HasCallStack` code. I feel like this could be a useful debugging tool, though it is somewhat limited in scope, i.e. in the limit it clearly devolves to a manual version of the `-prof` way. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:34 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by bgamari):
And worse, there doesn't seem to be any reason to expect a size heuristic to produce a proper chain of callstack-aware functions.
Ahh, yes, that's a good point. Unlike cost centers, if you miss any link in the chain you lose all locations "above" the missing link.
Well, cost-center stacks require the entire world to have been compiled with -prof, which can be pretty time-consuming.
Okay, fair enough. Personally I have configured Cabal to always profiled libraries so the cost generally isn't so high. I suppose if you don't do this switching to profiling would be rather painful, however. It's a shame that profiled code must be so incompatible with unprofiled. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:35 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): So, I guess it has been established that `HasCallStack` cannot be replaced with `-prof` and that it has independent utility, right? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:36 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by gridaphobe): Replying to [comment:36 saurabhnanda]:
So, I guess it has been established that `HasCallStack` cannot be replaced with `-prof` and that it has independent utility, right?
`HasCallStack` definitely has independent utility, yes. The question Ben is raising is whether this proposed flag to add `HasCallStack` constraints throughout a module has independent utility. I think the answer is still yes, as I argued above, but it's admittedly less obvious than the original use-case for `HasCallStack` (opt-in source locations on a per-function basis). -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:37 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by saurabhnanda): Is there **any way** to enable callstacks without recompiling all your (transitive) dependencies? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:38 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by AndreasK): * cc: AndreasK (added) -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:39 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by AndreasK): Replying to [comment:38 saurabhnanda]:
Is there **any way** to enable callstacks without recompiling all your (transitive) dependencies?
Currently it's implemented as a implicit parameter so I'm sure it would require recompilation. If you use profiled libraries maybe you could access CostCenter callstacks. But this provides less information and probably not what you want. Requiring recompilation seems like a reasonable limitation for such a functionality though. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:40 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.4.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by ntc2): * cc: ntc2 (added) -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:41 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#13360: Add a flag to enable inferring HasCallStack constraints -------------------------------------+------------------------------------- Reporter: gridaphobe | Owner: (none) Type: feature request | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.0.1 Resolution: | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by mentheta): * cc: mentheta (added) -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/13360#comment:43 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler
participants (1)
-
GHC