Skip to content

Commit

Permalink
Deprecate memcpy and memset (#591)
Browse files Browse the repository at this point in the history
* Deprecate memcpy and memset

Starting with ghc-9.8, copyBytes and fillBytes from base will use
dedicated primops that ghc can sometimes emit better code for if the
length is known and small enough.  We should take advantage of this
rather than using a foreign call that ghc won't recognize.

See also https://gitlab.haskell.org/ghc/ghc/-/merge_requests/9629

* clean up some fromIntegral calls

* remove foreign import for memcpy
  • Loading branch information
clyring authored Jun 11, 2023
1 parent 8d296b7 commit aa79cf8
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 34 deletions.
32 changes: 18 additions & 14 deletions Data/ByteString.hs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE Trustworthy #-}

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TupleSections #-}
{-# OPTIONS_HADDOCK prune #-}
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE TypeApplications #-}

-- |
-- Module : Data.ByteString
Expand Down Expand Up @@ -243,14 +245,15 @@ import Data.List.NonEmpty (NonEmpty(..))
import Data.Word (Word8)

import Control.Exception (IOException, catch, finally, assert, throwIO)
import Control.Monad (when, void)
import Control.Monad (when)

import Foreign.C.String (CString, CStringLen)
import Foreign.C.Types (CSize (CSize), CInt (CInt))
import Foreign.ForeignPtr (ForeignPtr, touchForeignPtr)
import Foreign.ForeignPtr.Unsafe(unsafeForeignPtrToPtr)
import Foreign.Marshal.Alloc (allocaBytes)
import Foreign.Marshal.Array (allocaArray)
import Foreign.Marshal.Utils
import Foreign.Ptr
import Foreign.Storable (Storable(..))

Expand Down Expand Up @@ -843,15 +846,13 @@ scanr1 f ps = case unsnoc ps of
-- | /O(n)/ 'replicate' @n x@ is a ByteString of length @n@ with @x@
-- the value of every element. The following holds:
--
-- > replicate w c = unfoldr w (\u -> Just (u,u)) c
--
-- This implementation uses @memset(3)@
-- > replicate w c = fst (unfoldrN w (\u -> Just (u,u)) c)
replicate :: Int -> Word8 -> ByteString
replicate w c
| w <= 0 = empty
| otherwise = unsafeCreateFp w $ \fptr ->
unsafeWithForeignPtr fptr $ \ptr ->
void $ memset ptr c (fromIntegral w)
fillBytes ptr c w
{-# INLINE replicate #-}

-- | /O(n)/, where /n/ is the length of the result. The 'unfoldr'
Expand Down Expand Up @@ -1720,12 +1721,15 @@ tailsNE p | null p = empty :| []
Note [Avoid NonEmpty combinators]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As of base-4.17, most of the NonEmpty API is surprisingly lazy.
As of base-4.18, most of the NonEmpty API is surprisingly lazy.
Using it without forcing the arguments yourself is just begging GHC
to make your code waste time allocating useless selector thunks.
This may change in the future. See also this CLC issue:
https://github.com/haskell/core-libraries-committee/issues/107
But until then, "refactor" with care!
(Even for uses of NonEmpty near lazy ByteStrings, we don't want
the extra laziness of the NonEmpty API.)
-}


Expand All @@ -1742,18 +1746,18 @@ sort (BS input l)
unsafeWithForeignPtr destFP $ \dest -> c_sort dest (fromIntegral l)
| otherwise = unsafeCreateFp l $ \p -> allocaArray 256 $ \arr -> do

_ <- memset (castPtr arr) 0 (256 * fromIntegral (sizeOf (undefined :: CSize)))
fillBytes (castPtr arr) 0 (256 * sizeOf (undefined :: Int))
unsafeWithForeignPtr input (\x -> countOccurrences arr x l)

let go 256 !_ = return ()
go i !ptr = do n <- peekElemOff arr i
when (n /= 0) $ void $ memset ptr (fromIntegral i) n
when (n /= 0) $
fillBytes ptr (fromIntegral @Int @Word8 i) n
go (i + 1) (ptr `plusPtr` fromIntegral n)
unsafeWithForeignPtr p (go 0)
where
-- Count the number of occurrences of each byte.
-- Used by 'sort'
countOccurrences :: Ptr CSize -> Ptr Word8 -> Int -> IO ()
countOccurrences :: Ptr Int -> Ptr Word8 -> Int -> IO ()
countOccurrences !counts !str !len = go 0
where
go !i | i == len = return ()
Expand All @@ -1773,7 +1777,7 @@ sort (BS input l)
useAsCString :: ByteString -> (CString -> IO a) -> IO a
useAsCString (BS fp l) action =
allocaBytes (l+1) $ \buf -> do
unsafeWithForeignPtr fp $ \p -> memcpy buf p l
unsafeWithForeignPtr fp $ \p -> copyBytes buf p l
pokeByteOff buf l (0::Word8)
action (castPtr buf)

Expand All @@ -1800,7 +1804,7 @@ packCString cstr = do
-- Haskell heap.
packCStringLen :: CStringLen -> IO ByteString
packCStringLen (cstr, len) | len >= 0 = createFp len $ \fp ->
unsafeWithForeignPtr fp $ \p -> memcpy p (castPtr cstr) len
unsafeWithForeignPtr fp $ \p -> copyBytes p (castPtr cstr) len
packCStringLen (_, len) =
moduleErrorIO "packCStringLen" ("negative length: " ++ show len)

Expand Down
31 changes: 11 additions & 20 deletions Data/ByteString/Internal/Type.hs
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,12 @@ module Data.ByteString.Internal.Type (
import Prelude hiding (concat, null)
import qualified Data.List as List

import Control.Monad (void)

import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
import Foreign.Ptr (Ptr, FunPtr, plusPtr)
import Foreign.Storable (Storable(..))
import Foreign.C.Types (CInt(..), CSize(..))
import Foreign.C.String (CString)
import Foreign.Marshal.Utils

#if !MIN_VERSION_base(4,13,0)
import Data.Semigroup (Semigroup ((<>)))
Expand Down Expand Up @@ -899,8 +898,8 @@ stimesNonNegativeInt n (BS fp len)
| len == 0 = empty
| len == 1 = unsafeCreateFp n $ \destfptr -> do
byte <- peekFp fp
void $ unsafeWithForeignPtr destfptr $ \destptr ->
memset destptr byte (fromIntegral n)
unsafeWithForeignPtr destfptr $ \destptr ->
fillBytes destptr byte n
| otherwise = unsafeCreateFp size $ \destptr -> do
memcpyFp destptr fp len
fillFrom destptr len
Expand Down Expand Up @@ -1057,38 +1056,30 @@ foreign import ccall unsafe "string.h memchr" c_memchr
:: Ptr Word8 -> CInt -> CSize -> IO (Ptr Word8)

memchr :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)
memchr p w = c_memchr p (fromIntegral w)
memchr p w sz = c_memchr p (fromIntegral w) sz

foreign import ccall unsafe "string.h memcmp" c_memcmp
:: Ptr Word8 -> Ptr Word8 -> CSize -> IO CInt

memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO CInt
memcmp p q s = c_memcmp p q (fromIntegral s)

foreign import ccall unsafe "string.h memcpy" c_memcpy
:: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)

{-# DEPRECATED memcpy "Use Foreign.Marshal.Utils.copyBytes instead" #-}
-- | deprecated since @bytestring-0.11.5.0@
memcpy :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()
memcpy p q s = void $ c_memcpy p q (fromIntegral s)
memcpy = copyBytes

memcpyFp :: ForeignPtr Word8 -> ForeignPtr Word8 -> Int -> IO ()
memcpyFp fp fq s = unsafeWithForeignPtr fp $ \p ->
unsafeWithForeignPtr fq $ \q -> memcpy p q s

{-
foreign import ccall unsafe "string.h memmove" c_memmove
:: Ptr Word8 -> Ptr Word8 -> CSize -> IO (Ptr Word8)
memmove :: Ptr Word8 -> Ptr Word8 -> CSize -> IO ()
memmove p q s = do c_memmove p q s
return ()
-}
unsafeWithForeignPtr fq $ \q -> copyBytes p q s

foreign import ccall unsafe "string.h memset" c_memset
:: Ptr Word8 -> CInt -> CSize -> IO (Ptr Word8)

{-# DEPRECATED memset "Use Foreign.Marshal.Utils.fillBytes instead" #-}
-- | deprecated since @bytestring-0.11.5.0@
memset :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)
memset p w = c_memset p (fromIntegral w)
memset p w sz = c_memset p (fromIntegral w) sz

-- ---------------------------------------------------------------------
--
Expand Down

0 comments on commit aa79cf8

Please sign in to comment.