Skip to content

Commit 8a0e096

Browse files
committed
Remove remaining uses of FFI under -fpure-haskell (#660)
All of these were standard C functions that GHC's JS backend actually somewhat supports; their shims can be found in the compiler source at "rts/js/mem.js". But it seems simpler to just get rid of all FFI uses with -fpure-haskell rather than try to keep track of which functions GHC supports. The pure Haskell implementation of memcmp runs about 6-7x as fast as the simple one-byte-at-a-time implementation for long equal buffers, which makes it... about the same speed as the pre-existing shim, even though the latter is also a one-byte- at-a-time implementation! Apparently GHC's JS backend is not yet able to produce efficient code for tight loops like these yet; the biggest problem is that it does not perform any loopification so each iteration must go through a generic-call indirection. Unfortunately that means that this patch probably makes 'strlen' and 'memchr' much slower with the JS backend. (cherry picked from commit 305604c)
1 parent 2d17761 commit 8a0e096

File tree

11 files changed

+134
-36
lines changed

11 files changed

+134
-36
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ cabal.sandbox.config
1010
dist-newstyle/
1111
cabal.project.local*
1212
.nvimrc
13+
.ghc.environment*

Data/ByteString/Builder/ASCII.hs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
{-# LANGUAGE ForeignFunctionInterface #-}
2-
{-# LANGUAGE Trustworthy #-}
3-
41
{-# OPTIONS_HADDOCK not-home #-}
52

63
-- | Copyright : (c) 2010 - 2011 Simon Meier

Data/ByteString/Builder/Prim/ASCII.hs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
{-# LANGUAGE ScopedTypeVariables, ForeignFunctionInterface #-}
2-
{-# LANGUAGE Trustworthy #-}
1+
{-# LANGUAGE ScopedTypeVariables #-}
32
-- | Copyright : (c) 2010 Jasper Van der Jeugt
43
-- (c) 2010 - 2011 Simon Meier
54
-- License : BSD3-style (see LICENSE)
@@ -82,7 +81,7 @@ import Data.ByteString.Builder.Prim.Binary
8281
import Data.ByteString.Builder.Prim.Internal
8382
import Data.ByteString.Builder.Prim.Internal.Floating
8483
import Data.ByteString.Builder.Prim.Internal.Base16
85-
import Data.ByteString.Utils.UnalignedWrite
84+
import Data.ByteString.Utils.UnalignedAccess
8685

8786
import Data.Char (ord)
8887

Data/ByteString/Builder/Prim/Binary.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ module Data.ByteString.Builder.Prim.Binary (
5757
import Data.ByteString.Builder.Prim.Internal
5858
import Data.ByteString.Builder.Prim.Internal.Floating
5959
import Data.ByteString.Utils.ByteOrder
60-
import Data.ByteString.Utils.UnalignedWrite
60+
import Data.ByteString.Utils.UnalignedAccess
6161

6262
import Foreign
6363

Data/ByteString/Builder/RealFloat/Internal.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ import Data.ByteString.Internal (c2w)
7373
import Data.ByteString.Builder.Prim.Internal (BoundedPrim, boundedPrim)
7474
import Data.ByteString.Builder.RealFloat.TableGenerator
7575
import Data.ByteString.Utils.ByteOrder
76-
import Data.ByteString.Utils.UnalignedWrite
76+
import Data.ByteString.Utils.UnalignedAccess
7777
#if PURE_HASKELL
7878
import qualified Data.ByteString.Internal.Pure as Pure
7979
#else

Data/ByteString/Internal/Pure.hs

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77

88
-- | Haskell implementation of C bits
99
module Data.ByteString.Internal.Pure
10-
( -- * fpstring.c
11-
intersperse
10+
( -- * standard string.h functions
11+
strlen
12+
, memchr
13+
, memcmp
14+
-- * fpstring.c
15+
, intersperse
1216
, countOcc
1317
, countOccBA
1418
, reverseBytes
@@ -38,13 +42,65 @@ import GHC.Int (Int8(..))
3842

3943
import Data.Bits (Bits(..), shiftR, (.&.))
4044
import Data.Word
41-
import Foreign.Ptr (plusPtr)
45+
import Foreign.Ptr (plusPtr, nullPtr)
4246
import Foreign.Storable (Storable(..))
4347
import Control.Monad (when)
4448
import Control.Exception (assert)
4549

50+
import Data.ByteString.Utils.ByteOrder
51+
import Data.ByteString.Utils.UnalignedAccess
52+
53+
----------------------------------------------------------------
54+
-- Haskell versions of standard functions in string.h
55+
----------------------------------------------------------------
56+
57+
strlen :: Ptr Word8 -> IO Int
58+
strlen = go 0 where
59+
go :: Int -> Ptr Word8 -> IO Int
60+
go !acc !p = do
61+
c <- peek p
62+
if | c == 0 -> pure acc
63+
| nextAcc <- acc + 1
64+
, nextAcc >= 0 -> go nextAcc (p `plusPtr` 1)
65+
| otherwise -> errorWithoutStackTrace
66+
"bytestring: strlen: String length does not fit in a Haskell Int"
67+
68+
memchr :: Ptr Word8 -> Word8 -> Int -> IO (Ptr Word8)
69+
memchr !p !target !len
70+
| len == 0 = pure nullPtr
71+
| otherwise = assert (len > 0) $ do
72+
c <- peek p
73+
if c == target
74+
then pure p
75+
else memchr (p `plusPtr` 1) target (len - 1)
76+
77+
memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO Int
78+
memcmp !p1 !p2 !len
79+
| len >= 8 = do
80+
w1 <- unalignedReadU64 p1
81+
w2 <- unalignedReadU64 p2
82+
let toBigEndian = whenLittleEndian byteSwap64
83+
if | w1 == w2
84+
-> memcmp (p1 `plusPtr` 8) (p2 `plusPtr` 8) (len - 8)
85+
| toBigEndian w1 < toBigEndian w2
86+
-> pure (0-1)
87+
| otherwise -> pure 1
88+
| otherwise = memcmp1 p1 p2 len
89+
90+
-- | Like 'memcmp', but definitely scans one byte at a time
91+
memcmp1 :: Ptr Word8 -> Ptr Word8 -> Int -> IO Int
92+
memcmp1 !p1 !p2 !len
93+
| len == 0 = pure 0
94+
| otherwise = assert (len > 0) $ do
95+
c1 <- peek p1
96+
c2 <- peek p2
97+
if | c1 == c2 -> memcmp1 (p1 `plusPtr` 1) (p2 `plusPtr` 1) (len - 1)
98+
| c1 < c2 -> pure (0-1)
99+
| otherwise -> pure 1
100+
101+
46102
----------------------------------------------------------------
47-
-- Haskell version of functions in fpstring.c
103+
-- Haskell versions of functions in fpstring.c
48104
----------------------------------------------------------------
49105

50106
-- | duplicate a string, interspersing the character through the elements of the
@@ -232,7 +288,7 @@ isValidUtf8' idx !len = go 0
232288

233289

234290
----------------------------------------------------------------
235-
-- Haskell version of functions in itoa.c
291+
-- Haskell versions of functions in itoa.c
236292
----------------------------------------------------------------
237293

238294

Data/ByteString/Internal/Type.hs

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
{-# LANGUAGE CPP, ForeignFunctionInterface, BangPatterns #-}
2-
{-# LANGUAGE UnliftedFFITypes, MagicHash,
3-
UnboxedTuples #-}
4-
{-# LANGUAGE TupleSections #-}
5-
{-# LANGUAGE TypeFamilies #-}
6-
{-# LANGUAGE PatternSynonyms, ViewPatterns #-}
1+
{-# LANGUAGE CPP #-}
72
{-# LANGUAGE Unsafe #-}
3+
4+
{-# LANGUAGE BangPatterns #-}
5+
{-# LANGUAGE MagicHash #-}
6+
{-# LANGUAGE PatternSynonyms #-}
87
{-# LANGUAGE TemplateHaskellQuotes #-}
8+
{-# LANGUAGE TupleSections #-}
9+
{-# LANGUAGE TypeFamilies #-}
10+
{-# LANGUAGE UnboxedTuples #-}
11+
{-# LANGUAGE UnliftedFFITypes #-}
12+
{-# LANGUAGE ViewPatterns #-}
13+
914
{-# OPTIONS_HADDOCK not-home #-}
1015

1116
-- |
@@ -129,11 +134,12 @@ import Prelude hiding (concat, null)
129134
import qualified Data.List as List
130135

131136
import Foreign.ForeignPtr (ForeignPtr, withForeignPtr)
132-
import Foreign.Ptr (Ptr, FunPtr, plusPtr)
137+
import Foreign.Ptr
133138
import Foreign.Storable (Storable(..))
134139
import Foreign.C.Types
135140
import Foreign.C.String (CString)
136141
import Foreign.Marshal.Utils
142+
import Foreign.Marshal.Alloc (finalizerFree)
137143

138144
#if PURE_HASKELL
139145
import qualified Data.ByteString.Internal.Pure as Pure
@@ -1104,24 +1110,42 @@ accursedUnutterablePerformIO (IO m) = case m realWorld# of (# _, r #) -> r
11041110
-- Standard C functions
11051111
--
11061112

1113+
memchr :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)
1114+
memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO CInt
1115+
{-# DEPRECATED memset "Use Foreign.Marshal.Utils.fillBytes instead" #-}
1116+
-- | deprecated since @bytestring-0.11.5.0@
1117+
memset :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)
1118+
1119+
#if !PURE_HASKELL
1120+
11071121
foreign import ccall unsafe "string.h strlen" c_strlen
11081122
:: CString -> IO CSize
11091123

1110-
foreign import ccall unsafe "static stdlib.h &free" c_free_finalizer
1111-
:: FunPtr (Ptr Word8 -> IO ())
1112-
11131124
foreign import ccall unsafe "string.h memchr" c_memchr
11141125
:: Ptr Word8 -> CInt -> CSize -> IO (Ptr Word8)
1115-
1116-
memchr :: Ptr Word8 -> Word8 -> CSize -> IO (Ptr Word8)
11171126
memchr p w sz = c_memchr p (fromIntegral w) sz
11181127

11191128
foreign import ccall unsafe "string.h memcmp" c_memcmp
11201129
:: Ptr Word8 -> Ptr Word8 -> CSize -> IO CInt
1121-
1122-
memcmp :: Ptr Word8 -> Ptr Word8 -> Int -> IO CInt
11231130
memcmp p q s = c_memcmp p q (fromIntegral s)
11241131

1132+
foreign import ccall unsafe "string.h memset" c_memset
1133+
:: Ptr Word8 -> CInt -> CSize -> IO (Ptr Word8)
1134+
memset p w sz = c_memset p (fromIntegral w) sz
1135+
1136+
#else
1137+
1138+
c_strlen :: CString -> IO CSize
1139+
c_strlen p = checkedCast <$!> Pure.strlen (castPtr p)
1140+
1141+
memchr p w len = Pure.memchr p w (checkedCast len)
1142+
1143+
memcmp p q s = checkedCast <$!> Pure.memcmp p q s
1144+
1145+
memset p w len = p <$ fillBytes p w (checkedCast len)
1146+
1147+
#endif
1148+
11251149
{-# DEPRECATED memcpy "Use Foreign.Marshal.Utils.copyBytes instead" #-}
11261150
-- | deprecated since @bytestring-0.11.5.0@
11271151
memcpy :: Ptr Word8 -> Ptr Word8 -> Int -> IO ()
@@ -1131,13 +1155,10 @@ memcpyFp :: ForeignPtr Word8 -> ForeignPtr Word8 -> Int -> IO ()
11311155
memcpyFp fp fq s = unsafeWithForeignPtr fp $ \p ->
11321156
unsafeWithForeignPtr fq $ \q -> copyBytes p q s
11331157

1134-
foreign import ccall unsafe "string.h memset" c_memset
1135-
:: Ptr Word8 -> CInt -> CSize -> IO (Ptr Word8)
1158+
c_free_finalizer :: FunPtr (Ptr Word8 -> IO ())
1159+
c_free_finalizer = finalizerFree
1160+
11361161

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

11421163
-- ---------------------------------------------------------------------
11431164
--

Data/ByteString/Short/Internal.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
{-# LANGUAGE DeriveDataTypeable #-}
44
{-# LANGUAGE DeriveGeneric #-}
55
{-# LANGUAGE DeriveLift #-}
6-
{-# LANGUAGE ForeignFunctionInterface #-}
76
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
87
{-# LANGUAGE LambdaCase #-}
98
{-# LANGUAGE MagicHash #-}

Data/ByteString/Utils/UnalignedWrite.hs renamed to Data/ByteString/Utils/UnalignedAccess.hs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
1+
-- |
2+
-- Module : Data.ByteString.Utils.UnalignedAccess
3+
-- Copyright : (c) Matthew Craven 2023-2024
4+
-- License : BSD-style
5+
-- Maintainer : [email protected]
6+
-- Stability : internal
7+
-- Portability : non-portable
8+
--
9+
-- Primitives for reading and writing at potentially-unaligned memory locations
10+
111
{-# LANGUAGE CPP #-}
212

313
{-# LANGUAGE MagicHash #-}
414
{-# LANGUAGE UnboxedTuples #-}
515

616
#include "bytestring-cpp-macros.h"
717

8-
module Data.ByteString.Utils.UnalignedWrite
18+
module Data.ByteString.Utils.UnalignedAccess
919
( unalignedWriteU16
1020
, unalignedWriteU32
1121
, unalignedWriteU64
1222
, unalignedWriteFloat
1323
, unalignedWriteDouble
24+
, unalignedReadU64
1425
) where
1526

1627
import Foreign.Ptr
@@ -42,6 +53,10 @@ unalignedWriteDouble :: Double -> Ptr Word8 -> IO ()
4253
unalignedWriteDouble = coerce $ \(D# x#) (Ptr p#) s
4354
-> (# writeWord8OffAddrAsDouble# p# 0# x# s, () #)
4455

56+
unalignedReadU64 :: Ptr Word8 -> IO Word64
57+
unalignedReadU64 = coerce $ \(Ptr p#) s
58+
-> case readWord8OffAddrAsWord64# p# 0# s of
59+
(# s', w64# #) -> (# s', W64# w64# #)
4560

4661
#elif HS_UNALIGNED_POKES_OK
4762
import Foreign.Storable
@@ -61,6 +76,8 @@ unalignedWriteFloat x p = poke (castPtr p) x
6176
unalignedWriteDouble :: Double -> Ptr Word8 -> IO ()
6277
unalignedWriteDouble x p = poke (castPtr p) x
6378

79+
unalignedReadU64 :: Ptr Word8 -> IO Word64
80+
unalignedReadU64 p = peek (castPtr p)
6481

6582
#else
6683
foreign import ccall unsafe "static fpstring.h fps_unaligned_write_u16"
@@ -73,5 +90,7 @@ foreign import ccall unsafe "static fpstring.h fps_unaligned_write_HsFloat"
7390
unalignedWriteFloat :: Float -> Ptr Word8 -> IO ()
7491
foreign import ccall unsafe "static fpstring.h fps_unaligned_write_HsDouble"
7592
unalignedWriteDouble :: Double -> Ptr Word8 -> IO ()
93+
foreign import ccall unsafe "static fpstring.h fps_unaligned_read_u64"
94+
unalignedReadU64 :: Ptr Word8 -> IO Word64
7695
#endif
7796

bytestring.cabal

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ library
121121
Data.ByteString.ReadInt
122122
Data.ByteString.ReadNat
123123
Data.ByteString.Utils.ByteOrder
124-
Data.ByteString.Utils.UnalignedWrite
124+
Data.ByteString.Utils.UnalignedAccess
125125

126126
default-language: Haskell2010
127127
other-extensions: CPP,

0 commit comments

Comments
 (0)