Skip to content

Unnecessary thunking of Integer operations

While playing around with code from this great post by @ekmett in ghc-vis I noticed some thunks that I didn’t expect.

Consider this code:

{-# LANGUAGE BangPatterns #-}
module Memo where

data Tree a = Tree (Tree a) a (Tree a)

nats :: Tree Integer
nats = go 0 1
    where
        go !n !s = Tree (go l s') n (go r s')
            where
                l = n + s
                r = l + s
                s' = s * 2

I would expect the l, r, and s' bindings to be evaluated eagerly. But this is the core I get:

Rec {
-- RHS size: {terms: 11, types: 17, coercions: 0, joins: 0/0}
go_r1Vm :: Integer -> Integer -> Tree Integer
[GblId, Arity=2, Str=<S,1*U><S,1*U>, Cpr=m1, Unf=OtherCon []]
go_r1Vm
  = \ (w_s1Sf :: Integer) (w1_s1Sg :: Integer) ->
      case Memo.$wgo w_s1Sf w1_s1Sg of
      { (# ww1_s1Su, ww2_s1Sv, ww3_s1Sw #) ->
      Memo.Tree @Integer ww1_s1Su ww2_s1Sv ww3_s1Sw
      }

-- RHS size: {terms: 32, types: 29, coercions: 0, joins: 0/2}
Memo.$wgo [InlPrag=NOUSERINLINE[2], Occ=LoopBreaker]
  :: Integer -> Integer -> (# Tree Integer, Integer, Tree Integer #)
[GblId, Arity=2, Str=<S,1*U><S,1*U>, Unf=OtherCon []]
Memo.$wgo
  = \ (w_s1Sf :: Integer) (w1_s1Sg :: Integer) ->
      case w_s1Sf of n_X0 { __DEFAULT ->
      case w1_s1Sg of s_X1 { __DEFAULT ->
      let {
        s'_s1PN :: Integer
        [LclId]
        s'_s1PN = GHC.Num.Integer.integerMul s_X1 Memo.f3 } in
      let {
        l_s1PM :: Integer
        [LclId]
        l_s1PM = GHC.Num.Integer.integerAdd n_X0 s_X1 } in
      (# go_r1Vm l_s1PM s'_s1PN, n_X0,
         case Memo.$wgo (GHC.Num.Integer.integerAdd l_s1PM s_X1) s'_s1PN of
         { (# ww1_X2, ww2_X3, ww3_X4 #) ->
         Memo.Tree @Integer ww1_X2 ww2_X3 ww3_X4
         } #)
      }
      }
end Rec }

Note the lazy let bindings!

(Also note the relatively pointless CPR w/w; it may be related.)

For some reason, the effect does not happen if I use Int instead of Integer; then I get the pretty

Rec {
-- RHS size: {terms: 33, types: 42, coercions: 0, joins: 0/2}
Memo.$wgo [InlPrag=NOUSERINLINE[2], Occ=LoopBreaker]
  :: GHC.Prim.Int# -> GHC.Prim.Int# -> (# Tree Int, Int, Tree Int #)
[GblId, Arity=2, Str=<L,U><L,U>, Unf=OtherCon []]
Memo.$wgo
  = \ (ww_s1SE :: GHC.Prim.Int#) (ww1_s1SI :: GHC.Prim.Int#) ->
      let {
        s'_s1PK :: GHC.Prim.Int#
        [LclId]
        s'_s1PK = GHC.Prim.*# ww1_s1SI 2# } in
      let {
        l_s1PI :: GHC.Prim.Int#
        [LclId]
        l_s1PI = GHC.Prim.+# ww_s1SE ww1_s1SI } in
      (# case Memo.$wgo l_s1PI s'_s1PK of
         { (# ww3_X1, ww4_X2, ww5_X3 #) ->
         Memo.Tree @Int ww3_X1 ww4_X2 ww5_X3
         },
         GHC.Types.I# ww_s1SE,
         case Memo.$wgo (GHC.Prim.+# l_s1PI ww1_s1SI) s'_s1PK of
         { (# ww3_X1, ww4_X2, ww5_X3 #) ->
         Memo.Tree @Int ww3_X1 ww4_X2 ww5_X3
         } #)
end Rec }

where we have no thunks and even unboxing. For Integer, I cannot expect unboxing, but I do expect eager evaluation, given the bang patterns in the code!

(Reproduced with 8.10 and 9.0.)

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