Note [The binder-swap substitution]
The binder-swap is implemented by the occ_bs_env field of OccEnv.
There are two main pieces:
* Given case x |> co of b { alts }
we add [x :-> (b, sym co)] to the occ_bs_env environment; this is
done by addBndrSwap.
* Then, at an occurrence of a variable, we look up in the occ_bs_env
to perform the swap. This is done by lookupBndrSwap.
Some tricky corners:
(BS1) We do the substitution before gathering occurrence info. So in
the above example, an occurrence of x turns into an occurrence
of b, and that's what we gather in the UsageDetails. It's as
if the binder-swap occurred before occurrence analysis. See
the computation of fun_uds in occAnalApp.
(BS2) When doing a lookup in occ_bs_env, we may need to iterate,
as you can see implemented in lookupBndrSwap. Why?
Consider case x of a { 1# -> e1; DEFAULT ->
case x of b { 2# -> e2; DEFAULT ->
case x of c { 3# -> e3; DEFAULT -> ..x..a..b.. }}}
At the first case addBndrSwap will extend occ_bs_env with
[x :-> a]
At the second case we occ-anal the scrutinee 'x', which looks up
'x in occ_bs_env, returning 'a', as it should.
Then addBndrSwap will add [a :-> b] to occ_bs_env, yielding
occ_bs_env = [x :-> a, a :-> b]
At the third case we'll again look up 'x' which returns 'a'.
But we don't want to stop the lookup there, else we'll end up with
case x of a { 1# -> e1; DEFAULT ->
case a of b { 2# -> e2; DEFAULT ->
case a of c { 3# -> e3; DEFAULT -> ..a..b..c.. }}}
Instead, we want iterate the lookup in addBndrSwap, to give
case x of a { 1# -> e1; DEFAULT ->
case a of b { 2# -> e2; DEFAULT ->
case b of c { 3# -> e3; DEFAULT -> ..c..c..c.. }}}
This makes a particular difference for case-merge, which works
only if the scrutinee is the case-binder of the immediately enclosing
case (Note [Merge Nested Cases] in GHC.Core.Opt.Simplify.Utils
See #19581 for the bug report that showed this up.
(BS3) We need care when shadowing. Suppose [x :-> b] is in occ_bs_env,
and we encounter:
(i) \x. blah
Here we want to delete the x-binding from occ_bs_env
(ii) \b. blah
This is harder: we really want to delete all bindings that
have 'b' free in the range. That is a bit tiresome to implement,
so we compromise. We keep occ_bs_rng, which is the set of
free vars of rng(occc_bs_env). If a binder shadows any of these
variables, we discard all of occ_bs_env. Safe, if a bit
brutal. NB, however: the simplifer de-shadows the code, so the
next time around this won't happen.
These checks are implemented in addInScope.
(i) is needed only for Ids, but (ii) is needed for tyvars too (#22623)
because if occ_bs_env has [x :-> ...a...] where `a` is a tyvar, we
must not replace `x` by `...a...` under /\a. ...x..., or similarly
under a case pattern match that binds `a`.
An alternative would be for the occurrence analyser to do cloning as
it goes. In principle it could do so, but it'd make it a bit more
complicated and there is no great benefit. The simplifer uses
cloning to get a no-shadowing situation, the care-when-shadowing
behaviour above isn't needed for long.
(BS4) The domain of occ_bs_env can include GlobaIds. Eg
case M.foo of b { alts }
We extend occ_bs_env with [M.foo :-> b]. That's fine.
(BS5) We have to apply the occ_bs_env substitution uniformly,
including to (local) rules and unfoldings.
(BS6) For interest (only),
see Historical Note [Care with binder-swap on dictionaries] References 0
This Note does not link to any other.
Referenced by 7
- GHC.Core.Opt.OccurAnal call site ×6
- Binder swap GHC.Core.Opt.OccurAnal