-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid per-byte loop in cstring{,Utf8} builders #569
base: master
Are you sure you want to change the base?
Changes from all commits
7f1aca0
01b5f36
3ce0346
f58840b
d674964
b297904
e1aab36
2603009
cd02c61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,5 +1,5 @@ | ||||||
{-# LANGUAGE ScopedTypeVariables, CPP, BangPatterns, RankNTypes, TupleSections #-} | ||||||
{-# LANGUAGE Unsafe #-} | ||||||
{-# LANGUAGE MagicHash, ViewPatterns, Unsafe #-} | ||||||
{-# OPTIONS_HADDOCK not-home #-} | ||||||
-- | Copyright : (c) 2010 - 2011 Simon Meier | ||||||
-- License : BSD3-style (see LICENSE) | ||||||
|
@@ -84,6 +84,8 @@ module Data.ByteString.Builder.Internal ( | |||||
-- , sizedChunksInsert | ||||||
|
||||||
, byteStringCopy | ||||||
, asciiLiteralCopy | ||||||
, modUtf8LitCopy | ||||||
, byteStringInsert | ||||||
, byteStringThreshold | ||||||
|
||||||
|
@@ -127,6 +129,8 @@ module Data.ByteString.Builder.Internal ( | |||||
) where | ||||||
|
||||||
import Control.Arrow (second) | ||||||
import Control.Monad (when) | ||||||
import Control.DeepSeq (NFData(..)) | ||||||
|
||||||
import Data.Semigroup (Semigroup(..)) | ||||||
import Data.List.NonEmpty (NonEmpty(..)) | ||||||
|
@@ -146,6 +150,11 @@ import Foreign | |||||
import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr) | ||||||
import System.IO.Unsafe (unsafeDupablePerformIO) | ||||||
|
||||||
#if !(PURE_HASKELL || defined(USE_MEMCHR)) | ||||||
import Foreign.C.String (CString) | ||||||
import GHC.Ptr (Ptr(..)) | ||||||
#endif | ||||||
|
||||||
------------------------------------------------------------------------------ | ||||||
-- Buffers | ||||||
------------------------------------------------------------------------------ | ||||||
|
@@ -154,11 +163,22 @@ import System.IO.Unsafe (unsafeDupablePerformIO) | |||||
data BufferRange = BufferRange {-# UNPACK #-} !(Ptr Word8) -- First byte of range | ||||||
{-# UNPACK #-} !(Ptr Word8) -- First byte /after/ range | ||||||
|
||||||
-- | @since 0.12.1.0 | ||||||
instance NFData BufferRange where | ||||||
rnf !_ = () | ||||||
|
||||||
-- | A 'Buffer' together with the 'BufferRange' of free bytes. The filled | ||||||
-- space starts at offset 0 and ends at the first free byte. | ||||||
data Buffer = Buffer {-# UNPACK #-} !(ForeignPtr Word8) | ||||||
{-# UNPACK #-} !BufferRange | ||||||
|
||||||
-- | Like the @NFData@ instance for @StrictByteString@, | ||||||
-- this does not force the @ForeignPtrContents@ field | ||||||
-- of the underlying @ForeignPtr@. | ||||||
-- | ||||||
-- @since 0.12.1.0 | ||||||
instance NFData Buffer where | ||||||
rnf !_ = () | ||||||
|
||||||
-- | Combined size of the filled and free space in the buffer. | ||||||
{-# INLINE bufferSize #-} | ||||||
|
@@ -876,6 +896,93 @@ byteStringInsert :: S.StrictByteString -> Builder | |||||
byteStringInsert = | ||||||
\bs -> builder $ \k (BufferRange op _) -> return $ insertChunk op bs k | ||||||
|
||||||
|
||||||
------------------------------------------------------------------------------ | ||||||
-- Raw CString encoding | ||||||
------------------------------------------------------------------------------ | ||||||
|
||||||
-- | Builder for raw 'Addr#' pointers to null-terminated primitive ASCII | ||||||
-- strings that are free of embedded (overlong-encoded as the two-byte sequence | ||||||
-- @0xC0 0x80@) null characters. | ||||||
-- | ||||||
-- @since 0.12.1.0 | ||||||
{-# INLINABLE asciiLiteralCopy #-} | ||||||
asciiLiteralCopy :: Ptr Word8 -> Int -> Builder | ||||||
asciiLiteralCopy = \ !ip !len -> builder $ \k br -> do | ||||||
let !ipe = ip `plusPtr` len | ||||||
wrappedBytesCopyStep (BufferRange ip ipe) k br | ||||||
|
||||||
getNextEmbeddedNull :: Ptr Word8 -> Int -> IO (Ptr Word8) | ||||||
#if PURE_HASKELL || defined(USE_MEMCHR) | ||||||
getNextEmbeddedNull p len = do | ||||||
c0loc <- S.memchr p 0xC0 (S.checkedCast len) | ||||||
if c0loc == nullPtr | ||||||
then pure c0loc | ||||||
else do | ||||||
let nextLoc = c0loc `plusPtr` 1 :: Ptr Word8 | ||||||
nextByte <- peek nextLoc | ||||||
if nextByte == 0x80 | ||||||
then pure c0loc | ||||||
else getNextEmbeddedNull nextLoc (p `minusPtr` nextLoc + len) | ||||||
|
||||||
#else | ||||||
getNextEmbeddedNull p _len = c_strstr (castPtr p) modifiedUtf8NUL | ||||||
|
||||||
-- | GHC represents @NUL@ in string literals via an overlong 2-byte encoding, | ||||||
-- which is part of "modified UTF-8" (GHC does not also implement CESU-8). | ||||||
modifiedUtf8NUL :: CString | ||||||
modifiedUtf8NUL = Ptr "\xc0\x80"# | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Let's keep the prefix consistent. |
||||||
|
||||||
foreign import ccall unsafe "string.h strstr" c_strstr | ||||||
:: CString -> CString -> IO (Ptr Word8) | ||||||
#endif | ||||||
|
||||||
|
||||||
-- | Builder for raw 'Addr#' pointers to null-terminated primitive UTF-8 | ||||||
-- encoded strings that may contain embedded overlong-encodings (as the | ||||||
-- two-byte sequence @0xC0 0x80@) of null characters. | ||||||
-- | ||||||
-- @since 0.12.1.0 | ||||||
{-# INLINABLE modUtf8LitCopy #-} | ||||||
modUtf8LitCopy :: Ptr Word8 -> Int -> Builder | ||||||
modUtf8LitCopy = \ !ip !len -> builder $ \k br -> do | ||||||
nullAt <- getNextEmbeddedNull ip len | ||||||
modUtf8_step ip len nullAt k br | ||||||
|
||||||
modUtf8_step :: Ptr Word8 -> Int -> Ptr Word8 -> BuildStep r -> BuildStep r | ||||||
modUtf8_step !ip !len ((== nullPtr) -> True) k br = | ||||||
-- Contains no encoded nulls, use simple copy codepath | ||||||
wrappedBytesCopyStep (BufferRange ip ipe) k br | ||||||
where | ||||||
!ipe = ip `plusPtr` len | ||||||
modUtf8_step !ip !len !nullAt k (BufferRange op0 ope) | ||||||
-- Copy as much of the null-free portion of the string as fits into the | ||||||
-- available buffer space. If the string is long enough, we may have asked | ||||||
-- for less than its full length, filling the buffer with the rest will go | ||||||
-- into the next builder step. | ||||||
| avail > nullFree = do | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you please check with |
||||||
when (nullFree > 0) (copyBytes op0 ip nullFree) | ||||||
clyring marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
pokeElemOff op0 nullFree 0 | ||||||
let used = nullFree + 2 | ||||||
len' = len - used | ||||||
!ip' = ip `plusPtr` used | ||||||
!op' = op0 `plusPtr` (nullFree + 1) | ||||||
nullAt' <- getNextEmbeddedNull ip' len' | ||||||
modUtf8_step ip' len' nullAt' k (BufferRange op' ope) | ||||||
| avail > 0 = do | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question, but also |
||||||
-- avail <= nullFree | ||||||
copyBytes op0 ip avail | ||||||
let len' = len - avail | ||||||
!ip' = ip `plusPtr` avail | ||||||
!op' = op0 `plusPtr` avail | ||||||
return $ bufferFull 1 op' (modUtf8_step ip' len' nullAt k) | ||||||
| otherwise = | ||||||
return $ bufferFull 1 op0 (modUtf8_step ip len nullAt k) | ||||||
where | ||||||
!avail = ope `minusPtr` op0 | ||||||
!nullFree = nullAt `minusPtr` ip | ||||||
|
||||||
|
||||||
-- Short bytestrings | ||||||
------------------------------------------------------------------------------ | ||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency with
asciiLiteralCopy
(or we might as well chose to useLit
for both)