[GHC] #12734: Missed use of solved dictionaries leads to context stack overflow

GHC ghc-devs at haskell.org
Mon Oct 24 14:33:48 UTC 2016


#12734: Missed use of solved dictionaries leads to context stack overflow
-------------------------------------+-------------------------------------
        Reporter:  danilo2           |                Owner:
            Type:  bug               |               Status:  new
        Priority:  high              |            Milestone:
       Component:  Compiler          |              Version:  8.0.1
      Resolution:                    |             Keywords:
Operating System:  Unknown/Multiple  |         Architecture:
 Type of failure:  GHC rejects       |  Unknown/Multiple
  valid program                      |            Test Case:
      Blocked By:                    |             Blocking:
 Related Tickets:                    |  Differential Rev(s):
       Wiki Page:                    |
-------------------------------------+-------------------------------------

Comment (by simonpj):

 OK I know what is going on.  With the cut-down example above, when
 checking the cod for `test_ghc_err` we are faced with
 {{{
     [W] Con m (TStk t lrs)

        where  m := KT A '[Type] IO
               bind := Expr Net '[Type]
               t := Net
               lrs := '[Type]
 }}}
 The `:=` are unifications that can take place pretty eagerly.

 Now let's try solving that wanted constraint
 {{{
     [W] Con m (TStk t lrs)

 = (Type synonym)
     [W] Con m (Stack lrs (Layer4 bind))

 = (inline lrs)
     [W] Con m (Stack '[Type] (Layer4 bind))

 --> instance decl for (Con m (Stack (l :' ls) t))
     [W] Con m (Stack '[] bind)
     [W] Con m (Layer4 bind Type)

 The first of these rapidly reduces to (Monad m).  The second is harder:

     [W] Con m (Layer4 bind Type)

 --> instance decl for (Con m (Layer4 expr Type)
     [W] bind ~ Expr t0 lrs0          -- C1
     [W] Con m (TStk t0 lrs0)         -- C2

     where t0, lrs0 are fresh unification variables.

 If we simplify (C1), using the fact that bind := Expr Net '[Type]
 we get

     t0 := Net
     lrs0 := lrs

 And now we have (C2):
     [W] Con m (TStk Net lrs)

 which is the very same thing we start with.  So we can
 "tie the knot" via the `inert_solved_dicts` and we are done.
 }}}
 This makes essential use of GHC's ability to solve a set of constraints
 with a ''recursive'' dictionary binding.

 So what goes wrong?

 * If we fail to prioritise C1 over C2, we'll embark on simplifying C2, not
 spotting the loop.  Result: an infinite regress.

 * We do prioritise equality constraints

 * But C1, namely `bind ~ Expr t0 lrs0` is actually a ''class'' constraint,
 not an equality.  It has a built-in reduction to the homogeneous-equality
 class constraint `bind ~~ Expr t0 lrs0`; and that in turn has a built-in
 reduction to the true equality constraint `bind ~# Expr t0 lrs0`.

 So a solution that works is to prioritise the two equality-class
 constraints `t1 ~ t2` and `t1 ~~ t2` just as we do equality constraints.
 That's a good thing to do anyway.

 But I'm concerned that it does not truly solve the problem.  Two worries:

 * To "tie the knot" we rely on saying "I've see this goal before".  But to
 be sure of that we should rewrite `inert_solved_dicts` with the current
 substitution, and currently we don't do that.  Maybe we should.  It's more
 work but we might in exchange sometimes get more sharing.

 * What if we have a class constraint `(C t1 t2)` where C has a perhaps-
 distant superclass `(s1 ~ s2)`.  Then should we prioritise the `(C t1 t2)`
 constraint?  But the superclass might not even be revealed until we do
 some reduction on `(C t1 t2)`.  This way lies madness.  I think wes should
 not even attempt to do this.

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/12734#comment:10>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list