Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
5bd6a964
by Rodrigo Mesquita at 2026-04-30T04:54:08-04:00
-
ce97fd3e
by Rodrigo Mesquita at 2026-04-30T04:54:08-04:00
12 changed files:
- + changelog.d/T27131
- rts/Interpreter.c
- rts/Messages.c
- rts/StgMiscClosures.cmm
- rts/Threads.c
- rts/Threads.h
- rts/include/rts/storage/Closures.h
- rts/include/stg/MiscClosures.h
- + testsuite/tests/rts/T27131.hs
- + testsuite/tests/rts/T27131.stdout
- + testsuite/tests/rts/T27131_c.c
- testsuite/tests/rts/all.T
Changes:
| 1 | +section: rts
|
|
| 2 | +synopsis: Add rts Message to set/unset TSO flags
|
|
| 3 | +issues: #27131
|
|
| 4 | +mrs: !15831
|
|
| 5 | +description: This enables e.g. toggling breakpoints from different threads,
|
|
| 6 | + which is necessary to safely implement features like pausing, per-thread
|
|
| 7 | + step-in, and more in the haskell debugger.
|
|
| 8 | + |
| ... | ... | @@ -416,12 +416,22 @@ void rts_disableStopNextBreakpointAll(void) |
| 416 | 416 | |
| 417 | 417 | void rts_enableStopNextBreakpoint(StgTSO* tso)
|
| 418 | 418 | {
|
| 419 | - tso->flags |= TSO_STOP_NEXT_BREAKPOINT;
|
|
| 419 | +#if defined(THREADED_RTS)
|
|
| 420 | + Capability* cap = rts_unsafeGetMyCapability();
|
|
| 421 | + setThreadFlag(cap, tso, TSO_STOP_NEXT_BREAKPOINT);
|
|
| 422 | +#else
|
|
| 423 | + tso->flags |= TSO_STOP_NEXT_BREAKPOINT;
|
|
| 424 | +#endif
|
|
| 420 | 425 | }
|
| 421 | 426 | |
| 422 | 427 | void rts_disableStopNextBreakpoint(StgTSO* tso)
|
| 423 | 428 | {
|
| 424 | - tso->flags &= ~TSO_STOP_NEXT_BREAKPOINT;
|
|
| 429 | +#if defined(THREADED_RTS)
|
|
| 430 | + Capability* cap = rts_unsafeGetMyCapability();
|
|
| 431 | + unsetThreadFlag(cap, tso, TSO_STOP_NEXT_BREAKPOINT);
|
|
| 432 | +#else
|
|
| 433 | + tso->flags &= ~TSO_STOP_NEXT_BREAKPOINT;
|
|
| 434 | +#endif
|
|
| 425 | 435 | }
|
| 426 | 436 | |
| 427 | 437 | /* ---------------------------------------------------------------------------
|
| ... | ... | @@ -430,12 +440,22 @@ void rts_disableStopNextBreakpoint(StgTSO* tso) |
| 430 | 440 | |
| 431 | 441 | void rts_enableStopAfterReturn(StgTSO* tso)
|
| 432 | 442 | {
|
| 443 | +#if defined(THREADED_RTS)
|
|
| 444 | + Capability* cap = rts_unsafeGetMyCapability();
|
|
| 445 | + setThreadFlag(cap, tso, TSO_STOP_AFTER_RETURN);
|
|
| 446 | +#else
|
|
| 433 | 447 | tso->flags |= TSO_STOP_AFTER_RETURN;
|
| 448 | +#endif
|
|
| 434 | 449 | }
|
| 435 | 450 | |
| 436 | 451 | void rts_disableStopAfterReturn(StgTSO* tso)
|
| 437 | 452 | {
|
| 453 | +#if defined(THREADED_RTS)
|
|
| 454 | + Capability* cap = rts_unsafeGetMyCapability();
|
|
| 455 | + unsetThreadFlag(cap, tso, TSO_STOP_AFTER_RETURN);
|
|
| 456 | +#else
|
|
| 438 | 457 | tso->flags &= ~TSO_STOP_AFTER_RETURN;
|
| 458 | +#endif
|
|
| 439 | 459 | }
|
| 440 | 460 | |
| 441 | 461 | /*
|
| ... | ... | @@ -35,7 +35,9 @@ void sendMessage(Capability *from_cap, Capability *to_cap, Message *msg) |
| 35 | 35 | i != &stg_MSG_TRY_WAKEUP_info &&
|
| 36 | 36 | i != &stg_IND_info && // can happen if a MSG_BLACKHOLE is revoked
|
| 37 | 37 | i != &stg_WHITEHOLE_info &&
|
| 38 | - i != &stg_MSG_CLONE_STACK_info) {
|
|
| 38 | + i != &stg_MSG_CLONE_STACK_info &&
|
|
| 39 | + i != &stg_MSG_SET_TSO_FLAG_info &&
|
|
| 40 | + i != &stg_MSG_UNSET_TSO_FLAG_info) {
|
|
| 39 | 41 | barf("sendMessage: %p", i);
|
| 40 | 42 | }
|
| 41 | 43 | }
|
| ... | ... | @@ -137,6 +139,16 @@ loop: |
| 137 | 139 | MessageCloneStack *cloneStackMessage = (MessageCloneStack*) m;
|
| 138 | 140 | handleCloneStackMessage(cap, cloneStackMessage);
|
| 139 | 141 | }
|
| 142 | + else if(i == &stg_MSG_SET_TSO_FLAG_info){
|
|
| 143 | + MessageUpdTSOFlag *u = (MessageUpdTSOFlag*) m;
|
|
| 144 | + u->tso->flags |= u->flag;
|
|
| 145 | + return;
|
|
| 146 | + }
|
|
| 147 | + else if(i == &stg_MSG_UNSET_TSO_FLAG_info){
|
|
| 148 | + MessageUpdTSOFlag *u = (MessageUpdTSOFlag*) m;
|
|
| 149 | + u->tso->flags &= ~u->flag;
|
|
| 150 | + return;
|
|
| 151 | + }
|
|
| 140 | 152 | else
|
| 141 | 153 | {
|
| 142 | 154 | barf("executeMessage: %p", i);
|
| ... | ... | @@ -855,6 +855,12 @@ INFO_TABLE_CONSTR(stg_MSG_NULL,1,0,0,PRIM,"MSG_NULL","MSG_NULL") |
| 855 | 855 | INFO_TABLE_CONSTR(stg_MSG_CLONE_STACK,3,0,0,PRIM,"MSG_CLONE_STACK","MSG_CLONE_STACK")
|
| 856 | 856 | { ccall pbarf("stg_MSG_CLONE_STACK object (%p) entered!", R1 "ptr") never returns; }
|
| 857 | 857 | |
| 858 | +INFO_TABLE_CONSTR(stg_MSG_SET_TSO_FLAG,2,1,0,PRIM,"MSG_SET_TSO_FLAG","MSG_SET_TSO_FLAG")
|
|
| 859 | +{ foreign "C" barf("stg_MSG_SET_TSO_FLAG object (%p) entered!", R1) never returns; }
|
|
| 860 | + |
|
| 861 | +INFO_TABLE_CONSTR(stg_MSG_UNSET_TSO_FLAG,2,1,0,PRIM,"MSG_UNSET_TSO_FLAG","MSG_UNSET_TSO_FLAG")
|
|
| 862 | +{ foreign "C" barf("stg_MSG_UNSET_TSO_FLAG object (%p) entered!", R1) never returns; }
|
|
| 863 | + |
|
| 858 | 864 | /* ----------------------------------------------------------------------------
|
| 859 | 865 | END_TSO_QUEUE
|
| 860 | 866 |
| ... | ... | @@ -376,6 +376,38 @@ migrateThread (Capability *from, StgTSO *tso, Capability *to) |
| 376 | 376 | tryWakeupThread(from, tso);
|
| 377 | 377 | }
|
| 378 | 378 | |
| 379 | +/* ----------------------------------------------------------------------------
|
|
| 380 | + {set,unset}ThreadFlag
|
|
| 381 | + |
|
| 382 | + sets or unsets a flag in a given TSO
|
|
| 383 | + ------------------------------------------------------------------------- */
|
|
| 384 | + |
|
| 385 | +#if defined(THREADED_RTS)
|
|
| 386 | +static void
|
|
| 387 | +updThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag, const StgInfoTable* info);
|
|
| 388 | + |
|
| 389 | +void setThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag)
|
|
| 390 | +{
|
|
| 391 | + updThreadFlag(from, tso, flag, &stg_MSG_SET_TSO_FLAG_info);
|
|
| 392 | +}
|
|
| 393 | + |
|
| 394 | +void unsetThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag)
|
|
| 395 | +{
|
|
| 396 | + updThreadFlag(from, tso, flag, &stg_MSG_UNSET_TSO_FLAG_info);
|
|
| 397 | +}
|
|
| 398 | + |
|
| 399 | +static void
|
|
| 400 | +updThreadFlag(Capability *from, StgTSO *tso, StgWord32 flag, const StgInfoTable* info)
|
|
| 401 | +{
|
|
| 402 | + MessageUpdTSOFlag *msg;
|
|
| 403 | + msg = (MessageUpdTSOFlag *)allocate(from,sizeofW(MessageUpdTSOFlag));
|
|
| 404 | + msg->tso = tso;
|
|
| 405 | + msg->flag = flag;
|
|
| 406 | + SET_HDR_RELEASE(msg, info, CCS_SYSTEM);
|
|
| 407 | + sendMessage(from, tso->cap, (Message*)msg);
|
|
| 408 | +}
|
|
| 409 | +#endif
|
|
| 410 | + |
|
| 379 | 411 | /* ----------------------------------------------------------------------------
|
| 380 | 412 | awakenBlockedQueue
|
| 381 | 413 |
| ... | ... | @@ -19,6 +19,11 @@ void checkBlockingQueues (Capability *cap, StgTSO *tso); |
| 19 | 19 | void tryWakeupThread (Capability *cap, StgTSO *tso);
|
| 20 | 20 | void migrateThread (Capability *from, StgTSO *tso, Capability *to);
|
| 21 | 21 | |
| 22 | +#if defined(THREADED_RTS)
|
|
| 23 | +void setThreadFlag (Capability *from, StgTSO *tso, StgWord32 flag);
|
|
| 24 | +void unsetThreadFlag (Capability *from, StgTSO *tso, StgWord32 flag);
|
|
| 25 | +#endif
|
|
| 26 | + |
|
| 22 | 27 | // Wakes up a thread on a Capability (probably a different Capability
|
| 23 | 28 | // from the one held by the current Task).
|
| 24 | 29 | //
|
| ... | ... | @@ -620,6 +620,12 @@ typedef struct MessageCloneStack_ { |
| 620 | 620 | StgTSO *tso;
|
| 621 | 621 | } MessageCloneStack;
|
| 622 | 622 | |
| 623 | +typedef struct MessageUpdTSOFlag_ {
|
|
| 624 | + StgHeader header;
|
|
| 625 | + Message *link;
|
|
| 626 | + StgTSO *tso;
|
|
| 627 | + StgWord flag;
|
|
| 628 | +} MessageUpdTSOFlag;
|
|
| 623 | 629 | |
| 624 | 630 | /* ----------------------------------------------------------------------------
|
| 625 | 631 | Compact Regions
|
| ... | ... | @@ -152,6 +152,8 @@ RTS_ENTRY(stg_MSG_TRY_WAKEUP); |
| 152 | 152 | RTS_ENTRY(stg_MSG_THROWTO);
|
| 153 | 153 | RTS_ENTRY(stg_MSG_BLACKHOLE);
|
| 154 | 154 | RTS_ENTRY(stg_MSG_CLONE_STACK);
|
| 155 | +RTS_ENTRY(stg_MSG_SET_TSO_FLAG);
|
|
| 156 | +RTS_ENTRY(stg_MSG_UNSET_TSO_FLAG);
|
|
| 155 | 157 | RTS_ENTRY(stg_MSG_NULL);
|
| 156 | 158 | RTS_ENTRY(stg_MVAR_TSO_QUEUE);
|
| 157 | 159 | RTS_ENTRY(stg_catch);
|
| 1 | +{-# LANGUAGE MagicHash #-}
|
|
| 2 | +{-# LANGUAGE UnliftedFFITypes #-}
|
|
| 3 | + |
|
| 4 | +module Main where
|
|
| 5 | + |
|
| 6 | +import Control.Concurrent
|
|
| 7 | +import Control.Monad
|
|
| 8 | +import Foreign.C.Types
|
|
| 9 | +import GHC.Conc.Sync (ThreadId(..), forkOn, myThreadId, setNumCapabilities)
|
|
| 10 | +import GHC.Exts (ThreadId#)
|
|
| 11 | + |
|
| 12 | +foreign import ccall unsafe "rts_enableStopNextBreakpoint"
|
|
| 13 | + rts_enableStopNextBreakpoint :: ThreadId# -> IO ()
|
|
| 14 | + |
|
| 15 | +foreign import ccall unsafe "rts_disableStopNextBreakpoint"
|
|
| 16 | + rts_disableStopNextBreakpoint :: ThreadId# -> IO ()
|
|
| 17 | + |
|
| 18 | +foreign import ccall unsafe "rts_enableStopAfterReturn"
|
|
| 19 | + rts_enableStopAfterReturn :: ThreadId# -> IO ()
|
|
| 20 | + |
|
| 21 | +foreign import ccall unsafe "rts_disableStopAfterReturn"
|
|
| 22 | + rts_disableStopAfterReturn :: ThreadId# -> IO ()
|
|
| 23 | + |
|
| 24 | +foreign import ccall unsafe "has_local_stop_next_breakpoint"
|
|
| 25 | + c_hasLocalStopNextBreakpoint :: IO CInt
|
|
| 26 | + |
|
| 27 | +foreign import ccall unsafe "has_local_stop_after_return"
|
|
| 28 | + c_hasLocalStopAfterReturn :: IO CInt
|
|
| 29 | + |
|
| 30 | +main :: IO ()
|
|
| 31 | +main = do
|
|
| 32 | + setNumCapabilities 2
|
|
| 33 | + checkFlag
|
|
| 34 | + "TSO_STOP_NEXT_BREAKPOINT"
|
|
| 35 | + rts_enableStopNextBreakpoint
|
|
| 36 | + rts_disableStopNextBreakpoint
|
|
| 37 | + c_hasLocalStopNextBreakpoint
|
|
| 38 | + checkFlag
|
|
| 39 | + "TSO_STOP_AFTER_RETURN"
|
|
| 40 | + rts_enableStopAfterReturn
|
|
| 41 | + rts_disableStopAfterReturn
|
|
| 42 | + c_hasLocalStopAfterReturn
|
|
| 43 | + |
|
| 44 | +checkFlag
|
|
| 45 | + :: String
|
|
| 46 | + -> (ThreadId# -> IO ())
|
|
| 47 | + -> (ThreadId# -> IO ())
|
|
| 48 | + -> IO CInt
|
|
| 49 | + -> IO ()
|
|
| 50 | +checkFlag label enable disable isMyThreadFlagSet = do
|
|
| 51 | + -- Print the main thread's capability (should be 0)
|
|
| 52 | + print =<< threadCapability =<< myThreadId
|
|
| 53 | + |
|
| 54 | + -- Target thread will write its own flag value here
|
|
| 55 | + targetCheckVar <- newEmptyMVar
|
|
| 56 | + |
|
| 57 | + -- Run the new TSO runs on capability 1
|
|
| 58 | + ThreadId tid# <- forkOn 1 $ do
|
|
| 59 | + replicateM_ 2 $ do
|
|
| 60 | + replyVar <- takeMVar targetCheckVar
|
|
| 61 | + isSet <- (/= 0) <$> isMyThreadFlagSet
|
|
| 62 | + putMVar replyVar isSet
|
|
| 63 | + |
|
| 64 | + -- Enable the other TSO's flag
|
|
| 65 | + enable tid#
|
|
| 66 | + -- It will check whether it is set and reply here
|
|
| 67 | + renderCheck label "set" =<< checkTarget targetCheckVar
|
|
| 68 | + |
|
| 69 | + -- Ditto.
|
|
| 70 | + disable tid#
|
|
| 71 | + renderCheck label "unset" . not =<< checkTarget targetCheckVar
|
|
| 72 | + |
|
| 73 | +checkTarget :: MVar (MVar Bool) -> IO Bool
|
|
| 74 | +checkTarget targetCheckVar = do
|
|
| 75 | + replyVar <- newEmptyMVar
|
|
| 76 | + putMVar targetCheckVar replyVar
|
|
| 77 | + takeMVar replyVar
|
|
| 78 | + |
|
| 79 | +renderCheck :: String -> String -> Bool -> IO ()
|
|
| 80 | +renderCheck label state ok = putStrLn $
|
|
| 81 | + label ++ " " ++ state ++ ": " ++ if ok then "ok" else "failed" |
| 1 | +(0,False)
|
|
| 2 | +TSO_STOP_NEXT_BREAKPOINT set: ok
|
|
| 3 | +TSO_STOP_NEXT_BREAKPOINT unset: ok
|
|
| 4 | +(0,False)
|
|
| 5 | +TSO_STOP_AFTER_RETURN set: ok
|
|
| 6 | +TSO_STOP_AFTER_RETURN unset: ok |
| 1 | +#include "Rts.h"
|
|
| 2 | + |
|
| 3 | +int has_local_stop_next_breakpoint(void)
|
|
| 4 | +{
|
|
| 5 | + CapabilityPublic *cap = (CapabilityPublic *) rts_unsafeGetMyCapability();
|
|
| 6 | + StgTSO *tso = cap->r.rCurrentTSO;
|
|
| 7 | + return (tso->flags & TSO_STOP_NEXT_BREAKPOINT) != 0;
|
|
| 8 | +}
|
|
| 9 | + |
|
| 10 | +int has_local_stop_after_return(void)
|
|
| 11 | +{
|
|
| 12 | + CapabilityPublic *cap = (CapabilityPublic *) rts_unsafeGetMyCapability();
|
|
| 13 | + StgTSO *tso = cap->r.rCurrentTSO;
|
|
| 14 | + return (tso->flags & TSO_STOP_AFTER_RETURN) != 0;
|
|
| 15 | +} |
| ... | ... | @@ -623,6 +623,13 @@ test('T20201b', [js_skip, exit_code(1)], compile_and_run, ['-with-rtsopts -A64z' |
| 623 | 623 | |
| 624 | 624 | test('T22012', [js_skip, extra_ways(['ghci'])], compile_and_run, ['T22012_c.c'])
|
| 625 | 625 | |
| 626 | +test('T27131',
|
|
| 627 | + [ only_ways(['threaded1', 'threaded2'])
|
|
| 628 | + , req_ghc_with_threaded_rts
|
|
| 629 | + , req_target_smp
|
|
| 630 | + ],
|
|
| 631 | + compile_and_run, ['T27131_c.c'])
|
|
| 632 | + |
|
| 626 | 633 | # Skip for JS platform as the JS RTS is always single threaded
|
| 627 | 634 | test('T22795a', [only_ways(['normal']), js_skip, req_ghc_with_threaded_rts], compile_and_run, ['-threaded'])
|
| 628 | 635 | test('T22795b', [only_ways(['normal']), js_skip], compile_and_run, ['-single-threaded'])
|