[Haskell-cafe] dangerous inlinePerformIO in Data.Binary(?)

Duncan Coutts duncan.coutts at worc.ox.ac.uk
Fri Jun 15 01:46:11 EDT 2007


On Fri, 2007-06-15 at 01:03 +0200, Udo Stenzel wrote:
> Greetings,
> 
> I was trying to understand the magic inside Data.Binary, and found two
> somewhat suspicious uses of inlinePerformIO, which imho has a far too
> innocuous name:

It's not really supposed to be a public api. We've decided to rename
Data.ByteString.Base to .Internal to make that fact clearer.

> | toLazyByteString :: Builder -> L.ByteString
> | toLazyByteString m = S.LPS $ inlinePerformIO $ do
> |     buf <- newBuffer defaultSize
> |     return (runBuilder (m `append` flush) (const []) buf)
> 
> Why is this safe?  Considering the GHC implementation of IO, isn't there
> a real danger that 'newBuffer defaultSize' is floated out and therefore
> every invocation of 'toLazyByteString' starts out with the same buffer?
> Isn't that exactly the reason why unsafePerformIO and runST are declared
> NOINLINE?

Yes, that seems very suspicious to me.

> The other occurence is:
> 
> | unsafeLiftIO :: (Buffer -> IO Buffer) -> Builder
> | unsafeLiftIO f =  Builder $ \ k buf -> inlinePerformIO $ do
> |     buf' <- f buf
> |     return (k buf')
> 
> which might be safe, since 'f buf' cannot float out of the lambda which
> binds 'buf', but still, all this stuff is inlined, something constant
> might get plugged in the place of buf and the result might be floated
> out to give us an even harder to find Heisenbug.

I think this is safe because of the continuation k. In the Builder
monoid, the continuation takes the place of the IO state token that gets
single threaded through everything.

Duncan



More information about the Haskell-Cafe mailing list