Better occurrence analysis for join points
Consider these two somewhat artificial programs
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 -> j A -> case v of (a,b) -> a
B -> j B -> case v of (a,b) -> a
C -> case v of (a,b) -> b C -> case v of (a,b) -> b
D -> [] D -> []
In (P2), 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
for v
.
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.
Now look at (P1). The occurrence analyser does not spot that v
is used at most once in any case branch. It returns ManyOcc
not OneOcc
. Why? Because of that join point. If we have
join j x = ...v...
in ...v...
then we don't know that v
is used at most once. It might be
join j x = ...v....
in case f v of
(a,b) -> ...j...
and now v
really might be used more than once.
Anyway, the point of this ticket is: the join point prevents what would otherwise be a sometimes-highly-benficial optimisation.
Idea
Make the occurrence analyser behave as if we'd inlined j
. That is:
- Analyse
j
's rhs, givinguds :: UsageDetails
- Carry an environment mapping join-point-ids to
UsageDetails
; extend it withj :-> uds
. - At an occurrence of
j
, unleash thoseuds
.