Florian Ragwitz pushed to branch wip/rafl/viewLExprEq at Glasgow Haskell Compiler / GHC
Commits:
-
fa10f2dd
by Florian Ragwitz at 2026-11-04T17:25:52-08:00
5 changed files:
- + changelog.d/viewpat-commoning
- compiler/GHC/HsToCore/Match.hs
- docs/users_guide/exts/view_patterns.rst
- testsuite/tests/deSugar/should_run/all.T
- + testsuite/tests/deSugar/should_run/viewpat-commoning.hs
Changes:
| 1 | +section: compiler
|
|
| 2 | +issues: #28084
|
|
| 3 | +mrs: !16937
|
|
| 4 | +synopsis: ViewPatterns commoning improvements
|
|
| 5 | +description:
|
|
| 6 | + |
|
| 7 | + When using :extension:`ViewPatterns`, GHC attempts to group occurrences of the
|
|
| 8 | + same view expression in multiple branches of a function definition or case
|
|
| 9 | + expression, e.g. ::
|
|
| 10 | + |
|
| 11 | + f (expensive -> False) = ...
|
|
| 12 | + f (expensive -> True) = ...
|
|
| 13 | + |
|
| 14 | + This avoids applying the ``expensive`` expression more than once. This
|
|
| 15 | + optimisation has been broken for most view expressions, including the above
|
|
| 16 | + example, since GHC 8.0.1, and has been restored in this release.
|
|
| 17 | + |
|
| 18 | + Note that :ghc-flag:`common subexpression elimination <-fcse>`, which is
|
|
| 19 | + enabled under :ghc-flag:`-O`, could often recover this optimisation, limiting
|
|
| 20 | + the impact of the bug on optimised programs.
|
|
| 21 | + |
|
| 22 | + The limited notion of when two view pattern expressions are "the same" has
|
|
| 23 | + also been extended to support type applications and expressions with a type
|
|
| 24 | + signature. See the "Efficiency" section of :extension:`ViewPatterns` for more
|
|
| 25 | + details. |
| ... | ... | @@ -1158,10 +1158,7 @@ viewLExprEq (e1,_) (e2,_) = lexp e1 e2 |
| 1158 | 1158 | wrap h h' && exp e e'
|
| 1159 | 1159 | exp (XExpr (ExpandedThingTc (HSE _ x))) (XExpr (ExpandedThingTc (HSE _ x')))
|
| 1160 | 1160 | = lexp x x'
|
| 1161 | - exp (HsVar _ i) (HsVar _ i') = i == i'
|
|
| 1162 | - exp (HsIPVar _ i) (HsIPVar _ i') =
|
|
| 1163 | - -- the instance for IPName derives using the id, so follow the HsVar case
|
|
| 1164 | - i == i'
|
|
| 1161 | + exp (HsVar _ i) (HsVar _ i') = i `eqLocated` i'
|
|
| 1165 | 1162 | exp (XExpr (ConLikeTc c)) (XExpr (ConLikeTc c')) = c == c'
|
| 1166 | 1163 | exp (HsOverLit _ l) (HsOverLit _ l') =
|
| 1167 | 1164 | -- Overloaded lits are equal if they have the same type
|
| ... | ... | @@ -1172,25 +1169,19 @@ viewLExprEq (e1,_) (e2,_) = lexp e1 e2 |
| 1172 | 1169 | -- (presumably to improve sharing)
|
| 1173 | 1170 | eqType (overLitType l) (overLitType l') && l == l'
|
| 1174 | 1171 | exp (HsApp _ e1 e2) (HsApp _ e1' e2') = lexp e1 e1' && lexp e2 e2'
|
| 1175 | - -- the fixities have been straightened out by now, so it's safe
|
|
| 1176 | - -- to ignore them?
|
|
| 1177 | - exp (OpApp _ l g ri) (OpApp _ l' o' ri') =
|
|
| 1178 | - lexp l l' && lexp g o' && lexp ri ri'
|
|
| 1179 | 1172 | exp (NegApp _ e n) (NegApp _ e' n') = lexp e e' && syn_exp n n'
|
| 1180 | - exp (SectionL _ e1 e2) (SectionL _ e1' e2') =
|
|
| 1181 | - lexp e1 e1' && lexp e2 e2'
|
|
| 1182 | - exp (SectionR _ e1 e2) (SectionR _ e1' e2') =
|
|
| 1183 | - lexp e1 e1' && lexp e2 e2'
|
|
| 1184 | 1173 | exp (ExplicitTuple _ es1 _) (ExplicitTuple _ es2 _) =
|
| 1185 | 1174 | eq_list tup_arg es1 es2
|
| 1186 | 1175 | exp (ExplicitSum _ _ _ e) (ExplicitSum _ _ _ e') = lexp e e'
|
| 1187 | 1176 | exp (HsIf _ e e1 e2) (HsIf _ e' e1' e2') =
|
| 1188 | 1177 | lexp e e' && lexp e1 e1' && lexp e2 e2'
|
| 1178 | + exp (HsAppType t e _) (HsAppType t' e' _) = t `eqType` t' && lexp e e'
|
|
| 1179 | + exp (ExprWithTySig _ (L _ e) _) e' = exp e e'
|
|
| 1180 | + exp e (ExprWithTySig _ (L _ e') _) = exp e e'
|
|
| 1189 | 1181 | |
| 1190 | 1182 | -- Enhancement: could implement equality for more expressions
|
| 1191 | 1183 | -- if it seems useful
|
| 1192 | - -- But no need for HsLit, ExplicitList, ExplicitTuple,
|
|
| 1193 | - -- because they cannot be functions
|
|
| 1184 | + -- But no need for HsLit, ExplicitList because they cannot be functions
|
|
| 1194 | 1185 | exp _ _ = False
|
| 1195 | 1186 | |
| 1196 | 1187 | ---------
|
| ... | ... | @@ -133,10 +133,10 @@ follows: |
| 133 | 133 | |
| 134 | 134 | The current notion of when two view pattern expressions are "the
|
| 135 | 135 | same" is very restricted: it is not even full syntactic equality.
|
| 136 | - However, it does include variables, literals, applications, and
|
|
| 137 | - tuples; e.g., two instances of ``view ("hi", "there")`` will be
|
|
| 138 | - collected. However, the current implementation does not compare up to
|
|
| 139 | - alpha-equivalence, so two instances of ``(x, view x -> y)`` will not
|
|
| 140 | - be coalesced.
|
|
| 136 | + However, it does include variables, literals, applications, type
|
|
| 137 | + applications, expressions with a type signature, and tuples; e.g.,
|
|
| 138 | + two instances of ``view ("hi", "there")`` will be collected. However,
|
|
| 139 | + the current implementation does not compare up to alpha-equivalence,
|
|
| 140 | + so two instances of ``(x, view x -> y)`` will not be coalesced.
|
|
| 141 | 141 | |
| 142 | 142 |
| ... | ... | @@ -78,3 +78,5 @@ test('T25065b', normal, compile_and_run, ['']) |
| 78 | 78 | test('Or5', exit_code(1), compile_and_run, [''])
|
| 79 | 79 | |
| 80 | 80 | test('LazyFieldAnnotationsSemantics', normal, compile_and_run, [''])
|
| 81 | + |
|
| 82 | +test('viewpat-commoning', normal, compile_and_run, ['']) |
| 1 | +-- regression test for viewLExprEq in GHC.HsToCore.Match, which wants to group
|
|
| 2 | +-- together certain view patterns if they're "equal" in order to avoid multiple
|
|
| 3 | +-- evaluations of the same expression.
|
|
| 4 | + |
|
| 5 | +-- CSE might perform the same optimisation later, so we disable optimisations
|
|
| 6 | +{-# OPTIONS_GHC -Wall -O0 #-}
|
|
| 7 | +{-# LANGUAGE ViewPatterns #-}
|
|
| 8 | + |
|
| 9 | +module Main where
|
|
| 10 | + |
|
| 11 | +import Control.Exception
|
|
| 12 | +import Control.Monad
|
|
| 13 | +import Data.IORef
|
|
| 14 | +import System.IO.Unsafe
|
|
| 15 | +import Data.Functor
|
|
| 16 | + |
|
| 17 | +{-# NOINLINE calls #-}
|
|
| 18 | +calls :: IORef Int
|
|
| 19 | +calls = unsafePerformIO (newIORef 0)
|
|
| 20 | + |
|
| 21 | +{-# NOINLINE count #-}
|
|
| 22 | +count :: a -> a
|
|
| 23 | +count x = unsafePerformIO $ modifyIORef' calls (+1) $> x
|
|
| 24 | + |
|
| 25 | +data X = A | B
|
|
| 26 | + |
|
| 27 | +{-# NOINLINE view #-}
|
|
| 28 | +view :: a -> X
|
|
| 29 | +view x = count x `seq` B
|
|
| 30 | + |
|
| 31 | +{-# NOINLINE viewX #-}
|
|
| 32 | +viewX :: a -> Int -> X
|
|
| 33 | +viewX = const view
|
|
| 34 | + |
|
| 35 | +var, par, app, neg, neg', cond, tup :: Int -> Int
|
|
| 36 | + |
|
| 37 | +var (view -> A) = 1
|
|
| 38 | +var (view -> B) = 2
|
|
| 39 | + |
|
| 40 | +par (((view)) -> A) = 1
|
|
| 41 | +par (view -> B) = 2
|
|
| 42 | + |
|
| 43 | +app (viewX True -> A) = 1
|
|
| 44 | +app (viewX True -> B) = 2
|
|
| 45 | + |
|
| 46 | +neg (viewX @Int (-1) -> A) = 1
|
|
| 47 | +neg (viewX @Int (-1) -> B) = 2
|
|
| 48 | + |
|
| 49 | +type Int' = Int
|
|
| 50 | +neg' (viewX (-1 :: Int) -> A) = 1
|
|
| 51 | +neg' (viewX (-1 :: Int') -> B) = 2
|
|
| 52 | + |
|
| 53 | +cond (if True then view else undefined -> A) = 1
|
|
| 54 | +cond (if True then view else undefined -> B) = 2
|
|
| 55 | + |
|
| 56 | +tup (viewX (True, False) -> A) = 1
|
|
| 57 | +tup (viewX (True, False) -> B) = 2
|
|
| 58 | + |
|
| 59 | +check :: String -> Int -> IO ()
|
|
| 60 | +check name action = do
|
|
| 61 | + writeIORef calls 0
|
|
| 62 | + _ <- evaluate action
|
|
| 63 | + got <- readIORef calls
|
|
| 64 | + unless (got == 1) $ putStrLn $ name ++ ": got " ++ show got ++ ", expected 1"
|
|
| 65 | + |
|
| 66 | +main :: IO ()
|
|
| 67 | +main = do
|
|
| 68 | + check "var" (var 0)
|
|
| 69 | + check "par" (par 0)
|
|
| 70 | + check "app" (app 0)
|
|
| 71 | + check "neg" (neg 0)
|
|
| 72 | + check "neg'" (neg' 0)
|
|
| 73 | + check "cond" (cond 0)
|
|
| 74 | + check "tup" (tup 0) |