Andreas Klebinger pushed to branch wip/ak/spec-loop at Glasgow Haskell Compiler / GHC Commits: 1702aa29 by Andreas Klebinger at 2026-08-28T14:36:04+02:00 Specialise: Stop looping on recursive dictionaries in interestingDict interestingDict now doesn't look through loopbreaker unfoldings. Doing so would cause infinite loops on certain dictionaries. Fixes #27705. - - - - - 6 changed files: - + changelog.d/T27705 - compiler/GHC/Core/Opt/Specialise.hs - + testsuite/tests/simplCore/should_run/T27705.hs - + testsuite/tests/simplCore/should_run/T27705.stdout - + testsuite/tests/simplCore/should_run/T27705_Inst.hs - testsuite/tests/simplCore/should_run/all.T Changes: ===================================== changelog.d/T27705 ===================================== @@ -0,0 +1,5 @@ +section: compiler +synopsis: Fixed an issue that caused the specializer to sometimes loop on recursive dictionary superclasses. +issues: #27705 +mrs: !16559 + ===================================== compiler/GHC/Core/Opt/Specialise.hs ===================================== @@ -3120,8 +3120,8 @@ interestingDict :: SpecEnv -> CoreExpr -> Bool -- This is a subtle and important function -- See Note [Interesting dictionary arguments] interestingDict env (Var v) -- See (ID3) and (ID5) + -- (ID6.a) Might fail for loop breaker dicts but that seems fine. | Just rhs <- maybeUnfoldingTemplate (idUnfolding v) - -- Might fail for loop breaker dicts but that seems fine. = interestingDict env rhs interestingDict env arg -- Main Plan: use exprIsConApp_maybe @@ -3136,9 +3136,9 @@ interestingDict env arg -- Main Plan: use exprIsConApp_maybe , isIPClass cls -- See (ID5) -> False - -- Otherwise we are unwrapping a unary type class + -- Shouldn't happen. | otherwise - -> exprIsHNF arg -- See (ID7) + -> pprTraceDebug "shouldn't happen anymore" (ppr arg) $ exprIsHNF arg -- See (ID7) | Just (_, _, data_con, _tys, args) <- exprIsConApp_maybe in_scope_env arg , Just cls <- tyConClass_maybe (dataConTyCon data_con) @@ -3152,7 +3152,8 @@ interestingDict env arg -- Main Plan: use exprIsConApp_maybe where arg_ty = exprType arg definitely_not_ip_like = not (couldBeIPLike arg_ty) - in_scope_env = ISE (substInScopeSet $ se_subst env) realIdUnfolding + -- idUnfolding rather than realIdUnfolding: See (ID6.a) + in_scope_env = ISE (substInScopeSet $ se_subst env) idUnfolding {- Note [Ticks on applications] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -3268,11 +3269,27 @@ case we can clearly specialise. But there are wrinkles: (Remember: a constraint tuple is just a class with N superclasses and no methods.) See discussion on #26831. -(ID7) A unary (single-method) class is currently represented by (meth |> co). We - will unwrap the cast (see (ID5)) and then want to reply "yes" if the method - has any struture. We rather arbitrarily use `exprIsHNF` for this. (We plan a - new story for unary classes, see #23109, and this special case will become - irrelevant.) +(ID6.a) If we deal with a recursive dictionary as in #27705 we want to avoid + infinite recursion while recursing into superclasses. + + For example we might have: + + class D1 a => D2 a + class D2 a => D1 a + + The primary concern is that we want to avoid looping on recursive instances. + We can achieve this by simply not looking through loop breakers by using idUnfolding + rather than realIdUnfolding. + + It's possible that this prevents specialization of edge cases that have loop breakers + in their recursive loop. But even if we can find a dictionary like this the simplifier + won't look through loopbreaker dictionaries either killing any potential benefit. + So while we could handle this case via a already-seen set or fuel we simply don't bother + for now. + +(ID7) A unary (single-method) class is currently handled by the same path as regular dicts + since they are represented by faking a regular Dictionary. + See Note [Unary class magic] for the details. (ID8) Sadly, if `exprIsConApp_maybe` says Nothing, we still want to treat a non-trivial argument as interesting. In T19695 we have this: ===================================== testsuite/tests/simplCore/should_run/T27705.hs ===================================== @@ -0,0 +1,9 @@ +module Main where + +import T27705_Inst + +-- The dictionaries (D1/D2) are mutually recursive. We have to watch +-- out for the specializer looping on them. This was first detected in #22802 +-- but no test was added, which caused it to break again #27705 :( +main :: IO () +main = print (b (3 :: Int)) ===================================== testsuite/tests/simplCore/should_run/T27705.stdout ===================================== @@ -0,0 +1 @@ +42 ===================================== testsuite/tests/simplCore/should_run/T27705_Inst.hs ===================================== @@ -0,0 +1,13 @@ +{-# LANGUAGE UndecidableInstances, UndecidableSuperClasses, FlexibleInstances #-} +module T27705_Inst where + +-- The two dictionaries are mutually recursive, and we have to ensure the specialiser +-- doesn't loop when it's peaking through their unfoldings. +class D2 a => D1 a +class D1 a => D2 a +instance D2 Int => D1 Int +instance D1 Int => D2 Int + +{-# NOINLINE b #-} +b :: D1 a => a -> Int +b _ = 42 ===================================== testsuite/tests/simplCore/should_run/all.T ===================================== @@ -123,3 +123,5 @@ test('T24359b', normal, compile_and_run, ['-O']) test('T23429', normal, compile_and_run, ['-O']) test('T27071', normal, compile_and_run, ['-O -fworker-wrapper-cbv']) test('T27005', [], multimod_compile_and_run, ['T27005', '-O']) +test('T27705', [extra_hc_opts('+RTS -M500M -RTS')], multimod_compile_and_run, + ['T27705', '-O2 -fexpose-all-unfoldings']) View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/1702aa29bb30b34b5be7c860b2ff88b7... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/1702aa29bb30b34b5be7c860b2ff88b7... You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
participants (1)
-
Andreas Klebinger (@AndreasK)