Opened 3 years ago

Last modified 3 years ago

#12274 new bug

GHC panic: simplifier ticks exhausted

Reported by: mrkkrp Owned by:
Priority: normal Milestone:
Component: Compiler Version: 8.0.1
Keywords: Inlining Cc:
Operating System: Linux Architecture: x86_64 (amd64)
Type of failure: Compile-time performance bug Test Case:
Blocked By: Blocking:
Related Tickets: Differential Rev(s):
Wiki Page:

Description

While compiling a project, I ran into this:

Preprocessing library stache-0.1.0...
[1 of 6] Compiling Text.Mustache.Type ( Text/Mustache/Type.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.0.0/build/Text/Mustache/Type.o ) [.stack-work/dist/x86_64-linux/Cabal-1.24.0.0/build/autogen/cabal_macros.h changed]
[2 of 6] Compiling Text.Mustache.Parser ( Text/Mustache/Parser.hs, .stack-work/dist/x86_64-linux/Cabal-1.24.0.0/build/Text/Mustache/Parser.o )
ghc: panic! (the 'impossible' happened)
  (GHC version 8.0.1 for x86_64-unknown-linux):
	Simplifier ticks exhausted
  When trying RuleFired Class op HEq_sc
  To increase the limit, use -fsimpl-tick-factor=N (default 100)
  If you need to do this, let GHC HQ know, and what factor you needed
  To see detailed counts use -ddump-simpl-stats
  Total ticks: 189602

Please report this as a GHC bug:  http://www.haskell.org/ghc/reportabug

The code is available here:

https://github.com/stackbuilders/stache

This only happens with GHC 8.0, with 7.10 it just takes forever (which should be a known issue, with 7.8 it's much faster), but nevertheless finishes.

With -fsimpl-tick-factor=150 the build suceeded.

Change History (5)

comment:1 Changed 3 years ago by thomie

Type of failure: Compile-time crashCompile-time performance bug

Some notes:

  • There are a lot of INLINE pragmas in Text.Mustache.Parser, maybe too many? For example, removing the INLINE on symbol fixes the panic.
  • A simpl-tick-factor of 110 fixes the panic, so the simplifier is going only very slightly over the limit. This is probably nothing to worry about.
  • The regression in compile time is the real problem. Fixing it will fix the panic as well. I reduced it to the following testcase: https://github.com/thomie/megaslow (no external dependencies).

Instructions:

$ ghc -O Char.hs
$ ghc -O -c -Rghc-timing Test.hs
GHC Bytes allocated for Test.hs
7.8.4 117MB
7.10.3 750MB
HEAD (ffe4660510a7ba4adce846f316db455ccd91142a) 724MB

comment:2 Changed 3 years ago by simonpj

Interesting. Thanks for reducing the test case. No time to look at it now, but can you characterise what is happening more. E.g. what happens with -dshow-passes? Does the program get really big at some point? What's the difference between 7.8 and 7.10. Is it non-linear somehow? etc.

comment:3 Changed 3 years ago by thomie

The problems start in the Specialise phase:

# 7.8.4:
# Result size of Simplifier = {terms: 62, types: 158, coercions: 1}
# Result size of Specialise = {terms: 62, types: 158, coercions: 1}
#
# 7.10.3:
# Result size of Simplifier = {terms: 66, types: 158, coercions: 1}
# Result size of Specialise = {terms: 751, types: 2,319, coercions: 169}
  • The output of -ddump-spec for 7.10 contains a list of Local rules for imported ids, but none for 7.8.
  • The output of -ddump-rule-firings contains 326 rules for 7.10, but only 20 for 7.8.

Some of the rule firings with 7.10:

Rule fired: Class op eof
Rule fired: Class op fmap
Rule fired: Class op fmap
Rule fired: Class op $p1Stream
Rule fired: Class op uncons
Rule fired: Class op updatePos
Rule fired: Class op updatePos
Rule fired: Class op uncons
Rule fired: Class op updatePos
Rule fired: Class op updatePos
Rule fired: Class op uncons
Rule fired: Class op updatePos
Rule fired: Class op updatePos
Rule fired: Class op uncons
Rule fired: Class op updatePos
Rule fired: Class op updatePos
Rule fired:
    SPEC/Test $fApplicativeParsecT @ Dec @ [Char] @ Identity
Rule fired:
    SPEC/Test $fAlternativeParsecT @ Dec @ [Char] @ Identity
Rule fired: Class op pure
Rule fired: Class op pure
...
...
...

Here is a simplified example:

Test.hs:

module Test where

import A

xs = mymap (+1) [1,2,3]

A.hs:

module A where

mymap :: Functor f => (a -> b) -> f a -> f b
mymap = fmap

ghc-7.8.4:

$ ghc-7.8.4 Test.hs -ddump-rule-firings -O -fforce-recomp
[1 of 2] Compiling A                ( A.hs, A.o )
[2 of 2] Compiling Test             ( Test.hs, Test.o )
Rule fired: Class op +
Rule fired: Class op fmap
Rule fired: map
Rule fired: mapList

ghc-7.10.3:

$ ghc-7.10.3 Test.hs -ddump-rule-firings -O -fforce-recomp
[1 of 2] Compiling A                ( A.hs, A.o )
[2 of 2] Compiling Test             ( Test.hs, Test.o )
Rule fired: Class op +
Rule fired: Class op fmap
Rule fired: Class op fmap
Rule fired: Class op fmap
Rule fired: map
Rule fired: mapList

Why does the Class op fmap fire three times with 7.10.3?

comment:4 Changed 3 years ago by simonpj

GHC 7.10 makes specialised versions of imported overloaded functions, at least if it can see its inlining.

In the example

  • mymap is overloaded, and is small, so its inlining is visible to module Test
  • mymap is called at type Integer in Test so GHC makes specialised copy, and adds a local RULE to rewrite mymap Integer to $smymap.
  • The fmap rule fires twice, once in the RHS of $smymap and once in its (small) unfolding.
  • But when GHC encounters the call to mymap is just inlines it anyway, before thinking about rules. That exposes another call to fmap so the rule fires again.

Because mymap inlines, all the specalisation is in vain. How to avoid that? Maybe one round of simplification so that outright inlining precedes any attempt to specialise.

Whether this is the original problem, I'm not sure.

comment:5 Changed 3 years ago by mpickering

Keywords: Inlining added
Note: See TracTickets for help on using tickets.