[GHC] #15125: Typeclass instance selection depends on the optimisation level

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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: -------------------------------------+------------------------------------- (See the attached files for a minimal case.) A file A defines a typeclass, and gives an incoherent instance for all types a, and exports a function relying on said typeclass. A file B defines some data types, makes them specific instances of that class. Which instance ends up being picked depends on the optimisation level they're compiled with. //A.hs// {{{#!hs class A a where someValue :: a -> Maybe Int instance {-# INCOHERENT #-} A a where someValue = const Nothing getInt :: A a => a -> Int getInt x = fromMaybe 0 $ someValue x }}} //B.hs// {{{#!hs data B = B Int instance A B where someValue (B x) = Just x getBInt :: Int getBInt = getInt $ B 42 }}} //Main.hs// {{{#!hs main = print getBInt }}} **Upon compiling with -O0, this prints 42; upon compiling with -O2, this print 0.** (Interestingly, if the redundant class constraint is removed from getInt, then it always prints 0.) -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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 nicuveo): * Attachment "poc.tar.bz2" added. A small proof of concept, with a script showing how to replicate the issue. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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: | -------------------------------------+------------------------------------- Description changed by nicuveo: Old description:
(See the attached files for a minimal case.)
A file A defines a typeclass, and gives an incoherent instance for all types a, and exports a function relying on said typeclass. A file B defines some data types, makes them specific instances of that class. Which instance ends up being picked depends on the optimisation level they're compiled with.
//A.hs// {{{#!hs class A a where someValue :: a -> Maybe Int
instance {-# INCOHERENT #-} A a where someValue = const Nothing
getInt :: A a => a -> Int getInt x = fromMaybe 0 $ someValue x }}}
//B.hs// {{{#!hs data B = B Int
instance A B where someValue (B x) = Just x
getBInt :: Int getBInt = getInt $ B 42 }}}
//Main.hs// {{{#!hs main = print getBInt }}}
**Upon compiling with -O0, this prints 42; upon compiling with -O2, this print 0.**
(Interestingly, if the redundant class constraint is removed from getInt, then it always prints 0.)
New description: (See the attached files for a minimal case.) A file A defines a typeclass, and gives an incoherent instance for all types a, and exports a function relying on said typeclass. A file B defines some data types, makes them specific instances of that class, and uses the function defined in A. Which instance ends up being picked for B depends on the optimisation level those files are compiled with. //A.hs// {{{#!hs class A a where someValue :: a -> Maybe Int instance {-# INCOHERENT #-} A a where someValue = const Nothing getInt :: A a => a -> Int getInt x = fromMaybe 0 $ someValue x }}} //B.hs// {{{#!hs data B = B Int instance A B where someValue (B x) = Just x getBInt :: Int getBInt = getInt $ B 42 }}} //Main.hs// {{{#!hs main = print getBInt }}} **Upon compiling with -O0, this prints 42; upon compiling with -O2, this prints 0.** (Interestingly, if the redundant class constraint is removed from getInt, then it always prints 0.) -- -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:1 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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 nicuveo): I tested with 8.4.2: it prints 0 in both cases. This seems inconsistent with the [http://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.... #extension-IncoherentInstances documented behaviour of incoherent instances]? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:2 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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 AntC):
This seems inconsistent with the ​documented behaviour of incoherent instances?
The naming is deliberate: only use `INCOHERENT` to diagnose behaviour you don't understand; never rely on ghc consistently picking an instance. The behaviour you're seeing in 8.4.2 is following the documentation. The critical part of that doco is
Eliminate any candidate **IX** for which ''both'' of the following hold:
The second sub-bullet doesn't hold, because neither instance is marked ''overlapping''/''overlappable''. So I'd expect to always pick the more general instance, marked `INCOHERENT`. Then I agree the optimisation level shouldn't influence instance selection. I guess that in optimising for module B, ghc forces the instance selection. Never the less, just don't use `INCOHERENT`. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:3 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

//If exactly one non-incoherent candidate remains, select it. If all remaining candidates are incoherent, select an arbitrary one. Otherwise
#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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 nicuveo): I do agree with you on never relying on INCOHERENT! I beg to disagree on the interpretation of the documentation, however. Sure, the INCOHERENT instance does not get eliminated, but the very next bullet point reads: the search fails (i.e. when more than one surviving candidate is not incoherent).// Since, out of the two candidates that remain, only one is not incoherent, the compiler should only pick that one. My understanding is therefore that this code should //always// print 42? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:4 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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 investigated. Turns out that this is a near-dup of #14434 -- a bug in the "short-cut solver" for type classes. Happily, it was fixed six months ago, and the fix is in 8.4. I also agree with comment:4. So I'll just close this. I don't think it's worth a new regression test. If anyone reading this has a concrete suggestion for making the user manual clearer, please do re-open and off your proposed text. The user manual can always do with improvement! -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:5 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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 nicuveo): I am afraid I still see this behaviour in 8.4.2... I replaced INCOHERENT with OVERLAPPABLE, built my small example with --resolver nightly-2018-05-09, and in both cases it shortcuts and picks the general instance and prints 0. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:6 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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 nicuveo): Since in 8.4.2 the behaviour does not depend on the optimization level, should I maybe close this bug and reopen ticket:14434? Or should I rewrite this one and create a better minimal test case? -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:7 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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 RyanGlScott): I'm quite confused, as I can't seem to trigger this bug in GHC 8.4.2 (or HEAD). First, it's worth noting that the tarball you provided has a slightly different program than the one you gave in the original description. The modules in the tarball are: {{{#!hs {-# LANGUAGE FlexibleInstances #-} {-# OPTIONS_GHC -fno-warn-simplifiable-class-constraints #-} module A where import Data.Maybe class A a where someValue :: a -> Maybe Int instance {-# INCOHERENT #-} A a where someValue = const Nothing getInt :: A a => a -> Int getInt x = fromMaybe 0 $ someValue x }}} {{{#!hs module B where import A data B = B Int data C = C Int instance A B where someValue (B x) = Just x getBInt :: Int getBInt = getInt $ B 42 getCInt :: Int getCInt = getInt $ C 42 }}} {{{#!hs -- Main.hs import B main :: IO () main = do putStrLn "==========================================" putStrLn $ "B: " ++ show getBInt putStrLn $ "C: " ++ show getCInt }}} I'll use these, since the programs in the original description do not compile. Second, when I compile and run these with GHC 8.4.2, I get the same answer, regardless of optimization level: {{{ $ /opt/ghc/8.4.2/bin/ghc -O0 -fforce-recomp Main.hs [1 of 3] Compiling A ( A.hs, A.o ) [2 of 3] Compiling B ( B.hs, B.o ) [3 of 3] Compiling Main ( Main.hs, Main.o ) Linking Main ... $ ./Main ========================================== B: 42 C: 0 $ /opt/ghc/8.4.2/bin/ghc -O2 -fforce-recomp Main.hs [1 of 3] Compiling A ( A.hs, A.o ) [2 of 3] Compiling B ( B.hs, B.o ) [3 of 3] Compiling Main ( Main.hs, Main.o ) Linking Main ... $ ./Main ========================================== B: 42 C: 0 }}} So unless you're using a yet more different version of this program, I'm inclined to agree that there is no bug here. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:8 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: new Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 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 nicuveo): Yes, I'm using the version in the tarball; I simplified it for the purpose of the bug description. And my apologies: I double-checked, and indeed, the code in the tarball works for 8.4.2; my stack setup was wrong. Sorry about that! ...however, sadly, there is the other bug I highlight in comment:6. If you replace //A.hs// to read: {{{#!hs instance A a where someValue = const Nothing }}} and //B.hs// to read: {{{#!hs instance {-# OVERLAPPING #-} A B where someValue (B x) = Just x }}} Then the same behaviour is back with 8.4.2: with -O0, it prints 42, with -O2, it prints 0. -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:9 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: closed Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 Resolution: duplicate | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: #14434 | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Changes (by RyanGlScott): * status: new => closed * resolution: => duplicate * related: => #14434 Comment: Indeed, that is a separate bug, so I'd recommend opening a new ticket so that it does not get lost in here. Some advice for the future: * Make sure to post the complete code. The files in the original description leave off several details like the language extensions and module names. Without these, we have to reverse-engineer them, which is quite annoying. * Don't bother posting `stack.yaml` files or other extraneous things like that. An ideal reproducing test case is one where you can simply invoke GHC on some files to produce the issue. And you already have such a test case! -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:10 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler

#15125: Typeclass instance selection depends on the optimisation level -------------------------------------+------------------------------------- Reporter: nicuveo | Owner: (none) Type: bug | Status: closed Priority: normal | Milestone: 8.6.1 Component: Compiler | Version: 8.2.2 Resolution: duplicate | Keywords: Operating System: Unknown/Multiple | Architecture: | Unknown/Multiple Type of failure: None/Unknown | Test Case: Blocked By: | Blocking: Related Tickets: #14434 | Differential Rev(s): Wiki Page: | -------------------------------------+------------------------------------- Comment (by nicuveo): Duly noted. Thanks! -- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/15125#comment:11 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler
participants (1)
-
GHC