diff options
author | Simon Peyton Jones <simonpj@microsoft.com> | 2018-12-12 17:22:07 +0000 |
---|---|---|
committer | Simon Peyton Jones <simonpj@microsoft.com> | 2018-12-12 17:38:25 +0000 |
commit | d77501cd5b9060e38acd50e11e0c5aae89d75b65 (patch) | |
tree | a6cba21b5adb5c04b476c92bf33436bd1a880981 /compiler/basicTypes | |
parent | ded4a1db4d61b1bc8b5fd73e8eb87cf572efda35 (diff) | |
download | haskell-d77501cd5b9060e38acd50e11e0c5aae89d75b65.tar.gz |
Improvements to demand analysis
This patch collects a few improvements triggered by Trac #15696,
and fixing Trac #16029
* Stop making toCleanDmd behave specially for unlifted types.
This special case was the cause of stupid behaviour in Trac
#16029. And to my joy I discovered the let/app invariant
rendered it unnecessary. (Maybe the special case pre-dated
the let/app invariant.)
Result: less special-case handling in the compiler, and
better perf for the compiled code.
* In WwLib.mkWWstr_one, treat seqDmd like U(AAA). It was not
being so treated before, which again led to stupid code.
* Update and improve Notes
There are .stderr test wibbles because we get slightly different
strictness signatures for an argumment of unlifted type:
<L,U> rather than <S,U> for Int#
<S,U> rather than <S(S),U(U)> for Int
Diffstat (limited to 'compiler/basicTypes')
-rw-r--r-- | compiler/basicTypes/Demand.hs | 124 |
1 files changed, 25 insertions, 99 deletions
diff --git a/compiler/basicTypes/Demand.hs b/compiler/basicTypes/Demand.hs index 88845426a0..ff71027eb6 100644 --- a/compiler/basicTypes/Demand.hs +++ b/compiler/basicTypes/Demand.hs @@ -74,7 +74,7 @@ import BasicTypes import Binary import Maybes ( orElse ) -import Type ( Type, isUnliftedType ) +import Type ( Type ) import TyCon ( isNewTyCon, isClassTyCon ) import DataCon ( splitDataProductType_maybe ) @@ -393,10 +393,15 @@ data UseDmd -- (in that case, use UHead) | UHead -- ^ May be used but its sub-components are - -- definitely *not* used. Roughly U(AAA) - -- e.g. the usage of @x@ in @x `seq` e@ - -- A polymorphic demand: used for values of all types, - -- including a type variable + -- definitely *not* used. For product types, UHead + -- is equivalent to U(AAA); see mkUProd. + -- + -- UHead is needed only to express the demand + -- of 'seq' and 'case' which are polymorphic; + -- i.e. the scrutinised value is of type 'a' + -- rather than a product type. That's why we + -- can't use UProd [A,A,A] + -- -- Since (UCall _ Abs) is ill-typed, UHead doesn't -- make sense for lambdas @@ -1100,81 +1105,6 @@ different: unused, so we can use absDmd there. * Further arguments *can* be used, of course. Hence topDmd is used. -Note [Worthy functions for Worker-Wrapper split] -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -For non-bottoming functions a worker-wrapper transformation takes into -account several possibilities to decide if the function is worthy for -splitting: - -1. The result is of product type and the function is strict in some -(or even all) of its arguments. The check that the argument is used is -more of sanity nature, since strictness implies usage. Example: - -f :: (Int, Int) -> Int -f p = (case p of (a,b) -> a) + 1 - -should be splitted to - -f :: (Int, Int) -> Int -f p = case p of (a,b) -> $wf a - -$wf :: Int -> Int -$wf a = a + 1 - -2. Sometimes it also makes sense to perform a WW split if the -strictness analysis cannot say for sure if the function is strict in -components of its argument. Then we reason according to the inferred -usage information: if the function uses its product argument's -components, the WW split can be beneficial. Example: - -g :: Bool -> (Int, Int) -> Int -g c p = case p of (a,b) -> - if c then a else b - -The function g is strict in is argument p and lazy in its -components. However, both components are used in the RHS. The idea is -since some of the components (both in this case) are used in the -right-hand side, the product must presumable be taken apart. - -Therefore, the WW transform splits the function g to - -g :: Bool -> (Int, Int) -> Int -g c p = case p of (a,b) -> $wg c a b - -$wg :: Bool -> Int -> Int -> Int -$wg c a b = if c then a else b - -3. If an argument is absent, it would be silly to pass it to a -function, hence the worker with reduced arity is generated. - - -Note [Worker-wrapper for bottoming functions] -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -We used not to split if the result is bottom. -[Justification: there's no efficiency to be gained.] - -But it's sometimes bad not to make a wrapper. Consider - fw = \x# -> let x = I# x# in case e of - p1 -> error_fn x - p2 -> error_fn x - p3 -> the real stuff -The re-boxing code won't go away unless error_fn gets a wrapper too. -[We don't do reboxing now, but in general it's better to pass an -unboxed thing to f, and have it reboxed in the error cases....] - -However we *don't* want to do this when the argument is not actually -taken apart in the function at all. Otherwise we risk decomposing a -massive tuple which is barely used. Example: - - f :: ((Int,Int) -> String) -> (Int,Int) -> a - f g pr = error (g pr) - - main = print (f fst (1, error "no")) - -Here, f does not take 'pr' apart, and it's stupid to do so. -Imagine that it had millions of fields. This actually happened -in GHC itself where the tuple was DynFlags - ************************************************************************ * * @@ -1406,25 +1336,20 @@ type DmdShell -- Describes the "outer shell" -- of a Demand = JointDmd (Str ()) (Use ()) -toCleanDmd :: Demand -> Type -> (DmdShell, CleanDemand) +toCleanDmd :: Demand -> (DmdShell, CleanDemand) -- Splits a Demand into its "shell" and the inner "clean demand" -toCleanDmd (JD { sd = s, ud = u }) expr_ty +toCleanDmd (JD { sd = s, ud = u }) = (JD { sd = ss, ud = us }, JD { sd = s', ud = u' }) -- See Note [Analyzing with lazy demand and lambdas] + -- See Note [Analysing with absent demand] where (ss, s') = case s of - Str x s' -> (Str x (), s') - Lazy | is_unlifted -> (Str VanStr (), HeadStr) - | otherwise -> (Lazy, HeadStr) + Str x s' -> (Str x (), s') + Lazy -> (Lazy, HeadStr) (us, u') = case u of - Use c u' -> (Use c (), u') - Abs | is_unlifted -> (Use One (), Used) - | otherwise -> (Abs, Used) - - is_unlifted = isUnliftedType expr_ty - -- See Note [Analysing with absent demand] - + Use c u' -> (Use c (), u') + Abs -> (Abs, Used) -- This is used in dmdAnalStar when post-processing -- a function's argument demand. So we only care about what @@ -1646,9 +1571,9 @@ There are several wrinkles: But we can post-process the results to ignore all the usage demands coming back. This is done by postProcessDmdType. -* But in the case of an *unlifted type* we must be extra careful, - because unlifted values are evaluated even if they are not used. - Example (see Trac #9254): +* In a previous incarnation of GHC we needed to be extra careful in the + case of an *unlifted type*, because unlifted values are evaluated + even if they are not used. Example (see Trac #9254): f :: (() -> (# Int#, () #)) -> () -- Strictness signature is -- <C(S(LS)), 1*C1(U(A,1*U()))> @@ -1668,10 +1593,11 @@ There are several wrinkles: usage of 'y', else 'g' will say 'y' is absent, and will w/w so that 'y' is bound to an aBSENT_ERROR thunk. - An alternative would be to replace the 'case y of ...' with (say) 0#, - but I have not tried that. It's not a common situation, but it is - not theoretical: unsafePerformIO's implementation is very very like - 'f' above. + However, the argument of toCleanDmd always satisfies the let/app + invariant; so if it is unlifted it is also okForSpeculation, and so + can be evaluated in a short finite time -- and that rules out nasty + cases like the one above. (I'm not quite sure why this was a + problem in an earlier version of GHC, but it isn't now.) ************************************************************************ |