Note [Unboxing evaluated arguments]
Consider this program (due to Roman):
data X a = X !a
foo :: X Int -> Int -> Int
foo x@(X a) n = go 0
where
go i | i < n = a + go (i+1)
| otherwise = 0
We want the worker for 'foo' to look like this:
$wfoo :: Int# -> Int# -> Int#
with the first argument unboxed, so that it is not eval'd each time around the
'go' loop (which would otherwise happen, since 'foo' is not strict in 'a'). It
is sound for the wrapper to pass an unboxed arg because X is strict
(see Note [Strictness and Unboxing] in "GHC.Core.Opt.DmdAnal"), so its argument
must be evaluated. And if we *don't* pass an unboxed argument, we can't even
repair it by adding a `seq` thus:
foo (X a) n = a `seq` go 0
because the seq is discarded (very early) since X is strict!
So here's what we do
* Since this has nothing to do with how 'foo' uses 'a', we leave demand
analysis alone, but account for the additional evaluatedness when
annotating the binder 'finaliseArgBoxities', which will retain the Unboxed
boxity on 'a' in the definition of 'foo' in the demand 'L!P(L)'; meaning
it's used lazily but unboxed nonetheless. This seems to contradict Note
[No lazy, Unboxed demands in demand signature], but we know that 'a' is
evaluated and thus can be unboxed.
* When 'finaliseArgBoxities' decides to unbox a record, it will zip the field demands
together with the respective 'StrictnessMark'. In case of 'x', it will pair
up the lazy field demand 'L!P(L)' on 'a' with 'MarkedStrict' to account for
the strict field.
* Said 'StrictnessMark' is passed to the recursive invocation of 'go_args' in
'finaliseArgBoxities' when deciding whether to unbox 'a'. 'a' was used lazily, but
since it also says 'MarkedStrict', we'll retain the 'Unboxed' boxity on 'a'.
* Worker/wrapper will consult 'canUnboxArg' for its unboxing decision. It will
/not/ look at the strictness bits of the demand, only at Boxity flags. As such,
it will happily unbox 'a' despite the lazy demand on it.
The net effect is that boxity analysis and the w/w transformation are more
aggressive about unboxing the strict arguments of a data constructor than when
looking at strictness info exclusively. It is very much like (Nested) CPR, which
needs its nested fields to be evaluated in order for it to unbox nestedly.
There is the usual danger of reboxing, which as usual we ignore. But
if X is monomorphic, and has an UNPACK pragma, then this optimisation
is even more important. We don't want the wrapper to rebox an unboxed
argument, and pass an Int to $wfoo!
This works in nested situations like T10482
data family Bar a
data instance Bar (a, b) = BarPair !(Bar a) !(Bar b)
newtype instance Bar Int = Bar Int
foo :: Bar ((Int, Int), Int) -> Int -> Int
foo f k = case f of BarPair x y ->
case burble of
True -> case x of
BarPair p q -> ...
False -> ...
The extra eagerness lets us produce a worker of type:
$wfoo :: Int# -> Int# -> Int# -> Int -> Int
$wfoo p# q# y# = ...
even though the `case x` is only lazily evaluated.
Historical note ------------
We used to add data-con strictness demands when demand analysing case
expression. However, it was noticed in #15696 that this misses some cases. For
instance, consider the program (from T10482)
data family Bar a
data instance Bar (a, b) = BarPair !(Bar a) !(Bar b)
newtype instance Bar Int = Bar Int
foo :: Bar ((Int, Int), Int) -> Int -> Int
foo f k =
case f of
BarPair x y -> case burble of
True -> case x of
BarPair p q -> ...
False -> ...
We really should be able to assume that `p` is already evaluated since it came
from a strict field of BarPair. This strictness would allow us to produce a
worker of type:
$wfoo :: Int# -> Int# -> Int# -> Int -> Int
$wfoo p# q# y# = ...
even though the `case x` is only lazily evaluated
Indeed before we fixed #15696 this would happen since we would float the inner
`case x` through the `case burble` to get:
foo f k =
case f of
BarPair x y -> case x of
BarPair p q -> case burble of
True -> ...
False -> ...
However, after fixing #15696 this could no longer happen (for the reasons
discussed in ticket:15696#comment:76). This means that the demand placed on `f`
would then be significantly weaker (since the False branch of the case on
`burble` is not strict in `p` or `q`).
Consequently, we now instead account for data-con strictness in mkWWstr_one,
applying the strictness demands to the final result of DmdAnal. The result is
that we get the strict demand signature we wanted even if we can't float
the case on `x` up through the case on `burble`. References 1
- Strictness and Unboxing GHC.Types.Demand
Referenced by 7
- No lazy, Unboxed demands in demand signature GHC.Core.Opt.DmdAnal ×2
- Finalising boxity for demand signatures GHC.Core.Opt.DmdAnal
- GHC.Core.Opt.DmdAnal call site
- Worker/wrapper for Strictness and Absence GHC.Core.Opt.WorkWrap.Utils
- Absent fillers GHC.Core.Opt.WorkWrap.Utils
- Strictness and Unboxing GHC.Types.Demand