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
-
4ed78760
by mangoiv at 2026-04-24T08:25:13-04:00
-
a03df654
by Cheng Shao at 2026-04-24T08:56:47-04:00
-
b1ae23de
by Cheng Shao at 2026-04-24T08:56:47-04:00
-
e45b5522
by sheaf at 2026-04-24T08:57:02-04:00
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:
| ... | ... | @@ -445,7 +445,7 @@ opsysVariables _ FreeBSD14 = mconcat |
| 445 | 445 | -- Prefer to use the system's clang-based toolchain and not gcc
|
| 446 | 446 | , "CC" =: "cc"
|
| 447 | 447 | , "CXX" =: "c++"
|
| 448 | - , "FETCH_GHC_VERSION" =: "9.10.1"
|
|
| 448 | + , "FETCH_GHC_VERSION" =: "9.10.3"
|
|
| 449 | 449 | , "CABAL_INSTALL_VERSION" =: "3.14.2.0"
|
| 450 | 450 | ]
|
| 451 | 451 | opsysVariables arch (Linux distro) = distroVariables arch distro
|
| ... | ... | @@ -1721,7 +1721,7 @@ |
| 1721 | 1721 | "CC": "cc",
|
| 1722 | 1722 | "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",
|
| 1723 | 1723 | "CXX": "c++",
|
| 1724 | - "FETCH_GHC_VERSION": "9.10.1",
|
|
| 1724 | + "FETCH_GHC_VERSION": "9.10.3",
|
|
| 1725 | 1725 | "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check",
|
| 1726 | 1726 | "RUNTEST_ARGS": "",
|
| 1727 | 1727 | "TEST_ENV": "x86_64-freebsd14-validate",
|
| ... | ... | @@ -4543,7 +4543,7 @@ |
| 4543 | 4543 | "CC": "cc",
|
| 4544 | 4544 | "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",
|
| 4545 | 4545 | "CXX": "c++",
|
| 4546 | - "FETCH_GHC_VERSION": "9.10.1",
|
|
| 4546 | + "FETCH_GHC_VERSION": "9.10.3",
|
|
| 4547 | 4547 | "IGNORE_PERF_FAILURES": "all",
|
| 4548 | 4548 | "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check",
|
| 4549 | 4549 | "RUNTEST_ARGS": "",
|
| ... | ... | @@ -5643,7 +5643,7 @@ |
| 5643 | 5643 | "CC": "cc",
|
| 5644 | 5644 | "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",
|
| 5645 | 5645 | "CXX": "c++",
|
| 5646 | - "FETCH_GHC_VERSION": "9.10.1",
|
|
| 5646 | + "FETCH_GHC_VERSION": "9.10.3",
|
|
| 5647 | 5647 | "INSTALL_CONFIGURE_ARGS": "--enable-strict-ghc-toolchain-check",
|
| 5648 | 5648 | "RUNTEST_ARGS": "",
|
| 5649 | 5649 | "TEST_ENV": "x86_64-freebsd14-validate"
|
| 1 | + |
|
| 2 | +<!--
|
|
| 1 | 3 | Thank you for your contribution to GHC!
|
| 2 | 4 | |
| 3 | -**Please read the checklist below to make sure your contribution fulfills these
|
|
| 4 | -expectations. Also please answer the following question in your MR description:**
|
|
| 5 | - |
|
| 6 | -**Where is the key part of this patch? That is, what should reviewers look at first?**
|
|
| 7 | - |
|
| 8 | -Please take a few moments to address the following points:
|
|
| 9 | - |
|
| 10 | - * [ ] if your MR touches `base` (or touches parts of `ghc-internal` used
|
|
| 11 | - or re-exported by `base`) more substantially than just amending comments
|
|
| 12 | - or documentation, you likely need to raise a
|
|
| 13 | - [CLC proposal](https://github.com/haskell/core-libraries-committee#base-package)
|
|
| 14 | - before merging it.
|
|
| 15 | - * [ ] if your MR may break existing programs (e.g. causes the
|
|
| 16 | - compiler to reject programs), please describe the expected breakage and add
|
|
| 17 | - the ~"user-facing" label. This will run ghc/head.hackage> to characterise
|
|
| 18 | - the effect of your change on Hackage.
|
|
| 19 | - * [ ] ensure that your commits are either individually buildable or squashed
|
|
| 20 | - * [ ] ensure that your commit messages describe *what they do*
|
|
| 21 | - (referring to tickets using `#NNNN` syntax when appropriate)
|
|
| 22 | - * [ ] have added source comments describing your change. For larger changes you
|
|
| 23 | - likely should add a [Note][notes] and cross-reference it from the relevant
|
|
| 24 | - places.
|
|
| 25 | - * [ ] add a [testcase to the testsuite][adding test].
|
|
| 26 | - * [ ] updates the users guide if applicable
|
|
| 27 | - * [ ] add a changelog entry in `changelog.d/` for user-facing changes (see [changelog guide][changelog]).
|
|
| 28 | - If this MR does not need a changelog entry, apply the ~"no-changelog" label.
|
|
| 5 | +Please read the checklist below to make sure your contribution fulfills these
|
|
| 6 | +expectations.
|
|
| 29 | 7 | |
| 30 | 8 | If you have any questions don't hesitate to open your merge request and inquire
|
| 31 | 9 | in a comment. If your patch isn't quite done yet please do add prefix your MR
|
| 32 | -title with `WIP:`.
|
|
| 10 | +title with Draft:
|
|
| 11 | + |
|
| 12 | +To make your contribution experience as smooth as possible, also check out
|
|
| 13 | +https://gitlab.haskell.org/ghc/ghc/-/wikis/Contributing-a-Patch
|
|
| 14 | +-->
|
|
| 15 | + |
|
| 16 | +## Changes contained in this patch
|
|
| 17 | +<!-- Where is the key part of this patch? That is, what should reviewers look at first? -->
|
|
| 18 | + |
|
| 19 | + |
|
| 20 | +## MR Checklist
|
|
| 21 | +<!-- Please take a few moments to address the following points: -->
|
|
| 22 | + |
|
| 23 | +- [ ] This MR solves the problem described in the following issue: <!-- issue number here (please open a new issue if there isn't one) -->
|
|
| 24 | +- [ ] A changelog entry was added in `changelog.d/` for user-facing changes (see [changelog guide][changelog]).
|
|
| 25 | + If this MR does not need a changelog entry, the ~"no-changelog" label was applied.
|
|
| 26 | +- [ ] 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).
|
|
| 27 | +- [ ] If this MR has the potential to break user programs, the ~"user-facing" label was applied to
|
|
| 28 | + test against head.hackage.
|
|
| 29 | +- [ ] All commits are either individually buildable or squashed.
|
|
| 30 | +- [ ] Commit messages describe *what they do*, referring to tickets using `#NNNNN` syntax.
|
|
| 31 | +- [ ] Source comments describing the change were added. For larger changes [notes][notes] and
|
|
| 32 | + cross-references from the relevant places were added (as applicable).
|
|
| 33 | +- [ ] [Testcases to the testsuite][adding test] were added (as applicable).
|
|
| 34 | +- [ ] The users guide was updated (as applicable).
|
|
| 33 | 35 | |
| 36 | +<!--
|
|
| 34 | 37 | By default a minimal validation pipeline is run on each merge request, the ~full-ci
|
| 35 | 38 | label can be applied to perform additional validation checks if your MR affects a more
|
| 36 | 39 | unusual configuration.
|
| 37 | 40 | |
| 38 | -Once your change is ready please remove the `WIP:` tag and wait for review. If
|
|
| 41 | +Once your change is ready please remove the `Draft:` tag and wait for review. If
|
|
| 39 | 42 | no one has offered a review in a few days then please leave a comment mentioning
|
| 40 | 43 | @triagers and apply the ~"Blocked on Review" label.
|
| 44 | +-->
|
|
| 41 | 45 | |
| 42 | 46 | [notes]: https://gitlab.haskell.org/ghc/ghc/wikis/commentary/coding-style#comments-in-the-source-code
|
| 43 | 47 | [adding test]: https://gitlab.haskell.org/ghc/ghc/wikis/building/running-tests/adding
|
| 1 | +section: compiler
|
|
| 2 | +synopsis: Reduce allocations when (de)serialising `Array` in the `ghc` library.
|
|
| 3 | +issues: #27109
|
|
| 4 | +mrs: !15805
|
|
| 5 | + |
|
| 6 | +description: {
|
|
| 7 | + The `ghc` library's `Binary` instance for `Array` was changed to
|
|
| 8 | + avoid allocating an intermediate list and to omit a redundant length
|
|
| 9 | + field during (de)serialisation.
|
|
| 10 | + |
|
| 11 | + This should only affect the `ghc` library's (de)serialisation code paths,
|
|
| 12 | + primarily when parsing HIE files and bytecode objects.
|
|
| 13 | +} |
| ... | ... | @@ -139,6 +139,7 @@ import Foreign.C |
| 139 | 139 | import System.IO
|
| 140 | 140 | import Data.Data
|
| 141 | 141 | import Data.IORef
|
| 142 | +import qualified Data.List.NonEmpty as NE
|
|
| 142 | 143 | import Data.Semigroup as Semi
|
| 143 | 144 | |
| 144 | 145 | import Foreign
|
| ... | ... | @@ -232,6 +233,7 @@ instance IsString FastString where |
| 232 | 233 | |
| 233 | 234 | instance Semi.Semigroup FastString where
|
| 234 | 235 | (<>) = appendFS
|
| 236 | + sconcat = concatFS . NE.toList
|
|
| 235 | 237 | |
| 236 | 238 | instance Monoid FastString where
|
| 237 | 239 | mempty = nilFS
|
| ... | ... | @@ -619,6 +621,42 @@ unpackFS fs = utf8DecodeShortByteString $ fs_sbs fs |
| 619 | 621 | zEncodeFS :: FastString -> FastZString
|
| 620 | 622 | zEncodeFS fs = fs_zenc fs
|
| 621 | 623 | |
| 624 | +-- Sometimes an `appendFS` operand is temporarily constructed, and we
|
|
| 625 | +-- should avoid retaining the unused `FastString` operand in the
|
|
| 626 | +-- table. The RULES below mitigate the issue by concatenating the
|
|
| 627 | +-- `ShortByteString`s instead when an operand is `fsLit` or
|
|
| 628 | +-- `mkFastString`, which cover most such `appendFS` use cases. See
|
|
| 629 | +-- #27205.
|
|
| 630 | + |
|
| 631 | +{-# RULES
|
|
| 632 | +"appendFS/fsLit y" forall x y.
|
|
| 633 | + appendFS x (fsLit y) =
|
|
| 634 | + mkFastStringShortByteString $
|
|
| 635 | + fs_sbs x Semi.<> utf8EncodeShortByteString y
|
|
| 636 | + #-}
|
|
| 637 | + |
|
| 638 | +{-# RULES
|
|
| 639 | +"appendFS/fsLit x" forall x y.
|
|
| 640 | + appendFS (fsLit x) y =
|
|
| 641 | + mkFastStringShortByteString $
|
|
| 642 | + utf8EncodeShortByteString x Semi.<> fs_sbs y
|
|
| 643 | + #-}
|
|
| 644 | + |
|
| 645 | +{-# RULES
|
|
| 646 | +"appendFS/mkFastString y" forall x y.
|
|
| 647 | + appendFS x (mkFastString y) =
|
|
| 648 | + mkFastStringShortByteString $
|
|
| 649 | + fs_sbs x Semi.<> utf8EncodeShortByteString y
|
|
| 650 | + #-}
|
|
| 651 | + |
|
| 652 | +{-# RULES
|
|
| 653 | +"appendFS/mkFastString x" forall x y.
|
|
| 654 | + appendFS (mkFastString x) y =
|
|
| 655 | + mkFastStringShortByteString $
|
|
| 656 | + utf8EncodeShortByteString x Semi.<> fs_sbs y
|
|
| 657 | + #-}
|
|
| 658 | + |
|
| 659 | +{-# INLINE[1] appendFS #-}
|
|
| 622 | 660 | appendFS :: FastString -> FastString -> FastString
|
| 623 | 661 | appendFS fs1 fs2 = mkFastStringShortByteString
|
| 624 | 662 | $ (Semi.<>) (fs_sbs fs1) (fs_sbs fs2)
|
| ... | ... | @@ -142,6 +142,8 @@ import Control.DeepSeq |
| 142 | 142 | import Control.Monad ( when, (<$!>), unless, forM_, void )
|
| 143 | 143 | import Foreign hiding (bit, setBit, clearBit, shiftL, shiftR, void)
|
| 144 | 144 | import Data.Array
|
| 145 | +import Data.Array.Base (unsafeFreezeIOArray)
|
|
| 146 | +import Data.Array.IArray (traverseArray_)
|
|
| 145 | 147 | import Data.Array.IO
|
| 146 | 148 | import Data.Array.Unsafe
|
| 147 | 149 | import qualified Data.Binary as Binary
|
| ... | ... | @@ -970,11 +972,12 @@ instance Binary a => Binary (NonEmpty a) where |
| 970 | 972 | instance (Ix a, Binary a, Binary b) => Binary (Array a b) where
|
| 971 | 973 | put_ bh arr = do
|
| 972 | 974 | put_ bh $ bounds arr
|
| 973 | - put_ bh $ elems arr
|
|
| 975 | + traverseArray_ (put_ bh) arr
|
|
| 976 | + |
|
| 974 | 977 | get bh = do
|
| 975 | - bounds <- get bh
|
|
| 976 | - xs <- get bh
|
|
| 977 | - return $ listArray bounds xs
|
|
| 978 | + (l, u) <- get bh
|
|
| 979 | + marr <- newGenArray (l, u) $ \_ -> get bh
|
|
| 980 | + unsafeFreezeIOArray marr
|
|
| 978 | 981 | |
| 979 | 982 | instance Binary a => Binary (SmallArray a) where
|
| 980 | 983 | put_ bh sa = do
|
| ... | ... | @@ -13,6 +13,7 @@ import time |
| 13 | 13 | import datetime
|
| 14 | 14 | import copy
|
| 15 | 15 | import glob
|
| 16 | +import random
|
|
| 16 | 17 | import sys
|
| 17 | 18 | from math import ceil, trunc, floor, log
|
| 18 | 19 | from pathlib import Path, PurePath
|
| ... | ... | @@ -648,6 +649,11 @@ def extra_files(files): |
| 648 | 649 | def _extra_files(name, opts, files):
|
| 649 | 650 | opts.extra_files.extend(files)
|
| 650 | 651 | |
| 652 | +def mini_quickcheck(name, opts):
|
|
| 653 | + miniqc = os.path.relpath(config.top / 'tests' / 'MiniQuickCheck.hs', opts.srcdir)
|
|
| 654 | + opts.extra_files.extend([miniqc])
|
|
| 655 | + opts.extra_run_opts += ' ' + str(random.getrandbits(64))
|
|
| 656 | + |
|
| 651 | 657 | # Record the size of a specific file
|
| 652 | 658 | def collect_size ( deviation, path ):
|
| 653 | 659 | return collect_size_func(deviation, lambda: path)
|
| 1 | +{-# LANGUAGE DataKinds #-}
|
|
| 2 | +{-# LANGUAGE DerivingStrategies #-}
|
|
| 3 | +{-# LANGUAGE GeneralisedNewtypeDeriving #-}
|
|
| 4 | +{-# LANGUAGE RecordWildCards #-}
|
|
| 5 | +{-# LANGUAGE TypeFamilies #-}
|
|
| 6 | + |
|
| 7 | +-- | A minimal QuickCheck-like property testing framework for use in the GHC
|
|
| 8 | +-- test suite.
|
|
| 9 | +--
|
|
| 10 | +-- We vendor this package to avoid depending on the real QuickCheck package,
|
|
| 11 | +-- as the latter (or one of its dependencies) may not build with the GHC version
|
|
| 12 | +-- being tested.
|
|
| 13 | +module MiniQuickCheck
|
|
| 14 | + ( -- * QuickCheck generator
|
|
| 15 | + Gen(..)
|
|
| 16 | + |
|
| 17 | + -- * QuickCheck typeclasses
|
|
| 18 | + , Arbitrary(..)
|
|
| 19 | + , IsProperty(..)
|
|
| 20 | + |
|
| 21 | + -- * QuickCheck properties
|
|
| 22 | + , PropertyCheck(..)
|
|
| 23 | + , PropertyTestArg(..)
|
|
| 24 | + , Property(..)
|
|
| 25 | + , forAll
|
|
| 26 | + , (===)
|
|
| 27 | + , propertyCompare
|
|
| 28 | + , propertyAnd
|
|
| 29 | + , getCheck
|
|
| 30 | + |
|
| 31 | + -- * QuickCheck test tree
|
|
| 32 | + , Test(..)
|
|
| 33 | + |
|
| 34 | + -- * Running QuickCheck tests
|
|
| 35 | + , Result(..)
|
|
| 36 | + , Iterations(..)
|
|
| 37 | + , runTestsMain
|
|
| 38 | + , runTests
|
|
| 39 | + , runTestInternal
|
|
| 40 | + |
|
| 41 | + -- * QuickCheck primitive generators
|
|
| 42 | + , arbitraryInt64
|
|
| 43 | + , arbitraryWord64
|
|
| 44 | + , integralDownsize
|
|
| 45 | + , wordDownsize
|
|
| 46 | + |
|
| 47 | + -- * QuickCheck newtypes
|
|
| 48 | + , NonZero(..)
|
|
| 49 | + , nonZero
|
|
| 50 | + , BoundedShiftAmount(..)
|
|
| 51 | + , BoundedBy(..)
|
|
| 52 | + ) where
|
|
| 53 | + |
|
| 54 | +-- base
|
|
| 55 | +import Control.Monad.IO.Class
|
|
| 56 | + ( liftIO )
|
|
| 57 | +import Data.Bits
|
|
| 58 | + ( (.|.), shiftL, shiftR
|
|
| 59 | + , FiniteBits, finiteBitSize
|
|
| 60 | + )
|
|
| 61 | +import Data.Int
|
|
| 62 | + ( Int8, Int16, Int32, Int64 )
|
|
| 63 | +import Data.IORef
|
|
| 64 | + ( newIORef, atomicModifyIORef' )
|
|
| 65 | +import Data.Kind
|
|
| 66 | + ( Type )
|
|
| 67 | +import Data.List
|
|
| 68 | + ( intercalate )
|
|
| 69 | +import Data.Proxy
|
|
| 70 | + ( Proxy(..) )
|
|
| 71 | +import Data.Word
|
|
| 72 | + ( Word8, Word16, Word32, Word64 )
|
|
| 73 | +import GHC.TypeNats
|
|
| 74 | + ( Nat, KnownNat, natVal )
|
|
| 75 | +import Numeric.Natural
|
|
| 76 | + ( Natural )
|
|
| 77 | +import System.Environment
|
|
| 78 | + ( getArgs )
|
|
| 79 | +import System.Exit
|
|
| 80 | + ( die, exitFailure )
|
|
| 81 | +import Text.Read
|
|
| 82 | + ( readMaybe )
|
|
| 83 | + |
|
| 84 | +-- transformers
|
|
| 85 | +import Control.Monad.Trans.Reader
|
|
| 86 | + ( ReaderT, runReaderT, ask, local )
|
|
| 87 | +import Control.Monad.Trans.State.Strict
|
|
| 88 | + ( State, state, runState )
|
|
| 89 | + |
|
| 90 | +--------------------------------------------------------------------------------
|
|
| 91 | +-- Core framework
|
|
| 92 | + |
|
| 93 | +newtype Gen a = Gen { runGen :: State Word64 a }
|
|
| 94 | + deriving newtype ( Functor, Applicative, Monad )
|
|
| 95 | + |
|
| 96 | +class Arbitrary a where
|
|
| 97 | + arbitrary :: Gen a
|
|
| 98 | + |
|
| 99 | +class IsProperty p where
|
|
| 100 | + property :: p -> Property
|
|
| 101 | + |
|
| 102 | +data PropertyCheck
|
|
| 103 | + = PropertyBinaryOp Bool String String String
|
|
| 104 | + | PropertyAnd PropertyCheck PropertyCheck
|
|
| 105 | + |
|
| 106 | +instance IsProperty PropertyCheck where
|
|
| 107 | + property check = Prop (pure (PropertyEOA check))
|
|
| 108 | + |
|
| 109 | +data PropertyTestArg
|
|
| 110 | + = PropertyEOA PropertyCheck
|
|
| 111 | + | PropertyArg String PropertyTestArg
|
|
| 112 | + |
|
| 113 | +getCheck :: PropertyTestArg -> ([String], PropertyCheck)
|
|
| 114 | +getCheck (PropertyEOA pc) = ([], pc)
|
|
| 115 | +getCheck (PropertyArg s pta) = let (ss, pc) = getCheck pta in (s:ss, pc)
|
|
| 116 | + |
|
| 117 | +data Property = Prop { unProp :: Gen PropertyTestArg }
|
|
| 118 | + |
|
| 119 | +instance (Show a, Arbitrary a, IsProperty prop) => IsProperty (a -> prop) where
|
|
| 120 | + property p = forAll arbitrary p
|
|
| 121 | + |
|
| 122 | +-- | Run a generator for a value of the given type and add it as an argument
|
|
| 123 | +-- to the property test.
|
|
| 124 | +forAll :: (Show a, IsProperty prop) => Gen a -> (a -> prop) -> Property
|
|
| 125 | +forAll generator tst = Prop $ do
|
|
| 126 | + a <- generator
|
|
| 127 | + augment a <$> unProp (property (tst a))
|
|
| 128 | + where
|
|
| 129 | + augment a arg = PropertyArg (show a) arg
|
|
| 130 | + |
|
| 131 | +-- | Build a @PropertyCheck@ by comparing two values with a named predicate.
|
|
| 132 | +propertyCompare :: Show a => String -> (a -> a -> Bool) -> a -> a -> PropertyCheck
|
|
| 133 | +propertyCompare s f a b = PropertyBinaryOp (f a b) s (show a) (show b)
|
|
| 134 | + |
|
| 135 | +-- | Check that two values are equal (by '==').
|
|
| 136 | +(===) :: (Show a, Eq a) => a -> a -> PropertyCheck
|
|
| 137 | +(===) = propertyCompare "==" (==)
|
|
| 138 | +infix 4 ===
|
|
| 139 | + |
|
| 140 | +-- | Conjunction of two property checks.
|
|
| 141 | +propertyAnd :: PropertyCheck -> PropertyCheck -> PropertyCheck
|
|
| 142 | +propertyAnd = PropertyAnd
|
|
| 143 | + |
|
| 144 | +--------------------------------------------------------------------------------
|
|
| 145 | +-- Test tree
|
|
| 146 | + |
|
| 147 | +-- | A named test or group of tests.
|
|
| 148 | +data Test where
|
|
| 149 | + Group :: String -> [Test] -> Test
|
|
| 150 | + Property :: IsProperty prop => String -> prop -> Test
|
|
| 151 | + |
|
| 152 | +--------------------------------------------------------------------------------
|
|
| 153 | +-- Test runner
|
|
| 154 | + |
|
| 155 | +newtype Iterations = Iterations { nbIterations :: Word }
|
|
| 156 | + deriving newtype ( Show, Eq, Ord )
|
|
| 157 | + |
|
| 158 | +-- | Outcome of running a test suite.
|
|
| 159 | +data Result = Success | Failure [[String]]
|
|
| 160 | + |
|
| 161 | +instance Semigroup Result where
|
|
| 162 | + Success <> y = y
|
|
| 163 | + x <> Success = x
|
|
| 164 | + Failure xs <> Failure ys = Failure (xs ++ ys)
|
|
| 165 | + |
|
| 166 | +instance Monoid Result where
|
|
| 167 | + mempty = Success
|
|
| 168 | + |
|
| 169 | +data RunS = RunS
|
|
| 170 | + { depth :: Int
|
|
| 171 | + , currentSeed :: Word64
|
|
| 172 | + , context :: [String]
|
|
| 173 | + }
|
|
| 174 | + |
|
| 175 | +putMsg :: String -> ReaderT RunS IO ()
|
|
| 176 | +putMsg s = do
|
|
| 177 | + n <- depth <$> ask
|
|
| 178 | + liftIO . putStrLn $ replicate (n * 2) ' ' ++ s
|
|
| 179 | + |
|
| 180 | +nest :: String -> ReaderT RunS IO a -> ReaderT RunS IO a
|
|
| 181 | +nest c = local (\s -> s { depth = depth s + 1, context = c : context s })
|
|
| 182 | + |
|
| 183 | +runPropertyCheck :: PropertyCheck -> ReaderT RunS IO Result
|
|
| 184 | +runPropertyCheck (PropertyBinaryOp ok desc s1 s2) =
|
|
| 185 | + if ok
|
|
| 186 | + then return Success
|
|
| 187 | + else do
|
|
| 188 | + ctx <- context <$> ask
|
|
| 189 | + let msg = "Failure: " ++ s1 ++ " " ++ desc ++ " " ++ s2
|
|
| 190 | + putMsg msg
|
|
| 191 | + return (Failure [msg : ctx])
|
|
| 192 | +runPropertyCheck (PropertyAnd a b) =
|
|
| 193 | + (<>) <$> runPropertyCheck a <*> runPropertyCheck b
|
|
| 194 | + |
|
| 195 | +runProperty :: Iterations -> Property -> ReaderT RunS IO Result
|
|
| 196 | +runProperty (Iterations iters) (Prop p) = do
|
|
| 197 | + startingSeed <- currentSeed <$> ask
|
|
| 198 | + loop iters startingSeed
|
|
| 199 | + where
|
|
| 200 | + loop 0 _ = do
|
|
| 201 | + putMsg ("Passed " ++ show iters ++ " iterations")
|
|
| 202 | + return Success
|
|
| 203 | + loop n s = do
|
|
| 204 | + let (pt, s') = runState (runGen p) s
|
|
| 205 | + (ss, pc) = getCheck pt
|
|
| 206 | + res <- runPropertyCheck pc
|
|
| 207 | + case res of
|
|
| 208 | + Success -> loop (n - 1) s'
|
|
| 209 | + Failure msgs -> do
|
|
| 210 | + let msg = "With arguments " ++ intercalate ", " ss ++ " (Seed: " ++ show s ++ ")"
|
|
| 211 | + putMsg msg
|
|
| 212 | + return (Failure (map (msg :) msgs))
|
|
| 213 | + |
|
| 214 | +-- | Run a single 'Test', accumulating all failures.
|
|
| 215 | +runTestInternal :: Iterations -> Test -> ReaderT RunS IO Result
|
|
| 216 | +runTestInternal iters (Group name tests) = do
|
|
| 217 | + let label = "Group " ++ name
|
|
| 218 | + putMsg label
|
|
| 219 | + env <- ask
|
|
| 220 | + nest label $ do
|
|
| 221 | + -- Compute initial seed for each test in the group, based on the
|
|
| 222 | + -- index of the test in the group.
|
|
| 223 | + let runOne idx t = do
|
|
| 224 | + let !s = snd $ stepLCG (currentSeed env + fromIntegral idx)
|
|
| 225 | + local (\e -> e { currentSeed = s }) (runTestInternal iters t)
|
|
| 226 | + mconcat <$> traverse (uncurry runOne) (zip [1..] tests)
|
|
| 227 | + |
|
| 228 | +runTestInternal iters (Property name p) = do
|
|
| 229 | + let label = "Running " ++ name
|
|
| 230 | + putMsg label
|
|
| 231 | + nest label (runProperty iters (property p))
|
|
| 232 | + |
|
| 233 | +showStack :: Int -> [String] -> String
|
|
| 234 | +showStack _ [] = ""
|
|
| 235 | +showStack n (s:ss) = replicate n ' ' ++ s ++ "\n" ++ showStack (n + 2) ss
|
|
| 236 | + |
|
| 237 | +-- | Standard @main@ entry point for tests using 'MiniQuickCheck'.
|
|
| 238 | +--
|
|
| 239 | +-- Reads a 'Word64' seed from the first command-line argument, then
|
|
| 240 | +-- delegates to 'runTests'.
|
|
| 241 | +runTestsMain :: Iterations -> Test -> IO ()
|
|
| 242 | +runTestsMain iters t = do
|
|
| 243 | + args <- getArgs
|
|
| 244 | + seed <- case args of
|
|
| 245 | + [arg] -> case readMaybe arg of
|
|
| 246 | + Just s -> pure s
|
|
| 247 | + Nothing -> die $ "Invalid seed: " ++ show arg
|
|
| 248 | + _ -> die "Usage: <test-name> <seed>"
|
|
| 249 | + runTests iters seed t
|
|
| 250 | + |
|
| 251 | +runTests :: Iterations -> Word64 -> Test -> IO ()
|
|
| 252 | +runTests iters seed t = do
|
|
| 253 | + res <- runReaderT (runTestInternal iters t) (RunS 0 seed [])
|
|
| 254 | + case res of
|
|
| 255 | + Success -> return ()
|
|
| 256 | + Failure tests -> do
|
|
| 257 | + putStrLn $ "Seed: " ++ show seed
|
|
| 258 | + putStrLn $ "These tests failed:\n"
|
|
| 259 | + ++ intercalate "\n" (map (showStack 0 . reverse) tests)
|
|
| 260 | + exitFailure
|
|
| 261 | + |
|
| 262 | +--------------------------------------------------------------------------------
|
|
| 263 | +-- Random number generation (linear congruences)
|
|
| 264 | + |
|
| 265 | +-- Constants from Knuth's MMIX
|
|
| 266 | + |
|
| 267 | +lcgMultiplier :: Word64
|
|
| 268 | +lcgMultiplier = 6364136223846793005
|
|
| 269 | +lcgIncrement :: Word64
|
|
| 270 | +lcgIncrement = 1442695040888963407
|
|
| 271 | + |
|
| 272 | +-- | Pure step function for the linear congruential generator
|
|
| 273 | +stepLCG :: Word64 -> (Word64, Word64)
|
|
| 274 | +stepLCG s =
|
|
| 275 | + let s' = s * lcgMultiplier + lcgIncrement
|
|
| 276 | + in (s', s')
|
|
| 277 | + |
|
| 278 | +--------------------------------------------------------------------------------
|
|
| 279 | +-- Primitive generators
|
|
| 280 | + |
|
| 281 | +-- | Generate a uniformly random 'Word64'.
|
|
| 282 | +arbitraryWord64 :: Gen Word64
|
|
| 283 | +arbitraryWord64 = Gen $ state stepLCG
|
|
| 284 | + |
|
| 285 | +-- | Generate a uniformly random 'Int64' (bit-reinterpretation of a Word64).
|
|
| 286 | +arbitraryInt64 :: Gen Int64
|
|
| 287 | +arbitraryInt64 = fromIntegral <$> arbitraryWord64
|
|
| 288 | + |
|
| 289 | +-- | Shrink a random 'Int64' down to a smaller integral type.
|
|
| 290 | +integralDownsize :: (Integral a, FiniteBits a) => Int64 -> a
|
|
| 291 | +integralDownsize = wordDownsize . fromIntegral
|
|
| 292 | + |
|
| 293 | +-- | Shrink a random 'Word64' down to a smaller integral type.
|
|
| 294 | +wordDownsize :: forall a. (Integral a, FiniteBits a) => Word64 -> a
|
|
| 295 | +wordDownsize w =
|
|
| 296 | + fromIntegral (w `shiftR` (64 - finiteBitSize (undefined :: a)))
|
|
| 297 | + -- take the higher bits (more random with our LCG)
|
|
| 298 | + |
|
| 299 | +--------------------------------------------------------------------------------
|
|
| 300 | +-- Basic Arbitrary instances
|
|
| 301 | + |
|
| 302 | +instance Arbitrary Bool where
|
|
| 303 | + arbitrary = ( == 1 ) . ( `shiftR` 63 ) <$> arbitraryWord64
|
|
| 304 | + |
|
| 305 | +instance Arbitrary Word64 where
|
|
| 306 | + arbitrary = arbitraryWord64
|
|
| 307 | +instance Arbitrary Word32 where
|
|
| 308 | + arbitrary = wordDownsize <$> arbitraryWord64
|
|
| 309 | +instance Arbitrary Word16 where
|
|
| 310 | + arbitrary = wordDownsize <$> arbitraryWord64
|
|
| 311 | +instance Arbitrary Word8 where
|
|
| 312 | + arbitrary = wordDownsize <$> arbitraryWord64
|
|
| 313 | +instance Arbitrary Word where
|
|
| 314 | + arbitrary = fromIntegral <$> arbitraryWord64
|
|
| 315 | + |
|
| 316 | +instance Arbitrary Int64 where
|
|
| 317 | + arbitrary = arbitraryInt64
|
|
| 318 | +instance Arbitrary Int32 where
|
|
| 319 | + arbitrary = integralDownsize <$> arbitraryInt64
|
|
| 320 | +instance Arbitrary Int16 where
|
|
| 321 | + arbitrary = integralDownsize <$> arbitraryInt64
|
|
| 322 | +instance Arbitrary Int8 where
|
|
| 323 | + arbitrary = integralDownsize <$> arbitraryInt64
|
|
| 324 | +instance Arbitrary Int where
|
|
| 325 | + arbitrary = fromIntegral <$> arbitraryInt64
|
|
| 326 | + |
|
| 327 | +-- | Generates a natural number with at most 192 bits set.
|
|
| 328 | +instance Arbitrary Natural where
|
|
| 329 | + arbitrary = do
|
|
| 330 | + cx <- ( `shiftR` 62 ) <$> arbitraryWord64
|
|
| 331 | + n1 <- fromIntegral <$> arbitraryWord64
|
|
| 332 | + n2 <- fromIntegral <$> arbitraryWord64
|
|
| 333 | + n3 <- fromIntegral <$> arbitraryWord64
|
|
| 334 | + |
|
| 335 | + pure $ case cx of
|
|
| 336 | + 0 -> n1
|
|
| 337 | + 1 -> (n1 `shiftL` 64) .|. n2
|
|
| 338 | + _ -> (n1 `shiftL` 128) .|. (n2 `shiftL` 64) .|. n3
|
|
| 339 | + |
|
| 340 | +-- | Generates an integer with at most 192 bits set.
|
|
| 341 | +instance Arbitrary Integer where
|
|
| 342 | + arbitrary = do
|
|
| 343 | + nat <- arbitrary @Natural
|
|
| 344 | + neg <- arbitrary @Bool
|
|
| 345 | + |
|
| 346 | + pure $
|
|
| 347 | + if neg
|
|
| 348 | + then negate (fromIntegral nat)
|
|
| 349 | + else fromIntegral nat
|
|
| 350 | + |
|
| 351 | +instance Arbitrary Char where
|
|
| 352 | + arbitrary = do
|
|
| 353 | + let high = fromIntegral (fromEnum (maxBound :: Char)) :: Word
|
|
| 354 | + x <- arbitrary
|
|
| 355 | + return (toEnum . fromIntegral $ x `mod` (high + 1))
|
|
| 356 | + |
|
| 357 | +--------------------------------------------------------------------------------
|
|
| 358 | +-- Useful newtypes for different Arbitrary instances
|
|
| 359 | + |
|
| 360 | +-- | Wrapper for non-zero values.
|
|
| 361 | +newtype NonZero a = NonZero { getNonZero :: a }
|
|
| 362 | + deriving (Eq, Ord, Bounded, Show)
|
|
| 363 | + |
|
| 364 | +-- | Generator that rejects zero values.
|
|
| 365 | +nonZero :: (Arbitrary a, Num a, Eq a) => Gen (NonZero a)
|
|
| 366 | +nonZero = do
|
|
| 367 | + x <- arbitrary
|
|
| 368 | + if x == 0 then nonZero else pure (NonZero x)
|
|
| 369 | + |
|
| 370 | +instance (Arbitrary a, Num a, Eq a) => Arbitrary (NonZero a) where
|
|
| 371 | + arbitrary = nonZero
|
|
| 372 | + |
|
| 373 | +-- | Shift amount bounded to @[0, finiteBitSize - 1]@.
|
|
| 374 | +newtype BoundedShiftAmount a = BoundedShiftAmount { getBoundedShiftAmount :: Int }
|
|
| 375 | + deriving (Eq, Ord, Show)
|
|
| 376 | + |
|
| 377 | +instance FiniteBits a => Arbitrary (BoundedShiftAmount a) where
|
|
| 378 | + arbitrary = do
|
|
| 379 | + x <- arbitrary
|
|
| 380 | + let w = finiteBitSize (undefined :: a)
|
|
| 381 | + pure $ BoundedShiftAmount (abs x `mod` w)
|
|
| 382 | + |
|
| 383 | +-- | @a `BoundedBy` n@ represents numbers with maximum absolute value @n@ (inclusive).
|
|
| 384 | +type BoundedBy :: Type -> Nat -> Type
|
|
| 385 | +newtype BoundedBy a n = BoundedBy { getBoundedBy :: a }
|
|
| 386 | + deriving (Eq, Ord, Show)
|
|
| 387 | + |
|
| 388 | +instance
|
|
| 389 | + forall n a
|
|
| 390 | + . ( KnownNat n, Integral a, Arbitrary a )
|
|
| 391 | + => Arbitrary ( a `BoundedBy` n ) where
|
|
| 392 | + arbitrary = BoundedBy . (`rem` (n + 1)) <$> arbitrary
|
|
| 393 | + where
|
|
| 394 | + n :: a
|
|
| 395 | + n = fromIntegral $ natVal @n Proxy |
| ... | ... | @@ -3,8 +3,6 @@ |
| 3 | 3 | # extra run flags
|
| 4 | 4 | # expected process return value, if not zero
|
| 5 | 5 | |
| 6 | -import random
|
|
| 7 | - |
|
| 8 | 6 | # some bugs only surface with -O, omitting optasm may cause them to
|
| 9 | 7 | # slip into releases! (e.g. #26711)
|
| 10 | 8 | setTestOpts(when(have_ncg(), extra_ways(['optasm'])))
|
| ... | ... | @@ -89,7 +87,14 @@ test('T20291', normal, compile_and_run, ['']) |
| 89 | 87 | test('T22282', normal, compile_and_run, [''])
|
| 90 | 88 | test('T22671', js_fragile(24259), compile_and_run, [''])
|
| 91 | 89 | # the high run timeout multiplier exists because of timeouts with the wasm backend
|
| 92 | -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'])
|
|
| 90 | +test('foundation',
|
|
| 91 | + [ mini_quickcheck
|
|
| 92 | + , run_timeout_multiplier(4)
|
|
| 93 | + , js_fragile(24259)
|
|
| 94 | + , extra_ways(['optasm','ghci','ghci-opt'])
|
|
| 95 | + ]
|
|
| 96 | + , multimod_compile_and_run
|
|
| 97 | + , ['foundation', '-fno-break-points'])
|
|
| 93 | 98 | test('T24066', normal, compile_and_run, [''])
|
| 94 | 99 | test('div01', normal, compile_and_run, [''])
|
| 95 | 100 | test('T24245', normal, compile_and_run, [''])
|
| ... | ... | @@ -23,252 +23,20 @@ module Main |
| 23 | 23 | ( main
|
| 24 | 24 | ) where
|
| 25 | 25 | |
| 26 | -import Data.Array.Byte
|
|
| 27 | -import Data.Bits (Bits((.&.), bit), FiniteBits, finiteBitSize)
|
|
| 28 | -import Data.Word
|
|
| 26 | +import Data.Bits (Bits((.&.), bit))
|
|
| 27 | +import Data.Function (on)
|
|
| 29 | 28 | import Data.Int
|
| 30 | -import GHC.Natural
|
|
| 31 | 29 | import Data.Typeable
|
| 30 | +import Data.Word
|
|
| 32 | 31 | import GHC.Int
|
| 33 | -import GHC.Word
|
|
| 34 | -import Data.Function
|
|
| 32 | +import GHC.Natural
|
|
| 35 | 33 | import GHC.Prim
|
| 36 | -import Control.Monad.Reader
|
|
| 37 | -import Data.List (intercalate)
|
|
| 38 | -import System.Environment (getArgs)
|
|
| 39 | -import Text.Read (readMaybe)
|
|
| 40 | -import Unsafe.Coerce
|
|
| 41 | 34 | import GHC.Types
|
| 42 | -import Data.Char
|
|
| 43 | -import System.Exit
|
|
| 44 | - |
|
| 35 | +import GHC.Word
|
|
| 45 | 36 | import qualified GHC.Internal.PrimopWrappers as Wrapper
|
| 46 | -import qualified GHC.Internal.Prim as Primop
|
|
| 47 | - |
|
| 48 | -newtype Gen a = Gen { runGen :: (ReaderT LCGGen IO a) }
|
|
| 49 | - deriving newtype (Functor, Applicative, Monad)
|
|
| 50 | - |
|
| 51 | -class Arbitrary a where
|
|
| 52 | - arbitrary :: Gen a
|
|
| 53 | - |
|
| 54 | -class IsProperty p where
|
|
| 55 | - property :: p -> Property
|
|
| 37 | +import qualified GHC.Internal.Prim as Primop
|
|
| 56 | 38 | |
| 57 | -data PropertyCheck = PropertyBinaryOp Bool String String String
|
|
| 58 | - | PropertyAnd PropertyCheck PropertyCheck
|
|
| 59 | - |
|
| 60 | -instance IsProperty PropertyCheck where
|
|
| 61 | - property check = Prop $ pure (PropertyEOA check)
|
|
| 62 | - |
|
| 63 | -data PropertyTestArg = PropertyEOA PropertyCheck
|
|
| 64 | - | PropertyArg String PropertyTestArg
|
|
| 65 | - |
|
| 66 | -getCheck :: PropertyTestArg -> ([String], PropertyCheck)
|
|
| 67 | -getCheck (PropertyEOA pc) = ([], pc)
|
|
| 68 | -getCheck (PropertyArg s pta ) = let (ss, pc) = getCheck pta in (s:ss, pc)
|
|
| 69 | - |
|
| 70 | -data Property = Prop { unProp :: Gen PropertyTestArg }
|
|
| 71 | - |
|
| 72 | -instance (Show a, Arbitrary a, IsProperty prop) => IsProperty (a -> prop) where
|
|
| 73 | - property p = forAll arbitrary p
|
|
| 74 | - |
|
| 75 | --- | Running a generator for a specific type under a property
|
|
| 76 | -forAll :: (Show a, IsProperty prop) => Gen a -> (a -> prop) -> Property
|
|
| 77 | -forAll generator tst = Prop $ do
|
|
| 78 | - a <- generator
|
|
| 79 | - augment a <$> unProp (property (tst a))
|
|
| 80 | - where
|
|
| 81 | - augment a arg = PropertyArg (show a) arg
|
|
| 82 | - |
|
| 83 | --- | A property that check for equality of its 2 members.
|
|
| 84 | -propertyCompare :: (Show a) => String -> (a -> a -> Bool) -> a -> a -> PropertyCheck
|
|
| 85 | -propertyCompare s f a b =
|
|
| 86 | - let sa = show a
|
|
| 87 | - sb = show b
|
|
| 88 | - in PropertyBinaryOp (a `f` b) s sa sb
|
|
| 89 | - |
|
| 90 | -(===) :: (Show a, Eq a) => a -> a -> PropertyCheck
|
|
| 91 | -(===) = propertyCompare "==" (==)
|
|
| 92 | -infix 4 ===
|
|
| 93 | - |
|
| 94 | -propertyAnd = PropertyAnd
|
|
| 95 | - |
|
| 96 | - |
|
| 97 | -data Test where
|
|
| 98 | - Group :: String -> [Test] -> Test
|
|
| 99 | - Property :: IsProperty prop => String -> prop -> Test
|
|
| 100 | - |
|
| 101 | - |
|
| 102 | -arbitraryInt64 :: Gen Int64
|
|
| 103 | -arbitraryInt64 = Gen $ do
|
|
| 104 | - h <- ask
|
|
| 105 | - W64# w <- liftIO (randomWord64 h)
|
|
| 106 | - return (I64# (unsafeCoerce# w))
|
|
| 107 | - |
|
| 108 | -integralDownsize :: (Integral a) => Int64 -> a
|
|
| 109 | -integralDownsize = fromIntegral
|
|
| 110 | - |
|
| 111 | -wordDownsize :: (Integral a) => Word64 -> a
|
|
| 112 | -wordDownsize = fromIntegral
|
|
| 113 | - |
|
| 114 | -arbitraryWord64 :: Gen Word64
|
|
| 115 | -arbitraryWord64 = Gen $ do
|
|
| 116 | - h <- ask
|
|
| 117 | - liftIO (randomWord64 h)
|
|
| 118 | - |
|
| 119 | -nonZero :: (Arbitrary a, Num a, Eq a) => Gen (NonZero a)
|
|
| 120 | -nonZero = do
|
|
| 121 | - x <- arbitrary
|
|
| 122 | - if x == 0 then nonZero else pure $ NonZero x
|
|
| 123 | - |
|
| 124 | -newtype NonZero a = NonZero { getNonZero :: a }
|
|
| 125 | - deriving (Eq,Ord,Bounded,Show)
|
|
| 126 | - |
|
| 127 | -instance (Arbitrary a, Num a, Eq a) => Arbitrary (NonZero a) where
|
|
| 128 | - arbitrary = nonZero
|
|
| 129 | - |
|
| 130 | --- | A newtype for shift amounts that are bounded by @wordSize - 1@
|
|
| 131 | -newtype BoundedShiftAmount a = BoundedShiftAmount {getBoundedShiftAmount :: Int}
|
|
| 132 | - deriving (Eq, Ord, Show)
|
|
| 133 | - |
|
| 134 | -instance (FiniteBits a) => Arbitrary (BoundedShiftAmount a) where
|
|
| 135 | - arbitrary = do
|
|
| 136 | - x <- arbitrary
|
|
| 137 | - let widthBits = finiteBitSize (undefined :: a)
|
|
| 138 | - pure $ BoundedShiftAmount (abs x `mod` widthBits)
|
|
| 139 | - |
|
| 140 | -instance Arbitrary Natural where
|
|
| 141 | - arbitrary = integralDownsize . (`mod` 10000) . abs <$> arbitraryInt64
|
|
| 142 | - |
|
| 143 | --- Bounded by Int64
|
|
| 144 | -instance Arbitrary Integer where
|
|
| 145 | - arbitrary = fromIntegral <$> arbitraryInt64
|
|
| 146 | - |
|
| 147 | -instance Arbitrary Int where
|
|
| 148 | - arbitrary = int64ToInt <$> arbitraryInt64
|
|
| 149 | -instance Arbitrary Word where
|
|
| 150 | - arbitrary = word64ToWord <$> arbitraryWord64
|
|
| 151 | -instance Arbitrary Word64 where
|
|
| 152 | - arbitrary = arbitraryWord64
|
|
| 153 | -instance Arbitrary Word32 where
|
|
| 154 | - arbitrary = wordDownsize <$> arbitraryWord64
|
|
| 155 | -instance Arbitrary Word16 where
|
|
| 156 | - arbitrary = wordDownsize <$> arbitraryWord64
|
|
| 157 | -instance Arbitrary Word8 where
|
|
| 158 | - arbitrary = wordDownsize <$> arbitraryWord64
|
|
| 159 | -instance Arbitrary Int64 where
|
|
| 160 | - arbitrary = arbitraryInt64
|
|
| 161 | -instance Arbitrary Int32 where
|
|
| 162 | - arbitrary = integralDownsize <$> arbitraryInt64
|
|
| 163 | -instance Arbitrary Int16 where
|
|
| 164 | - arbitrary = integralDownsize <$> arbitraryInt64
|
|
| 165 | -instance Arbitrary Int8 where
|
|
| 166 | - arbitrary = integralDownsize <$> arbitraryInt64
|
|
| 167 | - |
|
| 168 | -instance Arbitrary Char where
|
|
| 169 | - arbitrary = do
|
|
| 170 | - let high = fromIntegral $ fromEnum (maxBound :: Char) :: Word
|
|
| 171 | - (x::Word) <- arbitrary
|
|
| 172 | - let x' = mod x high
|
|
| 173 | - return (chr $ fromIntegral x')
|
|
| 174 | - |
|
| 175 | -int64ToInt :: Int64 -> Int
|
|
| 176 | -int64ToInt (I64# i) = I# (int64ToInt# i)
|
|
| 177 | - |
|
| 178 | - |
|
| 179 | -word64ToWord :: Word64 -> Word
|
|
| 180 | -word64ToWord (W64# i) = W# (word64ToWord# i)
|
|
| 181 | - |
|
| 182 | - |
|
| 183 | -data RunS = RunS { depth :: Int, rg :: LCGGen, context :: [String] }
|
|
| 184 | - |
|
| 185 | -newtype LCGGen = LCGGen { randomWord64 :: IO Word64 }
|
|
| 186 | - |
|
| 187 | -data LCGParams = LCGParams { seed :: Word64, a :: Word64, c :: Word64, m :: Word64 }
|
|
| 188 | - |
|
| 189 | -newLCGGen :: LCGParams -> IO LCGGen
|
|
| 190 | -newLCGGen LCGParams {seed = W64# seed#, ..} = do
|
|
| 191 | - MutableByteArray mba# <- IO $ \s0 -> case newByteArray# 8# s0 of
|
|
| 192 | - (# s1, mba# #) -> case writeWord64Array# mba# 0# seed# s1 of
|
|
| 193 | - s2 -> (# s2, MutableByteArray mba# #)
|
|
| 194 | - pure $ LCGGen $ IO $ \s0 -> case readWord64Array# mba# 0# s0 of
|
|
| 195 | - (# s1, old_val# #) ->
|
|
| 196 | - let old_val = W64# old_val#
|
|
| 197 | - !new_val@(W64# new_val#) = (old_val * a + c) `mod` m
|
|
| 198 | - in case writeWord64Array# mba# 0# new_val# s1 of
|
|
| 199 | - s2 -> (# s2, new_val #)
|
|
| 200 | - |
|
| 201 | -runPropertyCheck (PropertyBinaryOp res desc s1 s2) =
|
|
| 202 | - if res then return Success
|
|
| 203 | - else do
|
|
| 204 | - ctx <- context <$> ask
|
|
| 205 | - let msg = "Failure: " ++ s1 ++ desc ++ s2
|
|
| 206 | - putMsg msg
|
|
| 207 | - return (Failure [msg : ctx])
|
|
| 208 | -runPropertyCheck (PropertyAnd a1 a2) = (<>) <$> runPropertyCheck a1 <*> runPropertyCheck a2
|
|
| 209 | - |
|
| 210 | -runProperty :: Property -> ReaderT RunS IO Result
|
|
| 211 | -runProperty (Prop p) = do
|
|
| 212 | - let iterations = 1000 :: Int
|
|
| 213 | - loop iterations iterations
|
|
| 214 | - where
|
|
| 215 | - loop iterations 0 = do
|
|
| 216 | - putMsg ("Passed " ++ show iterations ++ " iterations")
|
|
| 217 | - return Success
|
|
| 218 | - loop iterations n = do
|
|
| 219 | - h <- rg <$> ask
|
|
| 220 | - p <- liftIO (runReaderT (runGen p) h)
|
|
| 221 | - let (ss, pc) = getCheck p
|
|
| 222 | - res <- runPropertyCheck pc
|
|
| 223 | - case res of
|
|
| 224 | - Success -> loop iterations (n-1)
|
|
| 225 | - Failure msgs -> do
|
|
| 226 | - let msg = ("With arguments " ++ intercalate ", " ss)
|
|
| 227 | - putMsg msg
|
|
| 228 | - return (Failure (map (msg :) msgs))
|
|
| 229 | - |
|
| 230 | -data Result = Success | Failure [[String]]
|
|
| 231 | - |
|
| 232 | -instance Semigroup Result where
|
|
| 233 | - Success <> x = x
|
|
| 234 | - x <> Success = x
|
|
| 235 | - (Failure xs) <> (Failure ys) = Failure (xs ++ ys)
|
|
| 236 | - |
|
| 237 | -instance Monoid Result where
|
|
| 238 | - mempty = Success
|
|
| 239 | - |
|
| 240 | -putMsg s = do
|
|
| 241 | - n <- depth <$> ask
|
|
| 242 | - liftIO . putStrLn $ replicate (n * 2) ' ' ++ s
|
|
| 243 | - |
|
| 244 | - |
|
| 245 | -nest c = local (\s -> s { depth = depth s + 1, context = c : context s })
|
|
| 246 | - |
|
| 247 | -runTestInternal :: Test -> ReaderT RunS IO Result
|
|
| 248 | -runTestInternal (Group name tests) = do
|
|
| 249 | - let label = ("Group " ++ name)
|
|
| 250 | - putMsg label
|
|
| 251 | - nest label (mconcat <$> mapM runTestInternal tests)
|
|
| 252 | -runTestInternal (Property name p) = do
|
|
| 253 | - let label = ("Running " ++ name)
|
|
| 254 | - putMsg label
|
|
| 255 | - nest label $ runProperty (property p)
|
|
| 256 | - |
|
| 257 | - |
|
| 258 | -runTests :: Word64 -> Test -> IO ()
|
|
| 259 | -runTests seed t = do
|
|
| 260 | - -- These params are the same ones as glibc uses.
|
|
| 261 | - h <- newLCGGen (LCGParams { seed, m = 2 ^ (31 :: Int), a = 1103515245, c = 12345 })
|
|
| 262 | - res <- runReaderT (runTestInternal t) (RunS 0 h [])
|
|
| 263 | - case res of
|
|
| 264 | - Success -> return ()
|
|
| 265 | - Failure tests -> do
|
|
| 266 | - putStrLn $ "Seed: " ++ show seed
|
|
| 267 | - putStrLn $ "These tests failed: \n" ++ intercalate " \n" (map (showStack 0 . reverse) tests)
|
|
| 268 | - exitFailure
|
|
| 269 | - |
|
| 270 | -showStack _ [] = ""
|
|
| 271 | -showStack n (s:ss) = replicate n ' ' ++ s ++ "\n" ++ showStack (n + 2) ss
|
|
| 39 | +import MiniQuickCheck
|
|
| 272 | 40 | |
| 273 | 41 | -------------------------------------------------------------------------------
|
| 274 | 42 | |
| ... | ... | @@ -325,8 +93,11 @@ testOperatorPrecedence _ = Group "Precedence" |
| 325 | 93 | , Property "+ and * (2)" $ \(a :: a) (b :: a) (c :: a) -> (a * b + c) === ((a * b) + c)
|
| 326 | 94 | , Property "- and * (1)" $ \(a :: a) (b :: a) (c :: a) -> (a - b * c) === (a - (b * c))
|
| 327 | 95 | , Property "- and * (2)" $ \(a :: a) (b :: a) (c :: a) -> (a * b - c) === ((a * b) - c)
|
| 328 | - , Property "* and ^ (1)" $ \(a :: a) (b :: Natural) (c :: a) -> (a ^ b * c) === ((a ^ b) * c)
|
|
| 329 | - , Property "* and ^ (2)" $ \(a :: a) (c :: Natural) (b :: a) -> (a * b ^ c) === (a * (b ^ c))
|
|
| 96 | + |
|
| 97 | + -- Bound the exponent to avoid OOM errors e.g.
|
|
| 98 | + -- GNU MP: Cannot allocate memory (size=4294938656)
|
|
| 99 | + , Property "* and ^ (1)" $ \(a :: a) (BoundedBy b :: Natural `BoundedBy` 100) (c :: a) -> (a ^ b * c) === ((a ^ b) * c)
|
|
| 100 | + , Property "* and ^ (2)" $ \(a :: a) (BoundedBy c :: Natural `BoundedBy` 100) (b :: a) -> (a * b ^ c) === (a * (b ^ c))
|
|
| 330 | 101 | ]
|
| 331 | 102 | |
| 332 | 103 | |
| ... | ... | @@ -454,19 +225,8 @@ instance TestPrimop LowerBitsAreDefined where |
| 454 | 225 | twoNonZero :: (a -> a -> b) -> a -> NonZero a -> b
|
| 455 | 226 | twoNonZero f x (NonZero y) = f x y
|
| 456 | 227 | |
| 457 | -getSeedFromArgs :: IO Word64
|
|
| 458 | -getSeedFromArgs = do
|
|
| 459 | - args <- getArgs
|
|
| 460 | - case args of
|
|
| 461 | - [arg] -> case readMaybe arg of
|
|
| 462 | - Just seed -> pure seed
|
|
| 463 | - Nothing -> die $ "Invalid seed (expected Word64): " ++ show arg
|
|
| 464 | - _ -> die "Usage: foundation <seed>"
|
|
| 465 | - |
|
| 466 | 228 | main :: IO ()
|
| 467 | -main = do
|
|
| 468 | - seed <- getSeedFromArgs
|
|
| 469 | - runTests seed (Group "ALL" [testNumberRefs, testPrimops])
|
|
| 229 | +main = runTestsMain (Iterations 1000) (Group "ALL" [testNumberRefs, testPrimops])
|
|
| 470 | 230 | |
| 471 | 231 | -- Test an interpreted primop vs a compiled primop
|
| 472 | 232 | testPrimops = Group "primop"
|
| ... | ... | @@ -80,7 +80,7 @@ test('simd002', [], compile_and_run, ['']) |
| 80 | 80 | test('simd003', [], compile_and_run, [''])
|
| 81 | 81 | test('simd004', [], compile_and_run, ['-O2'])
|
| 82 | 82 | test('simd005', [], compile_and_run, [''])
|
| 83 | -test('simd006', [], compile_and_run, [''])
|
|
| 83 | +test('simd006', [mini_quickcheck], multimod_compile_and_run, ['simd006', ''])
|
|
| 84 | 84 | test('simd007', [], compile_and_run, [''])
|
| 85 | 85 | test('simd008', [], compile_and_run, [''])
|
| 86 | 86 | test('simd009', [ req_th
|
| 1 | -{-# LANGUAGE MagicHash #-}
|
|
| 2 | -{-# LANGUAGE UnboxedTuples #-}
|
|
| 3 | -{-# LANGUAGE FlexibleContexts #-}
|
|
| 4 | -{-# LANGUAGE OverloadedStrings #-}
|
|
| 5 | -{-# LANGUAGE ScopedTypeVariables #-}
|
|
| 6 | -{-# LANGUAGE TypeFamilies #-}
|
|
| 7 | -{-# LANGUAGE DerivingStrategies #-}
|
|
| 1 | +{-# LANGUAGE MagicHash #-}
|
|
| 2 | +{-# LANGUAGE UnboxedTuples #-}
|
|
| 3 | +{-# LANGUAGE DerivingStrategies #-}
|
|
| 8 | 4 | {-# LANGUAGE GeneralisedNewtypeDeriving #-}
|
| 9 | -{-# LANGUAGE MagicHash #-}
|
|
| 10 | -{-# LANGUAGE RecordWildCards #-}
|
|
| 5 | +{-# LANGUAGE ScopedTypeVariables #-}
|
|
| 11 | 6 | |
| 12 | --- QuickCheck testing for SIMD operations
|
|
| 7 | +-- QuickCheck-like property tests for SIMD vector operations.
|
|
| 13 | 8 | |
| 14 | -module Main
|
|
| 15 | - ( main
|
|
| 16 | - ) where
|
|
| 9 | +module Main (main) where
|
|
| 17 | 10 | |
| 18 | -import Data.Word
|
|
| 19 | -import Data.Int
|
|
| 20 | -import GHC.Natural
|
|
| 21 | 11 | import Data.Coerce
|
| 22 | -import Data.Typeable
|
|
| 23 | -import Data.Proxy
|
|
| 24 | -import GHC.Int
|
|
| 25 | -import GHC.Word
|
|
| 26 | -import Data.Function
|
|
| 12 | +import Data.Word
|
|
| 27 | 13 | import GHC.Prim
|
| 28 | -import Control.Monad.Reader
|
|
| 29 | -import System.IO
|
|
| 30 | -import Foreign.Marshal.Alloc
|
|
| 31 | -import Foreign.Storable
|
|
| 32 | -import Foreign.Ptr
|
|
| 33 | -import Data.List (intercalate)
|
|
| 34 | -import Data.IORef
|
|
| 35 | -import Unsafe.Coerce
|
|
| 36 | 14 | import GHC.Exts
|
| 37 | 15 | import GHC.Float
|
| 38 | 16 | ( castFloatToWord32 , castWord32ToFloat
|
| 39 | 17 | , castDoubleToWord64, castWord64ToDouble
|
| 40 | 18 | )
|
| 41 | 19 | |
| 20 | +import MiniQuickCheck
|
|
| 42 | 21 | |
| 22 | +--------------------------------------------------------------------------------
|
|
| 23 | +-- Scalar wrappers that use bit-equality to test for equality.
|
|
| 43 | 24 | |
| 44 | -newtype Gen a = Gen { runGen :: (ReaderT LCGGen IO a) }
|
|
| 45 | - deriving newtype (Functor, Applicative, Monad)
|
|
| 46 | - |
|
| 47 | -class Arbitrary a where
|
|
| 48 | - arbitrary :: Gen a
|
|
| 49 | - |
|
| 50 | -class IsProperty p where
|
|
| 51 | - property :: p -> Property
|
|
| 52 | - |
|
| 53 | -data PropertyCheck = PropertyBinaryOp Bool String String String
|
|
| 54 | - | PropertyAnd PropertyCheck PropertyCheck
|
|
| 55 | - |
|
| 56 | -instance IsProperty PropertyCheck where
|
|
| 57 | - property check = Prop $ pure (PropertyEOA check)
|
|
| 58 | - |
|
| 59 | -data PropertyTestArg = PropertyEOA PropertyCheck
|
|
| 60 | - | PropertyArg String PropertyTestArg
|
|
| 61 | - |
|
| 62 | -getCheck :: PropertyTestArg -> ([String], PropertyCheck)
|
|
| 63 | -getCheck (PropertyEOA pc) = ([], pc)
|
|
| 64 | -getCheck (PropertyArg s pta ) = let (ss, pc) = getCheck pta in (s:ss, pc)
|
|
| 65 | - |
|
| 66 | -data Property = Prop { unProp :: Gen PropertyTestArg }
|
|
| 67 | - |
|
| 68 | -instance (Show a, Arbitrary a, IsProperty prop) => IsProperty (a -> prop) where
|
|
| 69 | - property p = forAll arbitrary p
|
|
| 70 | - |
|
| 71 | --- | Running a generator for a specific type under a property
|
|
| 72 | -forAll :: (Show a, IsProperty prop) => Gen a -> (a -> prop) -> Property
|
|
| 73 | -forAll generator tst = Prop $ do
|
|
| 74 | - a <- generator
|
|
| 75 | - augment a <$> unProp (property (tst a))
|
|
| 76 | - where
|
|
| 77 | - augment a arg = PropertyArg (show a) arg
|
|
| 78 | - |
|
| 79 | --- | A property that check for equality of its 2 members.
|
|
| 80 | -propertyCompare :: (Show a) => String -> (a -> a -> Bool) -> a -> a -> PropertyCheck
|
|
| 81 | -propertyCompare s f a b =
|
|
| 82 | - let sa = show a
|
|
| 83 | - sb = show b
|
|
| 84 | - in PropertyBinaryOp (a `f` b) s sa sb
|
|
| 85 | - |
|
| 86 | -(===) :: (Show a, Eq a) => a -> a -> PropertyCheck
|
|
| 87 | -(===) = propertyCompare "==" (==)
|
|
| 88 | -infix 4 ===
|
|
| 89 | - |
|
| 90 | -propertyAnd = PropertyAnd
|
|
| 91 | - |
|
| 92 | - |
|
| 93 | -data Test where
|
|
| 94 | - Group :: String -> [Test] -> Test
|
|
| 95 | - Property :: IsProperty prop => String -> prop -> Test
|
|
| 96 | - |
|
| 25 | +newtype FloatNT = FloatNT Float
|
|
| 26 | + deriving newtype (Show, Num)
|
|
| 97 | 27 | |
| 98 | -arbitraryInt64 :: Gen Int64
|
|
| 99 | -arbitraryInt64 = Gen $ do
|
|
| 100 | - h <- ask
|
|
| 101 | - W64# w <- liftIO (randomWord64 h)
|
|
| 102 | - return (I64# (unsafeCoerce# w))
|
|
| 28 | +instance Eq FloatNT where
|
|
| 29 | + FloatNT f1 == FloatNT f2 = castFloatToWord32 f1 == castFloatToWord32 f2
|
|
| 103 | 30 | |
| 104 | -integralDownsize :: (Integral a) => Int64 -> a
|
|
| 105 | -integralDownsize = fromIntegral
|
|
| 31 | +instance Arbitrary FloatNT where
|
|
| 32 | + arbitrary = FloatNT . castWord32ToFloat <$> arbitrary
|
|
| 106 | 33 | |
| 107 | -wordDownsize :: (Integral a) => Word64 -> a
|
|
| 108 | -wordDownsize = fromIntegral
|
|
| 34 | +newtype DoubleNT = DoubleNT Double
|
|
| 35 | + deriving newtype (Show, Num)
|
|
| 109 | 36 | |
| 110 | -arbitraryWord64 :: Gen Word64
|
|
| 111 | -arbitraryWord64 = Gen $ do
|
|
| 112 | - h <- ask
|
|
| 113 | - liftIO (randomWord64 h)
|
|
| 37 | +instance Eq DoubleNT where
|
|
| 38 | + DoubleNT d1 == DoubleNT d2 = castDoubleToWord64 d1 == castDoubleToWord64 d2
|
|
| 114 | 39 | |
| 40 | +instance Arbitrary DoubleNT where
|
|
| 41 | + arbitrary = DoubleNT . castWord64ToDouble <$> arbitrary
|
|
| 115 | 42 | |
| 116 | -instance Arbitrary Word64 where
|
|
| 117 | - arbitrary = arbitraryWord64
|
|
| 118 | -instance Arbitrary Word32 where
|
|
| 119 | - arbitrary = wordDownsize <$> arbitraryWord64
|
|
| 43 | +--------------------------------------------------------------------------------
|
|
| 44 | +-- Min/max for the types under test
|
|
| 120 | 45 | |
| 121 | 46 | class HasMinMax a where
|
| 122 | 47 | mini, maxi :: a -> a -> a
|
| 48 | + |
|
| 123 | 49 | instance HasMinMax FloatNT where
|
| 124 | 50 | mini (FloatNT (F# f1)) (FloatNT (F# f2)) = FloatNT (F# (minFloat# f1 f2))
|
| 125 | 51 | maxi (FloatNT (F# f1)) (FloatNT (F# f2)) = FloatNT (F# (maxFloat# f1 f2))
|
| 52 | + |
|
| 126 | 53 | instance HasMinMax DoubleNT where
|
| 127 | 54 | mini (DoubleNT (D# d1)) (DoubleNT (D# d2)) = DoubleNT (D# (minDouble# d1 d2))
|
| 128 | 55 | maxi (DoubleNT (D# d1)) (DoubleNT (D# d2)) = DoubleNT (D# (maxDouble# d1 d2))
|
| 129 | 56 | |
| 130 | -newtype FloatNT = FloatNT Float
|
|
| 131 | - deriving newtype (Show, Num)
|
|
| 132 | -instance Eq FloatNT where
|
|
| 133 | - FloatNT f1 == FloatNT f2 =
|
|
| 134 | - castFloatToWord32 f1 == castFloatToWord32 f2
|
|
| 135 | -instance Arbitrary FloatNT where
|
|
| 136 | - arbitrary = FloatNT . castWord32ToFloat <$> arbitrary
|
|
| 137 | -newtype DoubleNT = DoubleNT Double
|
|
| 138 | - deriving newtype (Show, Num)
|
|
| 139 | -instance Eq DoubleNT where
|
|
| 140 | - DoubleNT d1 == DoubleNT d2 =
|
|
| 141 | - castDoubleToWord64 d1 == castDoubleToWord64 d2
|
|
| 142 | -instance Arbitrary DoubleNT where
|
|
| 143 | - arbitrary = DoubleNT . castWord64ToDouble <$> arbitrary
|
|
| 144 | - |
|
| 57 | +--------------------------------------------------------------------------------
|
|
| 58 | +-- SIMD vector types
|
|
| 145 | 59 | |
| 146 | 60 | data FloatX4 = FX4# FloatX4#
|
| 61 | + |
|
| 147 | 62 | instance Show FloatX4 where
|
| 148 | - show (FX4# f) = case (unpackFloatX4# f) of
|
|
| 149 | - (# a, b, c, d #) -> show ((F# a), (F# b), (F# c), (F# d))
|
|
| 63 | + show (FX4# f) = case unpackFloatX4# f of
|
|
| 64 | + (# a, b, c, d #) -> show (F# a, F# b, F# c, F# d)
|
|
| 65 | + |
|
| 150 | 66 | instance Eq FloatX4 where
|
| 151 | - (FX4# a) == (FX4# b)
|
|
| 152 | - = case (unpackFloatX4# a) of
|
|
| 67 | + FX4# a == FX4# b
|
|
| 68 | + = case unpackFloatX4# a of
|
|
| 153 | 69 | (# a1, a2, a3, a4 #) ->
|
| 154 | - case (unpackFloatX4# b) of
|
|
| 155 | - (# b1, b2, b3, b4 #) -> FloatNT (F# a1) == FloatNT (F# b1) &&
|
|
| 156 | - FloatNT (F# a2) == FloatNT (F# b2) &&
|
|
| 157 | - FloatNT (F# a3) == FloatNT (F# b3) &&
|
|
| 158 | - FloatNT (F# a4) == FloatNT (F# b4)
|
|
| 70 | + case unpackFloatX4# b of
|
|
| 71 | + (# b1, b2, b3, b4 #) ->
|
|
| 72 | + FloatNT (F# a1) == FloatNT (F# b1) &&
|
|
| 73 | + FloatNT (F# a2) == FloatNT (F# b2) &&
|
|
| 74 | + FloatNT (F# a3) == FloatNT (F# b3) &&
|
|
| 75 | + FloatNT (F# a4) == FloatNT (F# b4)
|
|
| 76 | + |
|
| 159 | 77 | instance Arbitrary FloatX4 where
|
| 160 | 78 | arbitrary = do
|
| 161 | 79 | FloatNT (F# f1) <- arbitrary
|
| ... | ... | @@ -163,52 +81,59 @@ instance Arbitrary FloatX4 where |
| 163 | 81 | FloatNT (F# f3) <- arbitrary
|
| 164 | 82 | FloatNT (F# f4) <- arbitrary
|
| 165 | 83 | return $ FX4# (packFloatX4# (# f1, f2, f3, f4 #))
|
| 84 | + |
|
| 166 | 85 | instance Num FloatX4 where
|
| 167 | - FX4# x + FX4# y =
|
|
| 168 | - FX4# ( x `plusFloatX4#` y )
|
|
| 169 | - FX4# x - FX4# y =
|
|
| 170 | - FX4# ( x `minusFloatX4#` y )
|
|
| 171 | - negate ( FX4# x ) = FX4# ( negateFloatX4# x )
|
|
| 172 | - FX4# x * FX4# y =
|
|
| 173 | - FX4# ( x `timesFloatX4#` y )
|
|
| 174 | - abs = error "no"
|
|
| 175 | - signum = error "no"
|
|
| 176 | - fromInteger = error "no"
|
|
| 86 | + FX4# x + FX4# y = FX4# (x `plusFloatX4#` y)
|
|
| 87 | + FX4# x - FX4# y = FX4# (x `minusFloatX4#` y)
|
|
| 88 | + negate (FX4# x) = FX4# (negateFloatX4# x)
|
|
| 89 | + FX4# x * FX4# y = FX4# (x `timesFloatX4#` y)
|
|
| 90 | + abs = error "FloatX4: no abs"
|
|
| 91 | + signum = error "FloatX4: no signum"
|
|
| 92 | + fromInteger = error "FloatX4: no fromInteger"
|
|
| 93 | + |
|
| 177 | 94 | instance HasMinMax FloatX4 where
|
| 178 | 95 | mini (FX4# a) (FX4# b) = FX4# (minFloatX4# a b)
|
| 179 | 96 | maxi (FX4# a) (FX4# b) = FX4# (maxFloatX4# a b)
|
| 180 | 97 | |
| 98 | +--------------------------------------------------------------------------------
|
|
| 99 | + |
|
| 181 | 100 | data DoubleX2 = DX2# DoubleX2#
|
| 101 | + |
|
| 182 | 102 | instance Show DoubleX2 where
|
| 183 | - show (DX2# d) = case (unpackDoubleX2# d) of
|
|
| 184 | - (# a, b #) -> show ((D# a), (D# b))
|
|
| 103 | + show (DX2# d) = case unpackDoubleX2# d of
|
|
| 104 | + (# a, b #) -> show (D# a, D# b)
|
|
| 105 | + |
|
| 185 | 106 | instance Eq DoubleX2 where
|
| 186 | - (DX2# a) == (DX2# b)
|
|
| 187 | - = case (unpackDoubleX2# a) of
|
|
| 107 | + DX2# a == DX2# b
|
|
| 108 | + = case unpackDoubleX2# a of
|
|
| 188 | 109 | (# a1, a2 #) ->
|
| 189 | - case (unpackDoubleX2# b) of
|
|
| 190 | - (# b1, b2 #) -> DoubleNT (D# a1) == DoubleNT (D# b1) &&
|
|
| 191 | - DoubleNT (D# a2) == DoubleNT (D# b2)
|
|
| 110 | + case unpackDoubleX2# b of
|
|
| 111 | + (# b1, b2 #) ->
|
|
| 112 | + DoubleNT (D# a1) == DoubleNT (D# b1) &&
|
|
| 113 | + DoubleNT (D# a2) == DoubleNT (D# b2)
|
|
| 114 | + |
|
| 192 | 115 | instance Arbitrary DoubleX2 where
|
| 193 | 116 | arbitrary = do
|
| 194 | 117 | DoubleNT (D# d1) <- arbitrary
|
| 195 | 118 | DoubleNT (D# d2) <- arbitrary
|
| 196 | 119 | return $ DX2# (packDoubleX2# (# d1, d2 #))
|
| 120 | + |
|
| 197 | 121 | instance Num DoubleX2 where
|
| 198 | - DX2# x + DX2# y =
|
|
| 199 | - DX2# ( x `plusDoubleX2#` y )
|
|
| 200 | - DX2# x - DX2# y =
|
|
| 201 | - DX2# ( x `minusDoubleX2#` y )
|
|
| 202 | - negate ( DX2# x ) = DX2# ( negateDoubleX2# x )
|
|
| 203 | - DX2# x * DX2# y =
|
|
| 204 | - DX2# ( x `timesDoubleX2#` y )
|
|
| 205 | - abs = error "no"
|
|
| 206 | - signum = error "no"
|
|
| 207 | - fromInteger = error "no"
|
|
| 122 | + DX2# x + DX2# y = DX2# (x `plusDoubleX2#` y)
|
|
| 123 | + DX2# x - DX2# y = DX2# (x `minusDoubleX2#` y)
|
|
| 124 | + negate (DX2# x) = DX2# (negateDoubleX2# x)
|
|
| 125 | + DX2# x * DX2# y = DX2# (x `timesDoubleX2#` y)
|
|
| 126 | + abs = error "DoubleX2: no abs"
|
|
| 127 | + signum = error "DoubleX2: no signum"
|
|
| 128 | + fromInteger = error "DoubleX2: no fromInteger"
|
|
| 129 | + |
|
| 208 | 130 | instance HasMinMax DoubleX2 where
|
| 209 | 131 | mini (DX2# a) (DX2# b) = DX2# (minDoubleX2# a b)
|
| 210 | 132 | maxi (DX2# a) (DX2# b) = DX2# (maxDoubleX2# a b)
|
| 211 | 133 | |
| 134 | +--------------------------------------------------------------------------------
|
|
| 135 | +-- Expression language for generating random expressions over vector types.
|
|
| 136 | + |
|
| 212 | 137 | data Expr a where
|
| 213 | 138 | Lit :: a -> Expr a
|
| 214 | 139 | Add :: Expr a -> Expr a -> Expr a
|
| ... | ... | @@ -218,11 +143,12 @@ data Expr a where |
| 218 | 143 | Min :: Expr a -> Expr a -> Expr a
|
| 219 | 144 | Max :: Expr a -> Expr a -> Expr a
|
| 220 | 145 | deriving (Show, Eq)
|
| 146 | + |
|
| 221 | 147 | fmapExpr :: (a -> b) -> Expr a -> Expr b
|
| 222 | -fmapExpr f (Lit a) = Lit (f a)
|
|
| 148 | +fmapExpr f (Lit a) = Lit (f a)
|
|
| 223 | 149 | fmapExpr f (Add a b) = Add (fmapExpr f a) (fmapExpr f b)
|
| 224 | 150 | fmapExpr f (Sub a b) = Sub (fmapExpr f a) (fmapExpr f b)
|
| 225 | -fmapExpr f (Neg a) = Neg (fmapExpr f a)
|
|
| 151 | +fmapExpr f (Neg a) = Neg (fmapExpr f a)
|
|
| 226 | 152 | fmapExpr f (Mul a b) = Mul (fmapExpr f a) (fmapExpr f b)
|
| 227 | 153 | fmapExpr f (Min a b) = Min (fmapExpr f a) (fmapExpr f b)
|
| 228 | 154 | fmapExpr f (Max a b) = Max (fmapExpr f a) (fmapExpr f b)
|
| ... | ... | @@ -240,75 +166,16 @@ instance Arbitrary a => Arbitrary (Expr a) where |
| 240 | 166 | _ -> Lit <$> arbitrary
|
| 241 | 167 | |
| 242 | 168 | eval :: (Num a, HasMinMax a) => Expr a -> a
|
| 243 | -eval (Lit a) = a
|
|
| 169 | +eval (Lit a) = a
|
|
| 244 | 170 | eval (Add a b) = eval a + eval b
|
| 245 | 171 | eval (Sub a b) = eval a - eval b
|
| 246 | -eval (Neg a) = negate (eval a)
|
|
| 172 | +eval (Neg a) = negate (eval a)
|
|
| 247 | 173 | eval (Mul a b) = eval a * eval b
|
| 248 | 174 | eval (Min a b) = mini (eval a) (eval b)
|
| 249 | 175 | eval (Max a b) = maxi (eval a) (eval b)
|
| 250 | 176 | |
| 251 | -int64ToInt :: Int64 -> Int
|
|
| 252 | -int64ToInt (I64# i) = I# (int64ToInt# i)
|
|
| 253 | - |
|
| 254 | - |
|
| 255 | -word64ToWord :: Word64 -> Word
|
|
| 256 | -word64ToWord (W64# i) = W# (word64ToWord# i)
|
|
| 257 | - |
|
| 258 | - |
|
| 259 | -data RunS = RunS { depth :: Int, rg :: LCGGen }
|
|
| 260 | - |
|
| 261 | -newtype LCGGen = LCGGen { randomWord64 :: IO Word64 }
|
|
| 262 | - |
|
| 263 | -data LCGParams = LCGParams { seed :: Word64, a :: Word64, c :: Word64, m :: Word64 }
|
|
| 264 | - |
|
| 265 | -newLCGGen :: LCGParams -> IO LCGGen
|
|
| 266 | -newLCGGen LCGParams{..} = do
|
|
| 267 | - var <- newIORef (fromIntegral seed)
|
|
| 268 | - return $ LCGGen $ do
|
|
| 269 | - atomicModifyIORef' var (\old_v -> let new_val = (old_v * a + c) `mod` m in (new_val, new_val))
|
|
| 270 | - |
|
| 271 | - |
|
| 272 | -runPropertyCheck (PropertyBinaryOp res desc s1 s2) =
|
|
| 273 | - if res then return True else (putMsg ("Failure: " ++ s1 ++ desc ++ s2) >> return False)
|
|
| 274 | -runPropertyCheck (PropertyAnd a1 a2) = (&&) <$> runPropertyCheck a1 <*> runPropertyCheck a2
|
|
| 275 | - |
|
| 276 | -runProperty :: Property -> ReaderT RunS IO ()
|
|
| 277 | -runProperty (Prop p) = do
|
|
| 278 | - let iterations = 100
|
|
| 279 | - loop iterations iterations
|
|
| 280 | - where
|
|
| 281 | - loop iterations 0 = putMsg ("Passed " ++ show iterations ++ " iterations")
|
|
| 282 | - loop iterations n = do
|
|
| 283 | - h <- rg <$> ask
|
|
| 284 | - p <- liftIO (runReaderT (runGen p) h)
|
|
| 285 | - let (ss, pc) = getCheck p
|
|
| 286 | - res <- runPropertyCheck pc
|
|
| 287 | - if res then loop iterations (n-1)
|
|
| 288 | - else putMsg ("With arguments " ++ intercalate ", " ss)
|
|
| 289 | - |
|
| 290 | -putMsg s = do
|
|
| 291 | - n <- depth <$> ask
|
|
| 292 | - liftIO . putStrLn $ replicate (n * 2) ' ' ++ s
|
|
| 293 | - |
|
| 294 | -nest = local (\s -> s { depth = depth s + 1 })
|
|
| 295 | - |
|
| 296 | -runTestInternal :: Test -> ReaderT RunS IO ()
|
|
| 297 | -runTestInternal (Group name tests) = do
|
|
| 298 | - putMsg ("Group " ++ name)
|
|
| 299 | - nest (mapM_ runTestInternal tests)
|
|
| 300 | -runTestInternal (Property name p) = do
|
|
| 301 | - putMsg ("Running " ++ name)
|
|
| 302 | - nest $ runProperty (property p)
|
|
| 303 | - |
|
| 304 | - |
|
| 305 | -runTests :: Test -> IO ()
|
|
| 306 | -runTests t = do
|
|
| 307 | - -- These params are the same ones as glibc uses.
|
|
| 308 | - h <- newLCGGen (LCGParams { seed = 1238123213, m = 2^31, a = 1103515245, c = 12345 })
|
|
| 309 | - runReaderT (runTestInternal t) (RunS 0 h)
|
|
| 310 | - |
|
| 311 | --------------------------------------------------------------------------------
|
|
| 177 | +--------------------------------------------------------------------------------
|
|
| 178 | +-- Test groups
|
|
| 312 | 179 | |
| 313 | 180 | testFloatX4 :: Test
|
| 314 | 181 | testFloatX4 = Group "FloatX4"
|
| ... | ... | @@ -324,15 +191,12 @@ testFloatX4 = Group "FloatX4" |
| 324 | 191 | unpack :: FloatX4 -> ( FloatNT, FloatNT, FloatNT, FloatNT )
|
| 325 | 192 | unpack (FX4# f) = case unpackFloatX4# f of
|
| 326 | 193 | (# f1, f2, f3, f4 #) -> coerce ( F# f1, F# f2, F# f3, F# f4 )
|
| 194 | + |
|
| 327 | 195 | get1, get2, get3, get4 :: FloatX4 -> FloatNT
|
| 328 | - get1 (FX4# f) = case unpackFloatX4# f of
|
|
| 329 | - (# f1, _, _, _ #) -> FloatNT (F# f1)
|
|
| 330 | - get2 (FX4# f) = case unpackFloatX4# f of
|
|
| 331 | - (# _, f2, _, _ #) -> FloatNT (F# f2)
|
|
| 332 | - get3 (FX4# f) = case unpackFloatX4# f of
|
|
| 333 | - (# _, _, f3, _ #) -> FloatNT (F# f3)
|
|
| 334 | - get4 (FX4# f) = case unpackFloatX4# f of
|
|
| 335 | - (# _, _, _, f4 #) -> FloatNT (F# f4)
|
|
| 196 | + get1 (FX4# f) = case unpackFloatX4# f of (# f1, _, _, _ #) -> FloatNT (F# f1)
|
|
| 197 | + get2 (FX4# f) = case unpackFloatX4# f of (# _, f2, _, _ #) -> FloatNT (F# f2)
|
|
| 198 | + get3 (FX4# f) = case unpackFloatX4# f of (# _, _, f3, _ #) -> FloatNT (F# f3)
|
|
| 199 | + get4 (FX4# f) = case unpackFloatX4# f of (# _, _, _, f4 #) -> FloatNT (F# f4)
|
|
| 336 | 200 | |
| 337 | 201 | testDoubleX2 :: Test
|
| 338 | 202 | testDoubleX2 = Group "DoubleX2"
|
| ... | ... | @@ -346,16 +210,15 @@ testDoubleX2 = Group "DoubleX2" |
| 346 | 210 | unpack :: DoubleX2 -> ( DoubleNT, DoubleNT )
|
| 347 | 211 | unpack (DX2# d) = case unpackDoubleX2# d of
|
| 348 | 212 | (# d1, d2 #) -> coerce ( D# d1, D# d2 )
|
| 213 | + |
|
| 349 | 214 | get1, get2 :: DoubleX2 -> DoubleNT
|
| 350 | 215 | get1 (DX2# d) = case unpackDoubleX2# d of
|
| 351 | - (# d1, _ #) -> DoubleNT (D# d1)
|
|
| 216 | + (# d1, _ #) -> DoubleNT (D# d1)
|
|
| 352 | 217 | get2 (DX2# d) = case unpackDoubleX2# d of
|
| 353 | - (# _, d2 #) -> DoubleNT (D# d2)
|
|
| 218 | + (# _, d2 #) -> DoubleNT (D# d2)
|
|
| 354 | 219 | |
| 355 | 220 | testSIMD :: Test
|
| 356 | -testSIMD = Group "ALL"
|
|
| 357 | - [ testFloatX4
|
|
| 358 | - , testDoubleX2
|
|
| 359 | - ]
|
|
| 221 | +testSIMD = Group "ALL" [testFloatX4, testDoubleX2]
|
|
| 360 | 222 | |
| 361 | -main = runTests testSIMD |
|
| 223 | +main :: IO ()
|
|
| 224 | +main = runTestsMain (Iterations 100) testSIMD |