Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
-
87db83e2
by Cheng Shao at 2026-04-24T14:37:21-04:00
-
17e3a0b7
by Cheng Shao at 2026-04-24T14:37:21-04:00
-
2d30f7d3
by sheaf at 2026-04-24T14:38:23-04:00
-
a83c69ee
by sheaf at 2026-04-24T15:11:17-04:00
-
3faedf50
by sheaf at 2026-04-24T15:11:22-04:00
16 changed files:
- .gitlab/generate-ci/gen_ci.hs
- .gitlab/jobs.yaml
- + changelog.d/binary-array-no-list
- + changelog.d/hadrian-response-files.md
- compiler/GHC/Utils/Binary.hs
- hadrian/build-cabal.bat
- hadrian/src/Builder.hs
- hadrian/src/CommandLine.hs
- hadrian/src/Hadrian/Builder/Ar.hs
- hadrian/src/Hadrian/Utilities.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 | +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 | +} |
| 1 | +section: packaging
|
|
| 2 | +synopsis: Add a flag to tell Hadrian to keep response files
|
|
| 3 | +issues: #27184
|
|
| 4 | +mrs: !15906
|
|
| 5 | +description:
|
|
| 6 | + Hadrian can now be instructed to keep response files with the new
|
|
| 7 | + --keep-response-files command line flag. This is helpful when debugging a
|
|
| 8 | + build failure, as it allows re-running the failing command line invocation
|
|
| 9 | + without an error due to a missing response file. |
| ... | ... | @@ -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
|
| ... | ... | @@ -39,7 +39,9 @@ if %CABMAJOR% lss 3 ( |
| 39 | 39 | exit /B 2
|
| 40 | 40 | )
|
| 41 | 41 | |
| 42 | -for /F "tokens=*" %%a in ('"%CABAL%" --with-compiler=%GHC% path --output-format=key-value 2^>NUL ^| findstr /B "remote-repo-cache:"') do set REMOTE_REPO_CACHE=%%a
|
|
| 42 | +"%CABAL%" --with-compiler=%GHC% path --output-format=key-value > "%TEMP%\ghc_cabal_path.txt" 2>NUL
|
|
| 43 | +for /F "tokens=*" %%a in ('findstr /B "remote-repo-cache:" "%TEMP%\ghc_cabal_path.txt"') do set REMOTE_REPO_CACHE=%%a
|
|
| 44 | +del "%TEMP%\ghc_cabal_path.txt" 2>NUL
|
|
| 43 | 45 | set REMOTE_REPO_CACHE=%REMOTE_REPO_CACHE:remote-repo-cache: =%
|
| 44 | 46 | if not exist "%REMOTE_REPO_CACHE%\hackage.haskell.org" (
|
| 45 | 47 | echo Please run 'cabal update' first
|
| ... | ... | @@ -389,15 +389,13 @@ runHaddock :: FilePath -- ^ path to @haddock@ |
| 389 | 389 | -> [String]
|
| 390 | 390 | -> [FilePath] -- ^ input file paths
|
| 391 | 391 | -> Action ()
|
| 392 | -runHaddock haddockPath flagArgs fileInputs = withTempFile $ \tmp -> do
|
|
| 392 | +runHaddock haddockPath flagArgs fileInputs = withResponseFile $ \tmp -> do
|
|
| 393 | 393 | writeFile' tmp $ escapeArgs fileInputs
|
| 394 | 394 | cmd [haddockPath] flagArgs ('@' : tmp)
|
| 395 | 395 | |
| 396 | 396 | runGhcWithResponse :: FilePath -> [String] -> [FilePath] -> Action ()
|
| 397 | -runGhcWithResponse ghcPath flagArgs fileInputs = withTempFile $ \tmp -> do
|
|
| 398 | - |
|
| 397 | +runGhcWithResponse ghcPath flagArgs fileInputs = withResponseFile $ \tmp -> do
|
|
| 399 | 398 | writeFile' tmp $ escapeArgs fileInputs
|
| 400 | - |
|
| 401 | 399 | -- We can't put the flags in a response file, because some flags
|
| 402 | 400 | -- require empty arguments (such as the -dep-suffix flag), but
|
| 403 | 401 | -- that isn't supported yet due to #26560.
|
| ... | ... | @@ -3,7 +3,8 @@ module CommandLine ( |
| 3 | 3 | lookupBignum,
|
| 4 | 4 | cmdBignum, cmdBignumCheck, cmdProgressInfo, cmdCompleteSetting,
|
| 5 | 5 | cmdDocsArgs, cmdUnitIdHash, lookupBuildRoot, TestArgs(..), TestSpeed(..), defaultTestArgs,
|
| 6 | - cmdPrefix, cmdChangelogVersion, DocArgs(..), defaultDocArgs
|
|
| 6 | + cmdPrefix, cmdChangelogVersion, DocArgs(..), defaultDocArgs,
|
|
| 7 | + cmdKeepResponseFiles
|
|
| 7 | 8 | ) where
|
| 8 | 9 | |
| 9 | 10 | import Data.Either
|
| ... | ... | @@ -11,7 +12,7 @@ import qualified Data.HashMap.Strict as Map |
| 11 | 12 | import Data.List.Extra
|
| 12 | 13 | import Development.Shake hiding (Normal)
|
| 13 | 14 | import Flavour (DocTargets, DocTarget(..))
|
| 14 | -import Hadrian.Utilities hiding (buildRoot)
|
|
| 15 | +import Hadrian.Utilities hiding (buildRoot, keepResponseFiles)
|
|
| 15 | 16 | import Settings.Parser
|
| 16 | 17 | import System.Console.GetOpt
|
| 17 | 18 | import System.Environment
|
| ... | ... | @@ -37,6 +38,7 @@ data CommandLineArgs = CommandLineArgs |
| 37 | 38 | , testArgs :: TestArgs
|
| 38 | 39 | , docsArgs :: DocArgs
|
| 39 | 40 | , docTargets :: DocTargets
|
| 41 | + , keepResponseFiles :: Bool
|
|
| 40 | 42 | , prefix :: Maybe FilePath
|
| 41 | 43 | , changelogVersion :: Maybe String
|
| 42 | 44 | , completeStg :: Maybe String }
|
| ... | ... | @@ -58,6 +60,7 @@ defaultCommandLineArgs = CommandLineArgs |
| 58 | 60 | , testArgs = defaultTestArgs
|
| 59 | 61 | , docsArgs = defaultDocArgs
|
| 60 | 62 | , docTargets = Set.fromList [minBound..maxBound]
|
| 63 | + , keepResponseFiles = False
|
|
| 61 | 64 | , prefix = Nothing
|
| 62 | 65 | , changelogVersion = Nothing
|
| 63 | 66 | , completeStg = Nothing }
|
| ... | ... | @@ -143,6 +146,9 @@ readFreeze1 = Right $ \flags -> flags { freeze1 = True } |
| 143 | 146 | readFreeze2 = Right $ \flags -> flags { freeze1 = True, freeze2 = True }
|
| 144 | 147 | readSkipDepends = Right $ \flags -> flags { skipDepends = True }
|
| 145 | 148 | |
| 149 | +readKeepResponseFiles :: Either String (CommandLineArgs -> CommandLineArgs)
|
|
| 150 | +readKeepResponseFiles = Right $ \flags -> flags { keepResponseFiles = True }
|
|
| 151 | + |
|
| 146 | 152 | readUnitIdHash :: Either String (CommandLineArgs -> CommandLineArgs)
|
| 147 | 153 | readUnitIdHash = Right $ \flags ->
|
| 148 | 154 | trace "--hash-unit-ids is deprecated. It is enabled by release flavour or +hash_unit_ids flavour transformer" $
|
| ... | ... | @@ -301,6 +307,8 @@ optDescrs = |
| 301 | 307 | "Progress info style (None, Brief, Normal or Unicorn)."
|
| 302 | 308 | , Option [] ["docs"] (ReqArg readDocsArg "TARGET")
|
| 303 | 309 | "Strip down docs targets (none, no-haddocks, no-sphinx[-{html, pdfs, man}]."
|
| 310 | + , Option ['r'] ["keep-response-files"] (NoArg readKeepResponseFiles)
|
|
| 311 | + "Keep response files created during the build (for debugging)."
|
|
| 304 | 312 | , Option ['k'] ["keep-test-files"] (NoArg readTestKeepFiles)
|
| 305 | 313 | "Keep all the files generated when running the testsuite."
|
| 306 | 314 | , Option [] ["test-compiler"] (ReqArg readTestCompiler "TEST_COMPILER")
|
| ... | ... | @@ -377,11 +385,12 @@ cmdLineArgsMap = do |
| 377 | 385 | else return []
|
| 378 | 386 | let allSettings = cliSettings ++ fileSettings
|
| 379 | 387 | |
| 380 | - return $ insertExtra (progressInfo args) -- Accessed by Hadrian.Utilities
|
|
| 381 | - $ insertExtra (buildRoot args) -- Accessed by Hadrian.Utilities
|
|
| 382 | - $ insertExtra (testArgs args) -- Accessed by Settings.Builders.RunTest
|
|
| 383 | - $ insertExtra (docsArgs args) -- Accessed by Rules.Documentation
|
|
| 384 | - $ insertExtra allSettings -- Accessed by Settings
|
|
| 388 | + return $ insertExtra (progressInfo args) -- Accessed by Hadrian.Utilities
|
|
| 389 | + $ insertExtra (buildRoot args) -- Accessed by Hadrian.Utilities
|
|
| 390 | + $ insertExtra (KeepResponseFiles $ keepResponseFiles args) -- Accessed by Hadrian.Utilities
|
|
| 391 | + $ insertExtra (testArgs args) -- Accessed by Settings.Builders.RunTest
|
|
| 392 | + $ insertExtra (docsArgs args) -- Accessed by Rules.Documentation
|
|
| 393 | + $ insertExtra allSettings -- Accessed by Settings
|
|
| 385 | 394 | $ insertExtra args Map.empty
|
| 386 | 395 | |
| 387 | 396 | cmdLineArgs :: Action CommandLineArgs
|
| ... | ... | @@ -423,6 +432,9 @@ cmdBignum = bignum <$> cmdLineArgs |
| 423 | 432 | cmdBignumCheck :: Action Bool
|
| 424 | 433 | cmdBignumCheck = bignumCheck <$> cmdLineArgs
|
| 425 | 434 | |
| 435 | +cmdKeepResponseFiles :: Action Bool
|
|
| 436 | +cmdKeepResponseFiles = keepResponseFiles <$> cmdLineArgs
|
|
| 437 | + |
|
| 426 | 438 | cmdProgressInfo :: Action ProgressInfo
|
| 427 | 439 | cmdProgressInfo = progressInfo <$> cmdLineArgs
|
| 428 | 440 |
| ... | ... | @@ -40,7 +40,7 @@ runAr :: FilePath -- ^ path to @ar@ |
| 40 | 40 | -> [FilePath] -- ^ input file paths
|
| 41 | 41 | -> [CmdOption] -- ^ Additional options
|
| 42 | 42 | -> Action ()
|
| 43 | -runAr arPath flagArgs fileArgs buildOptions = withTempFile $ \tmp -> do
|
|
| 43 | +runAr arPath flagArgs fileArgs buildOptions = withResponseFile $ \tmp -> do
|
|
| 44 | 44 | writeFile' tmp $ unwords fileArgs
|
| 45 | 45 | cmd [arPath] flagArgs ('@' : tmp) buildOptions
|
| 46 | 46 |
| ... | ... | @@ -14,6 +14,7 @@ module Hadrian.Utilities ( |
| 14 | 14 | |
| 15 | 15 | -- * Paths
|
| 16 | 16 | BuildRoot (..), buildRoot, buildRootRules, isGeneratedSource,
|
| 17 | + KeepResponseFiles (..), keepResponseFiles, withResponseFile,
|
|
| 17 | 18 | |
| 18 | 19 | -- * File system operations
|
| 19 | 20 | copyFile, copyFileUntracked, createFileLink, fixFile,
|
| ... | ... | @@ -48,6 +49,7 @@ import Development.Shake hiding (Normal) |
| 48 | 49 | import Development.Shake.Classes
|
| 49 | 50 | import Development.Shake.FilePath
|
| 50 | 51 | import System.Environment (lookupEnv)
|
| 52 | +import System.IO (hClose, openTempFile)
|
|
| 51 | 53 | |
| 52 | 54 | import qualified Data.ByteString as BS
|
| 53 | 55 | import qualified Control.Exception.Base as IO
|
| ... | ... | @@ -317,6 +319,29 @@ buildRootRules = do |
| 317 | 319 | isGeneratedSource :: FilePath -> Action Bool
|
| 318 | 320 | isGeneratedSource file = buildRoot <&> (`isPrefixOf` file)
|
| 319 | 321 | |
| 322 | +newtype KeepResponseFiles = KeepResponseFiles Bool deriving (Eq, Show)
|
|
| 323 | + |
|
| 324 | +-- | Whether to retain response files after the build action that created them
|
|
| 325 | +-- completes. Mainly useful for debugging.
|
|
| 326 | +keepResponseFiles :: Action Bool
|
|
| 327 | +keepResponseFiles = do
|
|
| 328 | + KeepResponseFiles keep <- userSetting (KeepResponseFiles False)
|
|
| 329 | + return keep
|
|
| 330 | + |
|
| 331 | +-- | Run an action with a response file path.
|
|
| 332 | +--
|
|
| 333 | +-- With @--keep-response-files@, the file is left on disk.
|
|
| 334 | +withResponseFile :: (FilePath -> Action a) -> Action a
|
|
| 335 | +withResponseFile action = do
|
|
| 336 | + keep <- keepResponseFiles
|
|
| 337 | + if keep
|
|
| 338 | + then do
|
|
| 339 | + (tmp, h) <- liftIO $ openTempFile "." "hadrian-rsp"
|
|
| 340 | + liftIO $ hClose h
|
|
| 341 | + putInfo $ "Keeping response file: " ++ tmp
|
|
| 342 | + action tmp
|
|
| 343 | + else withTempFile action
|
|
| 344 | + |
|
| 320 | 345 | -- | Link a file tracking the link target. Create the target directory if
|
| 321 | 346 | -- missing.
|
| 322 | 347 | createFileLink :: FilePath -> FilePath -> Action ()
|
| ... | ... | @@ -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 |