Skip to content

Bytestring.Builder rules no longer fire with GHC 9.0.1

Summary

With GHC 9 and forwards (I tested 8.10.7, 9.0.1 and 9.2.1 where I initially discovered this) a rewrite rule from Data.ByteString.Builder no longer seems to fire.

I am unsure how to reproduce this with a minimal example, or even how many other rules this affects, as I am not that familiar with rewrite-rules or Data.ByteString.Builder's internals. I have been trying to implement a custom Builder to learn more about them, but got stuck on this issue and noticed this wasn't working in bytestring for ghc 9.X too.

Also I hope that this is right place to report this. bytestring builders rules have not changed all that much for a very long time and that specific rule hasn't either, so I think this is a GHC issue 🙈

Steps to reproduce

{-# OPTIONS_GHC -O2 -ddump-rule-firings -ddump-simpl -dsuppress-idinfo -dsuppress-coercions -dsuppress-type-applications -dsuppress-module-prefixes -dsuppress-type-signatures -dsuppress-uniques #-}
module Test (test) where

import qualified Data.ByteString.Builder as B

test :: B.Builder
test = B.word8 16 <> B.int8 32

Expected behavior

From my understanding this is supposed to trigger this rule and thus produce a single buffer size check.

With GHC 8.10.7 this produces the following output:
[1 of 1] Compiling Test             ( Test.hs, Test.o )
Rule fired: Class op <> (BUILTIN)
Rule fired: Class op fromInteger (BUILTIN)
Rule fired: integerToWord (BUILTIN)
Rule fired: narrow8Word# (BUILTIN)
Rule fired: Class op fromInteger (BUILTIN)
Rule fired: integerToInt (BUILTIN)
Rule fired: narrow8Int# (BUILTIN)
Rule fired: append/primBounded (Data.ByteString.Builder.Prim)
Rule fired: +# (BUILTIN)
Rule fired: int2Word# (BUILTIN)
Rule fired: narrow8Word# (BUILTIN)
Rule fired: int2Word# (BUILTIN)
Rule fired: narrow8Word# (BUILTIN)

==================== Tidy Core ====================
Result size of Tidy Core
  = {terms: 103, types: 88, coercions: 27, joins: 0/2}

-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
$trModule4 = "main"#

-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
$trModule3 = TrNameS $trModule4

-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
$trModule2 = "Test"#

-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
$trModule1 = TrNameS $trModule2

-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
$trModule = Module $trModule3 $trModule1

-- RHS size: {terms: 73, types: 38, coercions: 12, joins: 0/2}
$wtest
  = \ @ r w ww ww1 w1 ->
      case <# (minusAddr# ww1 ww) 2# of {
        __DEFAULT ->
          case writeWord8OffAddr# ww 0# 16## w1 of s2 { __DEFAULT ->
          let { ipv1 = plusAddr# ww 1# } in
          case writeWord8OffAddr# ipv1 0# 32## s2 of s1 { __DEFAULT ->
          ((w (BufferRange (plusAddr# ipv1 1#) ww1)) `cast` <Co:3>) s1
          }
          };
        1# ->
          (# w1,
             BufferFull
               2#
               ww
               ((\ ds eta ->
                   case ds of { BufferRange dt dt1 ->
                   case writeWord8OffAddr# dt 0# 16## eta of s2 { __DEFAULT ->
                   let { ipv1 = plusAddr# dt 1# } in
                   case writeWord8OffAddr# ipv1 0# 32## s2 of s1 { __DEFAULT ->
                   ((w (BufferRange (plusAddr# ipv1 1#) dt1)) `cast` <Co:3>) s1
                   }
                   }
                   })
                `cast` <Co:6>) #)
      }

-- RHS size: {terms: 12, types: 11, coercions: 0, joins: 0/0}
test1
  = \ @ r w w1 w2 ->
      case w1 of { BufferRange ww1 ww2 -> $wtest w ww1 ww2 w2 }

-- RHS size: {terms: 1, types: 0, coercions: 15, joins: 0/0}
test = test1 `cast` <Co:15>
With GHC 9.0.1 it instead produces this:
[1 of 1] Compiling Test             ( Test.hs, Test.o )
Rule fired: Class op <> (BUILTIN)
Rule fired: Class op fromInteger (BUILTIN)
Rule fired: Integer -> Word# (wrap) (BUILTIN)
Rule fired: narrow8Word# (BUILTIN)
Rule fired: Class op fromInteger (BUILTIN)
Rule fired: Integer -> Int# (wrap) (BUILTIN)
Rule fired: narrow8Int# (BUILTIN)
Rule fired: int2Word# (BUILTIN)
Rule fired: narrow8Word# (BUILTIN)
Rule fired: int2Word# (BUILTIN)
Rule fired: narrow8Word# (BUILTIN)

==================== Tidy Core ====================
Result size of Tidy Core
  = {terms: 151, types: 129, coercions: 91, joins: 0/3}

-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
$trModule4 = "main"#

-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
$trModule3 = TrNameS $trModule4

-- RHS size: {terms: 1, types: 0, coercions: 0, joins: 0/0}
$trModule2 = "Test"#

-- RHS size: {terms: 2, types: 0, coercions: 0, joins: 0/0}
$trModule1 = TrNameS $trModule2

-- RHS size: {terms: 3, types: 0, coercions: 0, joins: 0/0}
$trModule = Module $trModule3 $trModule1

-- RHS size: {terms: 121, types: 79, coercions: 69, joins: 0/3}
$wtest
  = \ @r w ww ww1 w1 ->
      let {
        lvl
          = \ ds eta ->
              case ds of { BufferRange dt3 dt4 ->
              case writeWord8OffAddr# dt3 0# 32## eta of s2 { __DEFAULT ->
              ((w (BufferRange (plusAddr# dt3 1#) dt4)) `cast` <Co:16>) s2
              }
              } } in
      case <# (minusAddr# ww1 ww) 1# of {
        __DEFAULT ->
          case writeWord8OffAddr# ww 0# 16## w1 of s2 { __DEFAULT ->
          let { dt = plusAddr# ww 1# } in
          case <# (minusAddr# ww1 dt) 1# of {
            __DEFAULT ->
              case writeWord8OffAddr# dt 0# 32## s2 of s1 { __DEFAULT ->
              ((w (BufferRange (plusAddr# dt 1#) ww1)) `cast` <Co:16>) s1
              };
            1# -> (# s2, BufferFull 1# dt (lvl `cast` <Co:7>) #)
          }
          };
        1# ->
          (# w1,
             BufferFull
               1#
               ww
               ((\ ds eta ->
                   case ds of { BufferRange dt3 dt4 ->
                   case writeWord8OffAddr# dt3 0# 16## eta of s2 { __DEFAULT ->
                   let { dt = plusAddr# dt3 1# } in
                   case <# (minusAddr# dt4 dt) 1# of {
                     __DEFAULT ->
                       case writeWord8OffAddr# dt 0# 32## s2 of s1 { __DEFAULT ->
                       ((w (BufferRange (plusAddr# dt 1#) dt4)) `cast` <Co:16>) s1
                       };
                     1# -> (# s2, BufferFull 1# dt (lvl `cast` <Co:7>) #)
                   }
                   }
                   })
                `cast` <Co:7>) #)
      }

-- RHS size: {terms: 12, types: 11, coercions: 0, joins: 0/0}
test1
  = \ @r w w1 w2 ->
      case w1 of { BufferRange ww1 ww2 -> $wtest w ww1 ww2 w2 }

-- RHS size: {terms: 1, types: 0, coercions: 22, joins: 0/0}
test = test1 `cast` <Co:22>

GHC 9.2.1 is similar, the append/primBounded (Data.ByteString.Builder.Prim) also does not fire.

Environment

  • GHC version used: 9.0.1, 9.2.1
Edited by Jannis
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information