summaryrefslogtreecommitdiff
path: root/testsuite/tests/codeGen
diff options
context:
space:
mode:
authorMatthew Craven <5086-clyring@users.noreply.gitlab.haskell.org>2023-03-14 22:13:38 -0400
committerMarge Bot <ben+marge-bot@smart-cactus.org>2023-04-04 01:04:10 -0400
commitf7da530c80c0117d5684bb52481e4a40d7e724cc (patch)
treef7842af425dd2513d9d0be63a2df045f416e5258 /testsuite/tests/codeGen
parent9095e297fbb46781fd182210609ce2a3f6c59b7a (diff)
downloadhaskell-f7da530c80c0117d5684bb52481e4a40d7e724cc.tar.gz
StgToCmm: Upgrade -fcheck-prim-bounds behavior
Fixes #21054. Additionally, we can now check for range overlap when generating Cmm for primops that use memcpy internally.
Diffstat (limited to 'testsuite/tests/codeGen')
-rw-r--r--testsuite/tests/codeGen/should_fail/CheckBoundsCompareByteArray2.hs19
-rw-r--r--testsuite/tests/codeGen/should_fail/CheckBoundsCompareByteArray3.hs19
-rw-r--r--testsuite/tests/codeGen/should_fail/CheckBoundsReadInt64Array.hs16
-rw-r--r--testsuite/tests/codeGen/should_fail/CheckBoundsReadSmallArray.hs14
-rw-r--r--testsuite/tests/codeGen/should_fail/CheckBoundsReadWord64Array.hs16
-rw-r--r--testsuite/tests/codeGen/should_fail/CheckBoundsReadWord8ArrayAsWord32.hs17
-rw-r--r--testsuite/tests/codeGen/should_fail/CheckOverlapCopyAddrToByteArray.hs14
-rw-r--r--testsuite/tests/codeGen/should_fail/CheckOverlapCopyByteArray.hs15
-rw-r--r--testsuite/tests/codeGen/should_fail/all.T16
-rw-r--r--testsuite/tests/codeGen/should_run/CheckBoundsOK.hs244
-rw-r--r--testsuite/tests/codeGen/should_run/all.T1
11 files changed, 387 insertions, 4 deletions
diff --git a/testsuite/tests/codeGen/should_fail/CheckBoundsCompareByteArray2.hs b/testsuite/tests/codeGen/should_fail/CheckBoundsCompareByteArray2.hs
new file mode 100644
index 0000000000..768948c7d8
--- /dev/null
+++ b/testsuite/tests/codeGen/should_fail/CheckBoundsCompareByteArray2.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE NondecreasingIndentation #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+
+module Main where
+
+import GHC.Exts
+import GHC.IO
+
+main :: IO ()
+main = do
+ IO $ \s0 ->
+ case newByteArray# 4# s0 of
+ (# s1, a_marr #) -> case newByteArray# 4# s1 of
+ (# s2, b_marr #) -> case unsafeFreezeByteArray# a_marr s2 of
+ (# s3, a_arr #) -> case unsafeFreezeByteArray# b_marr s2 of
+ (# s4, b_arr #) -> case compareByteArrays# a_arr (-1#) b_arr 0# 4# of
+ 0# -> (# s4, () #)
+
diff --git a/testsuite/tests/codeGen/should_fail/CheckBoundsCompareByteArray3.hs b/testsuite/tests/codeGen/should_fail/CheckBoundsCompareByteArray3.hs
new file mode 100644
index 0000000000..8a7dcd3123
--- /dev/null
+++ b/testsuite/tests/codeGen/should_fail/CheckBoundsCompareByteArray3.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE NondecreasingIndentation #-}
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+
+module Main where
+
+import GHC.Exts
+import GHC.IO
+
+main :: IO ()
+main = do
+ IO $ \s0 ->
+ case newByteArray# 4# s0 of
+ (# s1, a_marr #) -> case newByteArray# 4# s1 of
+ (# s2, b_marr #) -> case unsafeFreezeByteArray# a_marr s2 of
+ (# s3, a_arr #) -> case unsafeFreezeByteArray# b_marr s2 of
+ (# s4, b_arr #) -> case compareByteArrays# a_arr 2# b_arr 3# (-1#) of
+ 0# -> (# s4, () #)
+
diff --git a/testsuite/tests/codeGen/should_fail/CheckBoundsReadInt64Array.hs b/testsuite/tests/codeGen/should_fail/CheckBoundsReadInt64Array.hs
new file mode 100644
index 0000000000..1e08b4e84d
--- /dev/null
+++ b/testsuite/tests/codeGen/should_fail/CheckBoundsReadInt64Array.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+
+module Main where
+
+import GHC.Exts
+import GHC.IO
+
+main :: IO ()
+main = do
+ IO $ \s0 ->
+ case newByteArray# 23# s0 of
+ (# s1, marr #) ->
+ case readInt64Array# marr 2# s1 of
+ (# s2, _n #) -> (# s2, () #)
+
diff --git a/testsuite/tests/codeGen/should_fail/CheckBoundsReadSmallArray.hs b/testsuite/tests/codeGen/should_fail/CheckBoundsReadSmallArray.hs
new file mode 100644
index 0000000000..c9d8261f21
--- /dev/null
+++ b/testsuite/tests/codeGen/should_fail/CheckBoundsReadSmallArray.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+
+module Main where
+
+import GHC.Exts
+import GHC.IO
+
+main :: IO ()
+main = do
+ IO $ \s0 ->
+ case newSmallArray# 5# () s0 of
+ (# s1, marr #) -> readSmallArray# marr (-1#) s1
+
diff --git a/testsuite/tests/codeGen/should_fail/CheckBoundsReadWord64Array.hs b/testsuite/tests/codeGen/should_fail/CheckBoundsReadWord64Array.hs
new file mode 100644
index 0000000000..f5337d26c3
--- /dev/null
+++ b/testsuite/tests/codeGen/should_fail/CheckBoundsReadWord64Array.hs
@@ -0,0 +1,16 @@
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+
+module Main where
+
+import GHC.Exts
+import GHC.IO
+
+main :: IO ()
+main = do
+ IO $ \s0 ->
+ case newByteArray# 23# s0 of
+ (# s1, marr #) ->
+ case readWord64Array# marr (-1#) s1 of
+ (# s2, _n #) -> (# s2, () #)
+
diff --git a/testsuite/tests/codeGen/should_fail/CheckBoundsReadWord8ArrayAsWord32.hs b/testsuite/tests/codeGen/should_fail/CheckBoundsReadWord8ArrayAsWord32.hs
new file mode 100644
index 0000000000..4bade0a101
--- /dev/null
+++ b/testsuite/tests/codeGen/should_fail/CheckBoundsReadWord8ArrayAsWord32.hs
@@ -0,0 +1,17 @@
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+
+module Main where
+
+import GHC.Exts
+import GHC.IO
+
+main :: IO ()
+main = do
+ IO $ \s0 ->
+ case newByteArray# 7# s0 of
+ (# s1, marr #) ->
+ case readWord8ArrayAsWord32# marr (-3#) s1 of
+ -- only the last byte of the desired word32 is in bounds
+ (# s2, _n #) -> (# s2, () #)
+
diff --git a/testsuite/tests/codeGen/should_fail/CheckOverlapCopyAddrToByteArray.hs b/testsuite/tests/codeGen/should_fail/CheckOverlapCopyAddrToByteArray.hs
new file mode 100644
index 0000000000..7da97ef234
--- /dev/null
+++ b/testsuite/tests/codeGen/should_fail/CheckOverlapCopyAddrToByteArray.hs
@@ -0,0 +1,14 @@
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+
+module Main where
+
+import GHC.Exts
+import GHC.IO
+
+main :: IO ()
+main = do
+ IO $ \s0 ->
+ case newPinnedByteArray# 7# s0 of
+ (# s1, marr #) -> case mutableByteArrayContents# marr of
+ ptr -> (# copyAddrToByteArray# ptr marr 3# 4# s1, () #)
diff --git a/testsuite/tests/codeGen/should_fail/CheckOverlapCopyByteArray.hs b/testsuite/tests/codeGen/should_fail/CheckOverlapCopyByteArray.hs
new file mode 100644
index 0000000000..a6fdcb2dc4
--- /dev/null
+++ b/testsuite/tests/codeGen/should_fail/CheckOverlapCopyByteArray.hs
@@ -0,0 +1,15 @@
+{-# LANGUAGE UnboxedTuples #-}
+{-# LANGUAGE MagicHash #-}
+
+module Main where
+
+import GHC.Exts
+import GHC.IO
+
+main :: IO ()
+main = do
+ IO $ \s0 ->
+ case newByteArray# 7# s0 of
+ (# s1, marr #) -> case unsafeFreezeByteArray# marr s1 of
+ (# s2, arr #) -> (# copyByteArray# arr 3# marr 0# 4# s2, () #)
+
diff --git a/testsuite/tests/codeGen/should_fail/all.T b/testsuite/tests/codeGen/should_fail/all.T
index 3f2dacee46..60e863663a 100644
--- a/testsuite/tests/codeGen/should_fail/all.T
+++ b/testsuite/tests/codeGen/should_fail/all.T
@@ -10,10 +10,18 @@ def check_bounds_test(name):
[ignore_stderr, exit_code(127 if opsys('mingw32') else 134)],
compile_and_run, ['-fcheck-prim-bounds'])
-check_bounds_test('CheckBoundsWriteArray')
-check_bounds_test('CheckBoundsIndexArray')
+check_bounds_test('CheckBoundsWriteArray') # Check past end
+check_bounds_test('CheckBoundsIndexArray') # Check past end
+check_bounds_test('CheckBoundsReadSmallArray') # Check before start
check_bounds_test('CheckBoundsReadInt8Array')
-check_bounds_test('CheckBoundsReadWord8ArrayAsInt32')
+check_bounds_test('CheckBoundsReadInt64Array') # read past end
+check_bounds_test('CheckBoundsReadWord64Array') # read before start
+check_bounds_test('CheckBoundsReadWord8ArrayAsInt32') # Check last byte
+check_bounds_test('CheckBoundsReadWord8ArrayAsWord32') # Check first byte
check_bounds_test('CheckBoundsCopyByteArray')
-check_bounds_test('CheckBoundsCompareByteArray')
+check_bounds_test('CheckBoundsCompareByteArray') # Check last byte, 2nd array
+check_bounds_test('CheckBoundsCompareByteArray2') # Check first byte, 1st array
+check_bounds_test('CheckBoundsCompareByteArray3') # Check negative length
+check_bounds_test('CheckOverlapCopyByteArray')
+check_bounds_test('CheckOverlapCopyAddrToByteArray')
diff --git a/testsuite/tests/codeGen/should_run/CheckBoundsOK.hs b/testsuite/tests/codeGen/should_run/CheckBoundsOK.hs
new file mode 100644
index 0000000000..ce5a80375d
--- /dev/null
+++ b/testsuite/tests/codeGen/should_run/CheckBoundsOK.hs
@@ -0,0 +1,244 @@
+-- This test verifies that correct (not out-of-bounds) uses
+-- of primops that we can bounds-check with -fcheck-prim-bounds
+-- do not cause spurious bounds-checking failures.
+
+-- Currently this tests most ByteArray#, Array#, and SmallArray# operations.
+-- (Theoretically it could also test Addr# operations,
+-- since those /can/ be bounds-checked with the JS back-end.)
+
+{-# LANGUAGE CPP #-}
+
+{-# LANGUAGE GHC2021 #-}
+{-# LANGUAGE MagicHash #-}
+{-# LANGUAGE UnboxedTuples #-}
+
+module Main where
+
+import Data.Array.Byte
+import Data.Bits
+import Control.Monad
+import GHC.Exts
+import GHC.IO
+import GHC.Word
+import GHC.Int
+import GHC.Float
+import GHC.Stable
+import System.IO
+
+#define TEST_READ_WRITE(CONDITION, READ_OP, INDEX_OP, WRITE_OP) \
+ when (CONDITION) $ IO $ \s0 -> \
+ case (READ_OP) arrU# i# s0 of \
+ (# s1, v# #) -> case (WRITE_OP) arrP# i# v# s1 of \
+ s2 -> (# (WRITE_OP) arrU# i# ((INDEX_OP) arrF# i#) s2, () #)
+
+#define ALIGNED_RW(WIDTH, READ_OP, INDEX_OP, WRITE_OP) \
+ TEST_READ_WRITE(i < size `div` (WIDTH), READ_OP, INDEX_OP, WRITE_OP)
+
+#define UNALIGNED_RW(WIDTH, READ_OP, INDEX_OP, WRITE_OP) \
+ TEST_READ_WRITE(i + (WIDTH) <= size, READ_OP, INDEX_OP, WRITE_OP)
+
+#define TEST_CAS(WIDTH, CON, CAS_OP) \
+ when (i < size `div` (WIDTH)) $ IO $ \s0 -> \
+ case (0, 7) of \
+ (CON v0, CON v7) -> case (CAS_OP) arrU# i# v0 v7 s0 of \
+ (# s1, v' #) -> (# s1, () #)
+
+
+wrapEffect :: (State# RealWorld -> State# RealWorld) -> IO ()
+wrapEffect eff = IO (\s0 -> (# eff s0, () #))
+
+
+testByteArraysOfSize :: Int -> IO ()
+testByteArraysOfSize (size@(I# size#)) = do
+ let mkArr op = IO $ \s0 -> case op size# s0 of
+ (# s1, newArr #)
+ -> (# setByteArray# newArr 0# size# 123# s1,
+ MutableByteArray newArr #)
+ MutableByteArray arrU# <- mkArr newByteArray#
+ MutableByteArray arrP# <- mkArr newPinnedByteArray#
+ ByteArray arrF# <- do
+ MutableByteArray arrToFreeze <- mkArr newByteArray#
+ IO $ \s0 -> case unsafeFreezeByteArray# arrToFreeze s0 of
+ (# s1, frozenArr #) -> (# s1, ByteArray frozenArr #)
+ let !nws = finiteBitSize (0 :: Int) `div` 8
+ !bufP = mutableByteArrayContents# arrP#
+
+
+ forM_ [0..size] $ \i@(I# i#) -> do
+ -- test valid aligned read/write ops
+ -- (expressed via CPP macro because of non-uniform representations)
+ ALIGNED_RW(1, readWord8Array#, indexWord8Array#, writeWord8Array#)
+ ALIGNED_RW(2, readWord16Array#, indexWord16Array#, writeWord16Array#)
+ ALIGNED_RW(4, readWord32Array#, indexWord32Array#, writeWord32Array#)
+ ALIGNED_RW(8, readWord64Array#, indexWord64Array#, writeWord64Array#)
+ ALIGNED_RW(nws, readWordArray#, indexWordArray#, writeWordArray#)
+
+ ALIGNED_RW(1, readInt8Array#, indexInt8Array#, writeInt8Array#)
+ ALIGNED_RW(2, readInt16Array#, indexInt16Array#, writeInt16Array#)
+ ALIGNED_RW(4, readInt32Array#, indexInt32Array#, writeInt32Array#)
+ ALIGNED_RW(8, readInt64Array#, indexInt64Array#, writeInt64Array#)
+ ALIGNED_RW(nws, readIntArray#, indexIntArray#, writeIntArray#)
+
+ ALIGNED_RW(4, readFloatArray#, indexFloatArray#, writeFloatArray#)
+ ALIGNED_RW(8, readDoubleArray#, indexDoubleArray#, writeDoubleArray#)
+
+ ALIGNED_RW(1, readCharArray#, indexCharArray#, writeCharArray#)
+ ALIGNED_RW(4, readWideCharArray#, indexWideCharArray#, writeWideCharArray#)
+
+ -- TODO: What is the right condition is for Addr# with the JS backend?
+ ALIGNED_RW(nws, readAddrArray#, indexAddrArray#, writeAddrArray#)
+ ALIGNED_RW(nws, readStablePtrArray#, indexStablePtrArray#, writeStablePtrArray#)
+
+
+ -- test valid unaligned read/write ops
+ -- (expressed via CPP macro because of non-uniform representations)
+ -- no primops for unaligned word8 access
+ UNALIGNED_RW(2, readWord8ArrayAsWord16#, indexWord8ArrayAsWord16#, writeWord8ArrayAsWord16#)
+ UNALIGNED_RW(4, readWord8ArrayAsWord32#, indexWord8ArrayAsWord32#, writeWord8ArrayAsWord32#)
+ UNALIGNED_RW(8, readWord8ArrayAsWord64#, indexWord8ArrayAsWord64#, writeWord8ArrayAsWord64#)
+ UNALIGNED_RW(nws, readWord8ArrayAsWord#, indexWord8ArrayAsWord#, writeWord8ArrayAsWord#)
+
+ -- no primops for unaligned int8 access
+ UNALIGNED_RW(2, readWord8ArrayAsInt16#, indexWord8ArrayAsInt16#, writeWord8ArrayAsInt16#)
+ UNALIGNED_RW(4, readWord8ArrayAsInt32#, indexWord8ArrayAsInt32#, writeWord8ArrayAsInt32#)
+ UNALIGNED_RW(8, readWord8ArrayAsInt64#, indexWord8ArrayAsInt64#, writeWord8ArrayAsInt64#)
+ UNALIGNED_RW(nws, readWord8ArrayAsInt#, indexWord8ArrayAsInt#, writeWord8ArrayAsInt#)
+
+ UNALIGNED_RW(4, readWord8ArrayAsFloat#, indexWord8ArrayAsFloat#, writeWord8ArrayAsFloat#)
+ UNALIGNED_RW(8, readWord8ArrayAsDouble#, indexWord8ArrayAsDouble#, writeWord8ArrayAsDouble#)
+
+ UNALIGNED_RW(1, readWord8ArrayAsChar#, indexWord8ArrayAsChar#, writeWord8ArrayAsChar#)
+ UNALIGNED_RW(4, readWord8ArrayAsWideChar#, indexWord8ArrayAsWideChar#, writeWord8ArrayAsWideChar#)
+
+ -- TODO: What is the right condition is for Addr# with the JS backend?
+ UNALIGNED_RW(nws, readWord8ArrayAsAddr#, indexWord8ArrayAsAddr#, writeWord8ArrayAsAddr#)
+ UNALIGNED_RW(nws, readWord8ArrayAsStablePtr#, indexWord8ArrayAsStablePtr#, writeWord8ArrayAsStablePtr#)
+
+
+ when (i < size `div` nws) $ do
+ let testFetchModify :: (MutableByteArray# RealWorld -> Int# -> Int#
+ -> State# RealWorld -> (# State# RealWorld, Int# #))
+ -> IO ()
+ testFetchModify op
+ = IO (\s -> case op arrU# i# 137# s of (# s', _ #) -> (# s', () #) )
+ testFetchModify fetchXorIntArray#
+ testFetchModify fetchOrIntArray#
+ testFetchModify fetchNandIntArray#
+ testFetchModify fetchAndIntArray#
+ testFetchModify fetchSubIntArray#
+ testFetchModify fetchAddIntArray#
+
+ IO $ \s0 -> case atomicReadIntArray# arrU# i# s0 of
+ (# s1, v #) -> (# atomicWriteIntArray# arrP# i# v s1, () #)
+
+
+ TEST_CAS(8, I64#, casInt64Array#)
+ TEST_CAS(4, I32#, casInt32Array#)
+ TEST_CAS(2, I16#, casInt16Array#)
+ TEST_CAS(1, I8# , casInt8Array#)
+ TEST_CAS(nws, I#, casIntArray#)
+
+
+ -- test valid range ops
+ forM_ [0..size] $ \rangeLen@(I# rangeLen#) -> do
+ let ixs | rangeLen == 0 = [-4 .. size + 4] -- empty ranges are not out-of-bounds
+ | otherwise = [0 .. size - rangeLen]
+ forM_ ixs $ \i@(I# i#) -> do
+ wrapEffect (setByteArray# arrU# i# rangeLen# 234#)
+ forM_ ixs $ \j@(I# j#) -> do
+ wrapEffect (copyMutableByteArrayNonOverlapping# arrP# i# arrU# j# rangeLen#)
+ wrapEffect (copyByteArray# arrF# i# arrU# j# rangeLen#)
+ wrapEffect (copyMutableByteArray# arrU# i# arrP# j# rangeLen#)
+ wrapEffect (copyMutableByteArray# arrU# i# arrU# j# rangeLen#)
+ case compareByteArrays# arrF# i# arrF# j# rangeLen# of
+ v -> wrapEffect (setByteArray# arrP# j# rangeLen# (v `andI#` 255#))
+ let !rangeP = bufP `plusAddr#` j#
+ wrapEffect (copyAddrToByteArray# rangeP arrU# i# rangeLen#)
+ wrapEffect (copyMutableByteArrayToAddr# arrU# i# rangeP rangeLen#)
+ wrapEffect (copyByteArrayToAddr# arrF# i# rangeP rangeLen#)
+ when (abs (i - j) >= rangeLen) $
+ wrapEffect (copyMutableByteArrayNonOverlapping# arrU# i# arrU# j# rangeLen#)
+
+
+
+data Array a = Array (Array# a)
+data MutableArray s a = MutableArray (MutableArray# s a)
+data SmallArray a = SmallArray (SmallArray# a)
+data SmallMutableArray s a = SmallMutableArray (SmallMutableArray# s a)
+
+
+testArraysOfSize :: Int -> IO ()
+testArraysOfSize (size@(I# size#)) = do
+ let mkArr v = IO $ \s0 -> case newArray# size# v s0 of
+ (# s1, newArr #) -> (# s1, MutableArray newArr #)
+ MutableArray arrM# <- mkArr 0
+ Array arrF# <- do
+ MutableArray arrToFreeze <- mkArr 0
+ forM_ [0 .. size - 1] $ \(i@(I# i#)) -> do
+ wrapEffect (writeArray# arrM# i# i)
+ wrapEffect (writeArray# arrToFreeze i# i)
+
+ IO $ \s0 -> case unsafeFreezeArray# arrToFreeze s0 of
+ (# s1, frozenArr #) -> (# s1, Array frozenArr #)
+
+ forM_ [0 .. size - 1] $ \(i@(I# i#)) -> do
+
+ -- test read/index/write
+ IO $ \s0 -> case readArray# arrM# i# s0 of
+ (# s1, vm #) -> case indexArray# arrF# i# of
+ (# vf #) -> (# writeArray# arrM# i# (vm + vf) s1, () #)
+
+ -- test casArray
+ IO $ \s0 -> case casArray# arrM# i# 0 7 s0 of
+ (# s1, _, _ #) -> (# s1, () #)
+
+ -- test valid range ops
+ forM_ [0..size] $ \rangeLen@(I# rangeLen#) -> do
+ let ixs | rangeLen == 0 = [-4 .. size + 4] -- empty ranges are not out-of-bounds
+ | otherwise = [0 .. size - rangeLen]
+ forM_ ixs $ \(i@(I# i#)) -> do
+ forM_ ixs $ \(j@(I# j#)) -> do
+ wrapEffect (copyArray# arrF# i# arrM# j# rangeLen#)
+ wrapEffect (copyMutableArray# arrM# i# arrM# j# rangeLen#)
+
+
+testSmallArraysOfSize :: Int -> IO ()
+testSmallArraysOfSize (size@(I# size#)) = do
+ let mkArr v = IO $ \s0 -> case newSmallArray# size# v s0 of
+ (# s1, newArr #) -> (# s1, SmallMutableArray newArr #)
+ SmallMutableArray arrM# <- mkArr 0
+ SmallArray arrF# <- do
+ SmallMutableArray arrToFreeze <- mkArr 0
+ forM_ [0 .. size - 1] $ \(i@(I# i#)) -> do
+ wrapEffect (writeSmallArray# arrM# i# i)
+ wrapEffect (writeSmallArray# arrToFreeze i# i)
+
+ IO $ \s0 -> case unsafeFreezeSmallArray# arrToFreeze s0 of
+ (# s1, frozenArr #) -> (# s1, SmallArray frozenArr #)
+
+ forM_ [0 .. size - 1] $ \(i@(I# i#)) -> do
+
+ -- test read/index/write
+ IO $ \s0 -> case readSmallArray# arrM# i# s0 of
+ (# s1, vm #) -> case indexSmallArray# arrF# i# of
+ (# vf #) -> (# writeSmallArray# arrM# i# (vm + vf) s1, () #)
+
+ -- test casSmallArray
+ IO $ \s0 -> case casSmallArray# arrM# i# 0 7 s0 of
+ (# s1, _, _ #) -> (# s1, () #)
+
+ -- test valid range ops
+ forM_ [0..size] $ \rangeLen@(I# rangeLen#) -> do
+ let ixs | rangeLen == 0 = [-4 .. size + 4] -- empty ranges are not out-of-bounds
+ | otherwise = [0 .. size - rangeLen]
+ forM_ ixs $ \(i@(I# i#)) -> do
+ forM_ ixs $ \(j@(I# j#)) -> do
+ wrapEffect (copySmallArray# arrF# i# arrM# j# rangeLen#)
+ wrapEffect (copySmallMutableArray# arrM# i# arrM# j# rangeLen#)
+
+
+main :: IO ()
+main = forM_ ([0..4] ++ [24..32]) $ \size -> do
+ testByteArraysOfSize size
+ testArraysOfSize size
+ testSmallArraysOfSize size
diff --git a/testsuite/tests/codeGen/should_run/all.T b/testsuite/tests/codeGen/should_run/all.T
index fa3ec779d9..87ff271296 100644
--- a/testsuite/tests/codeGen/should_run/all.T
+++ b/testsuite/tests/codeGen/should_run/all.T
@@ -229,3 +229,4 @@ test('T20640b', normal, compile_and_run, [''])
test('T22296',[only_ways(llvm_ways)
,unless(arch('x86_64'), skip)],compile_and_run,[''])
test('T22798', normal, compile_and_run, ['-fregs-graph'])
+test('CheckBoundsOK', js_broken(23123), compile_and_run, ['-fcheck-prim-bounds'])