diff options
author | Matthew Craven <5086-clyring@users.noreply.gitlab.haskell.org> | 2023-03-14 22:13:38 -0400 |
---|---|---|
committer | Marge Bot <ben+marge-bot@smart-cactus.org> | 2023-04-04 01:04:10 -0400 |
commit | f7da530c80c0117d5684bb52481e4a40d7e724cc (patch) | |
tree | f7842af425dd2513d9d0be63a2df045f416e5258 /testsuite/tests/codeGen | |
parent | 9095e297fbb46781fd182210609ce2a3f6c59b7a (diff) | |
download | haskell-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')
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']) |