Magnus pushed to branch wip/mangoiv/9.12.5-rc3-fixes at Glasgow Haskell Compiler / GHC
Commits:
-
62792510
by Andreas Klebinger at 2026-07-08T22:59:13+02:00
5 changed files:
- + changelog.d/T27123.md
- compiler/GHC/StgToCmm/Bind.hs
- rts/Apply.cmm
- + testsuite/tests/rts/T27123.hs
- testsuite/tests/rts/all.T
Changes:
| 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 |
| ... | ... | @@ -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
|
| ... | ... | @@ -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)
|
| 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 |
| ... | ... | @@ -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.) |