Simon Peyton Jones pushed to branch wip/spj-try-opt-coercion at Glasgow Haskell Compiler / GHC
Commits:
-
f4eea9ba
by Simon Peyton Jones at 2026-01-07T11:40:17+00:00
-
68e18fc9
by Simon Peyton Jones at 2026-01-07T11:41:27+00:00
9 changed files:
- compiler/GHC/Core/Map/Expr.hs
- compiler/GHC/Core/Map/Type.hs
- compiler/GHC/Core/Opt/CSE.hs
- compiler/GHC/Core/Opt/Simplify/Env.hs
- compiler/GHC/Core/TyCo/Rep.hs
- compiler/GHC/Data/TrieMap.hs
- compiler/GHC/Iface/Make.hs
- compiler/GHC/IfaceToCore.hs
- compiler/GHC/Types/Id.hs
Changes:
| ... | ... | @@ -26,6 +26,7 @@ import GHC.Prelude |
| 26 | 26 | import GHC.Data.TrieMap
|
| 27 | 27 | import GHC.Core.Map.Type
|
| 28 | 28 | import GHC.Core
|
| 29 | +import GHC.Core.Coercion( coercionRKind )
|
|
| 29 | 30 | import GHC.Core.Type
|
| 30 | 31 | import GHC.Types.Tickish
|
| 31 | 32 | import GHC.Types.Var
|
| ... | ... | @@ -150,13 +151,16 @@ instance Eq (DeBruijn CoreExpr) where |
| 150 | 151 | |
| 151 | 152 | eqDeBruijnExpr :: DeBruijn CoreExpr -> DeBruijn CoreExpr -> Bool
|
| 152 | 153 | eqDeBruijnExpr (D env1 e1) (D env2 e2) = go e1 e2 where
|
| 153 | - go (Var v1) (Var v2) = eqDeBruijnVar (D env1 v1) (D env2 v2)
|
|
| 154 | - go (Lit lit1) (Lit lit2) = lit1 == lit2
|
|
| 155 | - go (Type t1) (Type t2) = eqDeBruijnType (D env1 t1) (D env2 t2)
|
|
| 156 | - -- See Note [Alpha-equality for Coercion arguments]
|
|
| 157 | - go (Coercion {}) (Coercion {}) = True
|
|
| 158 | - go (Cast e1 co1) (Cast e2 co2) = D env1 co1 == D env2 co2 && go e1 e2
|
|
| 159 | - go (App f1 a1) (App f2 a2) = go f1 f2 && go a1 a2
|
|
| 154 | + go (Var v1) (Var v2) = eqDeBruijnVar (D env1 v1) (D env2 v2)
|
|
| 155 | + go (Lit lit1) (Lit lit2) = lit1 == lit2
|
|
| 156 | + go (Type t1) (Type t2) = eqDeBruijnType (D env1 t1) (D env2 t2)
|
|
| 157 | + go (Coercion co1) (Coercion co2) = eqDeBruijnCoercion (D env1 co1) (D env2 co2)
|
|
| 158 | + go (Cast e1 co1) (Cast e2 co2) = go e1 e2 && eqDeBruijnType (D env1 (coercionRKind co1))
|
|
| 159 | + (D env2 (coercionRKind co2))
|
|
| 160 | + go (App f1 a1) (App f2 a2)
|
|
| 161 | + | isCoArg a1, isCoArg a2 = go f1 f2
|
|
| 162 | + | otherwise = go f1 f2 && go a1 a2
|
|
| 163 | + -- isCoArg: see Note [Coercions in expressions]
|
|
| 160 | 164 | go (Tick n1 e1) (Tick n2 e2)
|
| 161 | 165 | = eqDeBruijnTickish (D env1 n1) (D env2 n2)
|
| 162 | 166 | && go e1 e2
|
| ... | ... | @@ -204,12 +208,17 @@ eqDeBruijnTickish (D env1 t1) (D env2 t2) = go t1 t2 where |
| 204 | 208 | eqCoreExpr :: CoreExpr -> CoreExpr -> Bool
|
| 205 | 209 | eqCoreExpr e1 e2 = eqDeBruijnExpr (deBruijnize e1) (deBruijnize e2)
|
| 206 | 210 | |
| 207 | -{- Note [Alpha-equality for Coercion arguments]
|
|
| 208 | -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 209 | -The 'Coercion' constructor only appears in argument positions, and so, if the
|
|
| 210 | -functions are equal, then the arguments must have equal types. Because the
|
|
| 211 | -comparison for coercions (correctly) checks only their types, checking for
|
|
| 212 | -alpha-equality of the coercions is redundant.
|
|
| 211 | +{- Note [Coercions in expressions]
|
|
| 212 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 213 | +When a coercion appears in an expression, we mainly use `eqDeBruijnCoercion`
|
|
| 214 | +(see Note [Equality for coercions] in GHC.Core.Map.Type). But we can optimise
|
|
| 215 | + * Applications: f1 (CO: co1) = f2 (CO: co2)
|
|
| 216 | + If the two functions are equal their argument coercions must have equal
|
|
| 217 | + types, so no need to compare the coercions at all.
|
|
| 218 | + |
|
| 219 | + * Casts: e1 |> co1 = e2 |> co2
|
|
| 220 | + If e1 and e2 are equal, the coercionLKinds of the coercions are equal;
|
|
| 221 | + so we only need to compare the coercionRKinds
|
|
| 213 | 222 | -}
|
| 214 | 223 | |
| 215 | 224 | {- Note [Alpha-equality for let-bindings]
|
| ... | ... | @@ -20,6 +20,7 @@ module GHC.Core.Map.Type ( |
| 20 | 20 | TypeMapG, CoercionMapG,
|
| 21 | 21 | |
| 22 | 22 | DeBruijn(..), deBruijnize, eqDeBruijnType, eqDeBruijnVar,
|
| 23 | + eqDeBruijnCoercion,
|
|
| 23 | 24 | |
| 24 | 25 | BndrMap, xtBndr, lkBndr,
|
| 25 | 26 | VarMap, xtVar, lkVar, lkDFreeVar, xtDFreeVar,
|
| ... | ... | @@ -78,9 +79,14 @@ import Control.Monad ( (>=>) ) |
| 78 | 79 | ************************************************************************
|
| 79 | 80 | -}
|
| 80 | 81 | |
| 81 | --- We should really never care about the contents of a coercion. Instead,
|
|
| 82 | --- just look up the coercion's type.
|
|
| 82 | +{- Note [Equality for coercions]
|
|
| 83 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 84 | +A coercion is a proof, and any one proof is as good as any other.
|
|
| 85 | +So to compare coercions we just compare their types.
|
|
| 86 | +-}
|
|
| 87 | + |
|
| 83 | 88 | newtype CoercionMap a = CoercionMap (CoercionMapG a)
|
| 89 | + -- See [Equality for coercions]
|
|
| 84 | 90 | |
| 85 | 91 | -- TODO(22292): derive
|
| 86 | 92 | instance Functor CoercionMap where
|
| ... | ... | @@ -98,6 +104,9 @@ instance TrieMap CoercionMap where |
| 98 | 104 | |
| 99 | 105 | type CoercionMapG = GenMap CoercionMapX
|
| 100 | 106 | newtype CoercionMapX a = CoercionMapX (TypeMapX a)
|
| 107 | +-- CoercionMapX key point: two coercions are considered equal if
|
|
| 108 | +-- their coercionTypes are the same; so we just defer to TypeMap
|
|
| 109 | + |
|
| 101 | 110 | |
| 102 | 111 | -- TODO(22292): derive
|
| 103 | 112 | instance Functor CoercionMapX where
|
| ... | ... | @@ -113,11 +122,6 @@ instance TrieMap CoercionMapX where |
| 113 | 122 | filterTM f (CoercionMapX core_tm) = CoercionMapX (filterTM f core_tm)
|
| 114 | 123 | mapMaybeTM f (CoercionMapX core_tm) = CoercionMapX (mapMaybeTM f core_tm)
|
| 115 | 124 | |
| 116 | -instance Eq (DeBruijn Coercion) where
|
|
| 117 | - D env1 co1 == D env2 co2
|
|
| 118 | - = D env1 (coercionType co1) ==
|
|
| 119 | - D env2 (coercionType co2)
|
|
| 120 | - |
|
| 121 | 125 | lkC :: DeBruijn Coercion -> CoercionMapX a -> Maybe a
|
| 122 | 126 | lkC (D env co) (CoercionMapX core_tm) = lkT (D env $ coercionType co)
|
| 123 | 127 | core_tm
|
| ... | ... | @@ -126,6 +130,16 @@ xtC :: DeBruijn Coercion -> XT a -> CoercionMapX a -> CoercionMapX a |
| 126 | 130 | xtC (D env co) f (CoercionMapX m)
|
| 127 | 131 | = CoercionMapX (xtT (D env $ coercionType co) f m)
|
| 128 | 132 | |
| 133 | +-- This equality instance is needed for the equality test in leaf compression;
|
|
| 134 | +-- see GenMap and Note [Compressed TrieMap] in GHC.Data.TrieMap
|
|
| 135 | +instance Eq (DeBruijn Coercion) where
|
|
| 136 | + (==) = eqDeBruijnCoercion
|
|
| 137 | + |
|
| 138 | +eqDeBruijnCoercion :: DeBruijn Coercion -> DeBruijn Coercion -> Bool
|
|
| 139 | +eqDeBruijnCoercion (D env1 co1) (D env2 co2)
|
|
| 140 | + = eqDeBruijnType (D env1 (coercionType co1)) (D env2 (coercionType co2))
|
|
| 141 | + |
|
| 142 | + |
|
| 129 | 143 | {-
|
| 130 | 144 | ************************************************************************
|
| 131 | 145 | * *
|
| ... | ... | @@ -139,9 +153,7 @@ xtC (D env co) f (CoercionMapX m) |
| 139 | 153 | -- but it is strictly internal to this module. If you are including a 'TypeMap'
|
| 140 | 154 | -- inside another 'TrieMap', this is the type you want. Note that this
|
| 141 | 155 | -- lookup does not do a kind-check. Thus, all keys in this map must have
|
| 142 | --- the same kind. Also note that this map respects the distinction between
|
|
| 143 | --- @Type@ and @Constraint@, despite the fact that they are equivalent type
|
|
| 144 | --- synonyms in Core.
|
|
| 156 | +-- the same kind.
|
|
| 145 | 157 | type TypeMapG = GenMap TypeMapX
|
| 146 | 158 | |
| 147 | 159 | -- | @TypeMapX a@ is the base map from @DeBruijn Type@ to @a@, but without the
|
| ... | ... | @@ -191,9 +203,6 @@ instance TrieMap TypeMapX where |
| 191 | 203 | filterTM = filterT
|
| 192 | 204 | mapMaybeTM = mpT
|
| 193 | 205 | |
| 194 | -instance Eq (DeBruijn Type) where
|
|
| 195 | - (==) = eqDeBruijnType
|
|
| 196 | - |
|
| 197 | 206 | -- | An equality relation between two 'Type's (known below as @t1 :: k2@
|
| 198 | 207 | -- and @t2 :: k2@)
|
| 199 | 208 | data TypeEquality = TNEQ -- ^ @t1 /= t2@
|
| ... | ... | @@ -219,7 +228,7 @@ eqDeBruijnType env_t1@(D env1 t1) env_t2@(D env2 t2) = |
| 219 | 228 | |
| 220 | 229 | liftEquality :: Bool -> TypeEquality
|
| 221 | 230 | liftEquality False = TNEQ
|
| 222 | - liftEquality _ = TEQ
|
|
| 231 | + liftEquality True = TEQ
|
|
| 223 | 232 | |
| 224 | 233 | hasCast :: TypeEquality -> TypeEquality
|
| 225 | 234 | hasCast TEQ = TEQX
|
| ... | ... | @@ -272,7 +281,7 @@ eqDeBruijnType env_t1@(D env1 t1) env_t2@(D env2 t2) = |
| 272 | 281 | go (D (extendCME env tv) ty) (D (extendCME env' tv') ty')
|
| 273 | 282 | (CoercionTy {}, CoercionTy {})
|
| 274 | 283 | -> TEQ
|
| 275 | - _ -> TNEQ
|
|
| 284 | + _ -> TNEQ
|
|
| 276 | 285 | |
| 277 | 286 | -- These bangs make 'gos' strict in the CMEnv, which in turn
|
| 278 | 287 | -- keeps the CMEnv unboxed across the go/gos mutual recursion
|
| ... | ... | @@ -282,9 +291,6 @@ eqDeBruijnType env_t1@(D env1 t1) env_t2@(D env2 t2) = |
| 282 | 291 | gos e1 e2 tys1 tys2
|
| 283 | 292 | gos _ _ _ _ = TNEQ
|
| 284 | 293 | |
| 285 | -instance Eq (DeBruijn Var) where
|
|
| 286 | - (==) = eqDeBruijnVar
|
|
| 287 | - |
|
| 288 | 294 | eqDeBruijnVar :: DeBruijn Var -> DeBruijn Var -> Bool
|
| 289 | 295 | eqDeBruijnVar (D env1 v1) (D env2 v2) =
|
| 290 | 296 | case (lookupCME env1 v1, lookupCME env2 v2) of
|
| ... | ... | @@ -292,6 +298,14 @@ eqDeBruijnVar (D env1 v1) (D env2 v2) = |
| 292 | 298 | (Nothing, Nothing) -> v1 == v2
|
| 293 | 299 | _ -> False
|
| 294 | 300 | |
| 301 | +-- This equality instance is needed for the equality test in leaf compression;
|
|
| 302 | +-- see GenMap and Note [Compressed TrieMap] in GHC.Data.TrieMap
|
|
| 303 | +instance Eq (DeBruijn Type) where
|
|
| 304 | + (==) = eqDeBruijnType
|
|
| 305 | + |
|
| 306 | +instance Eq (DeBruijn Var) where
|
|
| 307 | + (==) = eqDeBruijnVar
|
|
| 308 | + |
|
| 295 | 309 | instance {-# OVERLAPPING #-}
|
| 296 | 310 | Outputable a => Outputable (TypeMapG a) where
|
| 297 | 311 | ppr m = text "TypeMap elts" <+> ppr (foldTM (:) m [])
|
| ... | ... | @@ -488,6 +502,7 @@ instance TrieMap LooseTypeMap where |
| 488 | 502 | filterTM f (LooseTypeMap m) = LooseTypeMap (filterTM f m)
|
| 489 | 503 | mapMaybeTM f (LooseTypeMap m) = LooseTypeMap (mapMaybeTM f m)
|
| 490 | 504 | |
| 505 | + |
|
| 491 | 506 | {-
|
| 492 | 507 | ************************************************************************
|
| 493 | 508 | * *
|
| ... | ... | @@ -450,7 +450,7 @@ cse_bind toplevel env_rhs env_body (in_id, in_rhs) out_id |
| 450 | 450 | | otherwise
|
| 451 | 451 | = (env_body', (out_id'', out_rhs))
|
| 452 | 452 | where
|
| 453 | - (env_body', out_id') = extendCSEnvWithBinding env_body in_id out_id out_rhs cse_done
|
|
| 453 | + (env_body', out_id') = extendCSEnvWithBinding env_body in_id out_id out_rhs cse_done
|
|
| 454 | 454 | (cse_done, out_rhs) = try_for_cse env_rhs in_rhs
|
| 455 | 455 | out_id'' | cse_done = zapStableUnfolding $
|
| 456 | 456 | delayInlining toplevel out_id'
|
| ... | ... | @@ -422,11 +422,12 @@ data SimplFloats |
| 422 | 422 | }
|
| 423 | 423 | |
| 424 | 424 | instance Outputable SimplFloats where
|
| 425 | - ppr (SimplFloats { sfLetFloats = lf, sfJoinFloats = jf, sfInScope = is })
|
|
| 425 | + ppr (SimplFloats { sfLetFloats = lf, sfJoinFloats = jf, sfInScope = _is })
|
|
| 426 | 426 | = text "SimplFloats"
|
| 427 | 427 | <+> braces (vcat [ text "lets: " <+> ppr lf
|
| 428 | 428 | , text "joins:" <+> ppr jf
|
| 429 | - , text "in_scope:" <+> ppr is ])
|
|
| 429 | +-- , text "in_scope:" <+> ppr _is
|
|
| 430 | + ])
|
|
| 430 | 431 | |
| 431 | 432 | emptyFloats :: SimplEnv -> SimplFloats
|
| 432 | 433 | emptyFloats env
|
| ... | ... | @@ -2098,8 +2098,9 @@ coercionIsSmall :: Coercion -> Bool |
| 2098 | 2098 | -- This function is called inside `exprIsTrivial` so it needs to be
|
| 2099 | 2099 | -- pretty efficient. It should return False quickly on a big coercion;
|
| 2100 | 2100 | -- it should /not/ traverse the big coercion!
|
| 2101 | +-- The literal constant 40# is pretty arbitrary
|
|
| 2101 | 2102 | coercionIsSmall co
|
| 2102 | - = not (isTrue# ((coercion_is_small co 100#) <# 0#))
|
|
| 2103 | + = not (isTrue# ((coercion_is_small co 40#) <# 0#))
|
|
| 2103 | 2104 | |
| 2104 | 2105 | coercion_is_small :: Coercion -> Int# -> Int#
|
| 2105 | 2106 | coercion_is_small co n = go co n
|
| ... | ... | @@ -359,7 +359,6 @@ mpList f (LM { lm_nil = mnil, lm_cons = mcons }) |
| 359 | 359 | |
| 360 | 360 | Note [Compressed TrieMap]
|
| 361 | 361 | ~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 362 | - |
|
| 363 | 362 | The GenMap constructor augments TrieMaps with leaf compression. This helps
|
| 364 | 363 | solve the performance problem detailed in #9960: suppose we have a handful
|
| 365 | 364 | H of entries in a TrieMap, each with a very large key, size K. If you fold over
|
| ... | ... | @@ -199,7 +199,11 @@ updateDecl decls m_stg_infos m_cmm_infos |
| 199 | 199 | m_cmm_infos
|
| 200 | 200 | tag_sigs = fromMaybe mempty m_stg_infos
|
| 201 | 201 | |
| 202 | - update_decl (IfaceId nm ty details infos)
|
|
| 202 | + update_decl decl@(IfaceId nm ty details infos)
|
|
| 203 | + | IfCoVarId <- details
|
|
| 204 | + = decl -- Coercions can appear at top level in interface files
|
|
| 205 | + -- but we generate no code for them and they have no LFInfo
|
|
| 206 | + |
|
| 203 | 207 | | let not_caffy = elemNameSet nm non_cafs
|
| 204 | 208 | , let mb_lf_info = lookupNameEnv lf_infos nm
|
| 205 | 209 | , let sig = lookupNameEnv tag_sigs nm
|
| ... | ... | @@ -1698,12 +1698,21 @@ tcIfaceExpr (IfaceCase scrut case_bndr alts) = do |
| 1698 | 1698 | return (Case scrut' case_bndr' (coreAltsType alts') alts')
|
| 1699 | 1699 | |
| 1700 | 1700 | tcIfaceExpr (IfaceLet (IfaceNonRec (IfLetBndr fs ty info ji) rhs) body)
|
| 1701 | + | IfaceCo co <- rhs
|
|
| 1702 | + = -- For CoVars we ignore `info` and `ji`
|
|
| 1703 | + do { name <- newIfaceName (mkVarOccFS (ifLclNameFS fs))
|
|
| 1704 | + ; ty' <- tcIfaceType ty
|
|
| 1705 | + ; let covar = mkLocalCoVar name ty'
|
|
| 1706 | + ; co' <- tcIfaceCo co
|
|
| 1707 | + ; body' <- extendIfaceIdEnv [covar] (tcIfaceExpr body)
|
|
| 1708 | + ; return (Let (NonRec covar (Coercion co')) body') }
|
|
| 1709 | + | otherwise
|
|
| 1701 | 1710 | = do { name <- newIfaceName (mkVarOccFS (ifLclNameFS fs))
|
| 1702 | 1711 | ; ty' <- tcIfaceType ty
|
| 1703 | 1712 | ; id_info <- tcIdInfo False {- Don't ignore prags; we are inside one! -}
|
| 1704 | 1713 | NotTopLevel name ty' info
|
| 1705 | 1714 | ; let id = mkLocalIdWithInfo name ManyTy ty' id_info
|
| 1706 | - `asJoinId_maybe` ji
|
|
| 1715 | + `asJoinId_maybe` ji
|
|
| 1707 | 1716 | ; rhs' <- tcIfaceExpr rhs
|
| 1708 | 1717 | ; body' <- extendIfaceIdEnv [id] (tcIfaceExpr body)
|
| 1709 | 1718 | ; return (Let (NonRec id rhs') body') }
|
| ... | ... | @@ -329,8 +329,8 @@ mkLocalIdOrCoVar name w ty |
| 329 | 329 | | isCoVarType ty = mkLocalCoVar name ty
|
| 330 | 330 | | otherwise = mkLocalId name w ty
|
| 331 | 331 | |
| 332 | - -- proper ids only; no covars!
|
|
| 333 | 332 | mkLocalIdWithInfo :: HasDebugCallStack => Name -> Mult -> Type -> IdInfo -> Id
|
| 333 | +-- Used for proper ids only; no covars!
|
|
| 334 | 334 | mkLocalIdWithInfo name w ty info =
|
| 335 | 335 | Var.mkLocalVar VanillaId name w (assert (not (isCoVarType ty)) ty) info
|
| 336 | 336 | -- Note [Free type variables]
|