module GHC.Data.FastString
(
bytesFS,
fastStringToByteString,
mkFastStringByteString,
fastZStringToByteString,
unsafeMkByteString,
fastStringToShortByteString,
mkFastStringShortByteString,
FastZString,
hPutFZS,
zString,
lengthFZS,
FastString(..),
NonDetFastString (..),
LexicalFastString (..),
fsLit,
mkFastString,
mkFastStringBytes,
mkFastStringByteList,
mkFastString#,
unpackFS,
unconsFS,
zEncodeFS,
uniqueOfFS,
lengthFS,
nullFS,
appendFS,
headFS,
concatFS,
consFS,
nilFS,
isUnderscoreFS,
lexicalCompareFS,
uniqCompareFS,
hPutFS,
getFastStringTable,
getFastStringZEncCounter,
PtrString (..),
sLit,
mkPtrString#,
mkPtrString,
unpackPtrString,
lengthPS
) where
#include "HsVersions.h"
import GHC.Prelude as Prelude
import GHC.Utils.Encoding
import GHC.Utils.IO.Unsafe
import GHC.Utils.Panic.Plain
import GHC.Utils.Misc
import GHC.Data.FastMutInt
import Control.Concurrent.MVar
import Control.DeepSeq
import Control.Monad
import Data.ByteString (ByteString)
import Data.ByteString.Short (ShortByteString)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import qualified Data.ByteString.Unsafe as BS
import qualified Data.ByteString.Short as SBS
#if !MIN_VERSION_bytestring(0,11,0)
import qualified Data.ByteString.Short.Internal as SBS
#endif
import Foreign.C
import System.IO
import Data.Data
import Data.IORef
import Data.Char
import Data.Semigroup as Semi
import Foreign
#if GHC_STAGE >= 2
import GHC.Conc.Sync (sharedCAF)
#endif
#if __GLASGOW_HASKELL__ < 811
import GHC.Base (unpackCString#,unpackNBytes#)
#endif
import GHC.Exts
import GHC.IO
bytesFS, fastStringToByteString :: FastString -> ByteString
bytesFS = fastStringToByteString
fastStringToByteString f = SBS.fromShort $ fs_sbs f
fastStringToShortByteString :: FastString -> ShortByteString
fastStringToShortByteString = fs_sbs
fastZStringToByteString :: FastZString -> ByteString
fastZStringToByteString (FastZString bs) = bs
unsafeMkByteString :: String -> ByteString
unsafeMkByteString = BSC.pack
hashFastString :: FastString -> Int
hashFastString fs = hashStr $ fs_sbs fs
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 :: !Int,
n_chars :: !Int,
fs_sbs :: !ShortByteString,
fs_zenc :: FastZString
}
instance Eq FastString where
f1 == f2 = uniq f1 == uniq f2
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"
instance NFData FastString where
rnf fs = seq fs ()
lexicalCompareFS :: FastString -> FastString -> Ordering
lexicalCompareFS fs1 fs2 =
if uniq fs1 == uniq fs2 then EQ else
utf8CompareShortByteString (fs_sbs fs1) (fs_sbs fs2)
uniqCompareFS :: FastString -> FastString -> Ordering
uniqCompareFS fs1 fs2 = compare (uniq fs1) (uniq fs2)
newtype NonDetFastString
= NonDetFastString FastString
deriving (Eq,Data)
instance Ord NonDetFastString where
compare (NonDetFastString fs1) (NonDetFastString fs2) = uniqCompareFS fs1 fs2
instance Show NonDetFastString where
show (NonDetFastString fs) = show fs
newtype LexicalFastString
= LexicalFastString FastString
deriving (Eq,Data)
instance Ord LexicalFastString where
compare (LexicalFastString fs1) (LexicalFastString fs2) = lexicalCompareFS fs1 fs2
instance Show LexicalFastString where
show (LexicalFastString fs) = show fs
data FastStringTable = FastStringTable
!FastMutInt
!FastMutInt
(Array# (IORef FastStringTableSegment))
data FastStringTableSegment = FastStringTableSegment
!(MVar ())
!FastMutInt
(MutableArray# RealWorld [FastString])
segmentBits, numSegments, segmentMask, initialNumBuckets :: Int
segmentBits = 8
numSegments = 256
segmentMask = 0xff
initialNumBuckets = 64
hashToSegment# :: Int# -> Int#
hashToSegment# hash# = hash# `andI#` segmentMask#
where
!(I# segmentMask#) = segmentMask
hashToIndex# :: MutableArray# RealWorld [FastString] -> Int# -> Int#
hashToIndex# buckets# hash# =
(hash# `uncheckedIShiftRL#` segmentBits#) `remInt#` size#
where
!(I# segmentBits#) = segmentBits
size# = sizeofMutableArray# buckets#
maybeResizeSegment :: IORef FastStringTableSegment -> IO FastStringTableSegment
maybeResizeSegment segmentRef = do
segment@(FastStringTableSegment lock counter old#) <- readIORef segmentRef
let oldSize# = sizeofMutableArray# old#
newSize# = oldSize# *# 2#
(I# n#) <- readFastMutInt counter
if isTrue# (n# <# newSize#)
then return segment
else do
resizedSegment@(FastStringTableSegment _ _ new#) <- IO $ \s1# ->
case newArray# newSize# [] s1# of
(# s2#, arr# #) -> (# s2#, FastStringTableSegment lock counter arr# #)
forM_ [0 .. (I# oldSize#) 1] $ \(I# i#) -> do
fsList <- IO $ readArray# old# i#
forM_ fsList $ \fs -> do
let
!(I# hash#) = hashFastString fs
idx# = hashToIndex# new# hash#
IO $ \s1# ->
case readArray# new# idx# s1# of
(# s2#, bucket #) -> case writeArray# new# idx# (fs: bucket) s2# of
s3# -> (# s3#, () #)
writeIORef segmentRef resizedSegment
return resizedSegment
stringTable :: FastStringTable
stringTable = unsafePerformIO $ do
let !(I# numSegments#) = numSegments
!(I# initialNumBuckets#) = initialNumBuckets
loop a# i# s1#
| isTrue# (i# ==# numSegments#) = s1#
| otherwise = case newMVar () `unIO` s1# of
(# s2#, lock #) -> case newFastMutInt 0 `unIO` s2# of
(# s3#, counter #) -> case newArray# initialNumBuckets# [] s3# of
(# s4#, buckets# #) -> case newIORef
(FastStringTableSegment lock counter buckets#) `unIO` s4# of
(# s5#, segment #) -> case writeArray# a# i# segment s5# of
s6# -> loop a# (i# +# 1#) s6#
uid <- newFastMutInt 603979776
n_zencs <- newFastMutInt 0
tab <- IO $ \s1# ->
case newArray# numSegments# (panic "string_table") s1# of
(# s2#, arr# #) -> case loop arr# 0# s2# of
s3# -> case unsafeFreezeArray# arr# s3# of
(# s4#, segments# #) ->
(# s4#, FastStringTable uid n_zencs segments# #)
#if GHC_STAGE < 2
return tab
#else
sharedCAF tab getOrSetLibHSghcFastStringTable
foreign import ccall unsafe "getOrSetLibHSghcFastStringTable"
getOrSetLibHSghcFastStringTable :: Ptr a -> IO (Ptr a)
#endif
mkFastString# :: Addr# -> FastString
mkFastString# a# = mkFastStringBytes ptr (ptrStrLength ptr)
where ptr = Ptr a#
mkFastStringWith
:: (Int -> FastMutInt-> IO FastString) -> ShortByteString -> IO FastString
mkFastStringWith mk_fs sbs = do
FastStringTableSegment lock _ buckets# <- readIORef segmentRef
let idx# = hashToIndex# buckets# hash#
bucket <- IO $ readArray# buckets# idx#
res <- bucket_match bucket sbs
case res of
Just found -> return found
Nothing -> do
noDuplicate
n <- get_uid
new_fs <- mk_fs n n_zencs
withMVar lock $ \_ -> insert new_fs
where
!(FastStringTable uid n_zencs segments#) = stringTable
get_uid = atomicFetchAddFastMut uid 1
!(I# hash#) = hashStr sbs
(# segmentRef #) = indexArray# segments# (hashToSegment# hash#)
insert fs = do
FastStringTableSegment _ counter buckets# <- maybeResizeSegment segmentRef
let idx# = hashToIndex# buckets# hash#
bucket <- IO $ readArray# buckets# idx#
res <- bucket_match bucket sbs
case res of
Just found -> return found
Nothing -> do
IO $ \s1# ->
case writeArray# buckets# idx# (fs : bucket) s1# of
s2# -> (# s2#, () #)
_ <- atomicFetchAddFastMut counter 1
return fs
bucket_match :: [FastString] -> ShortByteString -> IO (Maybe FastString)
bucket_match [] _ = return Nothing
bucket_match (fs@(FastString {fs_sbs=fs_sbs}) : ls) sbs
| fs_sbs == sbs = return (Just fs)
| otherwise = bucket_match ls sbs
mkFastStringBytes :: Ptr Word8 -> Int -> FastString
mkFastStringBytes !ptr !len =
unsafeDupablePerformIO $ do
sbs <- newSBSFromPtr ptr len
mkFastStringWith (mkNewFastStringShortByteString sbs) sbs
newSBSFromPtr :: Ptr a -> Int -> IO ShortByteString
newSBSFromPtr (Ptr src#) (I# len#) =
IO $ \s ->
case newByteArray# len# s of { (# s, dst# #) ->
case copyAddrToByteArray# src# dst# 0# len# s of { s ->
case unsafeFreezeByteArray# dst# s of { (# s, ba# #) ->
(# s, SBS.SBS ba# #) }}}
mkFastStringByteString :: ByteString -> FastString
mkFastStringByteString bs =
let sbs = SBS.toShort bs in
inlinePerformIO $
mkFastStringWith (mkNewFastStringShortByteString sbs) sbs
mkFastStringShortByteString :: ShortByteString -> FastString
mkFastStringShortByteString sbs =
inlinePerformIO $ mkFastStringWith (mkNewFastStringShortByteString sbs) sbs
mkFastString :: String -> FastString
mkFastString str =
inlinePerformIO $ do
sbs <- utf8EncodeShortByteString str
mkFastStringWith (mkNewFastStringShortByteString sbs) sbs
mkFastStringByteList :: [Word8] -> FastString
mkFastStringByteList str = mkFastStringShortByteString (SBS.pack str)
mkZFastString :: FastMutInt -> ShortByteString -> FastZString
mkZFastString n_zencs sbs = unsafePerformIO $ do
_ <- atomicFetchAddFastMut n_zencs 1
return $ mkFastZStringString (zEncodeString (utf8DecodeShortByteString sbs))
mkNewFastStringShortByteString :: ShortByteString -> Int
-> FastMutInt -> IO FastString
mkNewFastStringShortByteString sbs uid n_zencs = do
let zstr = mkZFastString n_zencs sbs
chars <- countUTF8Chars sbs
return (FastString uid chars sbs zstr)
hashStr :: ShortByteString -> Int
hashStr sbs@(SBS.SBS ba#) = loop 0# 0#
where
!(I# len#) = SBS.length sbs
loop h n =
if isTrue# (n ==# len#) then
I# h
else
let
#if __GLASGOW_HASKELL__ >= 901
!c = int8ToInt# (indexInt8Array# ba# n)
#else
!c = indexInt8Array# ba# n
#endif
!h2 = (h *# 16777619#) `xorI#` c
in
loop h2 (n +# 1#)
lengthFS :: FastString -> Int
lengthFS fs = n_chars fs
nullFS :: FastString -> Bool
nullFS fs = SBS.null $ fs_sbs fs
unpackFS :: FastString -> String
unpackFS fs = utf8DecodeShortByteString $ fs_sbs fs
zEncodeFS :: FastString -> FastZString
zEncodeFS fs = fs_zenc fs
appendFS :: FastString -> FastString -> FastString
appendFS fs1 fs2 = mkFastStringByteString
$ BS.append (bytesFS fs1) (bytesFS fs2)
concatFS :: [FastString] -> FastString
concatFS = mkFastStringShortByteString . mconcat . map fs_sbs
headFS :: FastString -> Char
headFS fs
| SBS.null $ fs_sbs fs = panic "headFS: Empty FastString"
headFS fs = head $ unpackFS fs
consFS :: Char -> FastString -> FastString
consFS c fs = mkFastString (c : unpackFS fs)
unconsFS :: FastString -> Maybe (Char, FastString)
unconsFS fs =
case unpackFS fs of
[] -> Nothing
(chr : str) -> Just (chr, mkFastString str)
uniqueOfFS :: FastString -> Int
uniqueOfFS fs = uniq fs
nilFS :: FastString
nilFS = mkFastString ""
isUnderscoreFS :: FastString -> Bool
isUnderscoreFS fs = fs == fsLit "_"
getFastStringTable :: IO [[[FastString]]]
getFastStringTable =
forM [0 .. numSegments 1] $ \(I# i#) -> do
let (# segmentRef #) = indexArray# segments# i#
FastStringTableSegment _ _ buckets# <- readIORef segmentRef
let bucketSize = I# (sizeofMutableArray# buckets#)
forM [0 .. bucketSize 1] $ \(I# j#) ->
IO $ readArray# buckets# j#
where
!(FastStringTable _ _ segments#) = stringTable
getFastStringZEncCounter :: IO Int
getFastStringZEncCounter = readFastMutInt n_zencs
where
!(FastStringTable _ n_zencs _) = stringTable
hPutFS :: Handle -> FastString -> IO ()
hPutFS handle fs = BS.hPut handle $ bytesFS fs
data PtrString = PtrString !(Ptr Word8) !Int
mkPtrString# :: Addr# -> PtrString
mkPtrString# a# = PtrString (Ptr a#) (ptrStrLength (Ptr a#))
mkPtrString :: String -> PtrString
mkPtrString 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 (PtrString p len)
)
unpackPtrString :: PtrString -> String
unpackPtrString (PtrString (Ptr p#) (I# n#)) = unpackNBytes# p# n#
lengthPS :: PtrString -> Int
lengthPS (PtrString _ n) = n
#if !MIN_VERSION_GLASGOW_HASKELL(9,0,0,0)
foreign import ccall unsafe "strlen"
cstringLength# :: Addr# -> Int#
#endif
ptrStrLength :: Ptr Word8 -> Int
ptrStrLength (Ptr a) = I# (cstringLength# a)
sLit :: String -> PtrString
sLit x = mkPtrString x
fsLit :: String -> FastString
fsLit x = mkFastString x