Simon Jakobi pushed to branch wip/sjakobi/T27459 at Glasgow Haskell Compiler / GHC
Commits:
-
125d9c16
by Simon Jakobi at 2026-08-22T17:03:19+02:00
5 changed files:
- compiler/GHC/Core/InstEnv.hs
- compiler/GHC/Core/RoughMap.hs
- + testsuite/tests/determinism/determ026/A.hs
- + testsuite/tests/determinism/determ026/Makefile
- + testsuite/tests/determinism/determ026/all.T
Changes:
| ... | ... | @@ -53,6 +53,8 @@ import GHC.Types.Name |
| 53 | 53 | import GHC.Types.Name.Set
|
| 54 | 54 | import GHC.Types.Id
|
| 55 | 55 | import GHC.Generics (Generic)
|
| 56 | +import Data.Function ( on )
|
|
| 57 | +import Data.List ( sortBy )
|
|
| 56 | 58 | import Data.List.NonEmpty ( NonEmpty (..), nonEmpty )
|
| 57 | 59 | import qualified Data.List.NonEmpty as NE
|
| 58 | 60 | import Data.Maybe ( isJust )
|
| ... | ... | @@ -1122,6 +1124,9 @@ data PotentialUnifiers |
| 1122 | 1124 | -- printing an error message. It can be expensive to compute all
|
| 1123 | 1125 | -- the unifiers because if you are matching something like C a[sk] then
|
| 1124 | 1126 | -- all instances will unify.
|
| 1127 | + -- CQ-REF[coherent-unifiers-sort]
|
|
| 1128 | + -- D~ "when printing an error message" is incomplete: TH's
|
|
| 1129 | + -- reifyInstances also consumes the full list.
|
|
| 1125 | 1130 | |
| 1126 | 1131 | {- Note [Recording coherence information in `PotentialUnifiers`]
|
| 1127 | 1132 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -1167,11 +1172,24 @@ instance Semigroup PotentialUnifiers where |
| 1167 | 1172 | NoUnifiers c1 <> NoUnifiers c2 = NoUnifiers (c1 `andCanEv` c2)
|
| 1168 | 1173 | NoUnifiers _ <> u = u
|
| 1169 | 1174 | OneOrMoreUnifiers (unifier :| unifiers) <> u
|
| 1170 | - = OneOrMoreUnifiers (unifier :| (unifiers <> getCoherentUnifiers u))
|
|
| 1171 | - |
|
| 1175 | + = OneOrMoreUnifiers (unifier :| (unifiers <> nonDetCoherentUnifiers u))
|
|
| 1176 | + |
|
| 1177 | +-- CQ[coherent-unifiers-sort]
|
|
| 1178 | +-- Q: Why does the exported accessor sort, and why is there a raw
|
|
| 1179 | +-- (non-deterministic, unexported) variant?
|
|
| 1180 | +-- A~ The unifiers are enumerated in non-deterministic order (see
|
|
| 1181 | +-- Note [Matches vs Unifiers] in GHC.Core.RoughMap). Sorting here makes
|
|
| 1182 | +-- the order deterministic for every external consumer (error messages,
|
|
| 1183 | +-- TH's reifyInstances). The internal construction sites (the Semigroup
|
|
| 1184 | +-- instance, add_unifier) must use the raw variant: they build the list
|
|
| 1185 | +-- incrementally, and sorting a partial list would both be wasted work
|
|
| 1186 | +-- and force the lazily-enumerated tail.
|
|
| 1172 | 1187 | getCoherentUnifiers :: PotentialUnifiers -> [ClsInst]
|
| 1173 | -getCoherentUnifiers NoUnifiers{} = []
|
|
| 1174 | -getCoherentUnifiers (OneOrMoreUnifiers cls) = NE.toList cls
|
|
| 1188 | +getCoherentUnifiers = sortBy (stableNameCmp `on` is_dfun_name) . nonDetCoherentUnifiers
|
|
| 1189 | + |
|
| 1190 | +nonDetCoherentUnifiers :: PotentialUnifiers -> [ClsInst]
|
|
| 1191 | +nonDetCoherentUnifiers NoUnifiers{} = []
|
|
| 1192 | +nonDetCoherentUnifiers (OneOrMoreUnifiers cls) = NE.toList cls
|
|
| 1175 | 1193 | |
| 1176 | 1194 | -- | Are there no *coherent* unifiers?
|
| 1177 | 1195 | nullUnifiers :: PotentialUnifiers -> Bool
|
| ... | ... | @@ -1262,7 +1280,7 @@ instEnvMatchesAndUnifiers (InstEnv rm) vis_mods cls tys |
| 1262 | 1280 | -- Note [Coherence and specialisation: overview]
|
| 1263 | 1281 | add_unifier item other_unifiers
|
| 1264 | 1282 | | not (isIncoherent item)
|
| 1265 | - = OneOrMoreUnifiers (item :| getCoherentUnifiers other_unifiers)
|
|
| 1283 | + = OneOrMoreUnifiers (item :| nonDetCoherentUnifiers other_unifiers)
|
|
| 1266 | 1284 | |
| 1267 | 1285 | -- So `item` is incoherent; see Note [Incoherent instances]
|
| 1268 | 1286 | | otherwise
|
| ... | ... | @@ -172,12 +172,19 @@ For the same reason the unifiers are enumerated in non-deterministic order (see |
| 172 | 172 | the RML_WildCard case of lookupRM'): a deterministic fold would have to inspect
|
| 173 | 173 | every entry of rm_known before it could produce the first unifier, defeating
|
| 174 | 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
|
|
| 175 | +GHC.Types.Unique.DFM). The matches, by contrast, are still enumerated
|
|
| 179 | 176 | deterministically.
|
| 180 | 177 | |
| 178 | +CQ[unifier-order-consumers]
|
|
| 179 | +Q: Where does the non-deterministic unifier order become user-visible, and
|
|
| 180 | + who restores determinism?
|
|
| 181 | +A~ Wherever the full list is consumed: class-instance consumers (error
|
|
| 182 | + messages, TH's reifyInstances — see #27459 review finding) all go through
|
|
| 183 | + getCoherentUnifiers in GHC.Core.InstEnv, which sorts; family-instance
|
|
| 184 | + conflicts are sorted where displayed, see reportConflictInstErr in
|
|
| 185 | + GHC.Tc.Instance.Family. Consumers that only test emptiness (the hot
|
|
| 186 | + paths) never observe the order.
|
|
| 187 | + |
|
| 181 | 188 | Note [Simple Matching Semantics]
|
| 182 | 189 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 183 | 190 | Suppose `rm` is a RoughMap representing a set of (key,vals) pairs,
|
| 1 | +{-# LANGUAGE TemplateHaskell #-}
|
|
| 2 | +module A where
|
|
| 3 | + |
|
| 4 | +-- reifyInstances at a bare type variable returns the unifying instances,
|
|
| 5 | +-- whose order must not depend on the order of Uniques (#27459).
|
|
| 6 | +--
|
|
| 7 | +-- The printed heads avoid pprint/show of anything containing a NameU:
|
|
| 8 | +-- those embed uniques and would differ between the two runs regardless
|
|
| 9 | +-- of instance order.
|
|
| 10 | + |
|
| 11 | +import Language.Haskell.TH
|
|
| 12 | + |
|
| 13 | +$(do let headName :: Type -> String
|
|
| 14 | + headName (AppT f _) = headName f
|
|
| 15 | + headName (ConT n) = show n
|
|
| 16 | + headName ListT = "[]"
|
|
| 17 | + headName (TupleT i) = "Tuple" ++ show i
|
|
| 18 | + headName ArrowT = "->"
|
|
| 19 | + headName _ = "<other-type>"
|
|
| 20 | + instHead (InstanceD _ _ (AppT _ arg) _) = headName arg
|
|
| 21 | + instHead _ = "<other-dec>"
|
|
| 22 | + insts <- reifyInstances ''Show [VarT (mkName "a")]
|
|
| 23 | + runIO (mapM_ (putStrLn . instHead) insts)
|
|
| 24 | + return []) |
| 1 | +TOP=../../..
|
|
| 2 | +include $(TOP)/mk/boilerplate.mk
|
|
| 3 | +include $(TOP)/mk/test.mk
|
|
| 4 | + |
|
| 5 | +# Check that the instance order returned by TH's reifyInstances doesn't
|
|
| 6 | +# depend on the order of Uniques (#27459): run a splice that prints the
|
|
| 7 | +# instances with two unique supplies and compare the output.
|
|
| 8 | +determ026:
|
|
| 9 | + $(RM) A.hi A.o
|
|
| 10 | + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -dinitial-unique=0 -dunique-increment=1 A.hs > A.out.normal
|
|
| 11 | + $(RM) A.hi A.o
|
|
| 12 | + '$(TEST_HC)' $(TEST_HC_OPTS) -v0 -dinitial-unique=16777215 -dunique-increment=-1 A.hs > A.out.reversed
|
|
| 13 | + diff A.out.normal A.out.reversed |
| 1 | +test('determ026',
|
|
| 2 | + [extra_files(['A.hs']), req_th],
|
|
| 3 | + makefile_test, ['determ026']) |