Simon Jakobi pushed to branch wip/sjakobi/T27459 at Glasgow Haskell Compiler / GHC
Commits:
-
449e126a
by Simon Jakobi at 2026-08-13T10:05:41+02:00
4 changed files:
- compiler/GHC/Core/RoughMap.hs
- compiler/GHC/Tc/Instance/Family.hs
- compiler/GHC/Types/Name/Env.hs
- compiler/GHC/Types/Unique/DFM.hs
Changes:
| ... | ... | @@ -168,6 +168,16 @@ each candidate. We only need the full list of unifiers when displaying error mes |
| 168 | 168 | Therefore the list is computed lazily so much work can be avoided constructing the
|
| 169 | 169 | list in the first place.
|
| 170 | 170 | |
| 171 | +For the same reason the unifiers are enumerated in non-deterministic order (see
|
|
| 172 | +the RML_WildCard case of lookupRM'): a deterministic fold would have to inspect
|
|
| 173 | +every entry of rm_known before it could produce the first unifier, defeating
|
|
| 174 | +that laziness (#27459; see Note [Cost of deterministic iteration] in
|
|
| 175 | +GHC.Types.Unique.DFM). The order is observable only where error messages are
|
|
| 176 | +rendered, so those sites must sort the unifiers they display; see
|
|
| 177 | +fuzzyClsInstCmp in GHC.Core.InstEnv and reportConflictInstErr in
|
|
| 178 | +GHC.Tc.Instance.Family. The matches, by contrast, are still enumerated
|
|
| 179 | +deterministically.
|
|
| 180 | + |
|
| 171 | 181 | Note [Simple Matching Semantics]
|
| 172 | 182 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 173 | 183 | Suppose `rm` is a RoughMap representing a set of (key,vals) pairs,
|
| ... | ... | @@ -420,15 +430,18 @@ lookupRM' (RML_NoKnownTc : tcs) rm = |
| 420 | 430 | |
| 421 | 431 | lookupRM' (RML_WildCard : tcs) rm =
|
| 422 | 432 | -- pprTrace "RM wild" (ppr tcs $$ ppr (eltsDNameEnv (rm_known rm))) $
|
| 423 | - let (m, u) = foldDNameEnv add_one (emptyBag, []) (rm_known rm)
|
|
| 433 | + let m = foldDNameEnv (\rm' acc -> fst (lookupRM' tcs rm') `unionBags` acc)
|
|
| 434 | + emptyBag (rm_known rm)
|
|
| 435 | + -- The unifiers are enumerated non-deterministically: hot consumers
|
|
| 436 | + -- only test whether the list is empty, and the lazy fold lets that
|
|
| 437 | + -- test stop at the first unifier instead of paying for deterministic
|
|
| 438 | + -- iteration over the whole map (#27459).
|
|
| 439 | + -- See Note [Matches vs Unifiers].
|
|
| 440 | + u = nonDetFoldDNameEnv (\rm' acc -> snd (lookupRM' tcs rm') ++ acc)
|
|
| 441 | + [] (rm_known rm)
|
|
| 424 | 442 | (u_m, u_u) = lookupRM' tcs (rm_wild rm)
|
| 425 | 443 | in ( rm_empty rm `unionBags` u_m `unionBags` m
|
| 426 | 444 | , bagToList (rm_empty rm) ++ u_u ++ u )
|
| 427 | - where
|
|
| 428 | - add_one :: RoughMap a -> (Bag a, [a]) -> (Bag a, [a])
|
|
| 429 | - add_one rm ~(m2, u2) = (m1 `unionBags` m2, u1 ++ u2)
|
|
| 430 | - where
|
|
| 431 | - (m1,u1) = lookupRM' tcs rm
|
|
| 432 | 445 | |
| 433 | 446 | unionRM :: RoughMap a -> RoughMap a -> RoughMap a
|
| 434 | 447 | unionRM RMEmpty a = a
|
| ... | ... | @@ -939,11 +939,18 @@ buildInjectivityError mkErr fam_tc branches |
| 939 | 939 | reportConflictInstErr :: FamInst -> [FamInst] -> TcRn ()
|
| 940 | 940 | reportConflictInstErr _ []
|
| 941 | 941 | = return () -- No conflicts
|
| 942 | -reportConflictInstErr fam_inst (conf_inst : _) =
|
|
| 942 | +reportConflictInstErr fam_inst (conf_inst1 : conf_insts) =
|
|
| 943 | + -- The conflicts are enumerated in non-deterministic order (see
|
|
| 944 | + -- Note [Matches vs Unifiers] in GHC.Core.RoughMap), so pick the one to
|
|
| 945 | + -- report deterministically.
|
|
| 943 | 946 | -- The sortBy just arranges that instances are displayed in order
|
| 944 | 947 | -- of source location, which reduced wobbling in error messages,
|
| 945 | 948 | -- and is better for users
|
| 946 | - let sorted = NE.sortBy (SrcLoc.leftmost_smallest `on` getSpan) (fam_inst NE.:| [conf_inst])
|
|
| 949 | + let conf_inst = minimumBy cmp_inst (conf_inst1 :| conf_insts)
|
|
| 950 | + cmp_inst f1 f2 = case (SrcLoc.leftmost_smallest `on` getSpan) f1 f2 of
|
|
| 951 | + EQ -> (stableNameCmp `on` (getName . famInstAxiom)) f1 f2
|
|
| 952 | + o -> o
|
|
| 953 | + sorted = NE.sortBy (SrcLoc.leftmost_smallest `on` getSpan) (fam_inst NE.:| [conf_inst])
|
|
| 947 | 954 | fi1 = NE.head sorted
|
| 948 | 955 | span = coAxBranchSpan (coAxiomSingleBranch (famInstAxiom fi1))
|
| 949 | 956 | getSpan = getSrcSpan . famInstAxiom
|
| ... | ... | @@ -40,6 +40,7 @@ module GHC.Types.Name.Env ( |
| 40 | 40 | plusDNameEnv_C,
|
| 41 | 41 | foldDNameEnv,
|
| 42 | 42 | nonDetStrictFoldDNameEnv,
|
| 43 | + nonDetFoldDNameEnv,
|
|
| 43 | 44 | -- ** Dependency analysis
|
| 44 | 45 | depAnal
|
| 45 | 46 | ) where
|
| ... | ... | @@ -224,3 +225,7 @@ plusDNameEnv_C = plusUDFM_C |
| 224 | 225 | nonDetStrictFoldDNameEnv :: (a -> b -> b) -> b -> DNameEnv a -> b
|
| 225 | 226 | nonDetStrictFoldDNameEnv = nonDetStrictFoldUDFM
|
| 226 | 227 | |
| 228 | +-- | Lazy nondeterministic fold; can stream and short-circuit.
|
|
| 229 | +nonDetFoldDNameEnv :: (a -> b -> b) -> b -> DNameEnv a -> b
|
|
| 230 | +nonDetFoldDNameEnv = nonDetFoldUDFM
|
|
| 231 | + |
| ... | ... | @@ -66,6 +66,7 @@ module GHC.Types.Unique.DFM ( |
| 66 | 66 | udfmToList,
|
| 67 | 67 | udfmToUfm,
|
| 68 | 68 | nonDetStrictFoldUDFM,
|
| 69 | + nonDetFoldUDFM,
|
|
| 69 | 70 | unsafeCastUDFMKey,
|
| 70 | 71 | alwaysUnsafeUfmToUdfm,
|
| 71 | 72 | ) where
|
| ... | ... | @@ -378,6 +379,15 @@ nonDetStrictFoldUDFM k z (UDFM m _i) = foldl' k' z m |
| 378 | 379 | where
|
| 379 | 380 | k' acc (TaggedVal v _) = k v acc
|
| 380 | 381 | |
| 382 | +-- | Performs a nondeterministic lazy right fold over the UniqDFM.
|
|
| 383 | +-- It's O(n), and lazy in the accumulator, so unlike 'foldUDFM' it can
|
|
| 384 | +-- stream and short-circuit; see Note [Cost of deterministic iteration].
|
|
| 385 | +-- If you use this please provide a justification why it doesn't introduce
|
|
| 386 | +-- nondeterminism.
|
|
| 387 | +nonDetFoldUDFM :: (elt -> a -> a) -> a -> UniqDFM key elt -> a
|
|
| 388 | +{-# INLINE nonDetFoldUDFM #-}
|
|
| 389 | +nonDetFoldUDFM k z (UDFM m _i) = M.foldr (k . taggedFst) z m
|
|
| 390 | + |
|
| 381 | 391 | {- Note [Cost of deterministic iteration]
|
| 382 | 392 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 383 | 393 | Deterministic iteration -- foldUDFM, eltsUDFM, udfmToList, and everything
|
| ... | ... | @@ -392,7 +402,8 @@ whether the result was non-empty. |
| 392 | 402 | |
| 393 | 403 | So: to test for emptiness, use isNullUDFM rather than null on eltsUDFM;
|
| 394 | 404 | for order-oblivious queries, prefer short-circuiting anyUDFM/allUDFM; and
|
| 395 | -if you don't need the deterministic order at all, use nonDetStrictFoldUDFM.
|
|
| 405 | +if you don't need the deterministic order at all, use nonDetStrictFoldUDFM
|
|
| 406 | +(or nonDetFoldUDFM when the fold should stream or short-circuit).
|
|
| 396 | 407 | -}
|
| 397 | 408 | |
| 398 | 409 | -- | Deterministic, in order of insertion.
|