compiler/deSugar/DsUtils.hs¶
Note [Localise pattern binders]¶
- Consider module M where
- [Just a] = e
- After renaming it looks like
- module M where
- [Just M.a] = e
We don’t generalise, since it’s a pattern binding, monomorphic, etc, so after desugaring we may get something like
- M.a = case e of (v:_) ->
- case v of Just M.a -> M.a
Notice the “M.a” in the pattern; after all, it was in the original pattern. However, after optimisation those pattern binders can become let-binders, and then end up floated to top level. They have a different unique by then (the simplifier is good about maintaining proper scoping), but it’s BAD to have two top-level bindings with the External Name M.a, because that turns into two linker symbols for M.a. It’s quite rare for this to actually happen – the only case I know of is tc003 compiled with the ‘hpc’ way – but that only makes it all the more annoying.
To avoid this, we craftily call ‘localiseId’ in the desugarer, which simply turns the External Name for the Id into an Internal one, but doesn’t change the unique. So the desugarer produces this:
- M.a{r8} = case e of (v:_) ->
- case v of Just a{r8} -> M.a{r8}
The unique is still ‘r8’, but the binding site in the pattern is now an Internal Name. Now the simplifier’s usual mechanisms will propagate that Name to all the occurrence sites, as well as un-shadowing it, so we’ll get
- M.a{r8} = case e of (v:_) ->
- case v of Just a{s77} -> a{s77}
In fact, even CoreSubst.simplOptExpr will do this, and simpleOptExpr runs on the output of the desugarer, so all is well by the end of the desugaring pass.
See also Note [MatchIds] in Match.hs
Note [mkSelectorBinds]¶
mkSelectorBinds is used to desugar a pattern binding {p = e}, in a binding group:
let { …; p = e; … } in body
where p binds x,y (this list of binders can be empty). There are two cases.
- —— Special case (A) ——-
- For a pattern that is just a variable,
- let !x = e in body
- ==>
- let x = e in x seq body
So we return the binding, with ‘x’ as the variable to seq.
- —— Special case (B) ——-
- For a pattern that is essentially just a tuple:
- A product type, so cannot fail
- Only one level, so that
- generating multiple matches is fine
- seq’ing it evaluates the same as matching it
- Then instead we generate
- { v = e ; x = case v of p -> x ; y = case v of p -> y }
with ‘v’ as the variable to force
- —— General case (C) ——-
- In the general case we generate these bindings:
- let { …; p = e; … } in body
- ==>
- let { t = case e of p -> (x,y)
- ; x = case t of (x,y) -> x ; y = case t of (x,y) -> y }
in t seq body
Note that we return 't' as the variable to force if the pattern
is strict (i.e. with -XStrict or an outermost-bang-pattern)
Note that (A) /includes/ the situation where
- The pattern binds exactly one variable
let !(Just (Just x) = e in body
- ==>
- let { t = case e of Just (Just v) -> Unit v
; v = case t of Unit v -> v }
in t seq body
The ‘Unit’ is a one-tuple; see Note [One-tuples] in TysWiredIn Note that forcing ‘t’ makes the pattern match happen, but does not force ‘v’.
- The pattern binds no variables
- let !(True,False) = e in body
- ==>
- let t = case e of (True,False) -> () in t seq body
- —— Examples ———-
- !(_, (_, a)) = e
- ==>
- t = case e of (_, (_, a)) -> Unit a a = case t of Unit a -> a
- Note that
- Forcing ‘t’ will force the pattern to match fully; e.g. will diverge if (snd e) is bottom
- But ‘a’ itself is not forced; it is wrapped in a one-tuple (see Note [One-tuples] in TysWiredIn)
- !(Just x) = e
- ==>
- t = case e of Just x -> Unit x x = case t of Unit x -> x
Again, forcing 't' will fail if 'e' yields Nothing.
Note that even though this is rather general, the special cases work out well:
One binder, not -XStrict:
let Just (Just v) = e in body
- ==>
- let t = case e of Just (Just v) -> Unit v
v = case t of Unit v -> v
in body
- ==>
- let v = case (case e of Just (Just v) -> Unit v) of
Unit v -> v
in body
- ==>
let v = case e of Just (Just v) -> v in body
- Non-recursive, -XStrict
let p = e in body
- ==>
- let { t = case e of p -> (x,y)
; x = case t of (x,y) -> x ; y = case t of (x,y) -> x }
in t seq body
- ==> {inline seq, float x,y bindings inwards}
let t = case e of p -> (x,y) in case t of t’ -> let { x = case t’ of (x,y) -> x
; y = case t’ of (x,y) -> x } in
body
- ==> {inline t, do case of case}
case e of p -> let t = (x,y) in let { x = case t’ of (x,y) -> x
; y = case t’ of (x,y) -> x } in
body
- ==> {case-cancellation, drop dead code}
case e of p -> body
Special case (B) is there to avoid fruitlessly taking the tuple apart and rebuilding it. For example, consider
{ K x y = e }
- where K is a product constructor. Then general case (A) does:
{ t = case e of K x y -> (x,y) ; x = case t of (x,y) -> x ; y = case t of (x,y) -> y }
In the lazy case we can’t optimise out this fruitless taking apart and rebuilding. Instead (B) builds
{ v = e ; x = case v of K x y -> x ; y = case v of K x y -> y }
which is better.
Note [Failure thunks and CPR]¶
(This note predates join points as formal entities (hence the quotation marks). We can’t use actual join points here (see above); if we did, this would also solve the CPR problem, since join points don’t get CPR’d. See Note [Don’t CPR join points] in WorkWrap.)
When we make a failure point we ensure that it does not look like a thunk. Example:
let fail = \rw -> error "urk"
in case x of
[] -> fail realWorld#
(y:ys) -> case ys of
[] -> fail realWorld#
(z:zs) -> (y,z)
Reason: we know that a failure point is always a “join point” and is entered at most once. Adding a dummy ‘realWorld’ token argument makes it clear that sharing is not an issue. And that in turn makes it more CPR-friendly. This matters a lot: if you don’t get it right, you lose the tail call property. For example, see #3403.
Note [decideBangHood]¶
With -XStrict we may make /outermost/ patterns more strict. E.g.
- let (Just x) = e in …
- ==>
let !(Just x) = e in …
- and
- f x = e
- ==>
f !x = e
This adjustment is done by decideBangHood,
- Just before constructing an EqnInfo, in Match
- (matchWrapper and matchSinglePat)
- When desugaring a pattern-binding in DsBinds.dsHsBind
Note that it is /not/ done recursively. See the -XStrict spec in the user manual.
- Specifically:
- ~pat => pat – when -XStrict (even if pat = ~pat’) !pat => !pat – always pat => !pat – when -XStrict pat => pat – otherwise