Skip to content

Constructor specialization requires eta expansion

I recently encountered a slightly tricky performance issue[1] in bytestring's Builder implementation which required explicit eta expansion in order for GHC to perform ConstrSpec.

We have

data BufferRange = BufferRange {-# UNPACK #-} !(Ptr Word8)  -- First byte of range
                               {-# UNPACK #-} !(Ptr Word8)  -- First byte /after/ range

data BuildSignal a = ... -- Just a vanilla ADT
type BuildStep a = BufferRange -> IO (BuildSignal a)

We end up with Core (full version https://gist.github.com/bgamari/091e3dac9c45ee9accf1#file-slow-hs-L1) that looks like this,

Main.$wa 
  :: GHC.Prim.Word#
     -> GHC.Prim.Int#
     -> forall r_aKUt.
        Data.ByteString.Builder.Internal.BuildStep r_aKUt
        -> Data.ByteString.Builder.Internal.BuildStep r_aKUt
Main.$wa =
  \ (ww_s10YU :: GHC.Prim.Word#)
    (ww1_s10YY :: GHC.Prim.Int#)
    (@ r_aKUt)
    (w_s10YR :: Data.ByteString.Builder.Internal.BuildStep r_aKUt) ->
    case ww1_s10YY of wild_XE {
      __DEFAULT -> (
          \ (eta1_X1Q :: Data.ByteString.Builder.Internal.BufferRange) ->
            case eta1_X1Q of BufferRange a b -> 
	      -- A bunch of code eventually ending in a recursive call to $wa
      )
      0 -> w_s10YR
    }

If w_s10YR is eta-expanded GHC will run ConstrSpec, eliminating the fields of BufferRange to be unpacked between iterations of $wa and substantially improving performance.

In [1] we had to manually eta-expand the empty case to ensure that this would happen. It would be great if GHC would identify cases like this.

[1] https://github.com/haskell/bytestring/pull/40

Trac metadata
Trac field Value
Version 7.8.4
Type Bug
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component Compiler
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information