Simon Jakobi pushed to branch wip/sjakobi/T27459 at Glasgow Haskell Compiler / GHC

Commits:

5 changed files:

Changes:

  • compiler/GHC/Core/InstEnv.hs
    ... ... @@ -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
    

  • compiler/GHC/Core/RoughMap.hs
    ... ... @@ -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,
    

  • testsuite/tests/determinism/determ026/A.hs
    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 [])

  • testsuite/tests/determinism/determ026/Makefile
    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

  • testsuite/tests/determinism/determ026/all.T
    1
    +test('determ026',
    
    2
    +     [extra_files(['A.hs']), req_th],
    
    3
    +     makefile_test, ['determ026'])