Skip to content

FloatOut reverts eta expansion of thunks

Call Arity works hard to identify thunks that can be eta expanded, because they are only called once, with arity > 0. As it does for lvl this example

{-# OPTIONS_GHC -O2 -fforce-recomp #-}
{-# LANGUAGE BangPatterns #-}

module Lib (main) where

expensive :: a -> a
expensive x = x
{-# NOINLINE expensive #-}

lvl :: Int -> Int
lvl = expensive (+ 42)
{-# NOINLINE lvl #-}

go :: Int -> Int -> Int
go 0 x = lvl x
go 1 x = lvl (x+1)
go n x = go (n-1) (x+1)

res :: Int
res = go 15 25

main :: IO ()
main = print res

Before SetLevels (after Call Arity), indeed there is

lvl
  = \ (eta_B1 :: Int) ->
      expensive
        @ (Int -> Int)
        (\ (ds_d2K7 :: Int) ->
           case ds_d2K7 of { GHC.Types.I# x_a2KB [Dmd=<S,U>] ->
           GHC.Types.I# (GHC.Prim.+# x_a2KB 42#)
           })
        eta_B1

lvl got eta expanded. But if you look at the output of -ddump-simpl, you'll see

lvl2_r3jd = expensive_rqN @ (Int -> Int) lvl1_r3dx

which is an un-eta-expanded form of lvl above. lvl2 is due to FloatOut, which identifies expensive (+ 42) as a MFE of lvl = \eta. expensive (+ 42) eta.

I'm proposing that lvl should stay eta expanded. Here's how to ensure that:

That \eta. should have a one-shot annotation. The only world in which it is OK to eta-expand a thunk is when the new manifest lambdas are one-shot (and that the thunk is not otherwise seqed, of course). And then we have to make sure that we preserve that one-shot-ness (#18355), so that FloatIn and the Simplifier will be able to float it back in.

Preserving that one-shot-ness is the hard part, but fortunately that seems to have been solved by Kinds are Calling Conventions.

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