Skip to content

Strictness signatures lie about boxing

Consider these two definitions

-- Str=<1L><1!P(L,L,L,A,A)><L><L><L><L><L><L><L><L>,
f True (x,y,z,_,_) b1 b2 b3 b4 b5 b6 b7 b8 = (x,y,z, b1, b2, b3, b4, b5, b6, b7, b8)
f False at         b1 b2 b3 b4 b5 b6 b7 b8 = f True at b1 b2 b3 b4 b5 b6 b7 b8


-- Str=<1L><1!P(L,L,L,A,A)><L><L><L>,
g True (x,y,z,_,_) b1 b2 b3 = (x,y,z, b1, b2, b3)
g False at         b1 b2 b3 = g True at b1 b2 b3

Both strictness signatures claim to unbox the second argument. But in fact f has too many arguments to worker/wrapper, so we get no worker/wrapper for f at all. But we do for g.

TL;DR: f's strictness signature lies about boxing.

It turns out that this bug means that fixing #20746 (closed) is not enough to fix boxing in GHC.IO.Handle.Text.hGetBuf. Why? Because demand analysis sees:

              let {
                act_s4jT [Dmd=LCL(C1(L))]
                  :: Handle__ -> State# RealWorld -> (# State# RealWorld, Int #)
                [LclId,
                 Arity=2,
                 Str=<1!P(LP(LCL(C1(C1(C1(C1(P(L,1P(1L))))))),A,A,A),A,LP(A,LCL(C1(C1(P(L,1P(1P(1L),L))))),A,A,A,A),A,L,A,L,A,L,L,A,A,ML,A,A,A,A)><L> {a3Ym->M!L},
                 Unf=Unf{Src=<vanilla>, TopLvl=False, Value=True, ConLike=True,
                         WorkFree=True, Expandable=True, Guidance=IF_ARGS [20 0] 222 0}]
                act_s4jT
                  = \ (h__a1Zv [Dmd=1!P(LP(LCL(C1(C1(C1(C1(P(L,1P(1L))))))),A,A,A),A,LP(A,LCL(C1(C1(P(L,1P(1P(1L),L))))),A,A,A,A),A,L,A,L,A,L,L,A,A,ML,A,A,A,A)]
                         :: Handle__)
                      (eta_X3 [OS=OneShot] :: State# RealWorld) ->
                      case h__a1Zv
                      of wild_X4 [Dmd=M!P(L!P(LCL(C1(C1(C1(C1(P(L,1P(1L))))))),A,A,A),A,L!P(A,LCL(C1(C1(P(L,1P(1P(1L),L))))),A,A,A,A),A,L!L,A,L!L,A,A,A,A,A,A,A,A,A,A)]
                      { Handle__ @dev_a2Ph @enc_state_a2Pi @dec_state_a2Pj
                                 $dRawIO_a2Pk [Dmd=L!P(LCL(C1(C1(C1(C1(P(L,1P(1L))))))),A,A,A)]
                                 $dIODevice_a2Pl [Dmd=A]
                                 $dBufferedIO_a2Pm [Dmd=L!P(A,LCL(C1(C1(P(L,1P(1P(1L),L))))),A,A,A,A)]
                                 $dTypeable_a2Pn [Dmd=A] ds_d3ri [Dmd=L!L] ds_d3rj [Dmd=A] bx_d3J4
                                 ds_d3rl [Dmd=A] bx_d3J5 bx_d3J6 bx_d3J7 [Dmd=A] ds_d3rp [Dmd=A]
                                 ds_d3rq [Dmd=ML] ds_d3rr [Dmd=A] ds_d3rs [Dmd=A] ds_d3rt [Dmd=A]
                                 ds_d3ru [Dmd=A] ->
                      case GHC.IO.Handle.Internals.$wflushCharReadBuffer
                             @dev_a2Ph
                             @enc_state_a2Pi
                             @dec_state_a2Pj
                             bx_d3J4
                             bx_d3J5
                             bx_d3J6
                             ds_d3rq
                             eta_X3
                      of ww17_a4f0
                      { __DEFAULT ->
                      case readMutVar# @RealWorld @(Buffer Word8) bx_d3J4 ww17_a4f0 of
                      { (# ipv_a3WM, ipv1_a3WN [Dmd=1!L] #) ->
                      case ipv1_a3WN of wild_X5 [Dmd=1!L]
                      { Buffer bx_d3J8 bx_d3J9 ds_d3rd bx_d3Ja bx_d3Jb bx_d3Jc bx_d3Jd ->
                      case ==# bx_d3Jc bx_d3Jd of {
                        __DEFAULT -> bufReadNonEmpty_s4fR wild_X4 ...blah...
                        1# ->        bufReadEmpty_s4fD    wild_X4 ...blah...
                      } }}}}

and those bufReadNonEmpty and bufReadEmpty calls are precisely like f: they claim to unbox wild_X4 but in fact do not do so becuase of too many arguments.

Result: hGetBuf allocates a lot more, for reasons described in #20746 (closed).

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