[Git][ghc/ghc][wip/T22404] Comments
Simon Peyton Jones (@simonpj)
gitlab at gitlab.haskell.org
Thu Feb 9 22:54:15 UTC 2023
Simon Peyton Jones pushed to branch wip/T22404 at Glasgow Haskell Compiler / GHC
Commits:
626ea35b by Simon Peyton Jones at 2023-02-09T22:55:06+00:00
Comments
- - - - -
1 changed file:
- compiler/GHC/Core/Opt/OccurAnal.hs
Changes:
=====================================
compiler/GHC/Core/Opt/OccurAnal.hs
=====================================
@@ -595,10 +595,119 @@ This showed up when compiling Control.Concurrent.Chan.getChanContents.
Hence the transitive rule_fv_env stuff described in
Note [Rules and loop breakers].
-------------------------------------------------------------
Note [Occurrence analysis for join points]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-ToDo: addresses #22404.
+Consider these two somewhat artificial programs (#22404)
+
+ Program (P1) Program (P2)
+ ------------------------------ -------------------------------------
+ let v = <small thunk> in let v = <small thunk> in
+ join j = case v of (a,b) -> a
+ in case x of in case x of
+ A -> case v of (a,b) -> a A -> j
+ B -> case v of (a,b) -> a B -> j
+ C -> case v of (a,b) -> b C -> case v of (a,b) -> b
+ D -> [] D -> []
+
+In (P1), `v` gets allocated, as a thunk, every time this code is executed. But
+notice that `v` occurs at most once in any case branch; the occurrence analyser
+spots this and returns a OneOcc{ occ_n_br = 3 } for `v`. Then the code in
+GHC.Core.Opt.Simplify.Utils.postInlineUnconditionally inlines `v` at its three
+use sites, and discards the let-binding. That way, we avoid allocating `v` in
+the A,B,C branches (though we still compute it of course), and branch D
+doesn't involve <small thunk> at all. This sometimes makes a Really Big
+Difference.
+
+In (P2) we have shared the common RHS of A, B, in a join point `j`. We would
+like to inline `v1 in just the same way as in (P1). But if we "andUDs"
+the usage from j's RHS and its body, we'll get ManyOccs for `v`. Important
+optimisation lost!
+
+The occurrence analyser therefore has clever code that behaves just as
+if you inlined `j` at all its call sites. Here is a tricky variant (P3)
+to keep in mind:
+ join j = case v of (a,b) -> a
+ in case f v of
+ A -> j
+ B -> j
+ C -> []
+If you mentally inline `j` you'll see that `v` is used twice on the path
+through A, so it should have ManyOcc. Bear this caes in mind!
+
+* We treat /non-recursive/ join points specially. Recursive join points
+ are treated like any other letrec, as before. Moreover, we only
+ deal with /pre-existing/ non-recursive join points, not the ones
+ that we discover for the first time in this sweep of the
+ occurrence analyser.
+
+* In occ_env, the new (occ_join_points :: IdEnv UsageDetails) maps
+ each in-scope non-recursive join point, such as `j` above, to
+ a "zeroed form" of its RHS's usage details. The "zeroed form"
+ * deletes ManyOccs
+ * maps a OneOcc to OneOcc{ occ_n_br = 0 }
+ In our example, occ_join_points will be extended with
+ [j :-> [v :-> OneOcc{occ_n_br=0}]]
+ See addJoinPoint.
+
+* At an occurence of a join point, we do everything as normal, but add in the
+ UsageDetails from the occ_join_points. See mkOneOcc.
+
+* At the NonRec binding of the join point, we use `orUDs`, not `andUDs` to
+ combine the usage from the RHS with the usage from the body.
+
+Here are the consequences
+
+* Because of the perhaps-surprising OneOcc{occ_n_br=0} idea of the zeroed
+ form, the occ_n_br field of a OneOcc binder still counts the number of
+ /actual lexical occurrences/ of the variable. In Program P2, for example,
+ `v` will end up with OneOcc{occ_n_br=2}, not occ_n_br=3. There are two
+ lexical occurrences of `v`!
+
+* In the tricky (P3) we'll get an `andUDs` of
+ * OneOcc{occ_n_br=0} from the occurrences of `j`)
+ * OneOcc{occ_n_br=1} from the (f v)
+ These are `andUDs` together, and hence `addOccInfo`, and hence
+ `v` gets ManyOccs, just as it should. Clever!
+
+There are a couple of tricky wrinkles
+
+(W1) Consider this example which shadows `j`:
+ join j = rhs in
+ in case x of { K j -> ..j..; ... }
+ Clearly when we come to the pattern `K j` we must drop the `j`
+ entry in occ_join_points.
+
+ This is done by `drop_shadowed_joins` in `addInScope`.
+
+(W2) Consider this example which shadows `v`:
+ join j = ...v...
+ in case x of { K v -> ..j..; ... }
+
+ We can't make j's occurrences in the K alternative give rise to an
+ occurrence of `v` (via occ_join_points), because it'll just be deleted by
+ the `K v` pattern. Yikes. This is rare because shadowing is rare, but
+ it definitely can happen. Solution: when bringing `v` into scope at
+ the `K v` pattern, chuck out of occ_join_points any elements whose
+ UsageDetails mentions `v`. Instead, just `andUDs` all that usage in
+ right here.
+
+ This is done by `add_bad_joins`` in `addInScope`; we use
+ `partitionVarEnv` to identify the `bad_joins` (the ones whose
+ UsageDetails mention the newly bound variables); then for any of /those/
+ that are actually mentioned in the body, use `andUDs` to add their
+ UsageDetails to the returned UsageDetails. Tricky!
+
+(W3) Consider this example, which shadows `j`, but this time in an argument
+ join j = rhs
+ in f (case x of { K j -> ...; ... })
+ We can zap the occ_join_points when looking at the argument, because
+ `j` can't posibly occur -- it's a join point! And the smaller
+ occ_join_points is, the better. Smaller to look up in, less faffing
+ in (W2).
+
+ This is done in setRhsCtxt.
+
+Wrinkles (W1) and (W2) are very similar to Note [Binder swap] (BS3).
Note [Finding join points]
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -2701,7 +2810,8 @@ setRhsCtxt :: OccEncl -> OccEnv -> OccEnv
setRhsCtxt ctxt !env
= env { occ_encl = ctxt
, occ_one_shots = []
- , occ_join_points = emptyVarEnv -- See XXXNoteXXX [OccAnal for join points]
+ , occ_join_points = emptyVarEnv
+ -- See (W3) of Note [Occurrence analysis for join points]
}
addOneShots :: OccEnv -> [OneShots] -> (OccEnv, [OneShots])
@@ -2733,7 +2843,7 @@ addInScope env@(OccEnv { occ_join_points = join_points })
= env { occ_bs_env = swap_env `delVarEnvList` bndrs }
drop_shadowed_joins :: OccEnv -> OccEnv
- -- See Note [Occurrence analysis for join points]
+ -- See Note [Occurrence analysis for join points] wrinkle (W1)
drop_shadowed_joins env = env { occ_join_points = good_joins `delVarEnvList` bndrs}
fix_up_uds :: WithUsageDetails a -> WithUsageDetails a
@@ -2744,7 +2854,17 @@ addInScope env@(OccEnv { occ_join_points = join_points })
where
trimmed_uds = uds `delDetails` bndrs
with_co_var_occs = trimmed_uds `addManyOccs` coVarOccs bndrs
- with_joins = nonDetStrictFoldUFM andUDs with_co_var_occs bad_joins
+ with_joins = add_bad_joins with_co_var_occs
+
+ add_bad_joins :: UsageDetails -> UsageDetails
+ add_bad_joins uds = nonDetStrictFoldUFM_Directly add_bad_join uds bad_joins
+
+ add_bad_join :: Unique -> UsageDetails -- Bad join and its usage details
+ -> UsageDetails -> UsageDetails
+ -- See Note [Occurrence analysis for join points] wrinkle (W2)
+ add_bad_join uniq bad_join_uds uds
+ | uniq `elemUFM_Directly` ud_env uds = uds `andUDs` bad_join_uds
+ | otherwise = uds
(bad_joins, good_joins) = partitionVarEnv bad_join_rhs join_points
@@ -2752,11 +2872,15 @@ addInScope env@(OccEnv { occ_join_points = join_points })
bad_join_rhs (UD { ud_env = rhs_usage }) = any (`elemVarEnv` rhs_usage) bndrs
addJoinPoint :: OccEnv -> Id -> UsageDetails -> OccEnv
-addJoinPoint env bndr rhs_uds@(UD { ud_env = rhs_occs })
- = env { occ_join_points = extendVarEnv (occ_join_points env) bndr join_occ_uds }
+addJoinPoint env bndr rhs_uds
+ = env { occ_join_points = extendVarEnv (occ_join_points env)
+ bndr (mkZeroedForm rhs_uds) }
+
+mkZeroedForm :: UsageDetails -> UsageDetails
+-- See Note [Occurrence analysis for join points] for "zeroed form"
+mkZeroedForm rhs_uds@(UD { ud_env = rhs_occs })
+ = emptyDetails { ud_env = mapMaybeUFM_Directly do_one rhs_occs }
where
- join_occ_uds = emptyDetails { ud_env = mapMaybeUFM_Directly do_one rhs_occs }
-
do_one :: Unique -> OccInfo -> Maybe OccInfo
do_one key occ = case doZappingByUnique rhs_uds key occ of
ManyOccs {} -> Nothing
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/626ea35bbc51f7ce1a63f6b8fa6aff49539307b4
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/626ea35bbc51f7ce1a63f6b8fa6aff49539307b4
You're receiving this email because of your account on gitlab.haskell.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.haskell.org/pipermail/ghc-commits/attachments/20230209/2f8eaedf/attachment-0001.html>
More information about the ghc-commits
mailing list