Skip to content

Possible overzealous unfolding

I was investigating why (>>=) in the Haxl monad is being inlined more than I would expect, and I ran into something I don't fully understand, and looks dubious.

Start from this standalone example:

{-# LANGUAGE ScopedTypeVariables, ExistentialQuantification #-}
module Haxl where

import Data.IORef
import Control.Exception

newtype GenHaxl u a = GenHaxl
  { unHaxl :: Int -> IORef () -> IO (Result u a) }

data Result u a
  = Done a
  | Throw SomeException
  | Blocked (Cont u a)

data Cont u a
  = forall b. Cont u b :>>= (b -> GenHaxl u a)
  | forall b. (b -> a) :<$> (Cont u b)

instance Monad (GenHaxl u) where
  return a = GenHaxl $ \_env _ref -> return (Done a)
  GenHaxl m >>= k = GenHaxl $ \env ref -> do
    e <- m env ref
    case e of
      Done a       -> unHaxl (k a) env ref
      Throw e      -> return (Throw e)
      Blocked cont -> return (Blocked (cont :>>= k))

instance Functor (GenHaxl u)
instance Applicative (GenHaxl u)

(it could be simplified further, but I've intentionally used the exact definition of >>= that is used in Haxl to be sure I'm not investigating the wrong thing)

Compile like this:

ghc -O -c Haxl.hs

and look at the .hi file:

ghc --show-iface Haxl.hi

see this:

ea159c3b107c307a4e76cd310efcc8bc
  $fMonadGenHaxl2 ::
    GenHaxl u a
    -> (a -> GenHaxl u b)
    -> Int
    -> IORef ()
    -> State# RealWorld
    -> (# State# RealWorld, Result u b #)
  {- Arity: 5, HasNoCafRefs,
     Strictness: <C(C(C(S(SS)))),1*C1(C1(C1(U(U,1*U))))><L,U><L,U><L,U><S,U>,
     Unfolding: InlineRule (5, True, False)
                (\ @ u
                   @ a
                   @ b
                   (ds :: GenHaxl u a)
                   (k :: a -> GenHaxl u b)
                   (env :: Int)
                   (ref :: IORef ())
                   (s :: State# RealWorld)[OneShot] ->
                 case (ds `cast` (N:GenHaxl[0] <u>_P <a>_R) env ref)
                        `cast`
                      (N:IO[0] <Result u a>_R)
                        s of ds1 { (#,#) ipv ipv1 ->
                 case ipv1 of wild {
                   Done a1
                   -> ((k a1) `cast` (N:GenHaxl[0] <u>_P <b>_R) env ref)
                        `cast`
                      (N:IO[0] <Result u b>_R)
                        ipv
                   Throw e -> (# ipv, Throw @ u @ b e #)
                   Blocked cont
                   -> (# ipv, Blocked @ u @ b (:>>= @ u @ b @ a cont k) #) } }) -}

That right there is the definition of >>=. Note that it has an InlineRule, which means that it will be unconditionally unfolded pretty much everywhere. I don't think this is right - there's no benefit to be had in inlining it unconditionally.

I delved in a bit more, and it seems this unfolding arises during worker-wrapper. Before WW we have

a_sVM
[LclId,
 Arity=5,
 Str=DmdType <C(C(C(S(SS)))),1*C1(C1(C1(U(U,1*U))))><L,U><L,U><L,U><S,U>,
 Unf=Unf{Src=<vanilla>, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True,
         Guidance=IF_ARGS [0 60 0 0 0] 94 60}]
a_sVM =
  \ @ u_XQR
    @ a_aPN
    @ b_aPO
    ds_dQP [Dmd=<C(C(C(S(SS)))),1*C1(C1(C1(U(U,1*U))))>]
    k_aEC
    env_aED
    ref_aEE
    s_aVC [Dmd=<S,U>, OS=OneShot] ->
    case (((ds_dQP `cast` ...) env_aED ref_aEE) `cast` ...) s_aVC
    of _ [Occ=Dead, Dmd=<L,A>]
    { (# ipv_aVF [Dmd=<S,U>], ipv1_aVG [Dmd=<S,1*U>] #) ->
    case ipv1_aVG of _ [Occ=Dead, Dmd=<L,A>] {
      Done a_aEG ->
        ((((k_aEC a_aEG) `cast` ...) env_aED ref_aEE) `cast` ...) ipv_aVF;
      Throw e_aEH -> (# ipv_aVF, Haxl.Throw e_aEH #);
      Blocked cont_aEI ->
        (# ipv_aVF, Haxl.Blocked (Haxl.:>>= cont_aEI k_aEC) #)
    }
    }

and after WW we have

a_sVM
[LclId,
 Arity=5,
 Str=DmdType <C(C(C(S(SS)))),1*C1(C1(C1(U(U,1*U))))><L,U><L,U><L,U><S,U>,
 Unf=Unf{Src=InlineStable, TopLvl=True, Value=True, ConLike=True,
         WorkFree=True, Expandable=True,
         Guidance=ALWAYS_IF(arity=5,unsat_ok=True,boring_ok=False)
         Tmpl= \ @ u_XQR
                 @ a_aPN
                 @ b_aPO
                 ds_dQP [Occ=Once]
                 k_aEC [Occ=Once*]
                 env_aED
                 ref_aEE
                 s_aVC [Occ=Once, OS=OneShot] ->
                 case (((ds_dQP `cast` ...) env_aED ref_aEE) `cast` ...) s_aVC
                 of _ [Occ=Dead]
                 { (# ipv_aVF [Occ=Once*], ipv1_aVG [Occ=Once!] #) ->
                 case ipv1_aVG of _ [Occ=Dead] {
                   Done a_aEG [Occ=Once] ->
                     ((((k_aEC a_aEG) `cast` ...) env_aED ref_aEE) `cast` ...) ipv_aVF;
                   Throw e_aEH [Occ=Once] -> (# ipv_aVF, Haxl.Throw e_aEH #);
                   Blocked cont_aEI [Occ=Once] ->
                     (# ipv_aVF, Haxl.Blocked (Haxl.:>>= cont_aEI k_aEC) #)
                 }
                 }}]
a_sVM =
  \ @ u_XQR
    @ a_aPN
    @ b_aPO
    ds_dQP [Dmd=<C(C(C(S(SS)))),1*C1(C1(C1(U(U,1*U))))>]
    k_aEC
    env_aED
    ref_aEE
    s_aVC [Dmd=<S,U>, OS=OneShot] ->
    case (((ds_dQP `cast` ...) env_aED ref_aEE) `cast` ...) s_aVC
    of _ [Occ=Dead, Dmd=<L,A>]
    { (# ipv_aVF [Dmd=<S,U>], ipv1_aVG [Dmd=<S,1*U>] #) ->
    case ipv1_aVG of _ [Occ=Dead, Dmd=<L,A>] {
      Done a_aEG ->
        ((((k_aEC a_aEG) `cast` ...) env_aED ref_aEE) `cast` ...) ipv_aVF;
      Throw e_aEH -> (# ipv_aVF, Haxl.Throw e_aEH #);
      Blocked cont_aEI ->
        (# ipv_aVF, Haxl.Blocked (Haxl.:>>= cont_aEI k_aEC) #)
    }
    }

For some unknown reason, this binding has acquired an always-on unfolding. There's no wrapper, we're just unfolding the whole thing.

Simon, can you shed any light here? I would like to tune unfolding sizes to reduce code bloat in our codebase, but with this unfolding being unconditional it doesn't work to use -funfolding-use-threshold, I can only use NOINLINE but that's too blunt.

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