Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
40764930
by sheaf at 2026-06-12T14:54:43-04:00
4 changed files:
- compiler/GHC/Tc/Solver/Equality.hs
- compiler/GHC/Tc/Solver/FunDeps.hs
- + testsuite/tests/perf/compiler/T26426.hs
- testsuite/tests/perf/compiler/all.T
Changes:
| ... | ... | @@ -2980,15 +2980,30 @@ But it's not so simple: |
| 2980 | 2980 | we kick out g1. Now we have two constraints
|
| 2981 | 2981 | [W] g1 : F a ~ a Int (arising from (F a ~ a Int))
|
| 2982 | 2982 | [W] g2{rw:g1} : F a ~ a Int (arising from (F alpha ~ F a))
|
| 2983 | - If we end up with g2 in the inert set (not g1) we'll get a very confusing
|
|
| 2984 | - error message that we can solve (F a ~ a Int)
|
|
| 2985 | - arising from F a ~ F a
|
|
| 2983 | + If we solve `g1` from `g2` we end up with
|
|
| 2984 | + g1 := g2
|
|
| 2985 | + [W] g2{} : F a ~ a Int (arising from (F alpha ~ F a))
|
|
| 2986 | + and hence (since alpha := a) we report that we can't solve (F a ~ a Int)
|
|
| 2987 | + arising from (F a ~ F a), which is extremely confusing. Moreover, it seems
|
|
| 2988 | + wrong to "solve" `g1` using `g2` when `g2` has itself been rewritten by `g1`!
|
|
| 2986 | 2989 | |
| 2987 | 2990 | TL;DR: Better to hang on to `g1` (with no rewriters), in preference
|
| 2988 | 2991 | to `g2` (which has a rewriter).
|
| 2989 | 2992 | |
| 2990 | 2993 | See (WRW11) in Note [Wanteds rewrite Wanteds: rewriter-sets]
|
| 2991 | 2994 | in GHC.Tc.Types.Constraint.
|
| 2995 | + |
|
| 2996 | + Note that the decision to prefer a constraint without rewriters over one that
|
|
| 2997 | + has rewriters can also have a /huge/ effect on performance. For instance, it
|
|
| 2998 | + avoids an **exponential** blow-up in the size of coercions produced when
|
|
| 2999 | + typechecking in T26426. In that program, we have coercions of the form:
|
|
| 3000 | + |
|
| 3001 | + co_i :: TaggedTypes as `Append` TaggedTypes '[ty]
|
|
| 3002 | + ~# TaggedTypes (as `Append` '[ty])
|
|
| 3003 | + |
|
| 3004 | + and each 'co_{i+1}' contains the previous 'co_i' twice. Without preferring
|
|
| 3005 | + Wanteds with no rewriters, we essentially end up inlining 'co_i' into 'co_{i+1}',
|
|
| 3006 | + which results in exponentially-sized proof terms, growing like O(2^i).
|
|
| 2992 | 3007 | -}
|
| 2993 | 3008 | |
| 2994 | 3009 | tryInertEqs :: EqCt -> SolverStage ()
|
| ... | ... | @@ -1198,6 +1198,56 @@ Key point: equations that are not relevant do not need to be considered for fund |
| 1198 | 1198 | and [W] I alpha ~ Int |> g2
|
| 1199 | 1199 | Here we definiteily want to take advantage of injectivity.
|
| 1200 | 1200 | |
| 1201 | +(CF6) This machinery can also have a significant positive effect on the size of
|
|
| 1202 | + proof terms. For example (simplification of T26426):
|
|
| 1203 | + |
|
| 1204 | + type family (++) a b where { '[] ++ ys = ys; (x:xs) ++ ys = x : (xs ++ ys) }
|
|
| 1205 | + type family MapId a where { MapId '[] = '[]; MapId (x:xs) = x : MapId xs }
|
|
| 1206 | + |
|
| 1207 | + app :: (MapId xs ++ MapId ys ~ MapId (xs ++ ys)) => Proxy xs -> Proxy ys -> Proxy (xs ++ ys)
|
|
| 1208 | + |
|
| 1209 | + test :: Proxy [ty_1, ..., ty_n]
|
|
| 1210 | + test = Proxy @'[ty_1]
|
|
| 1211 | + `app` Proxy @'[ty_2]
|
|
| 1212 | + ...
|
|
| 1213 | + `app` Proxy @'[ty_n]
|
|
| 1214 | + |
|
| 1215 | + Every `app` call gives rise to a Wanted of the form:
|
|
| 1216 | + |
|
| 1217 | + [W] MapId acc_i ++ MapId '[ty_i] ~ MapId (acc_i ++ '[ty_i])
|
|
| 1218 | + |
|
| 1219 | + while the overall result type gives us a Wanted of the form
|
|
| 1220 | + |
|
| 1221 | + [W] acc_n ++ '[ty_n] ~ [ty_1, ..., ty_n]
|
|
| 1222 | + |
|
| 1223 | + By using (CFFA) on this result Wanted, we deduce that we must have
|
|
| 1224 | + |
|
| 1225 | + acc_n ~ [ty_1, ..., ty_{n-1}]
|
|
| 1226 | + |
|
| 1227 | + which is a flat list. Repeating the process, (CFFA) allows us to deduce that
|
|
| 1228 | + |
|
| 1229 | + acc_i ~ [ty_1, ..., ty_{i-1}]
|
|
| 1230 | + |
|
| 1231 | + for all i. This allows the other Wanteds to be solved directly, giving rise to
|
|
| 1232 | + proof terms with the typical triangular O(n^2) shape
|
|
| 1233 | + |
|
| 1234 | + co_i = (O(i) proof that MapId acc_i ++ MapId '[ty_i] ~ acc_{i+1})
|
|
| 1235 | + ; (O(i) proof that acc_{i+1} ~ MapId (acc_i ++ '[ty_i]))
|
|
| 1236 | + |
|
| 1237 | + However, /without/ (CFFA), 'acc_i' is not unified with a flat list but is left
|
|
| 1238 | + as the nested application:
|
|
| 1239 | + |
|
| 1240 | + acc_i ~ (... (('[ty_1] ++ '[ty_2]) ++ '[ty_3]) ... ++ '[ty_{i-1}])
|
|
| 1241 | + |
|
| 1242 | + This means that 'MapId acc_i' is stuck until we reduce the above, which takes
|
|
| 1243 | + O(i^2) type family reduction steps, instead of O(i). The same applies to
|
|
| 1244 | + the other proof term involving 'MapId (acc_i ++ '[ty_i])'.
|
|
| 1245 | + Consequently, without (CFFA) the overall coercion size blows up to O(n^3).
|
|
| 1246 | + |
|
| 1247 | + The takeaway is that (CFFA) allows us to push in the (flat) result type,
|
|
| 1248 | + instead of relying on recursively built sub-proof terms, which brings down
|
|
| 1249 | + coercion sizes (in certain situations) from O(n^3) to O(n^2).
|
|
| 1250 | + |
|
| 1201 | 1251 | Note [Cache-caused loops]
|
| 1202 | 1252 | ~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 1203 | 1253 | It is very dangerous to cache a rewritten wanted family equation as 'solved' in
|
| 1 | +{-# LANGUAGE DataKinds #-}
|
|
| 2 | +{-# LANGUAGE TypeFamilies #-}
|
|
| 3 | +module T26426 (
|
|
| 4 | + someTypes
|
|
| 5 | +) where
|
|
| 6 | + |
|
| 7 | +import Data.Kind (Type)
|
|
| 8 | +import GHC.TypeLits (Symbol)
|
|
| 9 | + |
|
| 10 | +type family Append (left :: [k]) (right :: [k]) :: [k] where
|
|
| 11 | + Append '[] right = right
|
|
| 12 | + Append (a : rest) right = a : Append rest right
|
|
| 13 | + |
|
| 14 | +type family TaggedTypes (tags :: [(Symbol, Type)]) :: [Type] where
|
|
| 15 | + TaggedTypes '[] = '[]
|
|
| 16 | + TaggedTypes ('(_, typ) : rest) = typ : TaggedTypes rest
|
|
| 17 | + |
|
| 18 | +data Types (types :: [(Symbol, Type)]) = Types
|
|
| 19 | + |
|
| 20 | +mkTypes :: forall sym val. val -> Types '[ '(sym, val) ]
|
|
| 21 | +mkTypes _ = Types
|
|
| 22 | + |
|
| 23 | +appendTypes ::
|
|
| 24 | + -- This constraint is the one that causes the issue. If the next line is commented
|
|
| 25 | + -- out, then this module compiles quickly
|
|
| 26 | + Append (TaggedTypes left) (TaggedTypes right) ~ TaggedTypes (Append left right) =>
|
|
| 27 | + Types left -> Types right -> Types (Append left right)
|
|
| 28 | +appendTypes _ _ = Types
|
|
| 29 | + |
|
| 30 | +someTypes ::
|
|
| 31 | + Types
|
|
| 32 | + [ '("01", Int)
|
|
| 33 | + , '("02", Int)
|
|
| 34 | + , '("03", Int)
|
|
| 35 | + , '("04", Int)
|
|
| 36 | + , '("05", Int)
|
|
| 37 | + , '("06", Int)
|
|
| 38 | + , '("07", Int)
|
|
| 39 | + , '("08", Int)
|
|
| 40 | + , '("09", Int)
|
|
| 41 | + , '("10", Int)
|
|
| 42 | + , '("11", Int)
|
|
| 43 | + , '("12", Int)
|
|
| 44 | + , '("13", Int)
|
|
| 45 | + , '("14", Int)
|
|
| 46 | + , '("15", Int)
|
|
| 47 | + , '("16", Int)
|
|
| 48 | + ]
|
|
| 49 | + |
|
| 50 | +someTypes =
|
|
| 51 | + mkTypes @"01" 1 `appendTypes`
|
|
| 52 | + mkTypes @"02" 2 `appendTypes`
|
|
| 53 | + mkTypes @"03" 3 `appendTypes`
|
|
| 54 | + mkTypes @"04" 4 `appendTypes`
|
|
| 55 | + mkTypes @"05" 5 `appendTypes`
|
|
| 56 | + mkTypes @"06" 6 `appendTypes`
|
|
| 57 | + mkTypes @"07" 7 `appendTypes`
|
|
| 58 | + mkTypes @"08" 8 `appendTypes`
|
|
| 59 | + mkTypes @"09" 9 `appendTypes`
|
|
| 60 | + mkTypes @"10" 10 `appendTypes`
|
|
| 61 | + mkTypes @"11" 11 `appendTypes`
|
|
| 62 | + mkTypes @"12" 12 `appendTypes`
|
|
| 63 | + mkTypes @"13" 13 `appendTypes`
|
|
| 64 | + mkTypes @"14" 14 `appendTypes`
|
|
| 65 | + mkTypes @"15" 15 `appendTypes`
|
|
| 66 | + mkTypes @"16" 16 |
| ... | ... | @@ -87,6 +87,13 @@ test('T783', |
| 87 | 87 | ],
|
| 88 | 88 | compile,[''])
|
| 89 | 89 | |
| 90 | +test ('T26426'
|
|
| 91 | + , [ only_ways(['normal'])
|
|
| 92 | + , collect_compiler_stats('bytes allocated',4) ]
|
|
| 93 | + , compile
|
|
| 94 | + , ['']
|
|
| 95 | + )
|
|
| 96 | + |
|
| 90 | 97 | test('T5321Fun',
|
| 91 | 98 | [ only_ways(['normal']), # no optimisation for this one
|
| 92 | 99 | collect_compiler_runtime(2),
|