Note [Speculative evaluation]
Since call-by-value is much cheaper than call-by-need, we case-bind arguments
that are either
1. Strictly evaluated anyway, according to the DmdSig of the callee, or
2. ok-for-spec, according to 'exprOkForSpeculation'.
This includes DFuns `$fEqList a`, for example.
(Could identify more in the future; see reference to !1866 below.)
While (1) is a no-brainer and always beneficial, (2) is a bit
more subtle, as the careful haddock for 'exprOkForSpeculation'
points out. Still, by case-binding the argument we don't need
to allocate a thunk for it, whose closure must be retained as
long as the callee might evaluate it. And if it is evaluated on
most code paths anyway, we get to turn the unknown eval in the
callee into a known call at the call site.
Very Nasty Wrinkle
We must be very careful not to speculate recursive calls! Doing so
might well change termination behavior.
That comes up in practice for DFuns, which are considered ok-for-spec,
because they always immediately return a constructor.
See Note [NON-BOTTOM-DICTS invariant] in GHC.Core.
But not so if you speculate the recursive call, as #20836 shows:
class Foo m => Foo m where
runFoo :: m a -> m a
newtype Trans m a = Trans { runTrans :: m a }
instance Monad m => Foo (Trans m) where
runFoo = id
(NB: class Foo m => Foo m` looks weird and needs -XUndecidableSuperClasses. The
example in #20836 is more compelling, but boils down to the same thing.)
This program compiles to the following DFun for the `Trans` instance:
Rec {
$fFooTrans
= \ @m $dMonad -> C:Foo ($fFooTrans $dMonad) (\ @a -> id)
end Rec }
Note that the DFun immediately terminates and produces a dictionary, just
like DFuns ought to, but it calls itself recursively to produce the `Foo m`
dictionary. But alas, if we treat `$fFooTrans` as always-terminating, so
that we can speculate its calls, and hence use call-by-value, we get:
$fFooTrans
= \ @m $dMonad -> case ($fFooTrans $dMonad) of sc ->
C:Foo sc (\ @a -> id)
and that's an infinite loop!
Note that this bad-ness only happens in `$fFooTrans`'s own RHS. In the
*body* of the letrec, it's absolutely fine to use call-by-value on
`foo ($fFooTrans d)`.
Our solution is this: we track in cpe_rec_ids the set of enclosing
recursively-bound Ids, the RHSs of which we are currently transforming and then
in 'exprOkForSpecEval' (a special entry point to 'exprOkForSpeculation',
basically) we'll say that any binder in this set is not ok-for-spec.
Note if we have a letrec group `Rec { f1 = rhs1; ...; fn = rhsn }`, and we
prep up `rhs1`, we have to include not only `f1`, but all binders of the group
`f1..fn` in this set, otherwise our fix is not robust wrt. mutual recursive
DFuns.
NB: If at some point we decide to have a termination analysis for general
functions (#8655, !1866), we need to take similar precautions for (guarded)
recursive functions:
repeat x = x : repeat x
Same problem here: As written, repeat evaluates rapidly to WHNF. So `repeat x`
is a cheap call that we are willing to speculate, but *not* in repeat's RHS.
Fortunately, pce_rec_ids already has all the information we need in that case.
The problem is very similar to Note [Eta reduction in recursive RHSs].
Here as well as there it is *unsound* to change the termination properties
of the very function whose termination properties we are exploiting.
It is also similar to Note [Do not strictify a DFun's parameter dictionaries],
where marking recursive DFuns (of undecidable *instances*) strict in dictionary
*parameters* leads to quite the same change in termination as above. References 3
- Eta reduction in recursive RHSs GHC.Core.Opt.Arity
- Do not strictify a DFun's parameter dictionaries GHC.Core.Opt.DmdAnal
- NON-BOTTOM-DICTS invariant GHC.Core
Referenced by 10
- GHC.Core.Utils call site ×3
- Eta reduction in recursive RHSs GHC.Core.Opt.Arity ×2
- GHC.CoreToStg.Prep call site ×2
- NON-BOTTOM-DICTS invariant GHC.Core
- Do not strictify a DFun's parameter dictionaries GHC.Core.Opt.DmdAnal
- BindInfo and FloatInfo GHC.CoreToStg.Prep