Skip to content
Snippets Groups Projects
Commit 1706a009 authored by Sven Tennie's avatar Sven Tennie :smiley_cat:
Browse files

Add reproducer for dealloc instructions in switch table jump expressions (#25733)

Measures taken to make the test stable:

- Use 'a' as variable prefix, because X86 32bit stumbled over the
variable name 'i386'
- Flush stdout to make test output deterministic
- Use type annotations to support 32bit archs
parent 8d8acd94
No related branches found
No related tags found
1 merge request!13921Fix AArch64 switch/table jumps (#25733)
Pipeline #107400 passed with warnings
Pipeline: test-primops

#107404

    {-# LANGUAGE GHCForeignImportPrim #-}
    {-# LANGUAGE MagicHash #-}
    {-# LANGUAGE UnliftedFFITypes #-}
    import Data.Foldable
    import GHC.Exts
    import GHC.Int
    import System.IO
    foreign import prim "foo" foo :: Int64# -> Int64#
    main :: IO ()
    main = for_ [0 .. 9] $ \(I64# x#) -> do
    let !res = I64# (foo x#)
    putStrLn $ "Result: " ++ show res
    hFlush stdout
    Before switch
    Inside branch 0
    Before jump 0
    Return
    Result: 0
    Before switch
    Inside branch 1
    Before jump 1
    Return
    Result: 0
    Before switch
    Inside branch 8
    Before jump 8
    Return
    Result: 0
    Before switch
    Inside default branch
    Before jump default
    Return
    Result: 6
    Before switch
    Inside branch 6
    Before jump 6
    Return
    Result: 0
    Before switch
    Inside branch 7
    Before jump 7
    Return
    Result: 2
    Before switch
    Inside branch 4
    Before jump 4
    Return
    Result: 0
    Before switch
    Inside branch 5
    Before jump 5
    Return
    Result: 4
    Before switch
    Inside branch 2
    Before jump 2
    Return
    Result: 0
    Before switch
    Inside branch 3
    Before jump 3
    Return
    Result: 0
    -- | Generates the JumpTableNoStackDealloc test
    --
    -- The test is a reproducer of #25733 where stack spill slots where wrongly
    -- deallocated for switch table jumps. This only happened when the initial
    -- stack (RESERVED_C_STACK_BYTES, 2048 * SIZEOF_LONG, 16k on most 64bit
    -- architectures) was full. I.e. we were breaking the assumptions explained in
    -- Note [extra spill slots].
    --
    -- The strategy of this test is to first fill the C stack by having many local
    -- variables that are used later. This leads to stack spilling. In the broken
    -- implemetation of the CodeGen, the invalid stack pointer led to a
    -- segmentation fault / core dump. Its absence, i.e. a suceessful run of this
    -- test, shows that the stack pointer is intact.
    --
    -- The calculations in this test are pretty randomly choosen: They are only
    -- made to prevent inlining and constant folding.
    --
    -- The real test can be generated by running:
    -- `ghc JumpTableNoStackDeallocGen.hs && ./JumpTableNoStackDeallocGen > JumpTableNoStackDealloc_cmm.cmm`
    module Main where
    import Control.Monad
    import Data.List
    printVarDecl :: Int -> IO ()
    printVarDecl i = do
    putStrLn $ " I64 a" ++ show i ++ ";"
    putStrLn $ " a" ++ show i ++ " = ((" ++ show i ++ " :: I64) " ++ op ++ " x) % (10 :: I64);"
    where
    -- Add some variance. Otherwise, GHC inlines the expressions.
    op = case i of
    _
    | even i -> "+"
    | odd i -> "-"
    addVars :: [Int] -> String
    addVars = opVars "+"
    where
    opVars :: String -> [Int] -> String
    opVars op is = concat $ intersperse op $ map ((++) "a" . show) is
    printCase :: Int -> IO ()
    printCase i = do
    putStrLn $ " case " ++ show i ++ ": {"
    putStrLn $ " ccall printf(\"Inside branch " ++ show i ++ "\\n\");"
    putStrLn $ " res = a" ++ show i ++ ";"
    putStrLn $ " ccall printf(\"Before jump " ++ show i ++ "\\n\");"
    putStrLn $ " goto END;"
    putStrLn $ " }"
    main :: IO ()
    main = do
    -- This number depends on the amount of available registers. 2048 works for
    -- AArch64, adding a bit more to be safe in case an architecture has more
    -- free fregisters.
    let varNums = [0 .. 2060]
    putStrLn "// Generated by JumpTableNoStackDeallocGen.hs\n"
    putStrLn "#include \"Cmm.h\""
    putStrLn "foo (I64 x) {"
    forM_ varNums printVarDecl
    putStrLn "\n I64 res;"
    putStrLn " ccall printf(\"Before switch\\n\");"
    putStrLn $ " switch [0 .. 9] ((" ++ addVars varNums ++ ") % (10 :: I64)) {"
    forM_ [0 .. 8] printCase
    putStrLn " default: {"
    putStrLn " ccall printf(\"Inside default branch\\n\");"
    putStrLn " res = x * (2 :: I64);"
    putStrLn " ccall printf(\"Before jump default\\n\");"
    putStrLn " goto END;"
    putStrLn " }"
    putStrLn " }"
    putStrLn "END:"
    putStrLn " ccall printf(\"Return\\n\");"
    putStrLn " ccall fflush(NULL);"
    putStrLn $ " return (res);"
    putStrLn "}"
    This diff is collapsed.
    ......@@ -31,6 +31,15 @@ test('T22871',
    multi_compile_and_run,
    ['T22871', [('T22871_cmm.cmm', '')], ''])
    test('JumpTableNoStackDealloc',
    [ extra_run_opts('"' + config.libdir + '"')
    , req_cmm
    , when(arch('wasm32'), skip) # wasm32 doesn't support the printf() calls
    , when(arch('i386'), skip) # i386 doesn't support `MO_U_Rem W64` (`_c1::I64 % 10 :: W64`)
    ],
    multi_compile_and_run,
    ['JumpTableNoStackDealloc', [('JumpTableNoStackDealloc_cmm.cmm', '')], ''])
    test('AtomicFetch',
    [ extra_run_opts('"' + config.libdir + '"')
    , req_cmm
    ......
    0% Loading or .
    You are about to add 0 people to the discussion. Proceed with caution.
    Finish editing this message first!
    Please register or to comment