#4978: Continuation passing style loop doesn't compile into a loop
---------------------------------+------------------------------------------
Reporter: tibbe | Owner:
Type: bug | Status: new
Priority: normal | Milestone:
Component: Compiler | Version: 7.0.1
Keywords: | Testcase:
Blockedby: | Difficulty:
Os: Unknown/Multiple | Blocking:
Architecture: Unknown/Multiple | Failure: None/Unknown
---------------------------------+------------------------------------------
Changes (by tibbe):
* cc: johan.tibell@… (added)
Comment:
Ignore the comment about using the static argument transformation. This
was just my guess at a solution to the problem (get rid of the
continuation being passed around by hoisting it.)
I have a more realistic test case (taken directly from
`Data.Binary.Builder` and then simplified). Your one line fix doesn't work
here.
{{{
module Repro2 (test) where
import qualified Data.ByteString as S
import Data.ByteString.Internal (inlinePerformIO)
import Data.Monoid
import Foreign
newtype Builder = Builder {
runBuilder :: (Buffer -> [S.ByteString]) -> Buffer ->
[S.ByteString]
}
instance Monoid Builder where
mempty = empty
{-# INLINE mempty #-}
mappend = append
{-# INLINE mappend #-}
mconcat = foldr mappend mempty
{-# INLINE mconcat #-}
empty :: Builder
empty = Builder (\ k b -> k b)
{-# INLINE empty #-}
singleton :: Word8 -> Builder
singleton = writeN 1 . flip poke
{-# INLINE singleton #-}
append :: Builder -> Builder -> Builder
append (Builder f) (Builder g) = Builder (f . g)
{-# INLINE append #-}
-- Our internal buffer type
data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8)
{-# UNPACK #-} !Int -- offset
{-# UNPACK #-} !Int -- used bytes
{-# UNPACK #-} !Int -- length left
-- | Sequence an IO operation on the buffer
unsafeLiftIO :: (Buffer -> IO Buffer) -> Builder
unsafeLiftIO f = Builder $ \ k buf -> inlinePerformIO $ do
buf' <- f buf
return (k buf')
{-# INLINE unsafeLiftIO #-}
-- | Get the size of the buffer
withSize :: (Int -> Builder) -> Builder
withSize f = Builder $ \ k buf@(Buffer _ _ _ l) -> runBuilder (f l) k buf
-- | Ensure that there are at least @n@ many bytes available.
ensureFree :: Int -> Builder
ensureFree n = n `seq` withSize $ \ l ->
if n <= l then empty
else error "Stub implementation: not enough space in buffer"
{-# INLINE ensureFree #-}
-- | Ensure that @n@ many bytes are available, and then use @f@ to write
some
-- bytes into the memory.
writeN :: Int -> (Ptr Word8 -> IO ()) -> Builder
writeN n f = ensureFree n `append` unsafeLiftIO (writeNBuffer n f)
{-# INLINE writeN #-}
writeNBuffer :: Int -> (Ptr Word8 -> IO ()) -> Buffer -> IO Buffer
writeNBuffer n f (Buffer fp o u l) = do
withForeignPtr fp (\p -> f (p `plusPtr` (o+u)))
return (Buffer fp o (u+n) (l-n))
{-# INLINE writeNBuffer #-}
-- Test case
fromWord8s :: [Word8] -> Builder
fromWord8s [] = mempty
fromWord8s (x:xs) = singleton x `mappend` fromWord8s xs
word8s :: [Word8]
word8s = replicate 10000 97
{-# NOINLINE word8s #-}
test :: Builder
test = fromWord8s word8s
}}}
The problem is the same as before (a continuation getting in the way of a
simple loop). In particular, lets look at the core for `fromWord8s`:
{{{
fromWord8s [Occ=LoopBreaker]
:: [Word8] -> Builder
[GblId, Arity=1, Str=DmdType S]
fromWord8s =
\ (ys :: [Word8]) ->
case ys of _ {
[] -> empty;
: x xs ->
let {
a1_sTE [Dmd=Just L] :: Builder
[LclId, Str=DmdType]
a1_sTE = fromWord8s xs } in
(\ (x2_aN1
:: Buffer -> [Data.ByteString.Internal.ByteString]) ->
let {
k [Dmd=Just L]
:: Buffer -> [Data.ByteString.Internal.ByteString]
[LclId, Str=DmdType]
k = (a1_sTE `cast` ...) x2_aN1 } in
\ (buf :: Buffer) ->
case buf
of _ { Buffer addr# rb1_dMB o u l ->
case <=# 1 l of _ { -- Buffer size check
False -> lvl1_rU0 `cast` ...;
True ->
case x of _ { W8# x# ->
case writeWord8OffAddr#
@ RealWorld
(plusAddr# addr# (+# o u))
0
x#
realWorld#
of s2_aQv { __DEFAULT ->
case touch#
@ ForeignPtrContents rb1_dMB s2_aQv
of _ { __DEFAULT ->
k
(Buffer
addr#
rb1_dMB
o
(+# u 1)
(-# l 1))
}
}
}
}
})
`cast` ...
}
}}}
I don't understand why `empty` hasn't been inlined here and whether that's
the reason we don't get a nice loop.
This particular performance bug is pretty important. The performance
difference is almost 10x in my benchmarks, which are quite representative
for what people actually use the package for (e.g. serializing lists of
things).
--
Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/4978#comment:2>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
_______________________________________________
Glasgow-haskell-bugs mailing list
[email protected]
http://www.haskell.org/mailman/listinfo/glasgow-haskell-bugs