Magnus pushed to branch wip/mangoiv/ghc-9.12-bp at Glasgow Haskell Compiler / GHC
Commits:
-
5b5aac16
by Simon Jakobi at 2026-05-14T16:19:04+02:00
-
98f24314
by Matthew Pickering at 2026-05-14T16:19:58+02:00
-
d903097d
by Andreas Klebinger at 2026-05-14T16:20:44+02:00
-
52c2ba8f
by Luite Stegeman at 2026-05-14T16:24:20+02:00
15 changed files:
- compiler/GHC/Types/Demand.hs
- rts/CloneStack.c
- rts/CloneStack.h
- rts/Interpreter.c
- rts/Messages.c
- rts/StgMiscClosures.cmm
- + testsuite/tests/bytecode/T27001.hs
- + testsuite/tests/bytecode/T27001.stdout
- testsuite/tests/bytecode/all.T
- testsuite/tests/dmdanal/should_compile/T13143.stderr
- + testsuite/tests/dmdanal/should_compile/T27106.hs
- + testsuite/tests/dmdanal/should_compile/T27106.stderr
- testsuite/tests/dmdanal/should_compile/all.T
- testsuite/tests/rts/all.T
- + testsuite/tests/rts/cloneThreadStackMigrating.hs
Changes:
| 1 | 1 | |
| 2 | 2 | {-# LANGUAGE ViewPatterns #-}
|
| 3 | +{-# LANGUAGE LambdaCase #-}
|
|
| 3 | 4 | {-# LANGUAGE BinaryLiterals #-}
|
| 4 | 5 | {-# LANGUAGE PatternSynonyms #-}
|
| 5 | 6 | |
| ... | ... | @@ -2844,7 +2845,10 @@ instance Outputable DmdEnv where |
| 2844 | 2845 | = ppr div <> if null fv_elts then empty
|
| 2845 | 2846 | else braces (fsep (map pp_elt fv_elts))
|
| 2846 | 2847 | where
|
| 2847 | - pp_elt (uniq, dmd) = ppr uniq <> text "->" <> ppr dmd
|
|
| 2848 | + pp_elt (uniq, dmd) =
|
|
| 2849 | + sdocOption sdocSuppressUniques $ \case
|
|
| 2850 | + True -> ppr dmd
|
|
| 2851 | + False -> ppr uniq <> text "->" <> ppr dmd
|
|
| 2848 | 2852 | fv_elts = nonDetUFMToList fvs
|
| 2849 | 2853 | -- It's OK to use nonDetUFMToList here because we only do it for
|
| 2850 | 2854 | -- pretty printing
|
| ... | ... | @@ -89,15 +89,31 @@ void sendCloneStackMessage(StgTSO *tso, HsStablePtr mvar) { |
| 89 | 89 | sendMessage(srcCapability, tso->cap, (Message *)msg);
|
| 90 | 90 | }
|
| 91 | 91 | |
| 92 | -void handleCloneStackMessage(MessageCloneStack *msg){
|
|
| 93 | - StgStack* newStackClosure = cloneStack(msg->tso->cap, msg->tso->stackobj);
|
|
| 92 | +// The cap argument is the capability which is handling the CloneStack message
|
|
| 93 | +void handleCloneStackMessage(Capability *cap, MessageCloneStack *msg){
|
|
| 94 | + // We must check that the current owner of the thread we want to clone the stack for
|
|
| 95 | + // is still this capability.
|
|
| 96 | + Capability *owner = RELAXED_LOAD(&msg->tso->cap);
|
|
| 97 | + if (owner != cap) {
|
|
| 98 | + // The target TSO may have migrated after the message was queued on the old
|
|
| 99 | + // capability. In that case we must forward the request to the current
|
|
| 100 | + // owner; otherwise we would race with another capability mutating the
|
|
| 101 | + // stack while we clone it.
|
|
| 102 | + sendMessage(cap, owner, (Message *)msg);
|
|
| 103 | + return;
|
|
| 104 | + }
|
|
| 105 | + |
|
| 106 | + // At this point the executing capability owns the TSO, so it is the only
|
|
| 107 | + // capability that may safely inspect the live stack and the one whose
|
|
| 108 | + // allocator we must use for the cloned StgStack closure.
|
|
| 109 | + StgStack* newStackClosure = cloneStack(cap, msg->tso->stackobj);
|
|
| 94 | 110 | |
| 95 | 111 | // Lift StackSnapshot# to StackSnapshot by applying it's constructor.
|
| 96 | 112 | // This is necessary because performTryPutMVar() puts the closure onto the
|
| 97 | 113 | // stack for evaluation and stacks can not be evaluated (entered).
|
| 98 | - HaskellObj result = rts_apply(msg->tso->cap, StackSnapshot_constructor_closure, (HaskellObj) newStackClosure);
|
|
| 114 | + HaskellObj result = rts_apply(cap, StackSnapshot_constructor_closure, (HaskellObj) newStackClosure);
|
|
| 99 | 115 | |
| 100 | - bool putMVarWasSuccessful = performTryPutMVar(msg->tso->cap, msg->result, result);
|
|
| 116 | + bool putMVarWasSuccessful = performTryPutMVar(cap, msg->result, result);
|
|
| 101 | 117 | |
| 102 | 118 | if(!putMVarWasSuccessful) {
|
| 103 | 119 | barf("Can't put stack cloning result into MVar.");
|
| ... | ... | @@ -19,7 +19,7 @@ StgArrBytes* decodeClonedStack(Capability *cap, StgStack* stack); |
| 19 | 19 | #include "BeginPrivate.h"
|
| 20 | 20 | |
| 21 | 21 | #if defined(THREADED_RTS)
|
| 22 | -void handleCloneStackMessage(MessageCloneStack *msg);
|
|
| 22 | +void handleCloneStackMessage(Capability *cap, MessageCloneStack *msg);
|
|
| 23 | 23 | #endif
|
| 24 | 24 | |
| 25 | 25 | #include "EndPrivate.h" |
| ... | ... | @@ -179,6 +179,24 @@ tag functions as tag inference currently doesn't rely on those being properly ta |
| 179 | 179 | #define WITHIN_CHUNK_BOUNDS_W(n, s) \
|
| 180 | 180 | (RTS_LIKELY(((StgWord*) Sp_plusW(n)) < ((s)->stack + (s)->stack_size - sizeofW(StgUnderflowFrame))))
|
| 181 | 181 | |
| 182 | +/* Note [Checking for underflow frames]
|
|
| 183 | + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 184 | + |
|
| 185 | + We look at the stack slot at offset sizeof(StgUnderflowFrame) from
|
|
| 186 | + the start of the chunk to check if we're in the first check chunk.
|
|
| 187 | + Every non-first stack chunk has an underflow frame header at that offset.
|
|
| 188 | + |
|
| 189 | + We really should change this check, since this stack slot in the first
|
|
| 190 | + chunk may not be the start of a stack frame and could in theory contain
|
|
| 191 | + an arbitrary value.
|
|
| 192 | + |
|
| 193 | + In practice we're unlikely to have interpreted frames that low on the stack.
|
|
| 194 | + */
|
|
| 195 | +#define IS_UNDERFLOW_FRAME(info) \
|
|
| 196 | + ((info) == &stg_stack_underflow_frame_d_info || \
|
|
| 197 | + (info) == &stg_stack_underflow_frame_v16_info || \
|
|
| 198 | + (info) == &stg_stack_underflow_frame_v32_info || \
|
|
| 199 | + (info) == &stg_stack_underflow_frame_v64_info)
|
|
| 182 | 200 | |
| 183 | 201 | #define W64_TO_WDS(n) ((n * sizeof(StgWord64) / sizeof(StgWord)))
|
| 184 | 202 | |
| ... | ... | @@ -431,11 +449,9 @@ slow_spw(void *Sp, StgStack *cur_stack, StgWord offset_words){ |
| 431 | 449 | frame = (StgUnderflowFrame*)(cur_stack->stack + cur_stack->stack_size
|
| 432 | 450 | - sizeofW(StgUnderflowFrame));
|
| 433 | 451 | |
| 434 | - // 2a. Check it is an underflow frame (the top stack chunk won't have one).
|
|
| 435 | - if( frame->info == &stg_stack_underflow_frame_d_info
|
|
| 436 | - || frame->info == &stg_stack_underflow_frame_v16_info
|
|
| 437 | - || frame->info == &stg_stack_underflow_frame_v32_info
|
|
| 438 | - || frame->info == &stg_stack_underflow_frame_v64_info )
|
|
| 452 | + // 2a. Check it is an underflow frame (the first stack chunk won't have one).
|
|
| 453 | + // See Note [Checking for underflow frames]
|
|
| 454 | + if( IS_UNDERFLOW_FRAME(frame->info) )
|
|
| 439 | 455 | {
|
| 440 | 456 | |
| 441 | 457 | INTERP_TICK(it_underflow_lookups);
|
| ... | ... | @@ -452,9 +468,11 @@ slow_spw(void *Sp, StgStack *cur_stack, StgWord offset_words){ |
| 452 | 468 | }
|
| 453 | 469 | // 2b. Access the element if there is no underflow frame, it must be right
|
| 454 | 470 | // at the top of the stack.
|
| 455 | - else {
|
|
| 456 | - // Not actually in the underflow case
|
|
| 471 | + else if(Sp_plusW(offset_words) < (StgPtr)(cur_stack->stack + cur_stack->stack_size)) {
|
|
| 472 | + // Still inside the stack chunk
|
|
| 457 | 473 | return Sp_plusW(offset_words);
|
| 474 | + } else {
|
|
| 475 | + barf("slow_spw: offset_words %d is out of bounds", (int)offset_words);
|
|
| 458 | 476 | }
|
| 459 | 477 | }
|
| 460 | 478 | }
|
| ... | ... | @@ -1788,8 +1806,39 @@ run_BCO: |
| 1788 | 1806 | * =>
|
| 1789 | 1807 | * a_1 ... a_n, k
|
| 1790 | 1808 | */
|
| 1791 | - while(n-- > 0) {
|
|
| 1792 | - SpW(n+by) = ReadSpW(n);
|
|
| 1809 | + if (n == 0 || WITHIN_CAP_CHUNK_BOUNDS_W(n - 1 + by)) {
|
|
| 1810 | + while(n-- > 0) {
|
|
| 1811 | + SpW(n+by) = ReadSpW(n);
|
|
| 1812 | + }
|
|
| 1813 | + } else {
|
|
| 1814 | + // We write across a chunk boundary: Use safe access
|
|
| 1815 | + while(n-- > 0) {
|
|
| 1816 | + *((StgWord*)SafeSpWP(n+by)) = ReadSpW(n);
|
|
| 1817 | + }
|
|
| 1818 | + }
|
|
| 1819 | + |
|
| 1820 | + // If we SLIDE Sp past the chunk bounds we need to handle the underflow
|
|
| 1821 | + // (possibly multiple times)
|
|
| 1822 | + while (!WITHIN_CAP_CHUNK_BOUNDS_W(by)) {
|
|
| 1823 | + StgStack *stk = cap->r.rCurrentTSO->stackobj;
|
|
| 1824 | + StgUnderflowFrame *uf = (StgUnderflowFrame*)
|
|
| 1825 | + (stk->stack + stk->stack_size
|
|
| 1826 | + - sizeofW(StgUnderflowFrame));
|
|
| 1827 | + // See Note [Checking for underflow frames]
|
|
| 1828 | + if (IS_UNDERFLOW_FRAME(uf->info)) {
|
|
| 1829 | + W_ sp_to_uf = (StgWord*)uf - (StgWord*)Sp;
|
|
| 1830 | + Sp = (StgPtr)uf;
|
|
| 1831 | + SAVE_STACK_POINTERS;
|
|
| 1832 | + threadStackUnderflow(cap, cap->r.rCurrentTSO);
|
|
| 1833 | + LOAD_STACK_POINTERS;
|
|
| 1834 | + by -= sp_to_uf;
|
|
| 1835 | + } else if (Sp_plusW(by) < (StgPtr)(stk->stack + stk->stack_size)) {
|
|
| 1836 | + // we're within the first stack chunk, this chunk has
|
|
| 1837 | + // no underflow frame
|
|
| 1838 | + break;
|
|
| 1839 | + } else {
|
|
| 1840 | + barf("bci_SLIDE: Sp+by outside stack bounds");
|
|
| 1841 | + }
|
|
| 1793 | 1842 | }
|
| 1794 | 1843 | Sp_addW(by);
|
| 1795 | 1844 | INTERP_TICK(it_slides);
|
| ... | ... | @@ -135,7 +135,7 @@ loop: |
| 135 | 135 | }
|
| 136 | 136 | else if(i == &stg_MSG_CLONE_STACK_info){
|
| 137 | 137 | MessageCloneStack *cloneStackMessage = (MessageCloneStack*) m;
|
| 138 | - handleCloneStackMessage(cloneStackMessage);
|
|
| 138 | + handleCloneStackMessage(cap, cloneStackMessage);
|
|
| 139 | 139 | }
|
| 140 | 140 | else
|
| 141 | 141 | {
|
| ... | ... | @@ -47,6 +47,7 @@ import CLOSURE stg_ret_v_info; |
| 47 | 47 | /* See Note [Original thunk info table frames] in GHC.StgToCmm.Bind. */
|
| 48 | 48 | INFO_TABLE_RET (stg_orig_thunk_info_frame, RET_SMALL,
|
| 49 | 49 | W_ info_ptr,
|
| 50 | + PROF_HDR_FIELDS(W_, p1, p2)
|
|
| 50 | 51 | W_ thunk_info_ptr)
|
| 51 | 52 | /* no args => explicit stack */
|
| 52 | 53 | {
|
| 1 | +{-# LANGUAGE BangPatterns #-}
|
|
| 2 | +-- Test that SLIDE works correctly when it crosses a stack chunk boundary.
|
|
| 3 | +-- See #27001.
|
|
| 4 | +module Main where
|
|
| 5 | + |
|
| 6 | +go :: Int -> Double -> Double
|
|
| 7 | +go 0 !acc = acc
|
|
| 8 | +go n !acc = go (n - 1) (acc + 1.0)
|
|
| 9 | + |
|
| 10 | +result :: Double
|
|
| 11 | +result = go 100000 0.0
|
|
| 12 | + |
|
| 13 | +main :: IO ()
|
|
| 14 | +main = print result |
| 1 | +(0.15 secs,)
|
|
| 2 | +it :: ()
|
|
| 3 | +100000.0
|
|
| 4 | +(0.09 secs, 51,566,104 bytes) |
| ... | ... | @@ -9,3 +9,8 @@ test('T25975', extra_ways(ghci_ways), compile_and_run, |
| 9 | 9 | # Nullary data constructors
|
| 10 | 10 | test('T26216', extra_files(["T26216_aux.hs"]), ghci_script, ['T26216.script'])
|
| 11 | 11 | |
| 12 | + |
|
| 13 | +# SLIDE across stack chunk boundary (#27001)
|
|
| 14 | +test('T27001', [extra_files(['T27001.hs']), req_interp],
|
|
| 15 | + run_command,
|
|
| 16 | + ['{compiler} -e main -O -fno-unoptimized-core-for-interpreter T27001.hs']) |
| ... | ... | @@ -6,8 +6,8 @@ Result size of Tidy Core |
| 6 | 6 | Rec {
|
| 7 | 7 | -- RHS size: {terms: 4, types: 3, coercions: 0, joins: 0/0}
|
| 8 | 8 | T13143.$wf [InlPrag=NOINLINE, Occ=LoopBreaker]
|
| 9 | - :: forall {a}. (# #) -> a
|
|
| 10 | -[GblId, Arity=1, Str=<B>b{sBo->S}, Cpr=b, Unf=OtherCon []]
|
|
| 9 | + :: forall a. (# #) -> a
|
|
| 10 | +[GblId, Arity=1, Str=<B>b{S}, Cpr=b, Unf=OtherCon []]
|
|
| 11 | 11 | T13143.$wf = \ (@a) _ [Occ=Dead] -> T13143.$wf @a GHC.Types.(##)
|
| 12 | 12 | end Rec }
|
| 13 | 13 | |
| ... | ... | @@ -15,7 +15,7 @@ end Rec } |
| 15 | 15 | f [InlPrag=NOINLINE[final]] :: forall a. Int -> a
|
| 16 | 16 | [GblId,
|
| 17 | 17 | Arity=1,
|
| 18 | - Str=<B>b{sBo->S},
|
|
| 18 | + Str=<B>b{S},
|
|
| 19 | 19 | Cpr=b,
|
| 20 | 20 | Unf=Unf{Src=StableSystem, TopLvl=True,
|
| 21 | 21 | Value=True, ConLike=True, WorkFree=True, Expandable=True,
|
| ... | ... | @@ -66,7 +66,7 @@ T13143.$trModule |
| 66 | 66 | |
| 67 | 67 | -- RHS size: {terms: 2, types: 1, coercions: 0, joins: 0/0}
|
| 68 | 68 | lvl :: Int
|
| 69 | -[GblId, Str=b{sBo->S}, Cpr=b]
|
|
| 69 | +[GblId, Str=b{S}, Cpr=b]
|
|
| 70 | 70 | lvl = T13143.$wf @Int GHC.Types.(##)
|
| 71 | 71 | |
| 72 | 72 | Rec {
|
| 1 | +module T27106 where
|
|
| 2 | + |
|
| 3 | +{-# NOINLINE weird #-}
|
|
| 4 | +weird :: Int -> a
|
|
| 5 | +weird x = weird x |
| 1 | +weird [InlPrag=NOINLINE[final]] :: forall a. Int -> a
|
|
| 2 | +[GblId,
|
|
| 3 | + Arity=1,
|
|
| 4 | + Str=<B>b{S}, |
| ... | ... | @@ -45,6 +45,13 @@ test('T13077a', normal, compile, ['']) |
| 45 | 45 | # T13143: WW for NOINLINE function f
|
| 46 | 46 | test('T13143', [ grep_errmsg(r'^T13143\.\$wf') ], compile, ['-ddump-simpl -dsuppress-uniques'])
|
| 47 | 47 | |
| 48 | +# Uniques in the free variable part of a demand signature should be
|
|
| 49 | +# suppressed by -dsuppress-uniques.
|
|
| 50 | +test('T27106', normal, multimod_compile_filter,
|
|
| 51 | + ['T27106',
|
|
| 52 | + '-v0 -O -ddump-simpl -dsuppress-uniques',
|
|
| 53 | + r"sed -n '/^weird /,/.* Str=/p'"])
|
|
| 54 | + |
|
| 48 | 55 | # T15627
|
| 49 | 56 | # Absent bindings of unlifted types should be WW'ed away.
|
| 50 | 57 | # The idea is to check that both $wmutVar and $warray
|
| ... | ... | @@ -583,6 +583,15 @@ test('cloneMyStack_retBigStackFrame', [req_c, extra_files(['cloneStackLib.c']), |
| 583 | 583 | |
| 584 | 584 | test('cloneThreadStack', [req_c, only_ways(['threaded1']), extra_ways(['threaded1']), extra_files(['cloneStackLib.c']), req_ghc_with_threaded_rts], compile_and_run, ['cloneStackLib.c -threaded'])
|
| 585 | 585 | |
| 586 | +test('cloneThreadStackMigrating',
|
|
| 587 | + [ ignore_stdout
|
|
| 588 | + , only_ways(['threaded1'])
|
|
| 589 | + , extra_ways(['threaded1'])
|
|
| 590 | + , extra_run_opts('+RTS -N -DS -RTS')
|
|
| 591 | + , req_ghc_with_threaded_rts
|
|
| 592 | + , req_target_smp
|
|
| 593 | + ], compile_and_run, ['-threaded -debug -rtsopts'])
|
|
| 594 | + |
|
| 586 | 595 | test('decodeMyStack',
|
| 587 | 596 | [ omit_ghci, js_broken(22261) # cloneMyStack# not yet implemented
|
| 588 | 597 | ], compile_and_run, ['-finfo-table-map'])
|
| 1 | +module Main where
|
|
| 2 | + |
|
| 3 | +import Control.Concurrent
|
|
| 4 | +import Control.Monad
|
|
| 5 | +import GHC.Exts.Stack
|
|
| 6 | +import GHC.Stack.CloneStack
|
|
| 7 | + |
|
| 8 | +numWorkers :: Int
|
|
| 9 | +numWorkers = 100
|
|
| 10 | + |
|
| 11 | +startN :: Int
|
|
| 12 | +startN = 10
|
|
| 13 | + |
|
| 14 | +runForMicros :: Int
|
|
| 15 | +runForMicros = 1000000
|
|
| 16 | + |
|
| 17 | +fib :: Int -> Int
|
|
| 18 | +fib 0 = 1
|
|
| 19 | +fib 1 = 1
|
|
| 20 | +fib n = fib (n - 1) + fib (n - 2)
|
|
| 21 | + |
|
| 22 | +workerThread :: Int -> IO ()
|
|
| 23 | +workerThread n = do
|
|
| 24 | + fib n `seq` pure ()
|
|
| 25 | + workerThread (n + 1)
|
|
| 26 | + |
|
| 27 | +cloneThread :: ThreadId -> IO ()
|
|
| 28 | +cloneThread tid = forever $ do
|
|
| 29 | + snapshot <- cloneThreadStack tid
|
|
| 30 | + stack <- decodeStack snapshot
|
|
| 31 | + stack `seq` pure ()
|
|
| 32 | + |
|
| 33 | +main :: IO ()
|
|
| 34 | +main = do
|
|
| 35 | + tids <- replicateM numWorkers (forkIO $ workerThread startN)
|
|
| 36 | + mapM_ (forkIO . cloneThread) tids
|
|
| 37 | + threadDelay runForMicros |