{-# LANGUAGE BangPatterns, CPP, MagicHash, UnboxedTuples,
GeneralizedNewtypeDeriving #-}
{-# OPTIONS_GHC -O2 -funbox-strict-fields #-}
module FastString
(
fastStringToByteString,
mkFastStringByteString,
fastZStringToByteString,
unsafeMkByteString,
hashByteString,
FastZString,
hPutFZS,
zString,
lengthFZS,
FastString(..),
fsLit,
mkFastString,
mkFastStringBytes,
mkFastStringByteList,
mkFastStringForeignPtr,
mkFastString#,
unpackFS,
bytesFS,
zEncodeFS,
uniqueOfFS,
lengthFS,
nullFS,
appendFS,
headFS,
tailFS,
concatFS,
consFS,
nilFS,
hPutFS,
getFastStringTable,
hasZEncoding,
LitString (..),
sLit,
mkLitString#,
mkLitString,
unpackLitString,
lengthLS
) where
#include "HsVersions.h"
import GhcPrelude as Prelude
import Encoding
import FastFunctions
import Panic
import Util
import Control.DeepSeq
import Control.Monad
import Data.ByteString (ByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString.Internal as BS
import qualified Data.ByteString.Unsafe as BS
import Foreign.C
import GHC.Exts
import System.IO
import System.IO.Unsafe ( unsafePerformIO )
import Data.Data
import Data.IORef ( IORef, newIORef, readIORef, atomicModifyIORef' )
import Data.Maybe ( isJust )
import Data.Char
import Data.List ( elemIndex )
import Data.Semigroup as Semi
import GHC.IO ( IO(..), unsafeDupablePerformIO )
import Foreign
#if STAGE >= 2
import GHC.Conc.Sync (sharedCAF)
#endif
import GHC.Base ( unpackCString#, unpackNBytes# )
#define hASH_TBL_SIZE 4091
#define hASH_TBL_SIZE_UNBOXED 4091#
fastStringToByteString :: FastString -> ByteString
fastStringToByteString f = fs_bs f
fastZStringToByteString :: FastZString -> ByteString
fastZStringToByteString (FastZString bs) = bs
unsafeMkByteString :: String -> ByteString
unsafeMkByteString = BSC.pack
hashByteString :: ByteString -> Int
hashByteString bs
= inlinePerformIO $ BS.unsafeUseAsCStringLen bs $ \(ptr, len) ->
return $ hashStr (castPtr ptr) len
newtype FastZString = FastZString ByteString
deriving NFData
hPutFZS :: Handle -> FastZString -> IO ()
hPutFZS handle (FastZString bs) = BS.hPut handle bs
zString :: FastZString -> String
zString (FastZString bs) =
inlinePerformIO $ BS.unsafeUseAsCStringLen bs peekCAStringLen
lengthFZS :: FastZString -> Int
lengthFZS (FastZString bs) = BS.length bs
mkFastZStringString :: String -> FastZString
mkFastZStringString str = FastZString (BSC.pack str)
data FastString = FastString {
uniq :: {-# UNPACK #-} !Int,
n_chars :: {-# UNPACK #-} !Int,
fs_bs :: {-# UNPACK #-} !ByteString,
fs_ref :: {-# UNPACK #-} !(IORef (Maybe FastZString))
}
instance Eq FastString where
f1 == f2 = uniq f1 == uniq f2
instance Ord FastString where
a <= b = case cmpFS a b of { LT -> True; EQ -> True; GT -> False }
a < b = case cmpFS a b of { LT -> True; EQ -> False; GT -> False }
a >= b = case cmpFS a b of { LT -> False; EQ -> True; GT -> True }
a > b = case cmpFS a b of { LT -> False; EQ -> False; GT -> True }
max x y | x >= y = x
| otherwise = y
min x y | x <= y = x
| otherwise = y
compare a b = cmpFS a b
instance IsString FastString where
fromString = fsLit
instance Semi.Semigroup FastString where
(<>) = appendFS
instance Monoid FastString where
mempty = nilFS
mappend = (Semi.<>)
mconcat = concatFS
instance Show FastString where
show fs = show (unpackFS fs)
instance Data FastString where
toConstr _ = abstractConstr "FastString"
gunfold _ _ = error "gunfold"
dataTypeOf _ = mkNoRepType "FastString"
cmpFS :: FastString -> FastString -> Ordering
cmpFS f1@(FastString u1 _ _ _) f2@(FastString u2 _ _ _) =
if u1 == u2 then EQ else
compare (fastStringToByteString f1) (fastStringToByteString f2)
foreign import ccall unsafe "memcmp"
memcmp :: Ptr a -> Ptr b -> Int -> IO Int
data FastStringTable =
FastStringTable
{-# UNPACK #-} !(IORef Int)
(MutableArray# RealWorld (IORef [FastString]))
string_table :: FastStringTable
{-# NOINLINE string_table #-}
string_table = unsafePerformIO $ do
uid <- newIORef 603979776
tab <- IO $ \s1# -> case newArray# hASH_TBL_SIZE_UNBOXED (panic "string_table") s1# of
(# s2#, arr# #) ->
(# s2#, FastStringTable uid arr# #)
forM_ [0.. hASH_TBL_SIZE-1] $ \i -> do
bucket <- newIORef []
updTbl tab i bucket
#if STAGE < 2
return tab
#else
sharedCAF tab getOrSetLibHSghcFastStringTable
foreign import ccall unsafe "getOrSetLibHSghcFastStringTable"
getOrSetLibHSghcFastStringTable :: Ptr a -> IO (Ptr a)
#endif
lookupTbl :: FastStringTable -> Int -> IO (IORef [FastString])
lookupTbl (FastStringTable _ arr#) (I# i#) =
IO $ \ s# -> readArray# arr# i# s#
updTbl :: FastStringTable -> Int -> IORef [FastString] -> IO ()
updTbl (FastStringTable _uid arr#) (I# i#) ls = do
(IO $ \ s# -> case writeArray# arr# i# ls s# of { s2# -> (# s2#, () #) })
mkFastString# :: Addr# -> FastString
mkFastString# a# = mkFastStringBytes ptr (ptrStrLength ptr)
where ptr = Ptr a#
mkFastStringWith :: (Int -> IO FastString) -> Ptr Word8 -> Int -> IO FastString
mkFastStringWith mk_fs !ptr !len = do
let hash = hashStr ptr len
bucket <- lookupTbl string_table hash
ls1 <- readIORef bucket
res <- bucket_match ls1 len ptr
case res of
Just v -> return v
Nothing -> do
n <- get_uid
new_fs <- mk_fs n
atomicModifyIORef' bucket $ \ls2 ->
let delta_ls = case ls1 of
[] -> ls2
l:_ -> case l `elemIndex` ls2 of
Nothing -> panic "mkFastStringWith"
Just idx -> take idx ls2
in case inlinePerformIO (bucket_match delta_ls len ptr) of
Nothing -> (new_fs:ls2, new_fs)
Just fs -> (ls2,fs)
where
!(FastStringTable uid _arr) = string_table
get_uid = atomicModifyIORef' uid $ \n -> (n+1,n)
mkFastStringBytes :: Ptr Word8 -> Int -> FastString
mkFastStringBytes !ptr !len =
unsafeDupablePerformIO $
mkFastStringWith (copyNewFastString ptr len) ptr len
mkFastStringForeignPtr :: Ptr Word8 -> ForeignPtr Word8 -> Int -> IO FastString
mkFastStringForeignPtr ptr !fp len
= mkFastStringWith (mkNewFastString fp ptr len) ptr len
mkFastStringByteString :: ByteString -> FastString
mkFastStringByteString bs =
inlinePerformIO $
BS.unsafeUseAsCStringLen bs $ \(ptr, len) -> do
let ptr' = castPtr ptr
mkFastStringWith (mkNewFastStringByteString bs ptr' len) ptr' len
mkFastString :: String -> FastString
mkFastString str =
inlinePerformIO $ do
let l = utf8EncodedLength str
buf <- mallocForeignPtrBytes l
withForeignPtr buf $ \ptr -> do
utf8EncodeString ptr str
mkFastStringForeignPtr ptr buf l
mkFastStringByteList :: [Word8] -> FastString
mkFastStringByteList str =
inlinePerformIO $ do
let l = Prelude.length str
buf <- mallocForeignPtrBytes l
withForeignPtr buf $ \ptr -> do
pokeArray (castPtr ptr) str
mkFastStringForeignPtr ptr buf l
mkZFastString :: String -> FastZString
mkZFastString = mkFastZStringString
bucket_match :: [FastString] -> Int -> Ptr Word8 -> IO (Maybe FastString)
bucket_match [] _ _ = return Nothing
bucket_match (v@(FastString _ _ bs _):ls) len ptr
| len == BS.length bs = do
b <- BS.unsafeUseAsCString bs $ \buf ->
cmpStringPrefix ptr (castPtr buf) len
if b then return (Just v)
else bucket_match ls len ptr
| otherwise =
bucket_match ls len ptr
mkNewFastString :: ForeignPtr Word8 -> Ptr Word8 -> Int -> Int
-> IO FastString
mkNewFastString fp ptr len uid = do
ref <- newIORef Nothing
n_chars <- countUTF8Chars ptr len
return (FastString uid n_chars (BS.fromForeignPtr fp 0 len) ref)
mkNewFastStringByteString :: ByteString -> Ptr Word8 -> Int -> Int
-> IO FastString
mkNewFastStringByteString bs ptr len uid = do
ref <- newIORef Nothing
n_chars <- countUTF8Chars ptr len
return (FastString uid n_chars bs ref)
copyNewFastString :: Ptr Word8 -> Int -> Int -> IO FastString
copyNewFastString ptr len uid = do
fp <- copyBytesToForeignPtr ptr len
ref <- newIORef Nothing
n_chars <- countUTF8Chars ptr len
return (FastString uid n_chars (BS.fromForeignPtr fp 0 len) ref)
copyBytesToForeignPtr :: Ptr Word8 -> Int -> IO (ForeignPtr Word8)
copyBytesToForeignPtr ptr len = do
fp <- mallocForeignPtrBytes len
withForeignPtr fp $ \ptr' -> copyBytes ptr' ptr len
return fp
cmpStringPrefix :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool
cmpStringPrefix ptr1 ptr2 len =
do r <- memcmp ptr1 ptr2 len
return (r == 0)
hashStr :: Ptr Word8 -> Int -> Int
hashStr (Ptr a#) (I# len#) = loop 0# 0#
where
loop h n | isTrue# (n ==# len#) = I# h
| otherwise = loop h2 (n +# 1#)
where !c = ord# (indexCharOffAddr# a# n)
!h2 = (c +# (h *# 128#)) `remInt#`
hASH_TBL_SIZE#
lengthFS :: FastString -> Int
lengthFS f = n_chars f
hasZEncoding :: FastString -> Bool
hasZEncoding (FastString _ _ _ ref) =
inlinePerformIO $ do
m <- readIORef ref
return (isJust m)
nullFS :: FastString -> Bool
nullFS f = BS.null (fs_bs f)
unpackFS :: FastString -> String
unpackFS (FastString _ _ bs _) = utf8DecodeByteString bs
bytesFS :: FastString -> [Word8]
bytesFS fs = BS.unpack $ fastStringToByteString fs
zEncodeFS :: FastString -> FastZString
zEncodeFS fs@(FastString _ _ _ ref) =
inlinePerformIO $ do
m <- readIORef ref
case m of
Just zfs -> return zfs
Nothing -> do
atomicModifyIORef' ref $ \m' -> case m' of
Nothing -> let zfs = mkZFastString (zEncodeString (unpackFS fs))
in (Just zfs, zfs)
Just zfs -> (m', zfs)
appendFS :: FastString -> FastString -> FastString
appendFS fs1 fs2 = mkFastStringByteString
$ BS.append (fastStringToByteString fs1)
(fastStringToByteString fs2)
concatFS :: [FastString] -> FastString
concatFS = mkFastStringByteString . BS.concat . map fs_bs
headFS :: FastString -> Char
headFS (FastString _ 0 _ _) = panic "headFS: Empty FastString"
headFS (FastString _ _ bs _) =
inlinePerformIO $ BS.unsafeUseAsCString bs $ \ptr ->
return (fst (utf8DecodeChar (castPtr ptr)))
tailFS :: FastString -> FastString
tailFS (FastString _ 0 _ _) = panic "tailFS: Empty FastString"
tailFS (FastString _ _ bs _) =
inlinePerformIO $ BS.unsafeUseAsCString bs $ \ptr ->
do let (_, n) = utf8DecodeChar (castPtr ptr)
return $! mkFastStringByteString (BS.drop n bs)
consFS :: Char -> FastString -> FastString
consFS c fs = mkFastString (c : unpackFS fs)
uniqueOfFS :: FastString -> Int
uniqueOfFS (FastString u _ _ _) = u
nilFS :: FastString
nilFS = mkFastString ""
getFastStringTable :: IO [[FastString]]
getFastStringTable = do
buckets <- forM [0.. hASH_TBL_SIZE-1] $ \idx -> do
bucket <- lookupTbl string_table idx
readIORef bucket
return buckets
hPutFS :: Handle -> FastString -> IO ()
hPutFS handle fs = BS.hPut handle $ fastStringToByteString fs
data LitString = LitString !(Ptr Word8) !Int
mkLitString# :: Addr# -> LitString
mkLitString# a# = LitString (Ptr a#) (ptrStrLength (Ptr a#))
{-# INLINE mkLitString #-}
mkLitString :: String -> LitString
mkLitString s =
unsafePerformIO (do
let len = length s
p <- mallocBytes len
let
loop :: Int -> String -> IO ()
loop !_ [] = return ()
loop n (c:cs) = do
pokeByteOff p n (fromIntegral (ord c) :: Word8)
loop (1+n) cs
loop 0 s
return (LitString p len)
)
unpackLitString :: LitString -> String
unpackLitString (LitString (Ptr p#) (I# n#)) = unpackNBytes# p# n#
lengthLS :: LitString -> Int
lengthLS (LitString _ n) = n
foreign import ccall unsafe "strlen"
ptrStrLength :: Ptr Word8 -> Int
{-# NOINLINE sLit #-}
sLit :: String -> LitString
sLit x = mkLitString x
{-# NOINLINE fsLit #-}
fsLit :: String -> FastString
fsLit x = mkFastString x
{-# RULES "slit"
forall x . sLit (unpackCString# x) = mkLitString# x #-}
{-# RULES "fslit"
forall x . fsLit (unpackCString# x) = mkFastString# x #-}