[Git][ghc/ghc][wip/marge_bot_batch_merge_job] 5 commits: compiler: avoid unused temporary `appendFS` operands
Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC Commits: 5574ee10 by Cheng Shao at 2026-04-24T08:24:30-04:00 compiler: avoid unused temporary `appendFS` operands This patch fixes unused temporary `appendFS` operands in the codebase that are retained in the `FastString` table after concatenation. Rewrite rules are added so that if an operand is `fsLit`/`mkFastString`, the `appendFS` application is rewritten to append the `ShortByteString` operands first. The patch also fixes `sconcat` behavior to align with `mconcat` for the same reason. Fixes #27205. - - - - - 4ed78760 by mangoiv at 2026-04-24T08:25:13-04:00 contributing: adjust MR template to be less verbose - MR template only shows text that is relevant for submissiong - MR template was rewritten so it's readable from a user's and reviewer's perspective Resolves #27165 Co-Authored-By: @sheaf - - - - - a03df654 by Cheng Shao at 2026-04-24T08:56:47-04:00 ci: bump freebsd boot ghc to 9.10.3 This commit bumps freebsd boot ghc to 9.10.3 to align with other platforms and prevent outdated boot libs in boot ghc to block the freebsd job. - - - - - b1ae23de by Cheng Shao at 2026-04-24T08:56:47-04:00 compiler: improve Binary instance of Array This patch improves the `Binary` instance of `Array`: - We no longer allocate intermediate lists. When serializing an `Array`, we iterate over the elements directly; when deserializing it, we allocate the result `Array` and fill it in a loop. - Now we only serialize the array bounds tuple; the length field is not needed. Closes #27109. - - - - - e45b5522 by sheaf at 2026-04-24T08:57:02-04:00 Vendor mini-QuickCheck for testsuite This commit extracts the vendored QuickCheck implementation from the foundation testsuite to make it more broadly available in the GHC testsuite, and makes use of it in the simd006 test (which also used a vendored QuickCheck implementation). On the way, we update the linear congruential generator to avoid the shortcoming of only generating 31 bit large numbers. Fixes #25990 and #25969. - - - - - 12 changed files: - .gitlab/generate-ci/gen_ci.hs - .gitlab/jobs.yaml - .gitlab/merge_request_templates/Default.md - + changelog.d/binary-array-no-list - compiler/GHC/Data/FastString.hs - compiler/GHC/Utils/Binary.hs - testsuite/driver/testlib.py - + testsuite/tests/MiniQuickCheck.hs - testsuite/tests/numeric/should_run/all.T - testsuite/tests/numeric/should_run/foundation.hs - testsuite/tests/simd/should_run/all.T - testsuite/tests/simd/should_run/simd006.hs Changes: ===================================== .gitlab/generate-ci/gen_ci.hs ===================================== @@ -445,7 +445,7 @@ opsysVariables _ FreeBSD14 = mconcat -- Prefer to use the system's clang-based toolchain and not gcc , "CC" =: "cc" , "CXX" =: "c++" - , "FETCH_GHC_VERSION" =: "9.10.1" + , "FETCH_GHC_VERSION" =: "9.10.3" , "CABAL_INSTALL_VERSION" =: "3.14.2.0" ] opsysVariables arch (Linux distro) = distroVariables arch distro ===================================== .gitlab/jobs.yaml ===================================== @@ -1721,7 +1721,7 @@ "CC": "cc", "CONFIGURE_ARGS": "--with-iconv-includes=/usr/local/include --with-iconv-libraries=/usr/local/lib --with-system-libffi --with-ffi-includes=/usr/local/include --with-ffi-libraries=/usr/local/lib --with-gmp-includes=/usr/local/include --with-gmp-libraries=/usr/local/lib --enable-strict-ghc-toolchain-check", "CXX": "c++", - "FETCH_GHC_VERSION": "9.10.1", + "FETCH_GHC_VERSION": "9.10.3", "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", "RUNTEST_ARGS": "", "TEST_ENV": "x86_64-freebsd14-validate", @@ -4543,7 +4543,7 @@ "CC": "cc", "CONFIGURE_ARGS": "--with-iconv-includes=/usr/local/include --with-iconv-libraries=/usr/local/lib --with-system-libffi --with-ffi-includes=/usr/local/include --with-ffi-libraries=/usr/local/lib --with-gmp-includes=/usr/local/include --with-gmp-libraries=/usr/local/lib --enable-strict-ghc-toolchain-check", "CXX": "c++", - "FETCH_GHC_VERSION": "9.10.1", + "FETCH_GHC_VERSION": "9.10.3", "IGNORE_PERF_FAILURES": "all", "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", "RUNTEST_ARGS": "", @@ -5643,7 +5643,7 @@ "CC": "cc", "CONFIGURE_ARGS": "--with-iconv-includes=/usr/local/include --with-iconv-libraries=/usr/local/lib --with-system-libffi --with-ffi-includes=/usr/local/include --with-ffi-libraries=/usr/local/lib --with-gmp-includes=/usr/local/include --with-gmp-libraries=/usr/local/lib --enable-strict-ghc-toolchain-check", "CXX": "c++", - "FETCH_GHC_VERSION": "9.10.1", + "FETCH_GHC_VERSION": "9.10.3", "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check", "RUNTEST_ARGS": "", "TEST_ENV": "x86_64-freebsd14-validate" ===================================== .gitlab/merge_request_templates/Default.md ===================================== @@ -1,43 +1,47 @@ + +<!-- Thank you for your contribution to GHC! -**Please read the checklist below to make sure your contribution fulfills these -expectations. Also please answer the following question in your MR description:** - -**Where is the key part of this patch? That is, what should reviewers look at first?** - -Please take a few moments to address the following points: - - * [ ] if your MR touches `base` (or touches parts of `ghc-internal` used - or re-exported by `base`) more substantially than just amending comments - or documentation, you likely need to raise a - [CLC proposal](https://github.com/haskell/core-libraries-committee#base-package) - before merging it. - * [ ] if your MR may break existing programs (e.g. causes the - compiler to reject programs), please describe the expected breakage and add - the ~"user-facing" label. This will run ghc/head.hackage> to characterise - the effect of your change on Hackage. - * [ ] ensure that your commits are either individually buildable or squashed - * [ ] ensure that your commit messages describe *what they do* - (referring to tickets using `#NNNN` syntax when appropriate) - * [ ] have added source comments describing your change. For larger changes you - likely should add a [Note][notes] and cross-reference it from the relevant - places. - * [ ] add a [testcase to the testsuite][adding test]. - * [ ] updates the users guide if applicable - * [ ] add a changelog entry in `changelog.d/` for user-facing changes (see [changelog guide][changelog]). - If this MR does not need a changelog entry, apply the ~"no-changelog" label. +Please read the checklist below to make sure your contribution fulfills these +expectations. If you have any questions don't hesitate to open your merge request and inquire in a comment. If your patch isn't quite done yet please do add prefix your MR -title with `WIP:`. +title with Draft: + +To make your contribution experience as smooth as possible, also check out +https://gitlab.haskell.org/ghc/ghc/-/wikis/Contributing-a-Patch +--> + +## Changes contained in this patch +<!-- Where is the key part of this patch? That is, what should reviewers look at first? --> + + +## MR Checklist +<!-- Please take a few moments to address the following points: --> + +- [ ] This MR solves the problem described in the following issue: <!-- issue number here (please open a new issue if there isn't one) --> +- [ ] A changelog entry was added in `changelog.d/` for user-facing changes (see [changelog guide][changelog]). + If this MR does not need a changelog entry, the ~"no-changelog" label was applied. +- [ ] This MR does not make any significant changes to `base`, or it has an accompanying [CLC proposal](https://github.com/haskell/core-libraries-committee#base-package). +- [ ] If this MR has the potential to break user programs, the ~"user-facing" label was applied to + test against head.hackage. +- [ ] All commits are either individually buildable or squashed. +- [ ] Commit messages describe *what they do*, referring to tickets using `#NNNNN` syntax. +- [ ] Source comments describing the change were added. For larger changes [notes][notes] and + cross-references from the relevant places were added (as applicable). +- [ ] [Testcases to the testsuite][adding test] were added (as applicable). +- [ ] The users guide was updated (as applicable). +<!-- By default a minimal validation pipeline is run on each merge request, the ~full-ci label can be applied to perform additional validation checks if your MR affects a more unusual configuration. -Once your change is ready please remove the `WIP:` tag and wait for review. If +Once your change is ready please remove the `Draft:` tag and wait for review. If no one has offered a review in a few days then please leave a comment mentioning @triagers and apply the ~"Blocked on Review" label. +--> [notes]: https://gitlab.haskell.org/ghc/ghc/wikis/commentary/coding-style#comments-in... [adding test]: https://gitlab.haskell.org/ghc/ghc/wikis/building/running-tests/adding ===================================== changelog.d/binary-array-no-list ===================================== @@ -0,0 +1,13 @@ +section: compiler +synopsis: Reduce allocations when (de)serialising `Array` in the `ghc` library. +issues: #27109 +mrs: !15805 + +description: { + The `ghc` library's `Binary` instance for `Array` was changed to + avoid allocating an intermediate list and to omit a redundant length + field during (de)serialisation. + + This should only affect the `ghc` library's (de)serialisation code paths, + primarily when parsing HIE files and bytecode objects. +} ===================================== compiler/GHC/Data/FastString.hs ===================================== @@ -139,6 +139,7 @@ import Foreign.C import System.IO import Data.Data import Data.IORef +import qualified Data.List.NonEmpty as NE import Data.Semigroup as Semi import Foreign @@ -232,6 +233,7 @@ instance IsString FastString where instance Semi.Semigroup FastString where (<>) = appendFS + sconcat = concatFS . NE.toList instance Monoid FastString where mempty = nilFS @@ -619,6 +621,42 @@ unpackFS fs = utf8DecodeShortByteString $ fs_sbs fs zEncodeFS :: FastString -> FastZString zEncodeFS fs = fs_zenc fs +-- Sometimes an `appendFS` operand is temporarily constructed, and we +-- should avoid retaining the unused `FastString` operand in the +-- table. The RULES below mitigate the issue by concatenating the +-- `ShortByteString`s instead when an operand is `fsLit` or +-- `mkFastString`, which cover most such `appendFS` use cases. See +-- #27205. + +{-# RULES +"appendFS/fsLit y" forall x y. + appendFS x (fsLit y) = + mkFastStringShortByteString $ + fs_sbs x Semi.<> utf8EncodeShortByteString y + #-} + +{-# RULES +"appendFS/fsLit x" forall x y. + appendFS (fsLit x) y = + mkFastStringShortByteString $ + utf8EncodeShortByteString x Semi.<> fs_sbs y + #-} + +{-# RULES +"appendFS/mkFastString y" forall x y. + appendFS x (mkFastString y) = + mkFastStringShortByteString $ + fs_sbs x Semi.<> utf8EncodeShortByteString y + #-} + +{-# RULES +"appendFS/mkFastString x" forall x y. + appendFS (mkFastString x) y = + mkFastStringShortByteString $ + utf8EncodeShortByteString x Semi.<> fs_sbs y + #-} + +{-# INLINE[1] appendFS #-} appendFS :: FastString -> FastString -> FastString appendFS fs1 fs2 = mkFastStringShortByteString $ (Semi.<>) (fs_sbs fs1) (fs_sbs fs2) ===================================== compiler/GHC/Utils/Binary.hs ===================================== @@ -142,6 +142,8 @@ import Control.DeepSeq import Control.Monad ( when, (<$!>), unless, forM_, void ) import Foreign hiding (bit, setBit, clearBit, shiftL, shiftR, void) import Data.Array +import Data.Array.Base (unsafeFreezeIOArray) +import Data.Array.IArray (traverseArray_) import Data.Array.IO import Data.Array.Unsafe import qualified Data.Binary as Binary @@ -970,11 +972,12 @@ instance Binary a => Binary (NonEmpty a) where instance (Ix a, Binary a, Binary b) => Binary (Array a b) where put_ bh arr = do put_ bh $ bounds arr - put_ bh $ elems arr + traverseArray_ (put_ bh) arr + get bh = do - bounds <- get bh - xs <- get bh - return $ listArray bounds xs + (l, u) <- get bh + marr <- newGenArray (l, u) $ \_ -> get bh + unsafeFreezeIOArray marr instance Binary a => Binary (SmallArray a) where put_ bh sa = do ===================================== testsuite/driver/testlib.py ===================================== @@ -13,6 +13,7 @@ import time import datetime import copy import glob +import random import sys from math import ceil, trunc, floor, log from pathlib import Path, PurePath @@ -648,6 +649,11 @@ def extra_files(files): def _extra_files(name, opts, files): opts.extra_files.extend(files) +def mini_quickcheck(name, opts): + miniqc = os.path.relpath(config.top / 'tests' / 'MiniQuickCheck.hs', opts.srcdir) + opts.extra_files.extend([miniqc]) + opts.extra_run_opts += ' ' + str(random.getrandbits(64)) + # Record the size of a specific file def collect_size ( deviation, path ): return collect_size_func(deviation, lambda: path) ===================================== testsuite/tests/MiniQuickCheck.hs ===================================== @@ -0,0 +1,395 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralisedNewtypeDeriving #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE TypeFamilies #-} + +-- | A minimal QuickCheck-like property testing framework for use in the GHC +-- test suite. +-- +-- We vendor this package to avoid depending on the real QuickCheck package, +-- as the latter (or one of its dependencies) may not build with the GHC version +-- being tested. +module MiniQuickCheck + ( -- * QuickCheck generator + Gen(..) + + -- * QuickCheck typeclasses + , Arbitrary(..) + , IsProperty(..) + + -- * QuickCheck properties + , PropertyCheck(..) + , PropertyTestArg(..) + , Property(..) + , forAll + , (===) + , propertyCompare + , propertyAnd + , getCheck + + -- * QuickCheck test tree + , Test(..) + + -- * Running QuickCheck tests + , Result(..) + , Iterations(..) + , runTestsMain + , runTests + , runTestInternal + + -- * QuickCheck primitive generators + , arbitraryInt64 + , arbitraryWord64 + , integralDownsize + , wordDownsize + + -- * QuickCheck newtypes + , NonZero(..) + , nonZero + , BoundedShiftAmount(..) + , BoundedBy(..) + ) where + +-- base +import Control.Monad.IO.Class + ( liftIO ) +import Data.Bits + ( (.|.), shiftL, shiftR + , FiniteBits, finiteBitSize + ) +import Data.Int + ( Int8, Int16, Int32, Int64 ) +import Data.IORef + ( newIORef, atomicModifyIORef' ) +import Data.Kind + ( Type ) +import Data.List + ( intercalate ) +import Data.Proxy + ( Proxy(..) ) +import Data.Word + ( Word8, Word16, Word32, Word64 ) +import GHC.TypeNats + ( Nat, KnownNat, natVal ) +import Numeric.Natural + ( Natural ) +import System.Environment + ( getArgs ) +import System.Exit + ( die, exitFailure ) +import Text.Read + ( readMaybe ) + +-- transformers +import Control.Monad.Trans.Reader + ( ReaderT, runReaderT, ask, local ) +import Control.Monad.Trans.State.Strict + ( State, state, runState ) + +-------------------------------------------------------------------------------- +-- Core framework + +newtype Gen a = Gen { runGen :: State Word64 a } + deriving newtype ( Functor, Applicative, Monad ) + +class Arbitrary a where + arbitrary :: Gen a + +class IsProperty p where + property :: p -> Property + +data PropertyCheck + = PropertyBinaryOp Bool String String String + | PropertyAnd PropertyCheck PropertyCheck + +instance IsProperty PropertyCheck where + property check = Prop (pure (PropertyEOA check)) + +data PropertyTestArg + = PropertyEOA PropertyCheck + | PropertyArg String PropertyTestArg + +getCheck :: PropertyTestArg -> ([String], PropertyCheck) +getCheck (PropertyEOA pc) = ([], pc) +getCheck (PropertyArg s pta) = let (ss, pc) = getCheck pta in (s:ss, pc) + +data Property = Prop { unProp :: Gen PropertyTestArg } + +instance (Show a, Arbitrary a, IsProperty prop) => IsProperty (a -> prop) where + property p = forAll arbitrary p + +-- | Run a generator for a value of the given type and add it as an argument +-- to the property test. +forAll :: (Show a, IsProperty prop) => Gen a -> (a -> prop) -> Property +forAll generator tst = Prop $ do + a <- generator + augment a <$> unProp (property (tst a)) + where + augment a arg = PropertyArg (show a) arg + +-- | Build a @PropertyCheck@ by comparing two values with a named predicate. +propertyCompare :: Show a => String -> (a -> a -> Bool) -> a -> a -> PropertyCheck +propertyCompare s f a b = PropertyBinaryOp (f a b) s (show a) (show b) + +-- | Check that two values are equal (by '=='). +(===) :: (Show a, Eq a) => a -> a -> PropertyCheck +(===) = propertyCompare "==" (==) +infix 4 === + +-- | Conjunction of two property checks. +propertyAnd :: PropertyCheck -> PropertyCheck -> PropertyCheck +propertyAnd = PropertyAnd + +-------------------------------------------------------------------------------- +-- Test tree + +-- | A named test or group of tests. +data Test where + Group :: String -> [Test] -> Test + Property :: IsProperty prop => String -> prop -> Test + +-------------------------------------------------------------------------------- +-- Test runner + +newtype Iterations = Iterations { nbIterations :: Word } + deriving newtype ( Show, Eq, Ord ) + +-- | Outcome of running a test suite. +data Result = Success | Failure [[String]] + +instance Semigroup Result where + Success <> y = y + x <> Success = x + Failure xs <> Failure ys = Failure (xs ++ ys) + +instance Monoid Result where + mempty = Success + +data RunS = RunS + { depth :: Int + , currentSeed :: Word64 + , context :: [String] + } + +putMsg :: String -> ReaderT RunS IO () +putMsg s = do + n <- depth <$> ask + liftIO . putStrLn $ replicate (n * 2) ' ' ++ s + +nest :: String -> ReaderT RunS IO a -> ReaderT RunS IO a +nest c = local (\s -> s { depth = depth s + 1, context = c : context s }) + +runPropertyCheck :: PropertyCheck -> ReaderT RunS IO Result +runPropertyCheck (PropertyBinaryOp ok desc s1 s2) = + if ok + then return Success + else do + ctx <- context <$> ask + let msg = "Failure: " ++ s1 ++ " " ++ desc ++ " " ++ s2 + putMsg msg + return (Failure [msg : ctx]) +runPropertyCheck (PropertyAnd a b) = + (<>) <$> runPropertyCheck a <*> runPropertyCheck b + +runProperty :: Iterations -> Property -> ReaderT RunS IO Result +runProperty (Iterations iters) (Prop p) = do + startingSeed <- currentSeed <$> ask + loop iters startingSeed + where + loop 0 _ = do + putMsg ("Passed " ++ show iters ++ " iterations") + return Success + loop n s = do + let (pt, s') = runState (runGen p) s + (ss, pc) = getCheck pt + res <- runPropertyCheck pc + case res of + Success -> loop (n - 1) s' + Failure msgs -> do + let msg = "With arguments " ++ intercalate ", " ss ++ " (Seed: " ++ show s ++ ")" + putMsg msg + return (Failure (map (msg :) msgs)) + +-- | Run a single 'Test', accumulating all failures. +runTestInternal :: Iterations -> Test -> ReaderT RunS IO Result +runTestInternal iters (Group name tests) = do + let label = "Group " ++ name + putMsg label + env <- ask + nest label $ do + -- Compute initial seed for each test in the group, based on the + -- index of the test in the group. + let runOne idx t = do + let !s = snd $ stepLCG (currentSeed env + fromIntegral idx) + local (\e -> e { currentSeed = s }) (runTestInternal iters t) + mconcat <$> traverse (uncurry runOne) (zip [1..] tests) + +runTestInternal iters (Property name p) = do + let label = "Running " ++ name + putMsg label + nest label (runProperty iters (property p)) + +showStack :: Int -> [String] -> String +showStack _ [] = "" +showStack n (s:ss) = replicate n ' ' ++ s ++ "\n" ++ showStack (n + 2) ss + +-- | Standard @main@ entry point for tests using 'MiniQuickCheck'. +-- +-- Reads a 'Word64' seed from the first command-line argument, then +-- delegates to 'runTests'. +runTestsMain :: Iterations -> Test -> IO () +runTestsMain iters t = do + args <- getArgs + seed <- case args of + [arg] -> case readMaybe arg of + Just s -> pure s + Nothing -> die $ "Invalid seed: " ++ show arg + _ -> die "Usage: <test-name> <seed>" + runTests iters seed t + +runTests :: Iterations -> Word64 -> Test -> IO () +runTests iters seed t = do + res <- runReaderT (runTestInternal iters t) (RunS 0 seed []) + case res of + Success -> return () + Failure tests -> do + putStrLn $ "Seed: " ++ show seed + putStrLn $ "These tests failed:\n" + ++ intercalate "\n" (map (showStack 0 . reverse) tests) + exitFailure + +-------------------------------------------------------------------------------- +-- Random number generation (linear congruences) + +-- Constants from Knuth's MMIX + +lcgMultiplier :: Word64 +lcgMultiplier = 6364136223846793005 +lcgIncrement :: Word64 +lcgIncrement = 1442695040888963407 + +-- | Pure step function for the linear congruential generator +stepLCG :: Word64 -> (Word64, Word64) +stepLCG s = + let s' = s * lcgMultiplier + lcgIncrement + in (s', s') + +-------------------------------------------------------------------------------- +-- Primitive generators + +-- | Generate a uniformly random 'Word64'. +arbitraryWord64 :: Gen Word64 +arbitraryWord64 = Gen $ state stepLCG + +-- | Generate a uniformly random 'Int64' (bit-reinterpretation of a Word64). +arbitraryInt64 :: Gen Int64 +arbitraryInt64 = fromIntegral <$> arbitraryWord64 + +-- | Shrink a random 'Int64' down to a smaller integral type. +integralDownsize :: (Integral a, FiniteBits a) => Int64 -> a +integralDownsize = wordDownsize . fromIntegral + +-- | Shrink a random 'Word64' down to a smaller integral type. +wordDownsize :: forall a. (Integral a, FiniteBits a) => Word64 -> a +wordDownsize w = + fromIntegral (w `shiftR` (64 - finiteBitSize (undefined :: a))) + -- take the higher bits (more random with our LCG) + +-------------------------------------------------------------------------------- +-- Basic Arbitrary instances + +instance Arbitrary Bool where + arbitrary = ( == 1 ) . ( `shiftR` 63 ) <$> arbitraryWord64 + +instance Arbitrary Word64 where + arbitrary = arbitraryWord64 +instance Arbitrary Word32 where + arbitrary = wordDownsize <$> arbitraryWord64 +instance Arbitrary Word16 where + arbitrary = wordDownsize <$> arbitraryWord64 +instance Arbitrary Word8 where + arbitrary = wordDownsize <$> arbitraryWord64 +instance Arbitrary Word where + arbitrary = fromIntegral <$> arbitraryWord64 + +instance Arbitrary Int64 where + arbitrary = arbitraryInt64 +instance Arbitrary Int32 where + arbitrary = integralDownsize <$> arbitraryInt64 +instance Arbitrary Int16 where + arbitrary = integralDownsize <$> arbitraryInt64 +instance Arbitrary Int8 where + arbitrary = integralDownsize <$> arbitraryInt64 +instance Arbitrary Int where + arbitrary = fromIntegral <$> arbitraryInt64 + +-- | Generates a natural number with at most 192 bits set. +instance Arbitrary Natural where + arbitrary = do + cx <- ( `shiftR` 62 ) <$> arbitraryWord64 + n1 <- fromIntegral <$> arbitraryWord64 + n2 <- fromIntegral <$> arbitraryWord64 + n3 <- fromIntegral <$> arbitraryWord64 + + pure $ case cx of + 0 -> n1 + 1 -> (n1 `shiftL` 64) .|. n2 + _ -> (n1 `shiftL` 128) .|. (n2 `shiftL` 64) .|. n3 + +-- | Generates an integer with at most 192 bits set. +instance Arbitrary Integer where + arbitrary = do + nat <- arbitrary @Natural + neg <- arbitrary @Bool + + pure $ + if neg + then negate (fromIntegral nat) + else fromIntegral nat + +instance Arbitrary Char where + arbitrary = do + let high = fromIntegral (fromEnum (maxBound :: Char)) :: Word + x <- arbitrary + return (toEnum . fromIntegral $ x `mod` (high + 1)) + +-------------------------------------------------------------------------------- +-- Useful newtypes for different Arbitrary instances + +-- | Wrapper for non-zero values. +newtype NonZero a = NonZero { getNonZero :: a } + deriving (Eq, Ord, Bounded, Show) + +-- | Generator that rejects zero values. +nonZero :: (Arbitrary a, Num a, Eq a) => Gen (NonZero a) +nonZero = do + x <- arbitrary + if x == 0 then nonZero else pure (NonZero x) + +instance (Arbitrary a, Num a, Eq a) => Arbitrary (NonZero a) where + arbitrary = nonZero + +-- | Shift amount bounded to @[0, finiteBitSize - 1]@. +newtype BoundedShiftAmount a = BoundedShiftAmount { getBoundedShiftAmount :: Int } + deriving (Eq, Ord, Show) + +instance FiniteBits a => Arbitrary (BoundedShiftAmount a) where + arbitrary = do + x <- arbitrary + let w = finiteBitSize (undefined :: a) + pure $ BoundedShiftAmount (abs x `mod` w) + +-- | @a `BoundedBy` n@ represents numbers with maximum absolute value @n@ (inclusive). +type BoundedBy :: Type -> Nat -> Type +newtype BoundedBy a n = BoundedBy { getBoundedBy :: a } + deriving (Eq, Ord, Show) + +instance + forall n a + . ( KnownNat n, Integral a, Arbitrary a ) + => Arbitrary ( a `BoundedBy` n ) where + arbitrary = BoundedBy . (`rem` (n + 1)) <$> arbitrary + where + n :: a + n = fromIntegral $ natVal @n Proxy ===================================== testsuite/tests/numeric/should_run/all.T ===================================== @@ -3,8 +3,6 @@ # extra run flags # expected process return value, if not zero -import random - # some bugs only surface with -O, omitting optasm may cause them to # slip into releases! (e.g. #26711) setTestOpts(when(have_ncg(), extra_ways(['optasm']))) @@ -89,7 +87,14 @@ test('T20291', normal, compile_and_run, ['']) test('T22282', normal, compile_and_run, ['']) test('T22671', js_fragile(24259), compile_and_run, ['']) # the high run timeout multiplier exists because of timeouts with the wasm backend -test('foundation', [run_timeout_multiplier(4), js_fragile(24259), extra_ways(['optasm','ghci','ghci-opt']), extra_run_opts(str(random.getrandbits(64)))], compile_and_run, ['-fno-break-points']) +test('foundation', + [ mini_quickcheck + , run_timeout_multiplier(4) + , js_fragile(24259) + , extra_ways(['optasm','ghci','ghci-opt']) + ] + , multimod_compile_and_run + , ['foundation', '-fno-break-points']) test('T24066', normal, compile_and_run, ['']) test('div01', normal, compile_and_run, ['']) test('T24245', normal, compile_and_run, ['']) ===================================== testsuite/tests/numeric/should_run/foundation.hs ===================================== @@ -23,252 +23,20 @@ module Main ( main ) where -import Data.Array.Byte -import Data.Bits (Bits((.&.), bit), FiniteBits, finiteBitSize) -import Data.Word +import Data.Bits (Bits((.&.), bit)) +import Data.Function (on) import Data.Int -import GHC.Natural import Data.Typeable +import Data.Word import GHC.Int -import GHC.Word -import Data.Function +import GHC.Natural import GHC.Prim -import Control.Monad.Reader -import Data.List (intercalate) -import System.Environment (getArgs) -import Text.Read (readMaybe) -import Unsafe.Coerce import GHC.Types -import Data.Char -import System.Exit - +import GHC.Word import qualified GHC.Internal.PrimopWrappers as Wrapper -import qualified GHC.Internal.Prim as Primop - -newtype Gen a = Gen { runGen :: (ReaderT LCGGen IO a) } - deriving newtype (Functor, Applicative, Monad) - -class Arbitrary a where - arbitrary :: Gen a - -class IsProperty p where - property :: p -> Property +import qualified GHC.Internal.Prim as Primop -data PropertyCheck = PropertyBinaryOp Bool String String String - | PropertyAnd PropertyCheck PropertyCheck - -instance IsProperty PropertyCheck where - property check = Prop $ pure (PropertyEOA check) - -data PropertyTestArg = PropertyEOA PropertyCheck - | PropertyArg String PropertyTestArg - -getCheck :: PropertyTestArg -> ([String], PropertyCheck) -getCheck (PropertyEOA pc) = ([], pc) -getCheck (PropertyArg s pta ) = let (ss, pc) = getCheck pta in (s:ss, pc) - -data Property = Prop { unProp :: Gen PropertyTestArg } - -instance (Show a, Arbitrary a, IsProperty prop) => IsProperty (a -> prop) where - property p = forAll arbitrary p - --- | Running a generator for a specific type under a property -forAll :: (Show a, IsProperty prop) => Gen a -> (a -> prop) -> Property -forAll generator tst = Prop $ do - a <- generator - augment a <$> unProp (property (tst a)) - where - augment a arg = PropertyArg (show a) arg - --- | A property that check for equality of its 2 members. -propertyCompare :: (Show a) => String -> (a -> a -> Bool) -> a -> a -> PropertyCheck -propertyCompare s f a b = - let sa = show a - sb = show b - in PropertyBinaryOp (a `f` b) s sa sb - -(===) :: (Show a, Eq a) => a -> a -> PropertyCheck -(===) = propertyCompare "==" (==) -infix 4 === - -propertyAnd = PropertyAnd - - -data Test where - Group :: String -> [Test] -> Test - Property :: IsProperty prop => String -> prop -> Test - - -arbitraryInt64 :: Gen Int64 -arbitraryInt64 = Gen $ do - h <- ask - W64# w <- liftIO (randomWord64 h) - return (I64# (unsafeCoerce# w)) - -integralDownsize :: (Integral a) => Int64 -> a -integralDownsize = fromIntegral - -wordDownsize :: (Integral a) => Word64 -> a -wordDownsize = fromIntegral - -arbitraryWord64 :: Gen Word64 -arbitraryWord64 = Gen $ do - h <- ask - liftIO (randomWord64 h) - -nonZero :: (Arbitrary a, Num a, Eq a) => Gen (NonZero a) -nonZero = do - x <- arbitrary - if x == 0 then nonZero else pure $ NonZero x - -newtype NonZero a = NonZero { getNonZero :: a } - deriving (Eq,Ord,Bounded,Show) - -instance (Arbitrary a, Num a, Eq a) => Arbitrary (NonZero a) where - arbitrary = nonZero - --- | A newtype for shift amounts that are bounded by @wordSize - 1@ -newtype BoundedShiftAmount a = BoundedShiftAmount {getBoundedShiftAmount :: Int} - deriving (Eq, Ord, Show) - -instance (FiniteBits a) => Arbitrary (BoundedShiftAmount a) where - arbitrary = do - x <- arbitrary - let widthBits = finiteBitSize (undefined :: a) - pure $ BoundedShiftAmount (abs x `mod` widthBits) - -instance Arbitrary Natural where - arbitrary = integralDownsize . (`mod` 10000) . abs <$> arbitraryInt64 - --- Bounded by Int64 -instance Arbitrary Integer where - arbitrary = fromIntegral <$> arbitraryInt64 - -instance Arbitrary Int where - arbitrary = int64ToInt <$> arbitraryInt64 -instance Arbitrary Word where - arbitrary = word64ToWord <$> arbitraryWord64 -instance Arbitrary Word64 where - arbitrary = arbitraryWord64 -instance Arbitrary Word32 where - arbitrary = wordDownsize <$> arbitraryWord64 -instance Arbitrary Word16 where - arbitrary = wordDownsize <$> arbitraryWord64 -instance Arbitrary Word8 where - arbitrary = wordDownsize <$> arbitraryWord64 -instance Arbitrary Int64 where - arbitrary = arbitraryInt64 -instance Arbitrary Int32 where - arbitrary = integralDownsize <$> arbitraryInt64 -instance Arbitrary Int16 where - arbitrary = integralDownsize <$> arbitraryInt64 -instance Arbitrary Int8 where - arbitrary = integralDownsize <$> arbitraryInt64 - -instance Arbitrary Char where - arbitrary = do - let high = fromIntegral $ fromEnum (maxBound :: Char) :: Word - (x::Word) <- arbitrary - let x' = mod x high - return (chr $ fromIntegral x') - -int64ToInt :: Int64 -> Int -int64ToInt (I64# i) = I# (int64ToInt# i) - - -word64ToWord :: Word64 -> Word -word64ToWord (W64# i) = W# (word64ToWord# i) - - -data RunS = RunS { depth :: Int, rg :: LCGGen, context :: [String] } - -newtype LCGGen = LCGGen { randomWord64 :: IO Word64 } - -data LCGParams = LCGParams { seed :: Word64, a :: Word64, c :: Word64, m :: Word64 } - -newLCGGen :: LCGParams -> IO LCGGen -newLCGGen LCGParams {seed = W64# seed#, ..} = do - MutableByteArray mba# <- IO $ \s0 -> case newByteArray# 8# s0 of - (# s1, mba# #) -> case writeWord64Array# mba# 0# seed# s1 of - s2 -> (# s2, MutableByteArray mba# #) - pure $ LCGGen $ IO $ \s0 -> case readWord64Array# mba# 0# s0 of - (# s1, old_val# #) -> - let old_val = W64# old_val# - !new_val@(W64# new_val#) = (old_val * a + c) `mod` m - in case writeWord64Array# mba# 0# new_val# s1 of - s2 -> (# s2, new_val #) - -runPropertyCheck (PropertyBinaryOp res desc s1 s2) = - if res then return Success - else do - ctx <- context <$> ask - let msg = "Failure: " ++ s1 ++ desc ++ s2 - putMsg msg - return (Failure [msg : ctx]) -runPropertyCheck (PropertyAnd a1 a2) = (<>) <$> runPropertyCheck a1 <*> runPropertyCheck a2 - -runProperty :: Property -> ReaderT RunS IO Result -runProperty (Prop p) = do - let iterations = 1000 :: Int - loop iterations iterations - where - loop iterations 0 = do - putMsg ("Passed " ++ show iterations ++ " iterations") - return Success - loop iterations n = do - h <- rg <$> ask - p <- liftIO (runReaderT (runGen p) h) - let (ss, pc) = getCheck p - res <- runPropertyCheck pc - case res of - Success -> loop iterations (n-1) - Failure msgs -> do - let msg = ("With arguments " ++ intercalate ", " ss) - putMsg msg - return (Failure (map (msg :) msgs)) - -data Result = Success | Failure [[String]] - -instance Semigroup Result where - Success <> x = x - x <> Success = x - (Failure xs) <> (Failure ys) = Failure (xs ++ ys) - -instance Monoid Result where - mempty = Success - -putMsg s = do - n <- depth <$> ask - liftIO . putStrLn $ replicate (n * 2) ' ' ++ s - - -nest c = local (\s -> s { depth = depth s + 1, context = c : context s }) - -runTestInternal :: Test -> ReaderT RunS IO Result -runTestInternal (Group name tests) = do - let label = ("Group " ++ name) - putMsg label - nest label (mconcat <$> mapM runTestInternal tests) -runTestInternal (Property name p) = do - let label = ("Running " ++ name) - putMsg label - nest label $ runProperty (property p) - - -runTests :: Word64 -> Test -> IO () -runTests seed t = do - -- These params are the same ones as glibc uses. - h <- newLCGGen (LCGParams { seed, m = 2 ^ (31 :: Int), a = 1103515245, c = 12345 }) - res <- runReaderT (runTestInternal t) (RunS 0 h []) - case res of - Success -> return () - Failure tests -> do - putStrLn $ "Seed: " ++ show seed - putStrLn $ "These tests failed: \n" ++ intercalate " \n" (map (showStack 0 . reverse) tests) - exitFailure - -showStack _ [] = "" -showStack n (s:ss) = replicate n ' ' ++ s ++ "\n" ++ showStack (n + 2) ss +import MiniQuickCheck ------------------------------------------------------------------------------- @@ -325,8 +93,11 @@ testOperatorPrecedence _ = Group "Precedence" , Property "+ and * (2)" $ \(a :: a) (b :: a) (c :: a) -> (a * b + c) === ((a * b) + c) , Property "- and * (1)" $ \(a :: a) (b :: a) (c :: a) -> (a - b * c) === (a - (b * c)) , Property "- and * (2)" $ \(a :: a) (b :: a) (c :: a) -> (a * b - c) === ((a * b) - c) - , Property "* and ^ (1)" $ \(a :: a) (b :: Natural) (c :: a) -> (a ^ b * c) === ((a ^ b) * c) - , Property "* and ^ (2)" $ \(a :: a) (c :: Natural) (b :: a) -> (a * b ^ c) === (a * (b ^ c)) + + -- Bound the exponent to avoid OOM errors e.g. + -- GNU MP: Cannot allocate memory (size=4294938656) + , Property "* and ^ (1)" $ \(a :: a) (BoundedBy b :: Natural `BoundedBy` 100) (c :: a) -> (a ^ b * c) === ((a ^ b) * c) + , Property "* and ^ (2)" $ \(a :: a) (BoundedBy c :: Natural `BoundedBy` 100) (b :: a) -> (a * b ^ c) === (a * (b ^ c)) ] @@ -454,19 +225,8 @@ instance TestPrimop LowerBitsAreDefined where twoNonZero :: (a -> a -> b) -> a -> NonZero a -> b twoNonZero f x (NonZero y) = f x y -getSeedFromArgs :: IO Word64 -getSeedFromArgs = do - args <- getArgs - case args of - [arg] -> case readMaybe arg of - Just seed -> pure seed - Nothing -> die $ "Invalid seed (expected Word64): " ++ show arg - _ -> die "Usage: foundation <seed>" - main :: IO () -main = do - seed <- getSeedFromArgs - runTests seed (Group "ALL" [testNumberRefs, testPrimops]) +main = runTestsMain (Iterations 1000) (Group "ALL" [testNumberRefs, testPrimops]) -- Test an interpreted primop vs a compiled primop testPrimops = Group "primop" ===================================== testsuite/tests/simd/should_run/all.T ===================================== @@ -80,7 +80,7 @@ test('simd002', [], compile_and_run, ['']) test('simd003', [], compile_and_run, ['']) test('simd004', [], compile_and_run, ['-O2']) test('simd005', [], compile_and_run, ['']) -test('simd006', [], compile_and_run, ['']) +test('simd006', [mini_quickcheck], multimod_compile_and_run, ['simd006', '']) test('simd007', [], compile_and_run, ['']) test('simd008', [], compile_and_run, ['']) test('simd009', [ req_th ===================================== testsuite/tests/simd/should_run/simd006.hs ===================================== @@ -1,161 +1,79 @@ -{-# LANGUAGE MagicHash #-} -{-# LANGUAGE UnboxedTuples #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TypeFamilies #-} -{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE UnboxedTuples #-} +{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE GeneralisedNewtypeDeriving #-} -{-# LANGUAGE MagicHash #-} -{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} --- QuickCheck testing for SIMD operations +-- QuickCheck-like property tests for SIMD vector operations. -module Main - ( main - ) where +module Main (main) where -import Data.Word -import Data.Int -import GHC.Natural import Data.Coerce -import Data.Typeable -import Data.Proxy -import GHC.Int -import GHC.Word -import Data.Function +import Data.Word import GHC.Prim -import Control.Monad.Reader -import System.IO -import Foreign.Marshal.Alloc -import Foreign.Storable -import Foreign.Ptr -import Data.List (intercalate) -import Data.IORef -import Unsafe.Coerce import GHC.Exts import GHC.Float ( castFloatToWord32 , castWord32ToFloat , castDoubleToWord64, castWord64ToDouble ) +import MiniQuickCheck +-------------------------------------------------------------------------------- +-- Scalar wrappers that use bit-equality to test for equality. -newtype Gen a = Gen { runGen :: (ReaderT LCGGen IO a) } - deriving newtype (Functor, Applicative, Monad) - -class Arbitrary a where - arbitrary :: Gen a - -class IsProperty p where - property :: p -> Property - -data PropertyCheck = PropertyBinaryOp Bool String String String - | PropertyAnd PropertyCheck PropertyCheck - -instance IsProperty PropertyCheck where - property check = Prop $ pure (PropertyEOA check) - -data PropertyTestArg = PropertyEOA PropertyCheck - | PropertyArg String PropertyTestArg - -getCheck :: PropertyTestArg -> ([String], PropertyCheck) -getCheck (PropertyEOA pc) = ([], pc) -getCheck (PropertyArg s pta ) = let (ss, pc) = getCheck pta in (s:ss, pc) - -data Property = Prop { unProp :: Gen PropertyTestArg } - -instance (Show a, Arbitrary a, IsProperty prop) => IsProperty (a -> prop) where - property p = forAll arbitrary p - --- | Running a generator for a specific type under a property -forAll :: (Show a, IsProperty prop) => Gen a -> (a -> prop) -> Property -forAll generator tst = Prop $ do - a <- generator - augment a <$> unProp (property (tst a)) - where - augment a arg = PropertyArg (show a) arg - --- | A property that check for equality of its 2 members. -propertyCompare :: (Show a) => String -> (a -> a -> Bool) -> a -> a -> PropertyCheck -propertyCompare s f a b = - let sa = show a - sb = show b - in PropertyBinaryOp (a `f` b) s sa sb - -(===) :: (Show a, Eq a) => a -> a -> PropertyCheck -(===) = propertyCompare "==" (==) -infix 4 === - -propertyAnd = PropertyAnd - - -data Test where - Group :: String -> [Test] -> Test - Property :: IsProperty prop => String -> prop -> Test - +newtype FloatNT = FloatNT Float + deriving newtype (Show, Num) -arbitraryInt64 :: Gen Int64 -arbitraryInt64 = Gen $ do - h <- ask - W64# w <- liftIO (randomWord64 h) - return (I64# (unsafeCoerce# w)) +instance Eq FloatNT where + FloatNT f1 == FloatNT f2 = castFloatToWord32 f1 == castFloatToWord32 f2 -integralDownsize :: (Integral a) => Int64 -> a -integralDownsize = fromIntegral +instance Arbitrary FloatNT where + arbitrary = FloatNT . castWord32ToFloat <$> arbitrary -wordDownsize :: (Integral a) => Word64 -> a -wordDownsize = fromIntegral +newtype DoubleNT = DoubleNT Double + deriving newtype (Show, Num) -arbitraryWord64 :: Gen Word64 -arbitraryWord64 = Gen $ do - h <- ask - liftIO (randomWord64 h) +instance Eq DoubleNT where + DoubleNT d1 == DoubleNT d2 = castDoubleToWord64 d1 == castDoubleToWord64 d2 +instance Arbitrary DoubleNT where + arbitrary = DoubleNT . castWord64ToDouble <$> arbitrary -instance Arbitrary Word64 where - arbitrary = arbitraryWord64 -instance Arbitrary Word32 where - arbitrary = wordDownsize <$> arbitraryWord64 +-------------------------------------------------------------------------------- +-- Min/max for the types under test class HasMinMax a where mini, maxi :: a -> a -> a + instance HasMinMax FloatNT where mini (FloatNT (F# f1)) (FloatNT (F# f2)) = FloatNT (F# (minFloat# f1 f2)) maxi (FloatNT (F# f1)) (FloatNT (F# f2)) = FloatNT (F# (maxFloat# f1 f2)) + instance HasMinMax DoubleNT where mini (DoubleNT (D# d1)) (DoubleNT (D# d2)) = DoubleNT (D# (minDouble# d1 d2)) maxi (DoubleNT (D# d1)) (DoubleNT (D# d2)) = DoubleNT (D# (maxDouble# d1 d2)) -newtype FloatNT = FloatNT Float - deriving newtype (Show, Num) -instance Eq FloatNT where - FloatNT f1 == FloatNT f2 = - castFloatToWord32 f1 == castFloatToWord32 f2 -instance Arbitrary FloatNT where - arbitrary = FloatNT . castWord32ToFloat <$> arbitrary -newtype DoubleNT = DoubleNT Double - deriving newtype (Show, Num) -instance Eq DoubleNT where - DoubleNT d1 == DoubleNT d2 = - castDoubleToWord64 d1 == castDoubleToWord64 d2 -instance Arbitrary DoubleNT where - arbitrary = DoubleNT . castWord64ToDouble <$> arbitrary - +-------------------------------------------------------------------------------- +-- SIMD vector types data FloatX4 = FX4# FloatX4# + instance Show FloatX4 where - show (FX4# f) = case (unpackFloatX4# f) of - (# a, b, c, d #) -> show ((F# a), (F# b), (F# c), (F# d)) + show (FX4# f) = case unpackFloatX4# f of + (# a, b, c, d #) -> show (F# a, F# b, F# c, F# d) + instance Eq FloatX4 where - (FX4# a) == (FX4# b) - = case (unpackFloatX4# a) of + FX4# a == FX4# b + = case unpackFloatX4# a of (# a1, a2, a3, a4 #) -> - case (unpackFloatX4# b) of - (# b1, b2, b3, b4 #) -> FloatNT (F# a1) == FloatNT (F# b1) && - FloatNT (F# a2) == FloatNT (F# b2) && - FloatNT (F# a3) == FloatNT (F# b3) && - FloatNT (F# a4) == FloatNT (F# b4) + case unpackFloatX4# b of + (# b1, b2, b3, b4 #) -> + FloatNT (F# a1) == FloatNT (F# b1) && + FloatNT (F# a2) == FloatNT (F# b2) && + FloatNT (F# a3) == FloatNT (F# b3) && + FloatNT (F# a4) == FloatNT (F# b4) + instance Arbitrary FloatX4 where arbitrary = do FloatNT (F# f1) <- arbitrary @@ -163,52 +81,59 @@ instance Arbitrary FloatX4 where FloatNT (F# f3) <- arbitrary FloatNT (F# f4) <- arbitrary return $ FX4# (packFloatX4# (# f1, f2, f3, f4 #)) + instance Num FloatX4 where - FX4# x + FX4# y = - FX4# ( x `plusFloatX4#` y ) - FX4# x - FX4# y = - FX4# ( x `minusFloatX4#` y ) - negate ( FX4# x ) = FX4# ( negateFloatX4# x ) - FX4# x * FX4# y = - FX4# ( x `timesFloatX4#` y ) - abs = error "no" - signum = error "no" - fromInteger = error "no" + FX4# x + FX4# y = FX4# (x `plusFloatX4#` y) + FX4# x - FX4# y = FX4# (x `minusFloatX4#` y) + negate (FX4# x) = FX4# (negateFloatX4# x) + FX4# x * FX4# y = FX4# (x `timesFloatX4#` y) + abs = error "FloatX4: no abs" + signum = error "FloatX4: no signum" + fromInteger = error "FloatX4: no fromInteger" + instance HasMinMax FloatX4 where mini (FX4# a) (FX4# b) = FX4# (minFloatX4# a b) maxi (FX4# a) (FX4# b) = FX4# (maxFloatX4# a b) +-------------------------------------------------------------------------------- + data DoubleX2 = DX2# DoubleX2# + instance Show DoubleX2 where - show (DX2# d) = case (unpackDoubleX2# d) of - (# a, b #) -> show ((D# a), (D# b)) + show (DX2# d) = case unpackDoubleX2# d of + (# a, b #) -> show (D# a, D# b) + instance Eq DoubleX2 where - (DX2# a) == (DX2# b) - = case (unpackDoubleX2# a) of + DX2# a == DX2# b + = case unpackDoubleX2# a of (# a1, a2 #) -> - case (unpackDoubleX2# b) of - (# b1, b2 #) -> DoubleNT (D# a1) == DoubleNT (D# b1) && - DoubleNT (D# a2) == DoubleNT (D# b2) + case unpackDoubleX2# b of + (# b1, b2 #) -> + DoubleNT (D# a1) == DoubleNT (D# b1) && + DoubleNT (D# a2) == DoubleNT (D# b2) + instance Arbitrary DoubleX2 where arbitrary = do DoubleNT (D# d1) <- arbitrary DoubleNT (D# d2) <- arbitrary return $ DX2# (packDoubleX2# (# d1, d2 #)) + instance Num DoubleX2 where - DX2# x + DX2# y = - DX2# ( x `plusDoubleX2#` y ) - DX2# x - DX2# y = - DX2# ( x `minusDoubleX2#` y ) - negate ( DX2# x ) = DX2# ( negateDoubleX2# x ) - DX2# x * DX2# y = - DX2# ( x `timesDoubleX2#` y ) - abs = error "no" - signum = error "no" - fromInteger = error "no" + DX2# x + DX2# y = DX2# (x `plusDoubleX2#` y) + DX2# x - DX2# y = DX2# (x `minusDoubleX2#` y) + negate (DX2# x) = DX2# (negateDoubleX2# x) + DX2# x * DX2# y = DX2# (x `timesDoubleX2#` y) + abs = error "DoubleX2: no abs" + signum = error "DoubleX2: no signum" + fromInteger = error "DoubleX2: no fromInteger" + instance HasMinMax DoubleX2 where mini (DX2# a) (DX2# b) = DX2# (minDoubleX2# a b) maxi (DX2# a) (DX2# b) = DX2# (maxDoubleX2# a b) +-------------------------------------------------------------------------------- +-- Expression language for generating random expressions over vector types. + data Expr a where Lit :: a -> Expr a Add :: Expr a -> Expr a -> Expr a @@ -218,11 +143,12 @@ data Expr a where Min :: Expr a -> Expr a -> Expr a Max :: Expr a -> Expr a -> Expr a deriving (Show, Eq) + fmapExpr :: (a -> b) -> Expr a -> Expr b -fmapExpr f (Lit a) = Lit (f a) +fmapExpr f (Lit a) = Lit (f a) fmapExpr f (Add a b) = Add (fmapExpr f a) (fmapExpr f b) fmapExpr f (Sub a b) = Sub (fmapExpr f a) (fmapExpr f b) -fmapExpr f (Neg a) = Neg (fmapExpr f a) +fmapExpr f (Neg a) = Neg (fmapExpr f a) fmapExpr f (Mul a b) = Mul (fmapExpr f a) (fmapExpr f b) fmapExpr f (Min a b) = Min (fmapExpr f a) (fmapExpr f b) fmapExpr f (Max a b) = Max (fmapExpr f a) (fmapExpr f b) @@ -240,75 +166,16 @@ instance Arbitrary a => Arbitrary (Expr a) where _ -> Lit <$> arbitrary eval :: (Num a, HasMinMax a) => Expr a -> a -eval (Lit a) = a +eval (Lit a) = a eval (Add a b) = eval a + eval b eval (Sub a b) = eval a - eval b -eval (Neg a) = negate (eval a) +eval (Neg a) = negate (eval a) eval (Mul a b) = eval a * eval b eval (Min a b) = mini (eval a) (eval b) eval (Max a b) = maxi (eval a) (eval b) -int64ToInt :: Int64 -> Int -int64ToInt (I64# i) = I# (int64ToInt# i) - - -word64ToWord :: Word64 -> Word -word64ToWord (W64# i) = W# (word64ToWord# i) - - -data RunS = RunS { depth :: Int, rg :: LCGGen } - -newtype LCGGen = LCGGen { randomWord64 :: IO Word64 } - -data LCGParams = LCGParams { seed :: Word64, a :: Word64, c :: Word64, m :: Word64 } - -newLCGGen :: LCGParams -> IO LCGGen -newLCGGen LCGParams{..} = do - var <- newIORef (fromIntegral seed) - return $ LCGGen $ do - atomicModifyIORef' var (\old_v -> let new_val = (old_v * a + c) `mod` m in (new_val, new_val)) - - -runPropertyCheck (PropertyBinaryOp res desc s1 s2) = - if res then return True else (putMsg ("Failure: " ++ s1 ++ desc ++ s2) >> return False) -runPropertyCheck (PropertyAnd a1 a2) = (&&) <$> runPropertyCheck a1 <*> runPropertyCheck a2 - -runProperty :: Property -> ReaderT RunS IO () -runProperty (Prop p) = do - let iterations = 100 - loop iterations iterations - where - loop iterations 0 = putMsg ("Passed " ++ show iterations ++ " iterations") - loop iterations n = do - h <- rg <$> ask - p <- liftIO (runReaderT (runGen p) h) - let (ss, pc) = getCheck p - res <- runPropertyCheck pc - if res then loop iterations (n-1) - else putMsg ("With arguments " ++ intercalate ", " ss) - -putMsg s = do - n <- depth <$> ask - liftIO . putStrLn $ replicate (n * 2) ' ' ++ s - -nest = local (\s -> s { depth = depth s + 1 }) - -runTestInternal :: Test -> ReaderT RunS IO () -runTestInternal (Group name tests) = do - putMsg ("Group " ++ name) - nest (mapM_ runTestInternal tests) -runTestInternal (Property name p) = do - putMsg ("Running " ++ name) - nest $ runProperty (property p) - - -runTests :: Test -> IO () -runTests t = do - -- These params are the same ones as glibc uses. - h <- newLCGGen (LCGParams { seed = 1238123213, m = 2^31, a = 1103515245, c = 12345 }) - runReaderT (runTestInternal t) (RunS 0 h) - -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- Test groups testFloatX4 :: Test testFloatX4 = Group "FloatX4" @@ -324,15 +191,12 @@ testFloatX4 = Group "FloatX4" unpack :: FloatX4 -> ( FloatNT, FloatNT, FloatNT, FloatNT ) unpack (FX4# f) = case unpackFloatX4# f of (# f1, f2, f3, f4 #) -> coerce ( F# f1, F# f2, F# f3, F# f4 ) + get1, get2, get3, get4 :: FloatX4 -> FloatNT - get1 (FX4# f) = case unpackFloatX4# f of - (# f1, _, _, _ #) -> FloatNT (F# f1) - get2 (FX4# f) = case unpackFloatX4# f of - (# _, f2, _, _ #) -> FloatNT (F# f2) - get3 (FX4# f) = case unpackFloatX4# f of - (# _, _, f3, _ #) -> FloatNT (F# f3) - get4 (FX4# f) = case unpackFloatX4# f of - (# _, _, _, f4 #) -> FloatNT (F# f4) + get1 (FX4# f) = case unpackFloatX4# f of (# f1, _, _, _ #) -> FloatNT (F# f1) + get2 (FX4# f) = case unpackFloatX4# f of (# _, f2, _, _ #) -> FloatNT (F# f2) + get3 (FX4# f) = case unpackFloatX4# f of (# _, _, f3, _ #) -> FloatNT (F# f3) + get4 (FX4# f) = case unpackFloatX4# f of (# _, _, _, f4 #) -> FloatNT (F# f4) testDoubleX2 :: Test testDoubleX2 = Group "DoubleX2" @@ -346,16 +210,15 @@ testDoubleX2 = Group "DoubleX2" unpack :: DoubleX2 -> ( DoubleNT, DoubleNT ) unpack (DX2# d) = case unpackDoubleX2# d of (# d1, d2 #) -> coerce ( D# d1, D# d2 ) + get1, get2 :: DoubleX2 -> DoubleNT get1 (DX2# d) = case unpackDoubleX2# d of - (# d1, _ #) -> DoubleNT (D# d1) + (# d1, _ #) -> DoubleNT (D# d1) get2 (DX2# d) = case unpackDoubleX2# d of - (# _, d2 #) -> DoubleNT (D# d2) + (# _, d2 #) -> DoubleNT (D# d2) testSIMD :: Test -testSIMD = Group "ALL" - [ testFloatX4 - , testDoubleX2 - ] +testSIMD = Group "ALL" [testFloatX4, testDoubleX2] -main = runTests testSIMD +main :: IO () +main = runTestsMain (Iterations 100) testSIMD View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/b44d4322cb7e8a87259336ab7e06d9f... -- View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/b44d4322cb7e8a87259336ab7e06d9f... You're receiving this email because of your account on gitlab.haskell.org.
participants (1)
-
Marge Bot (@marge-bot)