Skip to content

Commit 09dc954

Browse files
authored
Implement stimes for Builder and ShortByteString (#611)
1 parent 39f4011 commit 09dc954

File tree

5 files changed

+58
-31
lines changed

5 files changed

+58
-31
lines changed

Data/ByteString/Builder/Internal.hs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,9 +128,7 @@ module Data.ByteString.Builder.Internal (
128128

129129
import Control.Arrow (second)
130130

131-
#if !(MIN_VERSION_base(4,11,0))
132-
import Data.Semigroup (Semigroup((<>)))
133-
#endif
131+
import Data.Semigroup (Semigroup(..))
134132

135133
import qualified Data.ByteString as S
136134
import qualified Data.ByteString.Internal.Type as S
@@ -382,9 +380,25 @@ empty = Builder ($)
382380
append :: Builder -> Builder -> Builder
383381
append (Builder b1) (Builder b2) = Builder $ b1 . b2
384382

383+
stimesBuilder :: Integral t => t -> Builder -> Builder
384+
{-# INLINABLE stimesBuilder #-}
385+
stimesBuilder n b
386+
| n >= 0 = go n
387+
| otherwise = stimesNegativeErr
388+
where go 0 = empty
389+
go k = b `append` go (k - 1)
390+
391+
stimesNegativeErr :: Builder
392+
-- See Note [Float error calls out of INLINABLE things]
393+
-- in Data.ByteString.Internal.Type
394+
stimesNegativeErr
395+
= errorWithoutStackTrace "stimes @Builder: non-negative multiplier expected"
396+
385397
instance Semigroup Builder where
386398
{-# INLINE (<>) #-}
387399
(<>) = append
400+
{-# INLINE stimes #-}
401+
stimes = stimesBuilder
388402

389403
instance Monoid Builder where
390404
{-# INLINE mempty #-}

Data/ByteString/Internal/Type.hs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -885,13 +885,36 @@ stimesPolymorphic nRaw !bs = case checkedIntegerToInt n of
885885
-- and the likelihood of potentially dangerous mistakes minimized.
886886

887887

888+
{-
889+
Note [Float error calls out of INLINABLE things]
890+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
891+
892+
If a function is marked INLINE or INLINABLE, then when ghc inlines or
893+
specializes it, it duplicates the function body exactly as written.
894+
895+
This feature is useful for systems of rewrite rules, but sometimes
896+
comes at a code-size cost. One situation where this cost generally
897+
comes with no compensating up-side is when the function in question
898+
calls `error` or something similar.
899+
900+
Such an `error` call is not meaningfully improved by the extra context
901+
inlining or specialization provides, and if inlining or specialization
902+
happens in a different module from where the function was originally
903+
defined, CSE will not be able to de-duplicate the error call floated
904+
out of the inlined RHS and the error call floated out of the original
905+
RHS. See also https://gitlab.haskell.org/ghc/ghc/-/issues/23823
906+
907+
To mitigate this, we manually float the error calls out of INLINABLE
908+
functions when it is possible to do so.
909+
-}
910+
888911
stimesNegativeErr :: ByteString
912+
-- See Note [Float error calls out of INLINABLE things]
889913
stimesNegativeErr
890-
= error "stimes @ByteString: non-negative multiplier expected"
914+
= errorWithoutStackTrace "stimes @ByteString: non-negative multiplier expected"
891915

892916
stimesOverflowErr :: ByteString
893-
-- Although this only appears once, it is extracted here to prevent it
894-
-- from being duplicated in specializations of 'stimesPolymorphic'
917+
-- See Note [Float error calls out of INLINABLE things]
895918
stimesOverflowErr = overflowError "stimes"
896919

897920
-- | Repeats the given ByteString n times.

Data/ByteString/Short/Internal.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ import Data.Data
184184
import Data.Monoid
185185
( Monoid(..) )
186186
import Data.Semigroup
187-
( Semigroup((<>)) )
187+
( Semigroup(..), stimesMonoid )
188188
import Data.String
189189
( IsString(..) )
190190
import Control.Applicative
@@ -313,6 +313,7 @@ instance Ord ShortByteString where
313313

314314
instance Semigroup ShortByteString where
315315
(<>) = append
316+
stimes = stimesMonoid
316317

317318
instance Monoid ShortByteString where
318319
mempty = empty

tests/Properties/ByteString.hs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,8 @@ tests =
250250
\x y -> B.unpack (mappend x y) === B.unpack x `mappend` B.unpack y
251251
, testProperty "<>" $
252252
\x y -> B.unpack (x <> y) === B.unpack x <> B.unpack y
253-
#ifndef BYTESTRING_SHORT
254253
, testProperty "stimes" $
255-
\(Sqrt (NonNegative n)) (Sqrt x) -> stimes (n :: Int) (x :: BYTESTRING_TYPE) === mtimesDefault n x
256-
#endif
254+
\(Sqrt (NonNegative n)) (Sqrt x) -> stimes (n :: Int) (x :: BYTESTRING_TYPE) === stimesMonoid n x
257255

258256
, testProperty "break" $
259257
\f x -> (B.unpack *** B.unpack) (B.break f x) === break f (B.unpack x)

tests/builder/Data/ByteString/Builder/Tests.hs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ import Foreign (minusPtr)
2828
import Data.Char (chr)
2929
import Data.Bits ((.|.), shiftL)
3030
import Data.Foldable
31-
#if !MIN_VERSION_base(4,11,0)
32-
import Data.Semigroup
33-
#endif
31+
import Data.Semigroup (Semigroup(..))
3432
import Data.Word
3533

3634
import qualified Data.ByteString as S
@@ -55,8 +53,11 @@ import System.Posix.Internals (c_unlink)
5553
import Test.Tasty (TestTree, TestName, testGroup)
5654
import Test.Tasty.QuickCheck
5755
( Arbitrary(..), oneof, choose, listOf, elements
58-
, counterexample, ioProperty, UnicodeString(..), Property, testProperty
59-
, (===), (.&&.), conjoin )
56+
, counterexample, ioProperty, Property, testProperty
57+
, (===), (.&&.), conjoin
58+
, UnicodeString(..), NonNegative(..)
59+
)
60+
import QuickCheckUtils
6061

6162

6263
tests :: [TestTree]
@@ -67,6 +68,7 @@ tests =
6768
, testPut
6869
, testRunBuilder
6970
, testWriteFile
71+
, testStimes
7072
] ++
7173
testsEncodingToBuilder ++
7274
testsBinary ++
@@ -199,6 +201,11 @@ testWriteFile =
199201
unless success (error msg)
200202
return success
201203

204+
testStimes :: TestTree
205+
testStimes = testProperty "stimes" $
206+
\(Sqrt (NonNegative n)) (Sqrt x) ->
207+
stimes (n :: Int) x === toLazyByteString (stimes n (lazyByteString x))
208+
202209
removeFile :: String -> IO ()
203210
removeFile fn = void $ withCString fn c_unlink
204211

@@ -319,22 +326,6 @@ recipeComponents (Recipe how firstSize otherSize cont as) =
319326
-- 'Arbitary' instances
320327
-----------------------
321328

322-
instance Arbitrary L.ByteString where
323-
arbitrary = L.fromChunks <$> listOf arbitrary
324-
shrink lbs
325-
| L.null lbs = []
326-
| otherwise = pure $ L.take (L.length lbs `div` 2) lbs
327-
328-
instance Arbitrary S.ByteString where
329-
arbitrary =
330-
trim S.drop =<< trim S.take =<< S.pack <$> listOf arbitrary
331-
where
332-
trim f bs = oneof [pure bs, f <$> choose (0, S.length bs) <*> pure bs]
333-
334-
shrink bs
335-
| S.null bs = []
336-
| otherwise = pure $ S.take (S.length bs `div` 2) bs
337-
338329
instance Arbitrary Mode where
339330
arbitrary = oneof
340331
[Threshold <$> arbitrary, pure Smart, pure Insert, pure Copy, pure Hex]

0 commit comments

Comments
 (0)