Magnus pushed to branch wip/mangoiv/9.12.5-rc3-fixes at Glasgow Haskell Compiler / GHC

Commits:

5 changed files:

Changes:

  • changelog.d/T27123.md
    1
    +section: compiler
    
    2
    +synopsis: Fix two crashes that could happen in a multithreaded setting when profiling.
    
    3
    +description: There were two bugs that could cause occasional segfaults or crashes with
    
    4
    +an `PAP object entered` error when profiling. They only happened when two threads
    
    5
    +where racing to evaluate the same thunk, and specific GC timings.
    
    6
    +mrs: !16214
    
    7
    +issues: #27123

  • compiler/GHC/StgToCmm/Bind.hs
    ... ... @@ -587,9 +587,8 @@ closureCodeBody top_lvl bndr cl_info cc args@(arg0:_) body fv_details
    587 587
                     -- ticky after heap check to avoid double counting
    
    588 588
                     ; tickyEnterFun cl_info
    
    589 589
                     ; enterCostCentreFun cc
    
    590
    -                    (CmmMachOp (mo_wordSub platform)
    
    591
    -                         [ CmmReg (CmmLocal node) -- See [NodeReg clobbered with loopification]
    
    592
    -                         , mkIntExpr platform (funTag platform cl_info) ])
    
    590
    +                    (cmmUntag platform (CmmReg (CmmLocal node))) -- See [NodeReg clobbered with loopification]
    
    591
    +
    
    593 592
                     ; fv_bindings <- mapM bind_fv fv_details
    
    594 593
                     -- Load free vars out of closure *after*
    
    595 594
                     -- heap check, to reduce live vars over check
    

  • rts/Apply.cmm
    ... ... @@ -99,12 +99,14 @@ again:
    99 99
         W_  info;
    
    100 100
         P_ untaggedfun;
    
    101 101
         W_ arity;
    
    102
    +    W_ closure_type;
    
    102 103
         // We must obey the correct heap object observation pattern in
    
    103 104
         // Note [Heap memory barriers] in SMP.h.
    
    104 105
         untaggedfun = UNTAG(fun);
    
    105 106
         info = %INFO_PTR(untaggedfun);
    
    107
    +    closure_type = TO_W_( %INFO_TYPE(%STD_INFO(info)) );
    
    106 108
         switch [INVALID_OBJECT .. N_CLOSURE_TYPES]
    
    107
    -        (TO_W_( %INFO_TYPE(%STD_INFO(info)) )) {
    
    109
    +        (closure_type) {
    
    108 110
             case
    
    109 111
                 IND,
    
    110 112
                 IND_STATIC:
    
    ... ... @@ -212,10 +214,17 @@ again:
    212 214
                 // We can't use the value of 'info' any more, because if
    
    213 215
                 // STK_CHK_GEN() did a GC then the closure we're looking
    
    214 216
                 // at may have changed, e.g. a THUNK_SELECTOR may have
    
    215
    -            // been evaluated by the GC.  So we reload the info
    
    216
    -            // pointer now.
    
    217
    +            // been evaluated by the GC.
    
    218
    +            // We always reload the info pointer now. And if
    
    219
    +            // the closure type changed we need to take a different case
    
    220
    +            // alt altogether so we retry from the start in that case.
    
    221
    +
    
    217 222
                 untaggedfun = UNTAG(fun);
    
    218 223
                 info = %INFO_PTR(untaggedfun);
    
    224
    +            if(closure_type != TO_W_( %INFO_TYPE(%STD_INFO(info)) ) )
    
    225
    +            {
    
    226
    +              goto again;
    
    227
    +            }
    
    219 228
     
    
    220 229
                 jump %ENTRY_CODE(info)
    
    221 230
                     (stg_restore_cccs_eval_info, CCCS)
    

  • testsuite/tests/rts/T27123.hs
    1
    +{-# OPTIONS_GHC -fno-full-laziness -fno-worker-wrapper #-}
    
    2
    +{-# LANGUAGE MagicHash, UnboxedTuples #-}
    
    3
    +
    
    4
    +-- This test checks that the auto-apply code (stg_ap_0_fast, stg_ap_p) is robust
    
    5
    +-- against another thread or the GC evaluating a closure at the same time.
    
    6
    +
    
    7
    +module Main
    
    8
    +    -- (main)
    
    9
    +where
    
    10
    +
    
    11
    +import Control.Monad
    
    12
    +import Control.Concurrent
    
    13
    +import System.IO
    
    14
    +import GHC.Data.SmallArray
    
    15
    +import GHC.Exts
    
    16
    +import GHC.IO
    
    17
    +
    
    18
    +type Arr = SmallMutableArray RealWorld (Int->Int)
    
    19
    +
    
    20
    +io :: (State# RealWorld -> (# State# RealWorld, a #)) -> IO a
    
    21
    +io f = IO f
    
    22
    +
    
    23
    +io_ :: (State# RealWorld -> State# RealWorld ) -> IO ()
    
    24
    +io_ f = IO (\s -> case f s of s2 -> (# s2, () #))
    
    25
    +
    
    26
    +{-# NOINLINE readSmallArray #-}
    
    27
    +readSmallArray (SmallMutableArray arr) (I# idx) = IO $ \s -> case readSmallArray# arr idx s of
    
    28
    +    (# s2, r #) -> (# s2, r #)
    
    29
    +
    
    30
    +-- Continually overwrites the array with unevaluated thunks that will evaluated to
    
    31
    +-- a PAP under profiling.
    
    32
    +{-# NOINLINE mkThunks #-}
    
    33
    +mkThunks :: Arr -> IO ()
    
    34
    +mkThunks arr = do
    
    35
    +    forever $ do
    
    36
    +      yield
    
    37
    +      forM_ [0..100] $ \_j -> do
    
    38
    +        forM_ [0..5 :: Int] $ \i -> do
    
    39
    +            -- With profiling results in a thunk that will evaluate to a PAP capturing the SCC
    
    40
    +            let g = {-# SCC g #-} succ
    
    41
    +            io_ (writeSmallArray arr i g)
    
    42
    +
    
    43
    +-- Evaluate the array repeatedly in the given order.
    
    44
    +{-# NOINLINE evaluateThunks #-}
    
    45
    +evaluateThunks :: Arr -> [Int] -> IO ()
    
    46
    +evaluateThunks arr idxs = do
    
    47
    +    forever $ do
    
    48
    +        yield
    
    49
    +        -- putStr "." >> hFlush stdout
    
    50
    +        forM [0..5000::Int] $ \j -> do
    
    51
    +            forM_ idxs $ \i -> do
    
    52
    +                !g <- readSmallArray arr i
    
    53
    +                seq (g i) (pure ())
    
    54
    +
    
    55
    +main :: IO ()
    
    56
    +main = do
    
    57
    +    -- We spawn three threads.
    
    58
    +    -- * Two are evaluating the thunks in the array in opposite directions
    
    59
    +    -- * One thread is writing thunks to the array.
    
    60
    +    -- The reading threads will race to evaluate the same thunk triggering potential
    
    61
    +    -- race conditions.
    
    62
    +    arr <- io (newSmallArray 6 (id))
    
    63
    +    _ <- forkIO $ do
    
    64
    +        evaluateThunks arr [0..5]
    
    65
    +    _ <- forkIO $ do
    
    66
    +        evaluateThunks arr [5,4..0]
    
    67
    +    forkIO $ mkThunks arr
    
    68
    +    threadDelay 10_000_000

  • testsuite/tests/rts/all.T
    ... ... @@ -639,3 +639,59 @@ test('T25280', [unless(opsys('linux'),skip),req_process,js_skip], compile_and_ru
    639 639
     test('T25560', [req_c_rts, ignore_stderr], compile_and_run, [''])
    
    640 640
     
    
    641 641
     test('TestProddableBlockSet', [req_c_rts], multimod_compile_and_run, ['TestProddableBlockSet.c', '-no-hs-main'])
    
    642
    +<<<<<<< HEAD
    
    643
    +||||||| parent of ed09895d7de (Fix a profiling race condition resulting in segfaults.)
    
    644
    +test('T22859',
    
    645
    +     [js_skip,
    
    646
    +      # This test is vulnerable to changes in allocation behaviour, so we disable it in some ways
    
    647
    +      when(arch('wasm32'), skip),
    
    648
    +      omit_ways(llvm_ways)],
    
    649
    +     compile_and_run, ['-with-rtsopts -A8K'])
    
    650
    +
    
    651
    +# These tests need access to the internal RTS headers.
    
    652
    +# TODO: there is probably some cleaner way to do this, and it should probably
    
    653
    +# be guarded for in-tree tests, since it cannot work against an arbitrary
    
    654
    +# installed ghc.
    
    655
    +
    
    656
    +test('TimeoutQueue',
    
    657
    +     [c_src, only_ways(['normal', 'debug'])], compile_and_run,
    
    658
    +     ['-debug -optc-Wall -optc-DDEBUG -I{top}/../rts'])
    
    659
    +
    
    660
    +test('ClosureTable',
    
    661
    +     [req_c, only_ways(['normal', 'debug']), extra_files(['ClosureTable_c.c'])], compile_and_run,
    
    662
    +     ['-debug -O0 ClosureTable_c.c -I{top}/../rts -I{top}/../rts/include'])
    
    663
    +
    
    664
    +test('resizeMutableByteArrayInPlace', [req_cmm, extra_ways(['optasm', 'sanity']), only_ways(['optasm', 'sanity'])], compile_and_run, [''])
    
    665
    +
    
    666
    +test('T27434',
    
    667
    +     extra_ways(['compacting_gc']),
    
    668
    +     compile_and_run, [''])
    
    669
    +=======
    
    670
    +test('T22859',
    
    671
    +     [js_skip,
    
    672
    +      # This test is vulnerable to changes in allocation behaviour, so we disable it in some ways
    
    673
    +      when(arch('wasm32'), skip),
    
    674
    +      omit_ways(llvm_ways)],
    
    675
    +     compile_and_run, ['-with-rtsopts -A8K'])
    
    676
    +
    
    677
    +# These tests need access to the internal RTS headers.
    
    678
    +# TODO: there is probably some cleaner way to do this, and it should probably
    
    679
    +# be guarded for in-tree tests, since it cannot work against an arbitrary
    
    680
    +# installed ghc.
    
    681
    +
    
    682
    +test('TimeoutQueue',
    
    683
    +     [c_src, only_ways(['normal', 'debug'])], compile_and_run,
    
    684
    +     ['-debug -optc-Wall -optc-DDEBUG -I{top}/../rts'])
    
    685
    +
    
    686
    +test('ClosureTable',
    
    687
    +     [req_c, only_ways(['normal', 'debug']), extra_files(['ClosureTable_c.c'])], compile_and_run,
    
    688
    +     ['-debug -O0 ClosureTable_c.c -I{top}/../rts -I{top}/../rts/include'])
    
    689
    +
    
    690
    +test('resizeMutableByteArrayInPlace', [req_cmm, extra_ways(['optasm', 'sanity']), only_ways(['optasm', 'sanity'])], compile_and_run, [''])
    
    691
    +
    
    692
    +test('T27123', [when(have_profiling(), extra_ways(['prof']))], compile_and_run, ['-O'])
    
    693
    +
    
    694
    +test('T27434',
    
    695
    +     extra_ways(['compacting_gc']),
    
    696
    +     compile_and_run, [''])
    
    697
    +>>>>>>> ed09895d7de (Fix a profiling race condition resulting in segfaults.)