Duncan Coutts pushed to branch wip/dcoutts/io-manager-io-primop-exceptions at Glasgow Haskell Compiler / GHC

Commits:

24 changed files:

Changes:

  • changelog.d/fix-parallel-gc-selector-race-27477
    1
    +section: rts
    
    2
    +synopsis: Fix a parallel GC race on weakly-ordered architectures (AArch64) that could crash or silently corrupt the heap
    
    3
    +description:
    
    4
    +  On weakly-ordered architectures such as AArch64 programs using
    
    5
    +  could crash during parallel garbage collection (segmentation fault
    
    6
    +  or ``internal error: eval_thunk_selector: strange selectee <n>``) or
    
    7
    +  continue running with silently corrupted heap data, due to a missing
    
    8
    +  memory barrier in the parallel GC.
    
    9
    +mrs: !16488
    
    10
    +issues: #27477

  • compiler/GHC/Builtin/primops.txt.pp
    ... ... @@ -3305,21 +3305,28 @@ section "Delay/wait operations"
    3305 3305
     
    
    3306 3306
     primop  DelayOp "delay#" GenPrimOp
    
    3307 3307
        Int# -> State# s -> State# s
    
    3308
    -   {Sleep specified number of microseconds.}
    
    3308
    +   {Suspend the calling thread for a specified number of microseconds. This
    
    3309
    +    operation is /interruptible/ by async exceptions.}
    
    3309 3310
        with
    
    3310 3311
        effect = ReadWriteEffect
    
    3311 3312
        out_of_line      = True
    
    3312 3313
     
    
    3313 3314
     primop  WaitReadOp "waitRead#" GenPrimOp
    
    3314 3315
        Int# -> State# s -> State# s
    
    3315
    -   {Block until input is available on specified file descriptor.}
    
    3316
    +   {Suspend the calling thread until input is available (or an I/O error
    
    3317
    +    occurs) on the given file descriptor. This operation can throw exceptions
    
    3318
    +    of type 'IOException'. The operation is also /interruptible/ by async
    
    3319
    +    exceptions.}
    
    3316 3320
        with
    
    3317 3321
        effect = ReadWriteEffect
    
    3318 3322
        out_of_line      = True
    
    3319 3323
     
    
    3320 3324
     primop  WaitWriteOp "waitWrite#" GenPrimOp
    
    3321 3325
        Int# -> State# s -> State# s
    
    3322
    -   {Block until output is possible on specified file descriptor.}
    
    3326
    +   {Suspend the calling thread until output is possible (or an I/O error
    
    3327
    +    occurs) on the given file descriptor. This operation can throw exceptions
    
    3328
    +    of type 'IOException'. The operation is also /interruptible/ by async
    
    3329
    +    exceptions.}
    
    3323 3330
        with
    
    3324 3331
        effect = ReadWriteEffect
    
    3325 3332
        out_of_line      = True
    

  • compiler/GHC/Hs/Doc.hs
    ... ... @@ -58,6 +58,7 @@ import Language.Haskell.Syntax.Doc
    58 58
     import Language.Haskell.Syntax.Extension
    
    59 59
     import Language.Haskell.Syntax.Module.Name
    
    60 60
     
    
    61
    +-- Exact print annotations ignore LHsDoc, so bare SrcSpan is fine
    
    61 62
     type instance Anno (WithHsDocIdentifiers (HsDocString (GhcPass pass)) (GhcPass pass)) = SrcSpan
    
    62 63
     
    
    63 64
     deriving instance (Data pass, Data (LIdP pass), Data a) => Data (WithHsDocIdentifiers a pass)
    

  • compiler/GHC/Hs/Extension/Pass.hs
    ... ... @@ -46,6 +46,7 @@ type instance XRec (GhcPass p) a = XRecGhc a
    46 46
     -- but pass-independent, source location
    
    47 47
     type XRecGhc a = GenLocated (Anno a) a
    
    48 48
     
    
    49
    +-- Exact print annotations ignore LHsDoc, so bare SrcSpan is fine
    
    49 50
     type instance Anno (HsDocString (GhcPass _))  = SrcSpan
    
    50 51
     type instance Anno HsDocStringChunk           = SrcSpan
    
    51 52
     
    

  • libraries/ghc-internal/include/RtsIfaceSymbols.h
    ... ... @@ -23,15 +23,11 @@ CLOSURE(GHCziInternalziIOziException, cannotCompactMutable_closure)
    23 23
     CLOSURE(GHCziInternalziControlziExceptionziBase, nonTermination_closure)
    
    24 24
     CLOSURE(GHCziInternalziControlziExceptionziBase, nestedAtomically_closure)
    
    25 25
     CLOSURE(GHCziInternalziControlziExceptionziBase, noMatchingContinuationPrompt_closure)
    
    26
    -#if defined(mingw32_HOST_OS)
    
    27
    -UNDEF_CLOSURE(GHCziInternalziEventziThread, blockedOnBadFD_closure)
    
    28
    -#else
    
    29
    -CLOSURE(GHCziInternalziEventziThread, blockedOnBadFD_closure)
    
    30
    -#endif
    
    31 26
     CLOSURE(GHCziInternalziConcziSync, runSparks_closure)
    
    32 27
     CLOSURE(GHCziInternalziConcziIO, ensureIOManagerIsRunning_closure)
    
    33 28
     CLOSURE(GHCziInternalziConcziIO, interruptIOManager_closure)
    
    34 29
     CLOSURE(GHCziInternalziConcziIO, ioManagerCapabilitiesChanged_closure)
    
    30
    +CLOSURE(GHCziInternalziConcziIO, raisePrimIOException_info)
    
    35 31
     CLOSURE(GHCziInternalziConcziSignal, runHandlersPtr_closure)
    
    36 32
     CLOSURE(GHCziInternalziTopHandler, flushStdHandles_closure)
    
    37 33
     CLOSURE(GHCziInternalziTopHandler, runMainIO_closure)
    

  • libraries/ghc-internal/src/GHC/Internal/Conc/IO.hs
    ... ... @@ -56,13 +56,19 @@ module GHC.Internal.Conc.IO
    56 56
             , win32ConsoleHandler
    
    57 57
             , toWin32ConsoleEvent
    
    58 58
     #endif
    
    59
    +        , raisePrimIOException -- exported for use within RTS
    
    59 60
             ) where
    
    60 61
     
    
    61 62
     import GHC.Internal.Base
    
    62 63
     import GHC.Internal.Conc.Sync as Sync
    
    63 64
     import GHC.Internal.Err (errorWithoutStackTrace)
    
    65
    +import GHC.Internal.Exception (SomeException, Exception(toException))
    
    66
    +import qualified GHC.Internal.Foreign.C.Error as C
    
    67
    +import GHC.Internal.Foreign.C.Types (CInt)
    
    68
    +import GHC.Internal.Maybe (Maybe(Nothing))
    
    64 69
     import GHC.Internal.STM as STM
    
    65
    -import GHC.Internal.Prim (delay#, waitRead#, waitWrite#)
    
    70
    +import GHC.Internal.Prim (Int#, State#, RealWorld,
    
    71
    +                          delay#, waitRead#, waitWrite#, raiseIO#)
    
    66 72
     import GHC.Internal.Real ( fromIntegral )
    
    67 73
     import GHC.Internal.System.Posix.Types
    
    68 74
     
    
    ... ... @@ -249,3 +255,39 @@ registerDelay _usecs
    249 255
     #if !defined(javascript_HOST_ARCH)
    
    250 256
     foreign import ccall unsafe "rtsSupportsBoundThreads" threaded :: Bool
    
    251 257
     #endif
    
    258
    +
    
    259
    +
    
    260
    +-- ---------------------------------------------------------------------------
    
    261
    +-- PrimIOException
    
    262
    +
    
    263
    +-- FIXME: raisePrimIOException throws an IOException, which is not ideal. It is
    
    264
    +-- not in a position to fill in most of the information for an IOException.
    
    265
    +-- Much better would be to have a new PrimIOException exception type, and for
    
    266
    +-- I/O primops to catch that and rethrow with the proper context. In particular
    
    267
    +-- we do not have a proper location, since that's fundamantally something from
    
    268
    +-- a higher level library.
    
    269
    +--
    
    270
    +-- Unfortunately, catching and throwing converts async exceptions to sync ones.
    
    271
    +-- This breaks any thunks that, via unsafePerformIO, use such operations. We
    
    272
    +-- have tests that check for this, e.g. T26341a T26341b. Catching and rethrowing
    
    273
    +-- here would break those tests.
    
    274
    +--
    
    275
    +-- See issue #2558, #24189 and #26368.
    
    276
    +
    
    277
    +-- | Internal helper funtion used by the in-RTS I\/O managers for reporting
    
    278
    +-- exceptions. Do not use directly.
    
    279
    +--
    
    280
    +-- Do not change the type signature \/ ABI without updating the Cmm callers!
    
    281
    +-- There is no signature checking for Cmm to Hs calls.
    
    282
    +--
    
    283
    +raisePrimIOException :: Int# -> State# RealWorld -> (# State# RealWorld, a #)
    
    284
    +raisePrimIOException errno# s =
    
    285
    +    let exception :: SomeException
    
    286
    +        exception = toException
    
    287
    +                      (C.errnoToIOError "threadWaitRead/Write"
    
    288
    +                                        (toCErrno errno#)
    
    289
    +                                        Nothing Nothing)
    
    290
    +     in raiseIO# exception s
    
    291
    +  where
    
    292
    +    toCErrno :: Int# -> C.Errno
    
    293
    +    toCErrno e = C.Errno ((fromIntegral :: Int -> CInt) (I# e))

  • libraries/ghc-internal/src/GHC/Internal/Event/Thread.hs
    ... ... @@ -23,14 +23,13 @@ import GHC.Internal.Types ()
    23 23
         , closeFdWith
    
    24 24
         , threadDelay
    
    25 25
         , registerDelay
    
    26
    -    , blockedOnBadFD -- used by RTS
    
    27 26
         ) where
    
    28 27
     
    
    29 28
     
    
    30 29
     -- TODO: Use new Windows I/O manager
    
    31 30
     import qualified GHC.Internal.Stack.Types as Rebindable
    
    32 31
     import GHC.Internal.Base
    
    33
    -import GHC.Internal.Control.Exception (finally, SomeException, toException)
    
    32
    +import GHC.Internal.Control.Exception (finally)
    
    34 33
     import GHC.Internal.Data.Foldable (forM_, mapM_, sequence_)
    
    35 34
     import GHC.Internal.Data.IORef (IORef, newIORef, readIORef, writeIORef, atomicWriteIORef)
    
    36 35
     import GHC.Internal.Data.Maybe (fromMaybe)
    
    ... ... @@ -185,10 +184,6 @@ threadWait evt fd = mask_ $ do
    185 184
         then ioError $ errnoToIOError "threadWait" eBADF Nothing Nothing
    
    186 185
         else return ()
    
    187 186
     
    
    188
    --- used at least by RTS in 'select()' IO manager backend
    
    189
    -blockedOnBadFD :: SomeException
    
    190
    -blockedOnBadFD = toException $ errnoToIOError "awaitEvent" eBADF Nothing Nothing
    
    191
    -
    
    192 187
     threadWaitSTM :: Event -> Fd -> IO (STM (), IO ())
    
    193 188
     threadWaitSTM evt fd = mask_ $ do
    
    194 189
       m <- newTVarIO Nothing
    

  • rts/HeapStackCheck.cmm
    ... ... @@ -20,6 +20,7 @@ import ReleaseSRWLockExclusives;
    20 20
     #if !defined(UnregisterisedCompiler)
    
    21 21
     import CLOSURE g0;
    
    22 22
     import CLOSURE large_alloc_lim;
    
    23
    +import CLOSURE ghc_hs_iface;
    
    23 24
     import CLOSURE stg_MSG_THROWTO_info;
    
    24 25
     import CLOSURE stg_MVAR_DIRTY_info;
    
    25 26
     import CLOSURE stg_WHITEHOLE_info;
    
    ... ... @@ -40,6 +41,8 @@ import CLOSURE stg_ret_l_info;
    40 41
     import CLOSURE stg_ret_n_info;
    
    41 42
     import CLOSURE stg_ret_p_info;
    
    42 43
     import CLOSURE stg_stack_save_entries;
    
    44
    +import CLOSURE stg_block_io_unit_info;
    
    45
    +import CLOSURE stg_block_io_int_info;
    
    43 46
     #endif
    
    44 47
     
    
    45 48
     /* Stack/Heap Check Failure
    
    ... ... @@ -694,6 +697,37 @@ stg_block_putmvar (P_ mvar, P_ val)
    694 697
        }
    
    695 698
     }
    
    696 699
     
    
    700
    +
    
    701
    +/* -----------------------------------------------------------------------------
    
    702
    +   STM-specific waiting
    
    703
    +   -------------------------------------------------------------------------- */
    
    704
    +
    
    705
    +stg_block_stmwait
    
    706
    +{
    
    707
    +    // When blocking on an MVar we have to be careful to only release
    
    708
    +    // the lock on the MVar at the very last moment (using
    
    709
    +    // BLOCK_BUT_FIRST()), since when we release the lock another
    
    710
    +    // Capability can wake up the thread, which modifies its stack and
    
    711
    +    // other state.  This is not a problem for STM, because STM
    
    712
    +    // wakeups are non-destructive; the waker simply calls
    
    713
    +    // tryWakeupThread() which sends a message to the owner
    
    714
    +    // Capability.  So the moment we release this lock we might start
    
    715
    +    // getting wakeup messages, but that's perfectly harmless.
    
    716
    +    //
    
    717
    +    // Furthermore, we *must* release these locks, just in case an
    
    718
    +    // exception is raised in this thread by
    
    719
    +    // maybePerformBlockedException() while exiting to the scheduler,
    
    720
    +    // which will abort the transaction, which needs to obtain a lock
    
    721
    +    // on all the TVars to remove the thread from the queues.
    
    722
    +    //
    
    723
    +    ccall stmWaitUnlock(MyCapability() "ptr", R3 "ptr");
    
    724
    +    BLOCK_GENERIC;
    
    725
    +}
    
    726
    +
    
    727
    +/* -----------------------------------------------------------------------------
    
    728
    +   Blocks for blackholes and throwto
    
    729
    +   -------------------------------------------------------------------------- */
    
    730
    +
    
    697 731
     stg_block_blackhole (P_ node)
    
    698 732
     {
    
    699 733
         Sp_adj(-2);
    
    ... ... @@ -730,6 +764,13 @@ stg_block_throwto (P_ tso, P_ exception)
    730 764
         }
    
    731 765
     }
    
    732 766
     
    
    767
    +/* -----------------------------------------------------------------------------
    
    768
    +   Thread blocking for I/O primops
    
    769
    +   -------------------------------------------------------------------------- */
    
    770
    +
    
    771
    +/* Thread blocking for primops used only by the win32-legacy I/O manager:
    
    772
    + * asyncRead, asyncWrite and asyncDoProc.
    
    773
    + */
    
    733 774
     #if defined(mingw32_HOST_OS)
    
    734 775
     INFO_TABLE_RET ( stg_block_async, RET_SMALL, W_ info_ptr, W_ len, W_ errCode )
    
    735 776
         return ()
    
    ... ... @@ -751,32 +792,171 @@ stg_block_async
    751 792
         Sp(2) = eintr;                 // errCode: interrupted
    
    752 793
         BLOCK_GENERIC;
    
    753 794
     }
    
    754
    -
    
    755 795
     #endif
    
    756 796
     
    
    797
    +/* Note [Thread blocking for new I/O primops]
    
    798
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    757 799
     
    
    758
    -/* -----------------------------------------------------------------------------
    
    759
    -   STM-specific waiting
    
    760
    -   -------------------------------------------------------------------------- */
    
    800
    +The classic I/O primops are:
    
    761 801
     
    
    762
    -stg_block_stmwait
    
    802
    +  waitRead#, waitWrite# :: Int# -> State# s -> State# s
    
    803
    +
    
    804
    +Note that they have no result, with wrapper types returning IO (). Each one
    
    805
    +simply suspends the calling thread until a condition occurs (I/O or time).
    
    806
    +
    
    807
    +Despite this external simplicity, internally there is more complexity with a
    
    808
    +number of cases to handle:
    
    809
    +
    
    810
    +At submission time:
    
    811
    + - synchronous failure, e.g. bad fd
    
    812
    + - synchronous success, e.g. waiting on a regular file, which is always ready
    
    813
    + - proceed asynchronously: block thread, return to scheduler
    
    814
    +
    
    815
    +Later, at I/O completion, I/O error, or async exception time:
    
    816
    + - reported failure, e.g. stale fd
    
    817
    + - success
    
    818
    +
    
    819
    +The waitRead/Write# also have non-reported failures, such as I/O errors,
    
    820
    +but these are treated as the success case since these primops are defined to
    
    821
    +wait for I/O or an error. We only report failures, via exceptions, for cases
    
    822
    +where it would be unsafe to proceed with a subsequent I/O operation on the
    
    823
    +same fd. In particular this includes when the fd was closed while a thread was
    
    824
    +waiting on the fd (since it's very unsafe to read/write an fd that is closed or
    
    825
    +worse: has been recycled to refer to a different file).
    
    826
    +
    
    827
    +The synchronous success and failure cases are handled by the initiating
    
    828
    +primops. In the blocking case, it relies on one of these stg_block_ functions,
    
    829
    +stack frames, and stack frame info table entry code. The key bits of the
    
    830
    +mechanism are:
    
    831
    +
    
    832
    + - The stg_block_ cmm function sets up the stack frame for the blocked thread.
    
    833
    + - The stack frame entries can be overwritten by the I/O manager, e.g. upon I/O
    
    834
    +   completion or error.
    
    835
    + - The stack frame's info table entry code is invoked when the thread is
    
    836
    +   resumed.
    
    837
    + - The entries in the stack frame become arguments to the info table entry code.
    
    838
    + - The result(s) returned by the info table entry code _are_ the result(s) of
    
    839
    +   the I/O primop.
    
    840
    +
    
    841
    +It is important to keep clear the distinction between the stack frame entries
    
    842
    +and the final primop result(s): the stack frame's info table entry code
    
    843
    +transforms the former into the latter.
    
    844
    +
    
    845
    +The convention we follow is that the stack frame contains two fields:
    
    846
    + - an outcome, using IOOpOutcome values: in-flight, success, failed, cancelled.
    
    847
    + - a result or error code. For outcome success it is a result. For failed it is
    
    848
    +   the error code (using posix errno codes), while for cancelled and in-flight
    
    849
    +   outcomes there is no result.
    
    850
    +
    
    851
    +This outcome+result/failure encoding is exactly the same as the corresponding
    
    852
    +fields in the StgAsyncIOOp. One day we might just use the StgAsyncIOOp itself,
    
    853
    +once all I/O managers use them.
    
    854
    +
    
    855
    +The waitRead/waitWrite# primops do not use a result, but later I/O primops will
    
    856
    +do. In particular read/write on files return a length, and other I/O primitives
    
    857
    +also have a single result.
    
    858
    +
    
    859
    +So this gives us two block functions and corresponding info tables:
    
    860
    +
    
    861
    + 1. stg_block_io_unit: with a stack frame containing outcome+result/failure.
    
    862
    +    On resume, throw exception on failure and otherwise return no args.
    
    863
    +    This is for primops with result IO ().
    
    864
    +
    
    865
    + 2. stg_block_io_int: with a stack frame containing outcome+result/failure.
    
    866
    +    On resume, throw exception on failure and otherwise return result.
    
    867
    +    This is for primops with result IO Int or IO Word.
    
    868
    +
    
    869
    + 3. stg_block_io_aiop: in future if we add async I/O primitives that return an
    
    870
    +    StgAsyncIOOp we would also need versions that return that (since even
    
    871
    +    submitting async I/O can block if there's too much I/O in flight).
    
    872
    +
    
    873
    +The stack frame layout used is:
    
    874
    + Sp[0]   stg_block_io_unit_info or stg_block_io_int_info
    
    875
    + Sp[1]   outcome
    
    876
    + Sp[2]   result or error
    
    877
    +
    
    878
    +C code should use setTsoIOOpOutcome to set this. But update this if the layout
    
    879
    +changes!
    
    880
    +
    
    881
    +
    
    882
    +Note [Calling convention for raisePrimIOException]
    
    883
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    884
    +
    
    885
    +In cases 1 & 2 we check the outcome and, on failure, we throw an exception. Our
    
    886
    +approach is to use a helper function implemeted in generated Haskell code, in
    
    887
    +ghc-internal. We need to perform a tail call to this helper function:
    
    888
    +
    
    889
    +    raisePrimIOException :: Int# -> State# s -> (# State# s, a #)
    
    890
    +
    
    891
    +The calling convention for top-level generated Haskell function like this uses
    
    892
    +R2 because it has two arguments -- the Int# and State# -- but the State#
    
    893
    +argument has a zero-width repesentation. The cmm call uses R1, R2, ... etc so
    
    894
    +to pass in R2 we have to use a dummy first argument which will be passed in R1
    
    895
    +(and ignored).
    
    896
    +*/
    
    897
    +
    
    898
    +/* stg_block_io_unit_info : the return info table for stg_block_io_unit */
    
    899
    +INFO_TABLE_RET ( stg_block_io_unit, RET_SMALL, W_ info_ptr,
    
    900
    +                 W_ outcome, W_ result )
    
    901
    +    return ()
    
    763 902
     {
    
    764
    -    // When blocking on an MVar we have to be careful to only release
    
    765
    -    // the lock on the MVar at the very last moment (using
    
    766
    -    // BLOCK_BUT_FIRST()), since when we release the lock another
    
    767
    -    // Capability can wake up the thread, which modifies its stack and
    
    768
    -    // other state.  This is not a problem for STM, because STM
    
    769
    -    // wakeups are non-destructive; the waker simply calls
    
    770
    -    // tryWakeupThread() which sends a message to the owner
    
    771
    -    // Capability.  So the moment we release this lock we might start
    
    772
    -    // getting wakeup messages, but that's perfectly harmless.
    
    773
    -    //
    
    774
    -    // Furthermore, we *must* release these locks, just in case an
    
    775
    -    // exception is raised in this thread by
    
    776
    -    // maybePerformBlockedException() while exiting to the scheduler,
    
    777
    -    // which will abort the transaction, which needs to obtain a lock
    
    778
    -    // on all the TVars to remove the thread from the queues.
    
    779
    -    //
    
    780
    -    ccall stmWaitUnlock(MyCapability() "ptr", R3 "ptr");
    
    903
    +    if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) {
    
    904
    +        /* See Note [Calling convention for raisePrimIOException] */
    
    905
    +        W_ dummyR1;
    
    906
    +        dummyR1 = 0;
    
    907
    +        jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface]))
    
    908
    +             (dummyR1, result);
    
    909
    +    } else {
    
    910
    +        /* success or cancelled outcomes */
    
    911
    +        return ();
    
    912
    +    }
    
    913
    +}
    
    914
    +
    
    915
    +/* Blocking for I/O primops with result IO ().
    
    916
    + * See Note [Thread blocking for new I/O primops].
    
    917
    + */
    
    918
    +stg_block_io_unit
    
    919
    +{
    
    920
    +    /* Fill out the stack frame with dummy values. The IO manager will
    
    921
    +     * overwrite these with the actual results when the I/O operation
    
    922
    +     * completes, fails, or is cancelled.
    
    923
    +     */
    
    924
    +    Sp_adj(-3);
    
    925
    +    Sp(0) = stg_block_io_unit_info;
    
    926
    +    Sp(1) = 0;                    // outcome: 0 indicates IOOpOutcomeInFlight
    
    927
    +    Sp(2) = 0;                    // result/errno
    
    928
    +    BLOCK_GENERIC;
    
    929
    +}
    
    930
    +
    
    931
    +/* stg_block_io_int_info : the return info table for stg_block_io_int */
    
    932
    +INFO_TABLE_RET ( stg_block_io_int, RET_SMALL, W_ info_ptr,
    
    933
    +                 W_ outcome, W_ result )
    
    934
    +    return ()
    
    935
    +{
    
    936
    +    if (outcome == 2 :: W_ /*IOOpOutcomeFailed*/) (likely: False) {
    
    937
    +        /* See Note [Calling convention for raisePrimIOException] */
    
    938
    +        W_ dummyR1;
    
    939
    +        dummyR1 = 0;
    
    940
    +        jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface]))
    
    941
    +             (dummyR1, result);
    
    942
    +    } else {
    
    943
    +        /* success or cancelled outcomes */
    
    944
    +        return (result);
    
    945
    +    }
    
    946
    +}
    
    947
    +
    
    948
    +/* Blocking for I/O primops with result IO Int or Word (or rather Int#/Word#).
    
    949
    + * See Note [Thread blocking for new I/O primops].
    
    950
    + */
    
    951
    +stg_block_io_int
    
    952
    +{
    
    953
    +    /* Fill out the stack frame with dummy values. The IO manager will
    
    954
    +     * overwrite these with the actual results when the I/O operation
    
    955
    +     * completes, fails, or is cancelled.
    
    956
    +     */
    
    957
    +    Sp_adj(-3);
    
    958
    +    Sp(0) = stg_block_io_int_info;
    
    959
    +    Sp(1) = 0;                    // outcome: 0 indicates IOOpOutcomeInFlight
    
    960
    +    Sp(2) = 0;                    // result/errno
    
    781 961
         BLOCK_GENERIC;
    
    782 962
     }

  • rts/IOManager.c
    ... ... @@ -399,21 +399,6 @@ void startIOManager(void)
    399 399
     
    
    400 400
         switch (iomgr_type) {
    
    401 401
     
    
    402
    -#if defined(IOMGR_ENABLED_SELECT) || defined(IOMGR_ENABLED_POLL)
    
    403
    -#if defined(IOMGR_ENABLED_SELECT)
    
    404
    -        case IO_MANAGER_SELECT:
    
    405
    -#endif
    
    406
    -#if defined(IOMGR_ENABLED_POLL)
    
    407
    -        case IO_MANAGER_POLL:
    
    408
    -#endif
    
    409
    -            /* Make the exception CAF a GC root. See initBuiltinGcRoots for
    
    410
    -             * similar examples. We throw this exception if a thread tries to
    
    411
    -             * wait on an invalid FD.
    
    412
    -             */
    
    413
    -            getStablePtr((StgPtr)blockedOnBadFD_closure);
    
    414
    -            break;
    
    415
    -#endif
    
    416
    -
    
    417 402
     #if defined(IOMGR_ENABLED_MIO_POSIX)
    
    418 403
             case IO_MANAGER_MIO_POSIX:
    
    419 404
                 /* Posix implementation in posix/Signals.c
    
    ... ... @@ -776,10 +761,10 @@ void interruptIOManager(CapIOManager *iomgr)
    776 761
     
    
    777 762
     
    
    778 763
     /* CMM primop. Result is true on success, or false on allocation failure. */
    
    779
    -bool syncIOWaitReady(CapIOManager *iomgr,
    
    780
    -                     StgTSO       *tso,
    
    781
    -                     IOReadOrWrite rw,
    
    782
    -                     HsInt         fd)
    
    764
    +IOSubmitResult syncIOWaitReady(CapIOManager *iomgr,
    
    765
    +                               StgTSO       *tso,
    
    766
    +                               IOReadOrWrite rw,
    
    767
    +                               HsInt         fd)
    
    783 768
     {
    
    784 769
         debugTrace(DEBUG_iomanager,
    
    785 770
                    "thread %ld waiting for %s I/O readiness on fd %d",
    
    ... ... @@ -795,7 +780,7 @@ bool syncIOWaitReady(CapIOManager *iomgr,
    795 780
                 tso->block_info.fd = fd;
    
    796 781
                 appendToIOBlockedQueue(iomgr, tso);
    
    797 782
                 RELEASE_STORE(&tso->why_blocked, why_blocked);
    
    798
    -            return true;
    
    783
    +            return IOSubmitResultAsyncContinue;
    
    799 784
             }
    
    800 785
     #endif
    
    801 786
     #if defined(IOMGR_ENABLED_POLL)
    

  • rts/IOManager.h
    ... ... @@ -325,8 +325,33 @@ typedef enum { IORead = 0, IOWrite = 1 } IOReadOrWrite;
    325 325
      * false on heap allocation failure.
    
    326 326
      */
    
    327 327
     
    
    328
    +/* Note [Encoding of result of I/O manager operations]
    
    329
    +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    330
    +We have quite a bit of information that to return from the I/O manager calls
    
    331
    +back to the I/O primops. It (somewhat uncomfortably) fits into a C int.
    
    332
    +
    
    333
    +We use the following encoding of the result:
    
    334
    +
    
    335
    + * negative: synchronous failure, with -errno as the result.
    
    336
    + * 0: ok, result successful, proceed to async: suspend the TSO.
    
    337
    + * 1: synchronous success, return without suspending the TSO. This can occur
    
    338
    +      for example when waiting on readiness of a regular file (which is always
    
    339
    +      ready).
    
    340
    + * 2: heap overflow
    
    341
    + * >2: unallocated status codes
    
    342
    +*/
    
    343
    +typedef int IOSubmitResult;
    
    344
    +
    
    345
    +/* Provide constants for IOSubmitResult */
    
    346
    +enum IOSubmitResultCodes {
    
    347
    +  /* negative numbers are -errno error codes */
    
    348
    +  IOSubmitResultAsyncContinue = 0,
    
    349
    +  IOSubmitResultSyncSuccess   = 1,
    
    350
    +  IOSubmitResultHeapOverflow  = 2
    
    351
    +};
    
    352
    +
    
    328 353
     /* Called from CMM primop */
    
    329
    -bool syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
    
    354
    +IOSubmitResult syncIOWaitReady(CapIOManager *iomgr, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
    
    330 355
     
    
    331 356
     void syncIOCancel(CapIOManager *iomgr, StgTSO *tso);
    
    332 357
     
    

  • rts/IOManagerInternals.h
    ... ... @@ -81,5 +81,25 @@ struct _CapIOManager {
    81 81
     
    
    82 82
     };
    
    83 83
     
    
    84
    +/* Fill in the outcome and result/error on the TSO's stack frame.
    
    85
    + * The TSO must be blocked using one of the stg_block_io_* blocking actions.
    
    86
    + *
    
    87
    + * Once the thread is resumed (by the scheduler) the code for stg_block_*_info
    
    88
    + * will pick up the info from the stack frame and use it to see if there's been
    
    89
    + * a failure, and if so to raise a PrimIOException.
    
    90
    + *
    
    91
    + * See Note [Thread blocking for new I/O primops].
    
    92
    + */
    
    93
    +INLINE_HEADER void setTsoIOOpOutcome (StgTSO *tso,
    
    94
    +                                      enum IOOpOutcome outcome,
    
    95
    +                                      uint32_t result)
    
    96
    +{
    
    97
    +    ASSERT((StgPtr *)tso->stackobj->sp[0] == (StgPtr *)&stg_block_io_unit_info
    
    98
    +        || (StgPtr *)tso->stackobj->sp[0] == (StgPtr *)&stg_block_io_int_info);
    
    99
    +
    
    100
    +    tso->stackobj->sp[1] = (W_)outcome;
    
    101
    +    tso->stackobj->sp[2] = (W_)result;
    
    102
    +}
    
    103
    +
    
    84 104
     #include "EndPrivate.h"
    
    85 105
     

  • rts/Prelude.h
    ... ... @@ -65,8 +65,6 @@ extern StgClosure ZCMain_main_closure;
    65 65
     #define overflowException_closure ghc_hs_iface->overflowException_closure
    
    66 66
     #define divZeroException_closure  ghc_hs_iface->divZZeroException_closure
    
    67 67
     
    
    68
    -#define blockedOnBadFD_closure    ghc_hs_iface->blockedOnBadFD_closure
    
    69
    -
    
    70 68
     #define Czh_con_info              ghc_hs_iface->Czh_con_info
    
    71 69
     #define Izh_con_info              ghc_hs_iface->Izh_con_info
    
    72 70
     #define Fzh_con_info              ghc_hs_iface->Fzh_con_info
    

  • rts/PrimOps.cmm
    ... ... @@ -2297,32 +2297,60 @@ stg_whereFromzh (P_ clos, W_ buf)
    2297 2297
        Thread I/O blocking primitives
    
    2298 2298
        -------------------------------------------------------------------------- */
    
    2299 2299
     
    
    2300
    -stg_waitReadzh ( W_ fd )
    
    2300
    +stg_handleIOSubmitResult ( CInt result )
    
    2301 2301
     {
    
    2302
    -    CBool ok; /* Ok, or heap alloc failure. */
    
    2302
    +    /* See Note [Encoding of result of I/O manager operations] */
    
    2303 2303
     
    
    2304
    -    (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr",
    
    2305
    -                                 CurrentTSO "ptr",
    
    2306
    -                                 /* IORead */ 0::CInt, fd);
    
    2307
    -    if (ok != 0::CBool) (likely: True) {
    
    2308
    -        jump stg_block_noregs();
    
    2309
    -    } else {
    
    2304
    +    /* case IOSubmitResultAsyncContinue */
    
    2305
    +    if (result == 0::CInt) (likely: True) {
    
    2306
    +        /* See Note [Thread blocking for new I/O primops] */
    
    2307
    +        jump stg_block_io_unit();
    
    2308
    +    }
    
    2309
    +
    
    2310
    +    /* case IOSubmitResultSyncSuccess*/
    
    2311
    +    if (result == 1::CInt) (likely: True) {
    
    2312
    +        /* Success, don't even go via scheduler. */
    
    2313
    +        return ();
    
    2314
    +    }
    
    2315
    +
    
    2316
    +    /* negative numbers are -errno error codes */
    
    2317
    +    if (result < 0::CInt) {
    
    2318
    +        /* See Note [Calling convention for raisePrimIOException] */
    
    2319
    +        W_ dummyR1; W_ errno;
    
    2320
    +        dummyR1 = 0;
    
    2321
    +        errno   = TO_W_(-result);
    
    2322
    +        jump %ENTRY_CODE(HsIface_raisePrimIOException_info(W_[ghc_hs_iface]))
    
    2323
    +                                                          (dummyR1, errno);
    
    2324
    +    }
    
    2325
    +
    
    2326
    +    /* case IOSubmitResultHeapOverflow */
    
    2327
    +    if (result == 2::CInt) {
    
    2310 2328
             jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface]));
    
    2329
    +
    
    2311 2330
         }
    
    2331
    +    ccall sbarf("syncIOWaitReady result encoding error") never returns;
    
    2332
    +}
    
    2333
    +
    
    2334
    +stg_waitReadzh ( W_ fd )
    
    2335
    +{
    
    2336
    +    CInt result;
    
    2337
    +
    
    2338
    +    (result) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr",
    
    2339
    +                                     CurrentTSO "ptr",
    
    2340
    +                                     /* IORead */ 0::CInt, fd);
    
    2341
    +
    
    2342
    +    jump stg_handleIOSubmitResult(result);
    
    2312 2343
     }
    
    2313 2344
     
    
    2314 2345
     stg_waitWritezh ( W_ fd )
    
    2315 2346
     {
    
    2316
    -    CBool ok; /* Ok, or heap alloc failure. */
    
    2347
    +    CInt result;
    
    2317 2348
     
    
    2318
    -    (ok) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr",
    
    2319
    -                                 CurrentTSO "ptr",
    
    2320
    -                                 /* IOWrite */ 1::CInt, fd);
    
    2321
    -    if (ok != 0::CBool) (likely: True) {
    
    2322
    -        jump stg_block_noregs();
    
    2323
    -    } else {
    
    2324
    -        jump stg_raisezh(HsIface_heapOverflow_closure(W_[ghc_hs_iface]));
    
    2325
    -    }
    
    2349
    +    (result) = ccall syncIOWaitReady(Capability_iomgr(MyCapability()) "ptr",
    
    2350
    +                                     CurrentTSO "ptr",
    
    2351
    +                                     /* IOWrite */ 1::CInt, fd);
    
    2352
    +
    
    2353
    +    jump stg_handleIOSubmitResult(result);
    
    2326 2354
     }
    
    2327 2355
     
    
    2328 2356
     stg_delayzh ( W_ us_delay )
    

  • rts/include/rts/RtsToHsIface.h
    ... ... @@ -28,7 +28,7 @@ typedef struct {
    28 28
         StgClosure *nonTermination_closure;  // GHC.Internal.Control.Exception.Base.nonTermination_closure
    
    29 29
         StgClosure *nestedAtomically_closure;  // GHC.Internal.Control.Exception.Base.nestedAtomically_closure
    
    30 30
         StgClosure *noMatchingContinuationPrompt_closure;  // GHC.Internal.Control.Exception.Base.noMatchingContinuationPrompt_closure
    
    31
    -    StgClosure *blockedOnBadFD_closure;  // GHC.Internal.Event.Thread.blockedOnBadFD_closure
    
    31
    +    StgClosure *raisePrimIOException_info; // GHC.Internal.Conc.IO.raisePrimIOException_info
    
    32 32
         StgClosure *runSparks_closure;  // GHC.Internal.Conc.Sync.runSparks_closure
    
    33 33
         StgClosure *ensureIOManagerIsRunning_closure;  // GHC.Internal.Conc.IO.ensureIOManagerIsRunning_closure
    
    34 34
         StgClosure *interruptIOManager_closure;  // GHC.Internal.Conc.IO.interruptIOManager_closure
    

  • rts/include/stg/MiscClosures.h
    ... ... @@ -369,6 +369,7 @@ RTS_FUN_DECL(stg_gc_bpp);
    369 369
     RTS_RET(stg_gc_fun);
    
    370 370
     RTS_FUN_DECL(__stg_gc_fun);
    
    371 371
     
    
    372
    +/* Blocking for various primops */
    
    372 373
     RTS_FUN_DECL(stg_yield_noregs);
    
    373 374
     RTS_FUN_DECL(stg_yield_to_interpreter);
    
    374 375
     RTS_FUN_DECL(stg_block_noregs);
    
    ... ... @@ -380,13 +381,19 @@ RTS_RET(stg_block_takemvar);
    380 381
     RTS_RET(stg_block_readmvar);
    
    381 382
     RTS_FUN_DECL(stg_block_putmvar);
    
    382 383
     RTS_RET(stg_block_putmvar);
    
    384
    +RTS_FUN_DECL(stg_block_stmwait);
    
    385
    +RTS_FUN_DECL(stg_block_throwto);
    
    386
    +RTS_RET(stg_block_throwto);
    
    387
    +
    
    388
    +/* Blocking for I/O primops */
    
    389
    +RTS_FUN_DECL(stg_block_io_unit);
    
    390
    +RTS_RET(stg_block_io_unit);
    
    391
    +RTS_FUN_DECL(stg_block_io_int);
    
    392
    +RTS_RET(stg_block_io_int);
    
    383 393
     #if defined(mingw32_HOST_OS)
    
    384 394
     RTS_FUN_DECL(stg_block_async);
    
    385 395
     RTS_RET(stg_block_async);
    
    386 396
     #endif
    
    387
    -RTS_FUN_DECL(stg_block_stmwait);
    
    388
    -RTS_FUN_DECL(stg_block_throwto);
    
    389
    -RTS_RET(stg_block_throwto);
    
    390 397
     
    
    391 398
     /* Entry/exit points from StgStartup.cmm */
    
    392 399
     
    

  • rts/posix/Poll.c
    ... ... @@ -20,7 +20,6 @@
    20 20
     #include "Prelude.h"
    
    21 21
     #include "RtsUtils.h"
    
    22 22
     #include "rts/Time.h"
    
    23
    -#include "RaiseAsync.h"
    
    24 23
     #include "Trace.h"
    
    25 24
     
    
    26 25
     #include "Poll.h"
    
    ... ... @@ -171,14 +170,13 @@ void freeCapabilityIOManagerPoll(CapIOManager *iomgr)
    171 170
     }
    
    172 171
     
    
    173 172
     
    
    174
    -/* Used to implement syncIOWaitReady.
    
    175
    - * Result is true on success, or false on allocation failure. */
    
    176
    -bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
    
    177
    -                         IOReadOrWrite rw, HsInt fd)
    
    173
    +/* Used to implement syncIOWaitReady. */
    
    174
    +IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
    
    175
    +                                   IOReadOrWrite rw, HsInt fd)
    
    178 176
     {
    
    179 177
         StgAsyncIOOp *aiop;
    
    180 178
         aiop = (StgAsyncIOOp *)allocateMightFail(iomgr->cap, sizeofW(StgAsyncIOOp));
    
    181
    -    if (RTS_UNLIKELY(aiop == NULL)) return false;
    
    179
    +    if (RTS_UNLIKELY(aiop == NULL)) return IOSubmitResultHeapOverflow;
    
    182 180
         SET_HDR(aiop, &stg_ASYNCIOOP_info, iomgr->cap->r.rCCCS);
    
    183 181
         aiop->notify.tso     = tso;
    
    184 182
         aiop->notify_type    = NotifyTSO;
    
    ... ... @@ -189,13 +187,12 @@ bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
    189 187
         return asyncIOWaitReadyPoll(iomgr, aiop, rw, fd);
    
    190 188
     }
    
    191 189
     
    
    192
    -/* Result is true on success, or false on allocation failure. */
    
    193
    -bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    
    194
    -                         IOReadOrWrite rw, int fd)
    
    190
    +IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    
    191
    +                                    IOReadOrWrite rw, int fd)
    
    195 192
     {
    
    196 193
         if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
    
    197 194
             bool ok = enlargeTables(iomgr);
    
    198
    -        if (RTS_UNLIKELY(!ok)) return false;
    
    195
    +        if (RTS_UNLIKELY(!ok)) return IOSubmitResultHeapOverflow;
    
    199 196
         }
    
    200 197
     
    
    201 198
         int ix = insertClosureTable(iomgr->cap, &iomgr->aiop_table, aiop);
    
    ... ... @@ -216,7 +213,7 @@ bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    216 213
                                        .events  = rw == IORead ? POLLIN : POLLOUT,
    
    217 214
                                        .revents = 0
    
    218 215
                                      };
    
    219
    -    return true;
    
    216
    +    return IOSubmitResultAsyncContinue;
    
    220 217
     }
    
    221 218
     
    
    222 219
     
    
    ... ... @@ -226,6 +223,7 @@ void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso)
    226 223
         ASSERT(aiop->notify_type == NotifyTSO);
    
    227 224
         ASSERT(indexClosureTable(&iomgr->aiop_table, aiop->index) == aiop);
    
    228 225
         ioCancel(iomgr, aiop);
    
    226
    +    setTsoIOOpOutcome(tso, aiop->outcome, aiop->result);
    
    229 227
         /* We cannot use the normal notifyIOCompletion here. We are in the context
    
    230 228
          * of throwTo, interrupting a thread blocked on IO via an async exception.
    
    231 229
          * We don't put the TSO back on the run queue or change the why_blocked
    
    ... ... @@ -284,26 +282,18 @@ static void notifyIOCompletion(CapIOManager *iomgr, StgAsyncIOOp *aiop)
    284 282
         switch (aiop->notify_type) {
    
    285 283
             case NotifyTSO:
    
    286 284
             {
    
    287
    -            if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) {
    
    288
    -                /* The fd is invalid: raise an IOError exception in the blocked
    
    289
    -                 * thread. (See bug #4934 for what happens without this.)
    
    290
    -                 */
    
    291
    -                StgTSO *tso = aiop->notify.tso;
    
    292
    -                debugTrace(DEBUG_iomanager,
    
    293
    -                           "Raising exception in thread %" FMT_StgThreadID
    
    294
    -                           " blocked on an invalid fd", tso->id);
    
    295
    -                raiseAsync(iomgr->cap, tso,
    
    296
    -                           (StgClosure *)blockedOnBadFD_closure,
    
    297
    -                           false, NULL);
    
    298
    -            } else {
    
    299
    -                /* We should be guaranteed that the tso is still on the same
    
    300
    -                 * cap because the tso was not on the run queue of any cap and
    
    301
    -                 * so is not subject to thread migration.
    
    302
    -                 */
    
    303
    -                StgTSO *tso = aiop->notify.tso;
    
    304
    -                pushOnRunQueue(iomgr->cap, tso);
    
    305
    -                RELEASE_STORE(&tso->why_blocked, NotBlocked);
    
    306
    -            }
    
    285
    +            /* We should be guaranteed that the tso is still on the same
    
    286
    +             * cap because the tso was not on the run queue of any cap and
    
    287
    +             * so is not subject to thread migration.
    
    288
    +             */
    
    289
    +            StgTSO *tso = aiop->notify.tso;
    
    290
    +            ASSERT(tso->cap == iomgr->cap);
    
    291
    +
    
    292
    +            /* Fill in the outcome and result/error on the TSO's stack frame */
    
    293
    +            setTsoIOOpOutcome(tso, aiop->outcome, aiop->result);
    
    294
    +            pushOnRunQueue(iomgr->cap, tso);
    
    295
    +            RELEASE_STORE(&tso->why_blocked, NotBlocked);
    
    296
    +
    
    307 297
                 /* For the TSO case, the aiop was only reachable from the TSO
    
    308 298
                  * itself, and thus it is now no longer be reachable at all.
    
    309 299
                  */
    

  • rts/posix/Poll.h
    ... ... @@ -20,13 +20,13 @@ void initCapabilityIOManagerPoll(CapIOManager *iomgr);
    20 20
     void freeCapabilityIOManagerPoll(CapIOManager *iomgr);
    
    21 21
     
    
    22 22
     /* Synchronous I/O and timer operations */
    
    23
    -bool syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
    
    24
    -                         IOReadOrWrite rw, HsInt fd);
    
    23
    +IOSubmitResult syncIOWaitReadyPoll(CapIOManager *iomgr, StgTSO *tso,
    
    24
    +                                   IOReadOrWrite rw, HsInt fd);
    
    25 25
     void syncIOCancelPoll(CapIOManager *iomgr, StgTSO *tso);
    
    26 26
     
    
    27 27
     /* Asynchronous operations */
    
    28
    -bool asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    
    29
    -                          IOReadOrWrite rw, int fd);
    
    28
    +IOSubmitResult asyncIOWaitReadyPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop,
    
    29
    +                                    IOReadOrWrite rw, int fd);
    
    30 30
     void asyncIOCancelPoll(CapIOManager *iomgr, StgAsyncIOOp *aiop);
    
    31 31
     
    
    32 32
     /* Scheduler operations */
    

  • rts/posix/Select.c
    ... ... @@ -15,7 +15,6 @@
    15 15
     #include "Signals.h"
    
    16 16
     #include "Schedule.h"
    
    17 17
     #include "Prelude.h"
    
    18
    -#include "RaiseAsync.h"
    
    19 18
     #include "RtsUtils.h"
    
    20 19
     #include "Capability.h"
    
    21 20
     #include "Select.h"
    
    ... ... @@ -480,13 +479,18 @@ awaitCompletedTimeoutsOrIOSelect(CapIOManager *iomgr, bool wait)
    480 479
                       IF_DEBUG(scheduler,
    
    481 480
                           debugBelch("Killing blocked thread %" FMT_StgThreadID
    
    482 481
                                      " on bad fd=%i\n", tso->id, fd));
    
    483
    -                  raiseAsync(iomgr->cap, tso,
    
    484
    -                      (StgClosure *)blockedOnBadFD_closure, false, NULL);
    
    482
    +
    
    483
    +                  /* Fill in the outcome and error on the TSO's stack frame */
    
    484
    +                  setTsoIOOpOutcome(tso, IOOpOutcomeFailed, EBADF);
    
    485
    +                  pushOnRunQueue(iomgr->cap,tso);
    
    486
    +                  RELEASE_STORE(&tso->why_blocked, NotBlocked);
    
    485 487
                       break;
    
    486 488
                   case RTS_FD_IS_READY:
    
    487 489
                       IF_DEBUG(scheduler,
    
    488 490
                           debugBelch("Waking up blocked thread %" FMT_StgThreadID "\n",
    
    489 491
                                      tso->id));
    
    492
    +                 /* Fill in the outcome and result on the TSO's stack frame */
    
    493
    +                  setTsoIOOpOutcome(tso, IOOpOutcomeSuccess, 0);
    
    490 494
                       pushOnRunQueue(iomgr->cap,tso);
    
    491 495
                       RELEASE_STORE(&tso->why_blocked, NotBlocked);
    
    492 496
                       break;
    

  • rts/sm/Evac.c
    ... ... @@ -1361,7 +1361,11 @@ selector_loop:
    1361 1361
         // from-space during marking, for example.  We rely on the property
    
    1362 1362
         // that evacuate() doesn't mind if it gets passed a to-space pointer.
    
    1363 1363
     
    
    1364
    -    info = RELAXED_LOAD((StgInfoTable**) &selectee->header.info);
    
    1364
    +    // NB. this load must be ordered: the selectee may be updated
    
    1365
    +    // concurrently by another GC thread's unchain_thunk_selectors(),
    
    1366
    +    // which publishes an indirection by writing the indirectee and
    
    1367
    +    // then RELEASE-storing the info pointer. See #27477.
    
    1368
    +    info = ACQUIRE_LOAD((StgInfoTable**) &selectee->header.info);
    
    1365 1369
     
    
    1366 1370
         if (IS_FORWARDING_PTR(info)) {
    
    1367 1371
             // We don't follow pointers into to-space; the constructor
    

  • testsuite/tests/rts/T27477.hs
    1
    +{-# LANGUAGE BangPatterns #-}
    
    2
    +
    
    3
    +-- Stress test for #27477: missing ACQUIRE in eval_thunk_selector
    
    4
    +-- (rts/sm/Evac.c).
    
    5
    +--
    
    6
    +-- With the bug present this test crashes or reports value mismatches
    
    7
    +-- within a few seconds on AArch64; on strongly-ordered targets
    
    8
    +-- it just passes.
    
    9
    +
    
    10
    +module Main (main) where
    
    11
    +
    
    12
    +import Control.Concurrent
    
    13
    +import Control.Exception
    
    14
    +import Control.Monad
    
    15
    +import Data.IORef
    
    16
    +import System.Environment
    
    17
    +import System.Exit
    
    18
    +import System.IO
    
    19
    +import System.Mem (performMinorGC)
    
    20
    +
    
    21
    +data T = T { nxt :: T, val :: !Int }
    
    22
    +
    
    23
    +-- expected values and the cells that should evaluate to them
    
    24
    +type Batch = ([Int], [T])
    
    25
    +
    
    26
    +tailD, branchD, branchesPerCell :: Int
    
    27
    +tailD = 12          -- shared tail length per cluster
    
    28
    +branchD = 3         -- private branch length
    
    29
    +branchesPerCell = 2
    
    30
    +-- tailD + branchD must stay below MAX_THUNK_SELECTOR_DEPTH (16)
    
    31
    +
    
    32
    +{-# NOINLINE spineEnd #-}
    
    33
    +spineEnd :: T
    
    34
    +spineEnd = errorWithoutStackTrace "spine end reached"
    
    35
    +
    
    36
    +-- Fully evaluated spine of constructors; cell i from the head has val i+1.
    
    37
    +buildSpine :: Int -> IO T
    
    38
    +buildSpine len = evaluate (go len (T spineEnd 0))
    
    39
    +  where
    
    40
    +    go :: Int -> T -> T
    
    41
    +    go 0 acc = acc
    
    42
    +    go k acc = go (k - 1) (T acc k)
    
    43
    +
    
    44
    +-- k selector thunks, shallow-first: cell i selects from cell i-1,
    
    45
    +-- cell 0 selects from t.  If t evaluates to spine cell m (val m+1),
    
    46
    +-- cell i evaluates to spine cell m+1+i (val m+2+i).
    
    47
    +{-# NOINLINE selChain #-}
    
    48
    +selChain :: Int -> T -> [T]
    
    49
    +selChain 0 _ = []
    
    50
    +selChain k t = let t' = nxt t in t' : selChain (k - 1) t'
    
    51
    +
    
    52
    +lenL :: [a] -> Int
    
    53
    +lenL = go 0
    
    54
    +  where
    
    55
    +    go !n [] = n
    
    56
    +    go !n (_ : xs) = go (n + 1) xs
    
    57
    +
    
    58
    +everyN :: Int -> [a] -> [a]
    
    59
    +everyN _ [] = []
    
    60
    +everyN n (x : xs) = x : everyN n (drop (n - 1) xs)
    
    61
    +
    
    62
    +-- One branch hanging off tail cell tj (which evaluates to val jval):
    
    63
    +-- branch cell i evaluates to jval+1+i.  Returned deepest-first.
    
    64
    +{-# NOINLINE mkBranch #-}
    
    65
    +mkBranch :: Int -> T -> IO Batch
    
    66
    +mkBranch jval tj = do
    
    67
    +  let cells = selChain branchD tj
    
    68
    +  _ <- evaluate (lenL cells)
    
    69
    +  let expected = [ jval + 1 + i | i <- [0 .. branchD - 1] ]
    
    70
    +  pure (reverse expected, reverse cells)
    
    71
    +
    
    72
    +-- A cluster: one private spine, one shared tail of selectors over it,
    
    73
    +-- branchesPerCell branches of selectors converging on every tail cell.
    
    74
    +-- Neither the spine nor the tail is returned: they must stay reachable
    
    75
    +-- only through the branch selectors.
    
    76
    +buildCluster :: IO [Batch]
    
    77
    +buildCluster = do
    
    78
    +  sp <- buildSpine (tailD + branchD + 4)
    
    79
    +  let tails = selChain tailD sp
    
    80
    +  _ <- evaluate (lenL tails)
    
    81
    +  bss <- forM (zip [0 ..] tails) $ \(j, tj) ->
    
    82
    +    replicateM branchesPerCell (mkBranch (j + 2) tj)
    
    83
    +  pure (concat bss)
    
    84
    +
    
    85
    +verifyBatch :: IORef Int -> Int -> Batch -> IO ()
    
    86
    +verifyBatch errs wid (es0, ts0) = go es0 ts0
    
    87
    +  where
    
    88
    +    go (e : es) (t : ts) = do
    
    89
    +      v <- evaluate (val t)
    
    90
    +      when (v /= e) $ do
    
    91
    +        hPutStrLn stderr $
    
    92
    +          "MISMATCH worker=" ++ show wid
    
    93
    +            ++ " expected=" ++ show e ++ " got=" ++ show v
    
    94
    +        modifyIORef' errs (+ 1)
    
    95
    +      go es ts
    
    96
    +    go _ _ = pure ()
    
    97
    +
    
    98
    +-- simple cyclic barrier
    
    99
    +newtype Barrier = Barrier (MVar (Int, [MVar ()]))
    
    100
    +
    
    101
    +newBarrier :: IO Barrier
    
    102
    +newBarrier = Barrier <$> newMVar (0, [])
    
    103
    +
    
    104
    +awaitBarrier :: Barrier -> Int -> IO ()
    
    105
    +awaitBarrier (Barrier st) total = do
    
    106
    +  w <- newEmptyMVar
    
    107
    +  join $ modifyMVar st $ \(k, ws) ->
    
    108
    +    if k + 1 == total
    
    109
    +      then pure ((0, []), mapM_ (\m -> putMVar m ()) ws)
    
    110
    +      else pure ((k + 1, w : ws), takeMVar w)
    
    111
    +
    
    112
    +worker :: Int -> Int -> Int -> Barrier -> [IORef [Batch]] -> IORef Int -> Int -> IO ()
    
    113
    +worker rounds clusters nWorkers bar slots errs wid =
    
    114
    +  forM_ [1 .. rounds] $ \(_r :: Int) -> do
    
    115
    +    -- build fresh selector clusters, publish them
    
    116
    +    batches <- concat <$> replicateM clusters buildCluster
    
    117
    +    writeIORef (slots !! wid) batches
    
    118
    +    awaitBarrier bar nWorkers
    
    119
    +    -- root each branch's deepest cell via fresh cons cells in our own
    
    120
    +    -- nursery (so our GC thread evacuates the shared cells itself), in
    
    121
    +    -- a per-worker interleaved order so that different GC threads
    
    122
    +    -- sweep different branches of the same shared tails concurrently
    
    123
    +    published <- mapM readIORef slots
    
    124
    +    let allBatches = concat published
    
    125
    +        ordered
    
    126
    +          | nWorkers > 1 =
    
    127
    +              concat [ everyN nWorkers (drop ((wid + k) `mod` nWorkers) allBatches)
    
    128
    +                     | k <- [0 .. nWorkers - 1] ]
    
    129
    +          | otherwise = allBatches
    
    130
    +        deepTs = [ t | (_, t : _) <- ordered ]
    
    131
    +        deepEs = [ e | (e : _, _) <- ordered ]
    
    132
    +    _ <- evaluate (lenL deepTs)
    
    133
    +    _ <- evaluate (lenL deepEs)
    
    134
    +    awaitBarrier bar nWorkers
    
    135
    +    -- run a parallel GC with everything live
    
    136
    +    when (wid == 0) performMinorGC
    
    137
    +    awaitBarrier bar nWorkers
    
    138
    +    -- force everything and check the values
    
    139
    +    verifyBatch errs wid (deepEs, deepTs)
    
    140
    +    awaitBarrier bar nWorkers
    
    141
    +
    
    142
    +main :: IO ()
    
    143
    +main = do
    
    144
    +  hSetBuffering stderr LineBuffering
    
    145
    +  args <- getArgs
    
    146
    +  let atDef d i = if length args > i then read (args !! i) else d
    
    147
    +      rounds = atDef 600 0
    
    148
    +      clusters = atDef 48 1
    
    149
    +  n <- getNumCapabilities
    
    150
    +  bar <- newBarrier
    
    151
    +  slots <- replicateM n (newIORef [])
    
    152
    +  errs <- newIORef 0
    
    153
    +  dones <- forM [0 .. n - 1] $ \i -> do
    
    154
    +    done <- newEmptyMVar
    
    155
    +    _ <- forkOn i $
    
    156
    +      (worker rounds clusters n bar slots errs i >> putMVar done Nothing)
    
    157
    +        `catch` \e -> putMVar done (Just (e :: SomeException))
    
    158
    +    pure done
    
    159
    +  results <- mapM takeMVar dones
    
    160
    +  forM_ results $ \r -> case r of
    
    161
    +    Just e -> do
    
    162
    +      hPutStrLn stderr ("worker exception: " ++ show e)
    
    163
    +      exitWith (ExitFailure 2)
    
    164
    +    Nothing -> pure ()
    
    165
    +  ec <- readIORef errs
    
    166
    +  when (ec > 0) $ do
    
    167
    +    hPutStrLn stderr ("FAILED: " ++ show ec ++ " mismatches")
    
    168
    +    exitWith (ExitFailure 2)
    
    169
    +  putStrLn "OK"

  • testsuite/tests/rts/T27477.stdout
    1
    +OK

  • testsuite/tests/rts/all.T
    ... ... @@ -720,3 +720,12 @@ test('T27585',
    720 720
          , extra_run_opts('+RTS -i0 -RTS')
    
    721 721
          ],
    
    722 722
          compile_and_run, ['-O -rtsopts'])
    
    723
    +
    
    724
    +test('T27477',
    
    725
    +     [ extra_run_opts('600 24 +RTS -N4 -A1m -RTS')
    
    726
    +     , req_ghc_smp
    
    727
    +     , req_target_smp
    
    728
    +     , only_ways(['threaded1', 'threaded2'])
    
    729
    +     ],
    
    730
    +     compile_and_run, ['-O2'])
    
    731
    +

  • utils/check-exact/ExactPrint.hs
    ... ... @@ -3404,13 +3404,13 @@ instance ExactPrint (HsCmd GhcPs) where
    3404 3404
           e' <- markAnnotated e
    
    3405 3405
           return (HsCmdLet (tkLet', tkIn') binds' e')
    
    3406 3406
     
    
    3407
    -  exact (HsCmdDo (an0,loc) (L l es)) = do
    
    3407
    +  exact (HsCmdDo (an0,loc) es) = do
    
    3408 3408
         debugM $ "HsCmdDo"
    
    3409 3409
         loc' <- printStringAtAA loc "do"
    
    3410 3410
         (an1,es') <- markAnnList' an0 $ do
    
    3411
    -      ee <- mapM markAnnotated es
    
    3411
    +      ee <- markAnnotated es
    
    3412 3412
           return ee
    
    3413
    -    return (HsCmdDo (an1,loc') (L l es'))
    
    3413
    +    return (HsCmdDo (an1,loc') es')
    
    3414 3414
     
    
    3415 3415
     -- ---------------------------------------------------------------------
    
    3416 3416
     
    
    ... ... @@ -4508,10 +4508,10 @@ instance ExactPrint [LocatedA (StmtLR GhcPs GhcPs (LocatedA (HsCmd GhcPs)))] whe
    4508 4508
           Just (initStmts, ls@(L _ (LastStmt _ _body _ _))) -> do
    
    4509 4509
             debugM $ "LocatedL [ExprLStmt: snocView"
    
    4510 4510
             ls' <- markAnnotated ls
    
    4511
    -        initStmts' <- markAnnotated initStmts
    
    4511
    +        initStmts' <- mapM markAnnotated initStmts
    
    4512 4512
             return (initStmts' ++ [ls'])
    
    4513 4513
           _ -> do
    
    4514
    -        stmts' <- markAnnotated stmts
    
    4514
    +        stmts' <- mapM markAnnotated stmts
    
    4515 4515
             return stmts'
    
    4516 4516
     
    
    4517 4517
     instance ExactPrint [Located HsDocStringChunk] where
    

  • utils/deriveConstants/Main.hs
    ... ... @@ -644,6 +644,7 @@ wanteds os = concat
    644 644
               ,structField C "HsIface" "divZZeroException_closure"
    
    645 645
               ,structField C "HsIface" "underflowException_closure"
    
    646 646
               ,structField C "HsIface" "overflowException_closure"
    
    647
    +          ,structField C "HsIface" "raisePrimIOException_info"
    
    647 648
               ,structField C "HsIface" "unpackCStringzh_info"
    
    648 649
               ,structField C "HsIface" "unpackCStringUtf8zh_info"
    
    649 650