Skip to content

Arity decrease through eta reduction

While working on !8277 (merged)/#20836 (closed) I came up with the following program:

import GHC.Exts

f, g :: a -> a
f = g
g x = f x
{-# NOINLINE f #-}
{-# NOINLINE g #-}

main = lazy g `seq` putStrLn "done"

(The lazy is just so that we don't discard the seq.)

The program should print done and return rapidly. Yet if you compile it with optimisations, you'll see <<loop>> instead.

The reason is that GHC eta-reduces \x -> f x to f, because f has arity 1 (just like g). But in doing so, it reduces the arity of g and consequently of f to 0! Immediately after in the next

Rec {
-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
f [InlPrag=NOINLINE] :: forall a. a -> a
[LclId,
 Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True,
         Guidance=ALWAYS_IF(arity=0,unsat_ok=True,boring_ok=True)}]
f = g

-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
g [InlPrag=NOINLINE, Occ=LoopBreaker] :: forall a. a -> a
[LclId,
 Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True,
         Guidance=ALWAYS_IF(arity=0,unsat_ok=True,boring_ok=True)}]
g = f
end Rec }

And that of course lets us fall into a black hole if we evaluate g, when before it would stop at the lambda.

(Not only has arity decreased to 0, we also keep saying Value=True for a very long time, hence the seq won't survive even after we eta-expanded. That's probably an issue that wouldn't arise if we didn't eta-reduce in the first place.)

In !8277 (comment 432209), @simonpj suggests

A plausible solution is to retreat on Note [Arity robustness] and zap arity info in zapFragileIdInfo; test for perf regressions (incl nofib).

We should do that.

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information