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

Commits:

16 changed files:

Changes:

  • compiler/GHC/Builtin/primops.txt.pp
    ... ... @@ -3185,6 +3185,128 @@ primop WaitWriteOp "waitWrite#" GenPrimOp
    3185 3185
        effect = ReadWriteEffect
    
    3186 3186
        out_of_line      = True
    
    3187 3187
     
    
    3188
    +------------------------------------------------------------------------
    
    3189
    +section "synchronous I/O operations"
    
    3190
    +  {These primops read up to n bytes from an open file into memory, or write up
    
    3191
    +   to n bytes from memory to an open file.
    
    3192
    +
    
    3193
    +   There are I\/O primops for all combinations of:
    
    3194
    +
    
    3195
    +   * read or write to open files
    
    3196
    +   * pinned byte array or raw pointer for the memory buffer
    
    3197
    +   * I/O at the current file pointer or at a given file offset
    
    3198
    +
    
    3199
    +   All of these operations are synchronous with respect to the calling
    
    3200
    +   thread. It is implementation defined whether these operations block just
    
    3201
    +   the calling thread or block all other Haskell threads running on the same
    
    3202
    +   capability. In practice this depends on the I\/O manager being used.
    
    3203
    +
    
    3204
    +   The result is the number of bytes transferred (if non-negative), or an
    
    3205
    +   error code if negative. The error code is a system error code which,
    
    3206
    +   depending on the platform, is either a C/Posix style errno, or a Win32 error
    
    3207
    +   code.
    
    3208
    +
    
    3209
    +   Note that partial\/short reads\/writes are possible. These are common with
    
    3210
    +   sockets and character devices, and rare with disk block devices, but they
    
    3211
    +   can happen and Posix and Win32 APIs say they can happen, so they must be
    
    3212
    +   handled.
    
    3213
    +
    
    3214
    +   The source\/destination buffer is either specified by a pointer, or by a
    
    3215
    +   (mutable, pinned) byte array and an offset within the array. This area is
    
    3216
    +   required to be at least n bytes large. This cannot be checked for the
    
    3217
    +   pointer variants but it is checked for the byte array variants. For the
    
    3218
    +   byte array variants, the array must be pinned (so that the location of the
    
    3219
    +   buffer is stable for the duration of the I/O operation) and this is
    
    3220
    +   checked.
    
    3221
    +
    
    3222
    +   The buffer must remain live for the duration of the I/O operation (until
    
    3223
    +   the I/O completes or the I/O is cancelled by an asynchronous exception).
    
    3224
    +   For the primop variants that use byte array buffers this is done
    
    3225
    +   automatically: the byte array is kept live for the duration of the
    
    3226
    +   operation. For the pointer primop variants it is the caller's
    
    3227
    +   responsibility to keep the buffer live for the duration of the I/O
    
    3228
    +   operation. For these synchronous primops, callers can rely on the guarantee
    
    3229
    +   that the I/O operation is complete or cancelled by the time the I/O primop
    
    3230
    +   returns.
    
    3231
    +
    
    3232
    +   For the primop variants that do I/O at the current file pointer, the
    
    3233
    +   current file pointer constitutes shared mutable state and should be treated
    
    3234
    +   appropriately. Concurrent reads or writes are possible, and will happen in
    
    3235
    +   some sequentially consistent order, but the order is not deterministic.
    
    3236
    +
    
    3237
    +   For the primop variants that do I/O at given offsets, note that this is
    
    3238
    +   only supported on seekable files, which in practice means disk files. There
    
    3239
    +   is no mutation to a shared file pointer in this case so it is safer to
    
    3240
    +   use concurrent reads and writes. Callers are however responsible for
    
    3241
    +   avoiding concurrent write operations to overlapping ranges (either
    
    3242
    +   write\/write or read\/write), as these would have unspecified results.
    
    3243
    +  }
    
    3244
    +------------------------------------------------------------------------
    
    3245
    +
    
    3246
    +primop  SyncIOReadAddrOp "syncIOReadAddr#" GenPrimOp
    
    3247
    +  Int# -> Addr# -> Word# ->
    
    3248
    +  State# RealWorld -> (# State# RealWorld, Int# #)
    
    3249
    +  {Args: fd, buf, byte count}
    
    3250
    +  with
    
    3251
    +  effect = ReadWriteEffect
    
    3252
    +  out_of_line = True
    
    3253
    +
    
    3254
    +primop  SyncIOReadByteArrayOp "syncIOReadByteArray#" GenPrimOp
    
    3255
    +  Int# -> MutableByteArray# RealWorld -> Word# -> Word# ->
    
    3256
    +  State# RealWorld -> (# State# RealWorld, Int# #)
    
    3257
    +  {Args: fd, buf, buf offset, byte count}
    
    3258
    +  with
    
    3259
    +  effect = ReadWriteEffect
    
    3260
    +  out_of_line = True
    
    3261
    +
    
    3262
    +primop  SyncIOReadAddrAtOp "syncIOReadAddrAt#" GenPrimOp
    
    3263
    +  Int# -> Addr# -> Word# -> Int64# ->
    
    3264
    +  State# RealWorld -> (# State# RealWorld, Int# #)
    
    3265
    +  {Args: fd, buf, byte count, file offset}
    
    3266
    +  with
    
    3267
    +  effect = ReadWriteEffect
    
    3268
    +  out_of_line = True
    
    3269
    +
    
    3270
    +primop  SyncIOReadByteArrayAtOp "syncIOReadByteArrayAt#" GenPrimOp
    
    3271
    +  Int# -> MutableByteArray# RealWorld -> Word# -> Word# -> Int64# ->
    
    3272
    +  State# RealWorld -> (# State# RealWorld, Int# #)
    
    3273
    +  {Args: fd, buf, buf offset, byte count, file offset}
    
    3274
    +  with
    
    3275
    +  effect = ReadWriteEffect
    
    3276
    +  out_of_line = True
    
    3277
    +
    
    3278
    +primop  SyncIOWriteAddrOp "syncIOWriteAddr#" GenPrimOp
    
    3279
    +  Int# -> Addr# -> Word# ->
    
    3280
    +  State# RealWorld -> (# State# RealWorld, Int# #)
    
    3281
    +  {Args: fd, buf, byte count}
    
    3282
    +  with
    
    3283
    +  effect = ReadWriteEffect
    
    3284
    +  out_of_line = True
    
    3285
    +
    
    3286
    +primop  SyncIOWriteByteArrayOp "syncIOWriteByteArray#" GenPrimOp
    
    3287
    +  Int# -> ByteArray# -> Word# -> Word# ->
    
    3288
    +  State# RealWorld -> (# State# RealWorld, Int# #)
    
    3289
    +  {Args: fd, buf, buf offset, byte count}
    
    3290
    +  with
    
    3291
    +  effect = ReadWriteEffect
    
    3292
    +  out_of_line = True
    
    3293
    +
    
    3294
    +primop  SyncIOWriteAddrAtOp "syncIOWriteAddrAt#" GenPrimOp
    
    3295
    +  Int# -> Addr# -> Word# -> Int64# ->
    
    3296
    +  State# RealWorld -> (# State# RealWorld, Int# #)
    
    3297
    +  {Args: fd, buf, byte count, file offset}
    
    3298
    +  with
    
    3299
    +  effect = ReadWriteEffect
    
    3300
    +  out_of_line = True
    
    3301
    +
    
    3302
    +primop  SyncIOWriteByteArrayAtOp "syncIOWriteByteArrayAt#" GenPrimOp
    
    3303
    +  Int# -> ByteArray# -> Word# -> Word# -> Int64# ->
    
    3304
    +  State# RealWorld -> (# State# RealWorld, Int# #)
    
    3305
    +  {Args: fd, buf, buf offset, byte count, file offset}
    
    3306
    +  with
    
    3307
    +  effect = ReadWriteEffect
    
    3308
    +  out_of_line = True
    
    3309
    +
    
    3188 3310
     ------------------------------------------------------------------------
    
    3189 3311
     section "Concurrency primitives"
    
    3190 3312
     ------------------------------------------------------------------------
    

  • compiler/GHC/StgToCmm/Prim.hs
    ... ... @@ -1714,6 +1714,14 @@ emitPrimOp cfg primop =
    1714 1714
       DelayOp -> alwaysExternal
    
    1715 1715
       WaitReadOp -> alwaysExternal
    
    1716 1716
       WaitWriteOp -> alwaysExternal
    
    1717
    +  SyncIOReadAddrOp -> alwaysExternal
    
    1718
    +  SyncIOReadByteArrayOp -> alwaysExternal
    
    1719
    +  SyncIOReadAddrAtOp -> alwaysExternal
    
    1720
    +  SyncIOReadByteArrayAtOp -> alwaysExternal
    
    1721
    +  SyncIOWriteAddrOp -> alwaysExternal
    
    1722
    +  SyncIOWriteByteArrayOp -> alwaysExternal
    
    1723
    +  SyncIOWriteAddrAtOp -> alwaysExternal
    
    1724
    +  SyncIOWriteByteArrayAtOp -> alwaysExternal
    
    1717 1725
       ForkOp -> alwaysExternal
    
    1718 1726
       ForkOnOp -> alwaysExternal
    
    1719 1727
       KillThreadOp -> alwaysExternal
    

  • hadrian/src/Oracles/Flag.hs
    ... ... @@ -37,6 +37,7 @@ data Flag = CrossCompiling
    37 37
               | UseLibdl
    
    38 38
               | UseLibbfd
    
    39 39
               | UseLibpthread
    
    40
    +          | UseLiburing
    
    40 41
               | NeedLibatomic
    
    41 42
               | UseGhcToolchain
    
    42 43
     
    
    ... ... @@ -61,6 +62,7 @@ flag f = do
    61 62
                 UseLibdl             -> "use-lib-dl"
    
    62 63
                 UseLibbfd            -> "use-lib-bfd"
    
    63 64
                 UseLibpthread        -> "use-lib-pthread"
    
    65
    +            UseLiburing          -> "use-lib-uring"
    
    64 66
                 NeedLibatomic        -> "need-libatomic"
    
    65 67
                 UseGhcToolchain      -> "use-ghc-toolchain"
    
    66 68
         value <- lookupSystemConfig key
    

  • rts/IOManager.c
    ... ... @@ -38,6 +38,11 @@
    38 38
     #include "posix/Timeout.h"
    
    39 39
     #endif
    
    40 40
     
    
    41
    +#if defined(IOMGR_ENABLED_URING)
    
    42
    +#include "posix/URing.h"
    
    43
    +#include "posix/Timeout.h"
    
    44
    +#endif
    
    45
    +
    
    41 46
     #if defined(IOMGR_ENABLED_MIO_POSIX)
    
    42 47
     #include "posix/Signals.h"
    
    43 48
     #include "Prelude.h"
    
    ... ... @@ -114,6 +119,14 @@ parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag)
    114 119
             return IOManagerAvailable;
    
    115 120
     #else
    
    116 121
             return IOManagerUnavailable;
    
    122
    +#endif
    
    123
    +    }
    
    124
    +    else if (strcmp("uring", iomgrstr) == 0) {
    
    125
    +#if defined(IOMGR_ENABLED_URING)
    
    126
    +        *flag = IO_MNGR_FLAG_URING;
    
    127
    +        return IOManagerAvailable;
    
    128
    +#else
    
    129
    +        return IOManagerUnavailable;
    
    117 130
     #endif
    
    118 131
         }
    
    119 132
         else if (strcmp("mio", iomgrstr) == 0) {
    
    ... ... @@ -218,6 +231,8 @@ void selectIOManager(void)
    218 231
                 iomgr_type = IO_MANAGER_SELECT;
    
    219 232
     #elif defined(IOMGR_DEFAULT_NON_THREADED_POLL)
    
    220 233
                 iomgr_type = IO_MANAGER_POLL;
    
    234
    +#elif defined(IOMGR_DEFAULT_NON_THREADED_URING)
    
    235
    +            iomgr_type = IO_MANAGER_URING;
    
    221 236
     #elif defined(IOMGR_DEFAULT_NON_THREADED_WINIO)
    
    222 237
                 iomgr_type = IO_MANAGER_WINIO;
    
    223 238
     #elif defined(IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY)
    
    ... ... @@ -240,6 +255,12 @@ void selectIOManager(void)
    240 255
                 break;
    
    241 256
     #endif
    
    242 257
     
    
    258
    +#if defined(IOMGR_ENABLED_URING)
    
    259
    +        case IO_MNGR_FLAG_URING:
    
    260
    +            iomgr_type = IO_MANAGER_URING;
    
    261
    +            break;
    
    262
    +#endif
    
    263
    +
    
    243 264
     #if defined(IOMGR_ENABLED_MIO_POSIX)
    
    244 265
             case IO_MNGR_FLAG_MIO:
    
    245 266
                 iomgr_type = IO_MANAGER_MIO_POSIX;
    
    ... ... @@ -282,6 +303,10 @@ char * showIOManager(void)
    282 303
             case IO_MANAGER_POLL:
    
    283 304
                 return "poll";
    
    284 305
     #endif
    
    306
    +#if defined(IOMGR_ENABLED_URING)
    
    307
    +        case IO_MANAGER_URING:
    
    308
    +            return "uring";
    
    309
    +#endif
    
    285 310
     #if defined(IOMGR_ENABLED_MIO_POSIX)
    
    286 311
             case IO_MANAGER_MIO_POSIX:
    
    287 312
                 return "mio";
    
    ... ... @@ -335,6 +360,12 @@ void initCapabilityIOManager(Capability *cap)
    335 360
                 break;
    
    336 361
     #endif
    
    337 362
     
    
    363
    +#if defined(IOMGR_ENABLED_URING)
    
    364
    +        case IO_MANAGER_URING:
    
    365
    +            initCapabilityIOManagerURing(cap, iomgr);
    
    366
    +            break;
    
    367
    +#endif
    
    368
    +
    
    338 369
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    339 370
             case IO_MANAGER_WIN32_LEGACY:
    
    340 371
                 iomgr->blocked_queue_hd = END_TSO_QUEUE;
    
    ... ... @@ -378,6 +409,11 @@ void initIOManager(void)
    378 409
                 break;
    
    379 410
     #endif
    
    380 411
     
    
    412
    +#if defined(IOMGR_ENABLED_URING)
    
    413
    +        case IO_MANAGER_URING:
    
    414
    +            break;
    
    415
    +#endif
    
    416
    +
    
    381 417
     #if defined(IOMGR_ENABLED_MIO_POSIX)
    
    382 418
             case IO_MANAGER_MIO_POSIX:
    
    383 419
                 /* Posix implementation in posix/Signals.c
    
    ... ... @@ -441,6 +477,18 @@ initIOManagerAfterFork(Capability **pcap)
    441 477
                  */
    
    442 478
                 ioManagerStartCap(pcap);
    
    443 479
                 break;
    
    480
    +#endif
    
    481
    +#if defined(IOMGR_ENABLED_URING)
    
    482
    +        //TODO: So currently there's no per-cap re-initialisation
    
    483
    +        // except for cap0.
    
    484
    +        case IO_MANAGER_URING:
    
    485
    +        barf("IOManager.c:initIOManagerAfterFork:URing:TODO");
    
    486
    +        {
    
    487
    +            for (unsigned int i = 0; i < getNumCapabilities(); i++) {
    
    488
    +                Capability *cap = getCapability(i);
    
    489
    +                initCapabilityIOManagerAfterForkURing(cap, cap->iomgr);
    
    490
    +            }
    
    491
    +        }
    
    444 492
     #endif
    
    445 493
             /* The IO_MANAGER_SELECT needs no initialisation */
    
    446 494
             /* The IO_MANAGER_POLL needs no initialisation */
    
    ... ... @@ -452,7 +500,13 @@ initIOManagerAfterFork(Capability **pcap)
    452 500
     }
    
    453 501
     
    
    454 502
     
    
    455
    -/* Called from setNumCapabilities.
    
    503
    +/* Called from setNumCapabilities, after all other per-capability changes have
    
    504
    + * been made. When the scheduler increases the number of capabilities, it
    
    505
    + * (indirectly) calls initCapabilityIOManager, for each new capability. So this
    
    506
    + * notification is only needed by I/O managers that need a global hook (not
    
    507
    + * per-cap), and/or need to be notified of there being fewer (as well as more)
    
    508
    + * capabilities. There is no per-capability notification for disabling a
    
    509
    + * capability (which occurs when the number of capabilities is reduced).
    
    456 510
      */
    
    457 511
     void notifyIOManagerCapabilitiesChanged(Capability **pcap)
    
    458 512
     {
    
    ... ... @@ -583,6 +637,18 @@ void markCapabilityIOManager(evac_fn evac, void *user, Capability *cap)
    583 637
             }
    
    584 638
     #endif
    
    585 639
     
    
    640
    +#if defined(IOMGR_ENABLED_URING)
    
    641
    +        case IO_MANAGER_URING:
    
    642
    +        {
    
    643
    +            CapIOManager *iomgr = cap->iomgr;
    
    644
    +            markClosureTable(evac, user, &iomgr->aiop_table);
    
    645
    +            evac(user, (StgClosure **)(void *)&iomgr->overflow_tso_q_hd);
    
    646
    +            evac(user, (StgClosure **)(void *)&iomgr->overflow_tso_q_tl);
    
    647
    +            evac(user, (StgClosure **)(void *)&iomgr->timeout_queue);
    
    648
    +            break;
    
    649
    +        }
    
    650
    +#endif
    
    651
    +
    
    586 652
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    587 653
             case IO_MANAGER_WIN32_LEGACY:
    
    588 654
             {
    
    ... ... @@ -608,8 +674,15 @@ void scavengeTSOIOManager(StgTSO *tso)
    608 674
                  * both of these are not GC pointers, so there is nothing to do.
    
    609 675
                  */
    
    610 676
     
    
    677
    +#if defined(IOMGR_ENABLED_POLL) \
    
    678
    + || defined(IOMGR_ENABLED_URING)
    
    679
    + 
    
    611 680
     #if defined(IOMGR_ENABLED_POLL)
    
    612 681
             case IO_MANAGER_POLL:
    
    682
    +#endif
    
    683
    +#if defined(IOMGR_ENABLED_URING)
    
    684
    +        case IO_MANAGER_URING:
    
    685
    +#endif
    
    613 686
                 /* BlockedOn{Read,Write} uses block_info.aiop
    
    614 687
                  * BlockedOnDelay        uses block_info.timeout
    
    615 688
                  * both of these are heap allocated, so we can do the same in all
    
    ... ... @@ -669,6 +742,12 @@ bool anyPendingTimeoutsOrIO(Capability *cap)
    669 742
                 return anyPendingTimeoutsOrIOPoll(cap->iomgr);
    
    670 743
     #endif
    
    671 744
     
    
    745
    +#if defined(IOMGR_ENABLED_URING)
    
    746
    +        case IO_MANAGER_URING:
    
    747
    +            return anyPendingTimeoutsOrIOURing(cap->iomgr);
    
    748
    +        //TODO: do we want to share code with poll here?
    
    749
    +#endif
    
    750
    +
    
    672 751
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    673 752
             case IO_MANAGER_WIN32_LEGACY:
    
    674 753
             {
    
    ... ... @@ -732,6 +811,12 @@ void pollCompletedTimeoutsOrIO(Capability *cap)
    732 811
               break;
    
    733 812
     #endif
    
    734 813
     
    
    814
    +#if defined(IOMGR_ENABLED_URING)
    
    815
    +        case IO_MANAGER_URING:
    
    816
    +          pollCompletedTimeoutsOrIOURing(cap);
    
    817
    +          break;
    
    818
    +#endif
    
    819
    +
    
    735 820
     #if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
    
    736 821
        (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
    
    737 822
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    ... ... @@ -765,6 +850,12 @@ void awaitCompletedTimeoutsOrIO(Capability *cap)
    765 850
               break;
    
    766 851
     #endif
    
    767 852
     
    
    853
    +#if defined(IOMGR_ENABLED_URING)
    
    854
    +        case IO_MANAGER_URING:
    
    855
    +          awaitCompletedTimeoutsOrIOURing(cap);
    
    856
    +          break;
    
    857
    +#endif
    
    858
    +
    
    768 859
     #if defined(IOMGR_ENABLED_WIN32_LEGACY) || \
    
    769 860
        (defined(IOMGR_ENABLED_WINIO) && !defined(THREADED_RTS))
    
    770 861
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    ... ... @@ -805,8 +896,11 @@ int syncIOWaitReady(Capability *cap,
    805 896
     #endif
    
    806 897
     #if defined(IOMGR_ENABLED_POLL)
    
    807 898
             case IO_MANAGER_POLL:
    
    808
    -            ASSERT(tso->why_blocked == NotBlocked);
    
    809 899
                 return syncIOWaitReadyPoll(cap, tso, rw, fd);
    
    900
    +#endif
    
    901
    +#if defined(IOMGR_ENABLED_URING)
    
    902
    +        case IO_MANAGER_URING:
    
    903
    +            return syncIOWaitReadyURing(cap, tso, rw, fd);
    
    810 904
     #endif
    
    811 905
             default:
    
    812 906
                 barf("waitRead# / waitWrite# not available for current I/O manager");
    
    ... ... @@ -814,6 +908,91 @@ int syncIOWaitReady(Capability *cap,
    814 908
     }
    
    815 909
     
    
    816 910
     
    
    911
    +int syncIOReadWrite(Capability *cap, StgTSO *tso,
    
    912
    +                    IOReadOrWrite rw, HsInt fd,
    
    913
    +                    StgClosure *live, void *buf,
    
    914
    +                    HsWord len)
    
    915
    +{
    
    916
    +    debugTrace(DEBUG_iomanager,
    
    917
    +               "thread %ld %s fd %d", (long) tso->id,
    
    918
    +               rw == IORead ? "reading from" : "writing to", (int) fd);
    
    919
    +    ASSERT(tso->why_blocked == NotBlocked);
    
    920
    +    switch (iomgr_type) {
    
    921
    +#if defined(IOMGR_ENABLED_URING)
    
    922
    +        case IO_MANAGER_URING:
    
    923
    +            return syncIOReadWriteURing(cap, tso, rw, (int)fd, live, buf,
    
    924
    +                                        (size_t)len, (off_t)(-1));
    
    925
    +            /* off_t = -1 means use and update the file pointer. */
    
    926
    +#endif
    
    927
    +/*
    
    928
    +#if defined(IOMGR_ENABLED_SELECT) \
    
    929
    + || defined(IOMGR_ENABLED_POLL)
    
    930
    +#if defined(IOMGR_ENABLED_SELECT)
    
    931
    +        case IO_MANAGER_SELECT:
    
    932
    +#endif
    
    933
    +#if defined(IOMGR_ENABLED_POLL)
    
    934
    +        case IO_MANAGER_POLL:
    
    935
    +#endif
    
    936
    +        {
    
    937
    +            if (rw == IORead) {
    
    938
    +                read((int) fd, buf, (size_t) len);
    
    939
    +            } else {
    
    940
    +                write((int) fd, buf, (size_t) len);
    
    941
    +            }
    
    942
    +            //TODO: Ugg! We need to tell the primop to return synchonrously!
    
    943
    +            //Need to change the return type. Should use an out arg for the
    
    944
    +            //GC alloc retry.
    
    945
    +        }
    
    946
    +#endif
    
    947
    +*/
    
    948
    +        default:
    
    949
    +            barf("syncIORead/Write# not available for current I/O manager");
    
    950
    +    }
    
    951
    +}
    
    952
    +
    
    953
    +int syncIOReadWriteAt(Capability *cap, StgTSO *tso,
    
    954
    +                      IOReadOrWrite rw, HsInt fd,
    
    955
    +                      StgClosure *live, void *buf,
    
    956
    +                      HsWord len, HsInt64 off)
    
    957
    +{
    
    958
    +    debugTrace(DEBUG_iomanager,
    
    959
    +               "thread %ld %s fd %d", (long) tso->id,
    
    960
    +               rw == IORead ? "reading from" : "writing to", (int) fd);
    
    961
    +    ASSERT(tso->why_blocked == NotBlocked);
    
    962
    +    switch (iomgr_type) {
    
    963
    +#if defined(IOMGR_ENABLED_URING)
    
    964
    +        case IO_MANAGER_URING:
    
    965
    +            return syncIOReadWriteURing(cap, tso, rw,
    
    966
    +                                        (int)fd, live, buf,
    
    967
    +                                        (size_t)len, (off_t)off);
    
    968
    +#endif
    
    969
    +/*
    
    970
    +#if defined(IOMGR_ENABLED_SELECT) \
    
    971
    + || defined(IOMGR_ENABLED_POLL)
    
    972
    +#if defined(IOMGR_ENABLED_SELECT)
    
    973
    +        case IO_MANAGER_SELECT:
    
    974
    +#endif
    
    975
    +#if defined(IOMGR_ENABLED_POLL)
    
    976
    +        case IO_MANAGER_POLL:
    
    977
    +#endif
    
    978
    +        {
    
    979
    +            if (rw == IORead) {
    
    980
    +                pread((int)fd, buf, (size_t)len, (off_t)off);
    
    981
    +            } else {
    
    982
    +                pwrite((int)fd, buf, (size_t)len, (off_t)off);
    
    983
    +            }
    
    984
    +            //TODO: Ugg! We need to tell the primop to return synchonrously!
    
    985
    +            //Need to change the return type. Should use an out arg for the
    
    986
    +            //GC alloc retry.
    
    987
    +        }
    
    988
    +#endif
    
    989
    +*/
    
    990
    +        default:
    
    991
    +            barf("syncIORead/Write# not available for current I/O manager");
    
    992
    +    }
    
    993
    +}
    
    994
    +
    
    995
    +
    
    817 996
     void syncIOCancel(Capability *cap, StgTSO *tso)
    
    818 997
     {
    
    819 998
         debugTrace(DEBUG_iomanager, "cancelling I/O for thread %ld", (long) tso->id);
    
    ... ... @@ -829,6 +1008,11 @@ void syncIOCancel(Capability *cap, StgTSO *tso)
    829 1008
                 syncIOCancelPoll(cap, tso);
    
    830 1009
                 break;
    
    831 1010
     #endif
    
    1011
    +#if defined(IOMGR_ENABLED_URING)
    
    1012
    +        case IO_MANAGER_URING:
    
    1013
    +            syncIOCancelURing(cap, tso);
    
    1014
    +            break;
    
    1015
    +#endif
    
    832 1016
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    833 1017
             case IO_MANAGER_WIN32_LEGACY:
    
    834 1018
                 removeThreadFromDeQueue(cap, &cap->iomgr->blocked_queue_hd,
    
    ... ... @@ -862,8 +1046,15 @@ int syncDelay(Capability *cap, StgTSO *tso, HsInt us_delay)
    862 1046
                 return 0;
    
    863 1047
             }
    
    864 1048
     #endif
    
    1049
    +#if defined(IOMGR_ENABLED_POLL) \
    
    1050
    + || defined(IOMGR_ENABLED_URING)
    
    1051
    +
    
    865 1052
     #if defined(IOMGR_ENABLED_POLL)
    
    866 1053
             case IO_MANAGER_POLL:
    
    1054
    +#endif
    
    1055
    +#if defined(IOMGR_ENABLED_URING)
    
    1056
    +        case IO_MANAGER_URING:
    
    1057
    +#endif
    
    867 1058
                 return syncDelayTimeout(cap, tso, us_delay);
    
    868 1059
     #endif
    
    869 1060
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    ... ... @@ -903,8 +1094,15 @@ void syncDelayCancel(Capability *cap, StgTSO *tso)
    903 1094
                 removeThreadFromQueue(cap, &cap->iomgr->sleeping_queue, tso);
    
    904 1095
                 break;
    
    905 1096
     #endif
    
    1097
    +#if defined(IOMGR_ENABLED_POLL) \
    
    1098
    + || defined(IOMGR_ENABLED_URING)
    
    1099
    +
    
    906 1100
     #if defined(IOMGR_ENABLED_POLL)
    
    907 1101
             case IO_MANAGER_POLL:
    
    1102
    +#endif
    
    1103
    +#if defined(IOMGR_ENABLED_URING)
    
    1104
    +        case IO_MANAGER_URING:
    
    1105
    +#endif
    
    908 1106
                 syncDelayCancelTimeout(cap, tso);
    
    909 1107
                 break;
    
    910 1108
     #endif
    

  • rts/IOManager.h
    ... ... @@ -46,6 +46,9 @@
    46 46
     #if defined(IOMGR_BUILD_POLL) && !defined(THREADED_RTS)
    
    47 47
         #define IOMGR_ENABLED_POLL
    
    48 48
     #endif
    
    49
    +#if defined(IOMGR_BUILD_URING) && !defined(THREADED_RTS)
    
    50
    +    #define IOMGR_ENABLED_URING
    
    51
    +#endif
    
    49 52
     #if defined(IOMGR_BUILD_MIO) && defined(THREADED_RTS)
    
    50 53
     /* For MIO, it is really two separate I/O manager implementations: one for
    
    51 54
      * Windows and one for non-Windows. This is clear from both the C code on the
    
    ... ... @@ -110,6 +113,11 @@
    110 113
     #else
    
    111 114
         #define IOMGR_ENABLED_STR_POLL ""
    
    112 115
     #endif
    
    116
    +#if defined(IOMGR_ENABLED_URING)
    
    117
    +    #define IOMGR_ENABLED_STR_URING " uring"
    
    118
    +#else
    
    119
    +    #define IOMGR_ENABLED_STR_URING ""
    
    120
    +#endif
    
    113 121
     #if defined(IOMGR_ENABLED_MIO_POSIX) || defined(IOMGR_ENABLED_MIO_WIN32)
    
    114 122
         #define IOMGR_ENABLED_STR_MIO " mio"
    
    115 123
     #else
    
    ... ... @@ -128,6 +136,7 @@
    128 136
     #define IOMGRS_ENABLED_STR \
    
    129 137
               IOMGR_ENABLED_STR_SELECT \
    
    130 138
               IOMGR_ENABLED_STR_POLL \
    
    139
    +          IOMGR_ENABLED_STR_URING \
    
    131 140
               IOMGR_ENABLED_STR_MIO \
    
    132 141
               IOMGR_ENABLED_STR_WINIO \
    
    133 142
               IOMGR_ENABLED_STR_WIN32_LEGACY
    
    ... ... @@ -143,6 +152,9 @@ typedef enum {
    143 152
     #if defined(IOMGR_ENABLED_POLL)
    
    144 153
         IO_MANAGER_POLL,
    
    145 154
     #endif
    
    155
    +#if defined(IOMGR_ENABLED_URING)
    
    156
    +    IO_MANAGER_URING,
    
    157
    +#endif
    
    146 158
     #if defined(IOMGR_ENABLED_MIO_POSIX)
    
    147 159
         IO_MANAGER_MIO_POSIX,
    
    148 160
     #endif
    
    ... ... @@ -298,7 +310,7 @@ void scavengeTSOIOManager(StgTSO *tso);
    298 310
     /* Several code paths are almost identical between read and write paths. In
    
    299 311
      * such cases we use a shared code path with an enum to say which we're doing.
    
    300 312
      */
    
    301
    -typedef enum { IORead, IOWrite } IOReadOrWrite;
    
    313
    +typedef enum { IORead = 0, IOWrite = 1 } IOReadOrWrite;
    
    302 314
     
    
    303 315
     /* Synchronous operations: I/O and delays. As synchronous operations they
    
    304 316
      * necessarily operate on threads. The thread is suspended until the operation
    
    ... ... @@ -310,7 +322,18 @@ typedef enum { IORead, IOWrite } IOReadOrWrite;
    310 322
      * GC to free up at least n words and then retry the operation.
    
    311 323
      */
    
    312 324
     
    
    313
    -int syncIOWaitReady(Capability *cap, StgTSO *tso, IOReadOrWrite rw, HsInt fd);
    
    325
    +int syncIOWaitReady(Capability *cap, StgTSO *tso,
    
    326
    +                    IOReadOrWrite rw, HsInt fd);
    
    327
    +
    
    328
    +int syncIOReadWrite(Capability *cap, StgTSO *tso,
    
    329
    +                    IOReadOrWrite rw, HsInt fd,
    
    330
    +                    StgClosure *live, void *buf,
    
    331
    +                    HsWord len);
    
    332
    +
    
    333
    +int syncIOReadWriteAt(Capability *cap, StgTSO *tso,
    
    334
    +                      IOReadOrWrite rw, HsInt fd,
    
    335
    +                      StgClosure *live, void *buf,
    
    336
    +                      HsWord len, HsInt64 off);
    
    314 337
     
    
    315 338
     void syncIOCancel(Capability *cap, StgTSO *tso);
    
    316 339
     
    

  • rts/IOManagerInternals.h
    ... ... @@ -42,7 +42,8 @@ struct _CapIOManager {
    42 42
         StgTSO *sleeping_queue;
    
    43 43
     #endif
    
    44 44
     
    
    45
    -#if defined(IOMGR_ENABLED_POLL)
    
    45
    +#if defined(IOMGR_ENABLED_POLL) \
    
    46
    + || defined(IOMGR_ENABLED_URING)
    
    46 47
         /* AIOP and timeout collections shared by several I/O manager impls */
    
    47 48
         ClosureTable     aiop_table;
    
    48 49
         StgTimeoutQueue *timeout_queue;
    
    ... ... @@ -53,6 +54,54 @@ struct _CapIOManager {
    53 54
         struct pollfd *aiop_poll_table;
    
    54 55
     #endif
    
    55 56
     
    
    57
    +#if defined(IOMGR_ENABLED_URING)
    
    58
    +    /* io_uring library structure */
    
    59
    +    struct io_uring *uring;
    
    60
    +
    
    61
    +    /* The number of operations submitted (by Haskell threads to the I/O
    
    62
    +       manager) and not yet notified of completion. */
    
    63
    +    int n_submitted_b;  /* for blocking operations */
    
    64
    +    int n_submitted_nb; /* for non-blocking operations */
    
    65
    +
    
    66
    +    /* The number of operations pending in the submission queue, but not yet
    
    67
    +       submitted to the kernel (so not in-flight). */
    
    68
    +    int n_prepared_b;  /* for blocking operations */
    
    69
    +    int n_prepared_nb; /* for non-blocking operations */
    
    70
    +
    
    71
    +    /* The number of operations submitted to the kernel but where the
    
    72
    +       corresponding completion has not yet been processed. */
    
    73
    +    int n_inflight_b;  /* for blocking operations */
    
    74
    +    int n_inflight_nb; /* for non-blocking operations */
    
    75
    +
    
    76
    +    /* The limit on the number of operations we allow to be in-flight */
    
    77
    +    int limit_inflight_b;  /* for blocking operations */
    
    78
    +    int limit_inflight_nb; /* for non-blocking operations */
    
    79
    +
    
    80
    +    /* The number of operations pending in the overflow queue (so not in the
    
    81
    +       submission queue or in flight) */
    
    82
    +    /* no overflow for blocking operations */
    
    83
    +    int n_overflow_nb;  /* for non-blocking operations */
    
    84
    +    
    
    85
    +    /* Invariants:
    
    86
    +         n_submitted_b  = n_prepared_b  + n_inflight_b
    
    87
    +         n_submitted_nb = n_prepared_nb + n_inflight_nb + n_overflow_nb
    
    88
    +         n_prepared_b + n_prepared_nb <= size of submission queue
    
    89
    +     */
    
    90
    +
    
    91
    +    /* A queue of threads blocked on I/O submission and a parallel queue of
    
    92
    +     * their corresponding SQEs. This is only used when there are more pending
    
    93
    +     * (non-blocking) I/O operations than the inflight limit.
    
    94
    +     */
    
    95
    +    StgTSO *overflow_tso_q_hd, *overflow_tso_q_tl;
    
    96
    +    struct overflow_sqe_q_t {
    
    97
    +        struct io_uring_sqe     *sqe;
    
    98
    +        struct overflow_sqe_q_t *next;
    
    99
    +#if defined(DEBUG)
    
    100
    +        StgThreadID              tid;
    
    101
    +#endif
    
    102
    +    } *overflow_sqe_q_hd, *overflow_sqe_q_tl;
    
    103
    +#endif
    
    104
    +
    
    56 105
     #if defined(IOMGR_ENABLED_WIN32_LEGACY)
    
    57 106
         /* Thread queue for threads blocked on I/O completion. */
    
    58 107
         StgTSO *blocked_queue_hd;
    

  • rts/PrimOps.cmm
    ... ... @@ -2602,6 +2602,108 @@ stg_delayzh ( W_ us_delay )
    2602 2602
         }
    
    2603 2603
     }
    
    2604 2604
     
    
    2605
    +/*
    
    2606
    +int syncIOReadWrite(Capability *cap, StgTSO *tso,
    
    2607
    +                    IOReadOrWrite rw, HsInt fd,
    
    2608
    +                    StgClosure *live, void *buf,
    
    2609
    +                    HsWord len);
    
    2610
    +
    
    2611
    +int syncIOReadWriteAt(Capability *cap, StgTSO *tso,
    
    2612
    +                      IOReadOrWrite rw, HsInt fd,
    
    2613
    +                      StgClosure *live, void *buf,
    
    2614
    +                      HsWord len, HsInt64 off);
    
    2615
    +*/
    
    2616
    +
    
    2617
    +#define SYNCIO_BLOCK_OR_GC(fail)                                        \
    
    2618
    +    if (fail == 0) (likely: True) {                                     \
    
    2619
    +        jump stg_block_noregs();                                        \
    
    2620
    +    } else {                                                            \
    
    2621
    +        /* TODO: should invoke GC, requesting 'fail' words, then retry  \
    
    2622
    +           see: https://gitlab.haskell.org/ghc/ghc/-/issues/24105 */    \
    
    2623
    +        jump stg_raisezh(ghczminternal_GHCziInternalziIOziException_heapOverflow_closure); \
    
    2624
    +    }
    
    2625
    +    //TODO: need to handle returning synchonrously, for non-uring I/O managers.
    
    2626
    +    //or if uring optimises the read case.
    
    2627
    +
    
    2628
    +stg_syncIOReadAddrzh ( W_ fd, W_ buf, W_ len )
    
    2629
    +{
    
    2630
    +    W_ fail; /* Request this many words on heap alloc failure. */
    
    2631
    +    (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
    
    2632
    +                                   0::I32 /* IORead */, fd,
    
    2633
    +                                   stg_ASYNCIO_LIVE0_closure, buf,
    
    2634
    +                                   len);
    
    2635
    +    SYNCIO_BLOCK_OR_GC(fail);
    
    2636
    +}
    
    2637
    +
    
    2638
    +stg_syncIOReadByteArrayzh ( W_ fd, P_ buf, W_ boff, W_ len )
    
    2639
    +{
    
    2640
    +    W_ fail; /* Request this many words on heap alloc failure. */
    
    2641
    +    (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
    
    2642
    +                                   0::I32 /* IORead */, fd,
    
    2643
    +                                   buf, buf + SIZEOF_StgArrBytes + boff,
    
    2644
    +                                   len);
    
    2645
    +    SYNCIO_BLOCK_OR_GC(fail);
    
    2646
    +}
    
    2647
    +
    
    2648
    +stg_syncIOReadAddrAtzh ( W_ fd, W_ buf, W_ len, I64 foff )
    
    2649
    +{
    
    2650
    +    W_ fail; /* Request this many words on heap alloc failure. */
    
    2651
    +    (fail) = ccall syncIOReadWriteAt(MyCapability() "ptr", CurrentTSO "ptr",
    
    2652
    +                                     0::I32 /* IORead */, fd,
    
    2653
    +                                     stg_ASYNCIO_LIVE0_closure, buf,
    
    2654
    +                                     len, foff);
    
    2655
    +    SYNCIO_BLOCK_OR_GC(fail);
    
    2656
    +}
    
    2657
    +
    
    2658
    +stg_syncIOReadByteArrayAtzh ( W_ fd, P_ buf, W_ boff, W_ len, I64 foff )
    
    2659
    +{
    
    2660
    +    W_ fail; /* Request this many words on heap alloc failure. */
    
    2661
    +    (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
    
    2662
    +                                   0::I32 /* IORead */, fd,
    
    2663
    +                                   buf, buf + SIZEOF_StgArrBytes + boff,
    
    2664
    +                                   len, foff);
    
    2665
    +    SYNCIO_BLOCK_OR_GC(fail);    
    
    2666
    +}
    
    2667
    +
    
    2668
    +stg_syncIOWriteAddrzh ( W_ fd, W_ buf, W_ len )
    
    2669
    +{
    
    2670
    +    W_ fail; /* Request this many words on heap alloc failure. */
    
    2671
    +    (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
    
    2672
    +                                   1::I32 /* IOWrite */, fd,
    
    2673
    +                                   stg_ASYNCIO_LIVE0_closure, buf,
    
    2674
    +                                   len);
    
    2675
    +    SYNCIO_BLOCK_OR_GC(fail);
    
    2676
    +}
    
    2677
    +
    
    2678
    +stg_syncIOWriteByteArrayzh ( W_ fd, P_ buf, W_ boff, W_ len )
    
    2679
    +{
    
    2680
    +    W_ fail; /* Request this many words on heap alloc failure. */
    
    2681
    +    (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
    
    2682
    +                                   1::I32 /* IOWrite */, fd,
    
    2683
    +                                   buf, buf + SIZEOF_StgArrBytes + boff,
    
    2684
    +                                   len);
    
    2685
    +    SYNCIO_BLOCK_OR_GC(fail);
    
    2686
    +}
    
    2687
    +
    
    2688
    +stg_syncIOWriteAddrAtzh ( W_ fd, W_ buf, W_ len, I64 foff )
    
    2689
    +{
    
    2690
    +    W_ fail; /* Request this many words on heap alloc failure. */
    
    2691
    +    (fail) = ccall syncIOReadWriteAt(MyCapability() "ptr", CurrentTSO "ptr",
    
    2692
    +                                     1::I32 /* IOWrite */, fd,
    
    2693
    +                                     stg_ASYNCIO_LIVE0_closure, buf,
    
    2694
    +                                     len, foff);
    
    2695
    +    SYNCIO_BLOCK_OR_GC(fail);
    
    2696
    +}
    
    2697
    +
    
    2698
    +stg_syncIOWriteByteArrayAtzh ( W_ fd, P_ buf, W_ boff, W_ len, I64 foff )
    
    2699
    +{
    
    2700
    +    W_ fail; /* Request this many words on heap alloc failure. */
    
    2701
    +    (fail) = ccall syncIOReadWrite(MyCapability() "ptr", CurrentTSO "ptr",
    
    2702
    +                                   1::I32 /* IOWrite */, fd,
    
    2703
    +                                   buf, buf + SIZEOF_StgArrBytes + boff,
    
    2704
    +                                   len, foff);
    
    2705
    +    SYNCIO_BLOCK_OR_GC(fail);    
    
    2706
    +}
    
    2605 2707
     
    
    2606 2708
     #if defined(mingw32_HOST_OS)
    
    2607 2709
     stg_asyncReadzh ( W_ fd, W_ is_sock, W_ len, W_ buf )
    

  • rts/configure.ac
    ... ... @@ -397,6 +397,23 @@ GHC_IOMANAGER_ENABLE([poll], [EnableIOManagerPoll], [IOMGR_BUILD_POLL],
    397 397
                 #include <poll.h>])
    
    398 398
        fi])
    
    399 399
     
    
    400
    +GHC_IOMANAGER_ENABLE([uring], [EnableIOManagerURing], [IOMGR_BUILD_URING],
    
    401
    +  [if test "$HostOS" = "linux"; then
    
    402
    +       AC_CHECK_HEADER([liburing.h], [HaveLiburingH=YES],
    
    403
    +           [AC_MSG_WARN([liburing.h is required by the uring I/O manager])],[])
    
    404
    +       AC_CHECK_LIB([uring], [io_uring_check_version], [HaveLiburing=YES],
    
    405
    +           [AC_MSG_WARN([liburing is required by the uring I/O manager])], [])
    
    406
    +       if test "$HaveLiburingH" = "YES" && test "$HaveLiburing" = YES; then
    
    407
    +           EnableIOManagerURing=YES
    
    408
    +           LinkLiburing=uring
    
    409
    +       else
    
    410
    +           EnableIOManagerURing=NO
    
    411
    +       fi
    
    412
    +   else
    
    413
    +       EnableIOManagerURing=NO
    
    414
    +   fi])
    
    415
    +AC_SUBST(LinkLiburing)
    
    416
    +
    
    400 417
     GHC_IOMANAGER_ENABLE([mio], [EnableIOManagerMIO], [IOMGR_BUILD_MIO],
    
    401 418
       [EnableIOManagerMIO=YES])
    
    402 419
     
    
    ... ... @@ -415,6 +432,7 @@ if test "$HostOS" = "mingw32"; then
    415 432
       GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [winio], [EnableIOManagerWinIO])
    
    416 433
       GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [mio], [EnableIOManagerMIO])
    
    417 434
     else
    
    435
    +  GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [uring], [EnableIOManagerURing])
    
    418 436
       GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [select], [EnableIOManagerSelect])
    
    419 437
       GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [poll], [EnableIOManagerPoll])
    
    420 438
       GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [mio], [EnableIOManagerMIO])
    
    ... ... @@ -432,6 +450,9 @@ GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
    432 450
     GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
    
    433 451
                                     [poll], [IOMGR_DEFAULT_NON_THREADED_POLL])
    
    434 452
     
    
    453
    +GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
    
    454
    +                                [uring], [IOMGR_DEFAULT_NON_THREADED_URING])
    
    455
    +
    
    435 456
     GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
    
    436 457
                                     [winio], [IOMGR_DEFAULT_NON_THREADED_WINIO])
    
    437 458
     
    

  • rts/include/rts/Constants.h
    ... ... @@ -271,7 +271,9 @@
    271 271
        by tryWakeupThread() */
    
    272 272
     #define ThreadMigrating     13
    
    273 273
     
    
    274
    -/* Next number is 15.  */
    
    274
    +#define BlockedOnIOSubmission 15
    
    275
    +
    
    276
    +/* Next number is 16.  */
    
    275 277
     
    
    276 278
     /*
    
    277 279
      * These constants are returned to the scheduler by a thread that has
    

  • rts/include/rts/Flags.h
    ... ... @@ -247,6 +247,7 @@ typedef enum _IO_MANAGER_FLAG {
    247 247
         /* All other choices pick only the requested one, with no fallback. */
    
    248 248
         IO_MNGR_FLAG_SELECT,          /* Unix only,    non-threaded RTS only */
    
    249 249
         IO_MNGR_FLAG_POLL,            /* Unix only,    non-threaded RTS only */
    
    250
    +    IO_MNGR_FLAG_URING,           /* Linux only,   non-threaded RTS only */
    
    250 251
         IO_MNGR_FLAG_MIO,             /* cross-platform,   threaded RTS only */
    
    251 252
         IO_MNGR_FLAG_WINIO,           /* Windows only                        */
    
    252 253
         IO_MNGR_FLAG_WIN32_LEGACY,    /* Windows only, non-threaded RTS only */
    
    ... ... @@ -272,6 +273,8 @@ typedef struct _MISC_FLAGS {
    272 273
                                       * for the linker, NULL ==> off */
    
    273 274
         IO_MANAGER_FLAG ioManager;   /* The I/O manager to use.  */
    
    274 275
         uint32_t numIoWorkerThreads; /* Number of I/O worker threads to use.  */
    
    276
    +    uint32_t io_uring_sq_entries; /* io_uring submission queue size */
    
    277
    +    uint32_t io_uring_cq_entries; /* io_uring completion queue size */
    
    275 278
     } MISC_FLAGS;
    
    276 279
     
    
    277 280
     /* See Note [Synchronization of flags and base APIs] */
    

  • rts/include/rts/storage/Closures.h
    ... ... @@ -713,7 +713,13 @@ union NotifyCompletion {
    713 713
     enum NotifyCompletionType {
    
    714 714
         NotifyTSO  = 0,
    
    715 715
         NotifyMVar = 1,
    
    716
    -    NotifyTVar = 2
    
    716
    +    NotifyTVar = 2,
    
    717
    +    NotifyNone = 3
    
    718
    +    /* If a TSO receives an async exception while it's waiting on I/O then
    
    719
    +     * the TSO stops waiting but the I/O may still be outstanding, and we
    
    720
    +     * need the corresponding StgAsyncIOOp until the I/O completes. We use
    
    721
    +     * NotifyNone in this case to avoid disturbing the original TSO.
    
    722
    +     */
    
    717 723
     };
    
    718 724
     
    
    719 725
     /* A node in the leftist heap. */
    
    ... ... @@ -786,9 +792,9 @@ typedef struct {
    786 792
           // to know which capability an aiop is on.
    
    787 793
         uint16_t capno;
    
    788 794
     
    
    789
    -      // Tells us which thing the notify union above contains.
    
    790
    -      // This is a value from enum IONotify but we don't use the enum type
    
    791
    -      // here due to portability concerns for this size C enum bitfield.
    
    795
    +      // Tells us which thing the notify union above contains. This is a value
    
    796
    +      // from enum NotifyCompletionType but we don't use the enum type here
    
    797
    +      // due to portability concerns for this size C enum bitfield.
    
    792 798
         uint16_t notify_type: 2;
    
    793 799
     
    
    794 800
           // The outcome:
    
    ... ... @@ -816,3 +822,15 @@ typedef struct {
    816 822
           // We handle this in the INFO_TABLE_CONSTR decl for stg_ASYNCIOOP using
    
    817 823
           // Either32Or64Bit(4,2) for the non-pointer words.
    
    818 824
     } StgAsyncIOOp;
    
    825
    +
    
    826
    +struct io_uring_sqe;
    
    827
    +typedef struct {
    
    828
    +    StgHeader header;
    
    829
    +
    
    830
    +      // Any heap object to keep alive for the duration of the I/O operation,
    
    831
    +      // for example I/O buffers.
    
    832
    +    StgClosure *live;
    
    833
    +    
    
    834
    +    struct io_uring_sqe *sqe;
    
    835
    +} StgAsyncURingSQE;
    
    836
    +

  • rts/posix/Poll.c
    ... ... @@ -347,7 +347,7 @@ void pollCompletedTimeoutsOrIOPoll(Capability *cap)
    347 347
     #if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
    
    348 348
             /* We could use poll here, since we use no timeout, but for
    
    349 349
                consistency we use the same syscall as at the other call site. */
    
    350
    -        struct timespec tv = (struct timespec) { .tv_sec = 0, .tv_nsec = 0 };
    
    350
    +        struct timespec tv = { .tv_sec = 0, .tv_nsec = 0 };
    
    351 351
             int res = ppoll(iomgr->aiop_poll_table, nfds, &tv, NULL);
    
    352 352
     
    
    353 353
             debugTrace(DEBUG_iomanager,
    

  • rts/posix/URing.c
    1
    +/* -----------------------------------------------------------------------------
    
    2
    + *
    
    3
    + * (c) The GHC Team 2021-2023
    
    4
    + *
    
    5
    + * An I/O manager based on the Linux io_uring API.
    
    6
    + *
    
    7
    + * ---------------------------------------------------------------------------*/
    
    8
    +
    
    9
    +#include "rts/PosixSource.h"
    
    10
    +#include "Rts.h"
    
    11
    +
    
    12
    +#include "IOManager.h" // defines IOMGR_ENABLED_URING
    
    13
    +
    
    14
    +#if defined(IOMGR_ENABLED_URING)
    
    15
    +
    
    16
    +#include "Capability.h"
    
    17
    +#include "Threads.h"
    
    18
    +#include "Schedule.h"
    
    19
    +#include "Prelude.h"
    
    20
    +#include "RtsUtils.h"
    
    21
    +#include "rts/Time.h"
    
    22
    +#include "RaiseAsync.h"
    
    23
    +
    
    24
    +#include "URing.h"
    
    25
    +#include "Signals.h"
    
    26
    +
    
    27
    +#include <liburing.h>
    
    28
    +#include <poll.h> // for poll() flags POLLIN POLLOUT
    
    29
    +#include <limits.h>
    
    30
    +#include <errno.h>
    
    31
    +#include <fcntl.h>
    
    32
    +
    
    33
    +#include "IOManagerInternals.h"
    
    34
    +#include "Timeout.h"
    
    35
    +
    
    36
    +/******************************************************************************
    
    37
    +
    
    38
    +This I/O manager is based on the Linux io_uring API. We rely on the liburing
    
    39
    +library, rather than using the system calls directly.
    
    40
    +
    
    41
    +Introduction
    
    42
    +============
    
    43
    +
    
    44
    +The io_uring API is an _almost_ generic mechanism for performing Linux syscalls
    
    45
    +asynchronously. It supports a range of I/O related operations, including
    
    46
    +ordinary file read and write, and waiting for I/O readiness. It works using a
    
    47
    +queue to submit I/O operations, and another queue to receive I/O completions.
    
    48
    +The io_uring documentation calls these the "submission queue", abbreviated SQ,
    
    49
    +and "completion queue", abbreviated CQ. The corresponding queue entries are
    
    50
    +abbreviated as SQEs and CQEs.
    
    51
    +
    
    52
    +There is a single system call to both submit operations and/or wait for I/O
    
    53
    +completion (or a timeout). It is also possible to poll for new entries in the
    
    54
    +completion queue without using a system call at all. This fits the RTS
    
    55
    +scheduler design quite well. Every time round the scheduler loop we have to
    
    56
    +do a non-blocking check for I/O completion, and it is only when there are no
    
    57
    +runnable threads that we want to block and wait for I/O (or timers). So in busy
    
    58
    +applications there are a lot more non-blocking than blocking checks for I/O
    
    59
    +completion. So being able to do the non-blocking poll without needing a system
    
    60
    +call should save significantly on system calls, compared to other APIs.
    
    61
    +
    
    62
    +We use the liburing C library, rather than the system calls directly. This
    
    63
    +provides a degree of convenience and portability across kernel versions.
    
    64
    +
    
    65
    +Synchronous I/O
    
    66
    +---------------
    
    67
    +
    
    68
    +Classically, asynchronous I/O APIs are slower than ordinary synchronous I/O
    
    69
    +APIs for the case of buffered I/O reads where the requested data is already in
    
    70
    +the OS page cache (if they support buffered I/O at all). This requires
    
    71
    +applications that use asynchronous I/O (and thus care about performance) to use
    
    72
    +it only in some cases, and use synchronous I/O when that is expected to be
    
    73
    +faster. This is complex, and thus relatively few applications use asynchronous
    
    74
    +I/O. Furthermore, knowing whether data is in the page cache is something that
    
    75
    +only the kernel knows reliably. User space can only make educated guesses.
    
    76
    +
    
    77
    +A major selling point of io_uring is that it (mostly) solves this problem. It
    
    78
    +is designed work for buffered I/O (as well as direct I/O). Operations are
    
    79
    +submitted by putting entries (SQEs) into the submission queue (SQ) and then
    
    80
    +entering the kernel (io_uring_enter). The kernel starts work on all the
    
    81
    +operations. If any of them can complete synchronously then the kernel can place
    
    82
    +the completion entries (CQEs) into the completion queue (CQ). Thus the
    
    83
    +synchronous operations complete synchronously. Furthermore, the decision about
    
    84
    +whether it can complete synchronously is made dynamically by the kernel (e.g.
    
    85
    +based on whether the data is available in the page cache). The result is that a
    
    86
    +simple file read where the data is available in the page cache can be as quick
    
    87
    +as a normal synchronous file read system call.
    
    88
    +
    
    89
    +Overall, this allows the same API to be used for all I/O, without having to
    
    90
    +guess about synchronous vs asynchronous completions. Furthermore, io_uring
    
    91
    +supports both disk I/O and sockets I/O, whereas older APIs only supported one
    
    92
    +or the other (see e.g. epoll for sockets and Linux/Posix AIO for disk files).
    
    93
    +Overall this allows a less complex solution, by using the same relatively
    
    94
    +uniform API for everything (even through the API itself is somewhat more
    
    95
    +complex than other APIs).
    
    96
    +
    
    97
    +General strategy
    
    98
    +----------------
    
    99
    +
    
    100
    +Our I/O strategy with io_uring is to prepare but not submit I/O operations in
    
    101
    +the I/O primops, and then submit the operations in the scheduler. That is, in
    
    102
    +the I/O primops we put the I/O operations (SQEs) into the submission queue (SQ),
    
    103
    +but we don't yet call into the kernel to inform it of the new operations.
    
    104
    +Instead we do that in the scheduler. At this point in the scheduler, we can
    
    105
    +both submit any pending I/O operations and handle any completions. This lets us
    
    106
    +submit and collect I/O with a single system call. Handling I/O completions
    
    107
    +typically results in waking up threads, which the scheduler can then deal with.
    
    108
    +
    
    109
    +Consider the important example of simple disk reads that complete synchronously.
    
    110
    +In this case the completion is available immediately after io_uring_enter
    
    111
    +returns and the scheduler can reschedule the thread that submitted the I/O. So
    
    112
    +the end result _should_ be as fast as a normal synchronous blocking read()
    
    113
    +system call (but benchmarks are needed to verify this).
    
    114
    +
    
    115
    +Important fast-path cases
    
    116
    +-------------------------
    
    117
    +
    
    118
    +There are a few important special fast-path cases to keep in mind:
    
    119
    +
    
    120
    +1. Common case: as above, a Haskell thread has pending I/O, so we submit
    
    121
    +   it. This requires a system call. Afterwards we also process any
    
    122
    +   completions. This means any I/O operations that complete synchronously
    
    123
    +   get handled immediately and with a single system call. This is crucial
    
    124
    +   for performance of ordinary buffered I/O in the common case that the data
    
    125
    +   is available in the page cache.
    
    126
    +
    
    127
    +2. Common case: no pending I/O to submit, but there is outstanding I/O
    
    128
    +   that we need to see if it has completed. This does not require a
    
    129
    +   system call. We just need to look at the completion ring.
    
    130
    +
    
    131
    +3. Rare case: we have pending I/O to submit but the kernel refuses to
    
    132
    +   accept more I/O because the completion queue is full. In this case
    
    133
    +   we process the completion queue first, and then retry. This ends up
    
    134
    +   using two system calls.
    
    135
    +
    
    136
    +I/O submission overload
    
    137
    +-----------------------
    
    138
    +
    
    139
    +Asynchronous I/O APIs enable I/O to be submitted without waiting and thus they
    
    140
    +must deal with the problem of having too much I/O in flight at once for the
    
    141
    +resources available.
    
    142
    +
    
    143
    +For example, epoll_ctl will return ENOMEM or ENOSPC if it cannot allocate the
    
    144
    +necessary memory or hits a resource limit. The epoll backend for MIO turns
    
    145
    +these failures into Haskell exceptions. This works ok in practice for epoll
    
    146
    +because epoll only supports one kind of async I/O operation: waiting for I/O
    
    147
    +readiness. This has relatively low resource use and so systems generally hit
    
    148
    +other resource limits first, e.g. file descriptors.
    
    149
    +
    
    150
    +On the other hand io_uring supports a variety of I/O operations with different
    
    151
    +behaviour and resource use. They can roughly be divided into two categories:
    
    152
    +1. cheap blocking operations; and
    
    153
    +2. expensive non-blocking operations.
    
    154
    +
    
    155
    +The cheap blocking operations include waiting for I/O readiness on pipes and
    
    156
    +sockets, as with epoll, but it also includes blocking reads/write/send/recv on
    
    157
    +pipes and sockets. These operations tend to have very low resource use and
    
    158
    +correspondingly the kernel can support very many outstanding operations. This
    
    159
    +is needed for some large scale networking use cases where there can be very
    
    160
    +many sockets in use at once.
    
    161
    +
    
    162
    +The comparatively expensive non-blocking operations include read and write on
    
    163
    +disk files and various file and file system operations. These operations tend
    
    164
    +to have higher resource use and so the kernel cannot support too many of them
    
    165
    +in progress at once. Furthermore, for non-blocking operations there isn't much
    
    166
    +need to have huge numbers in progress at once: the performance benefit is
    
    167
    +limited by the hardware concurrency (CPUs, SSD queue depth etc).
    
    168
    +
    
    169
    +Unfortunately, the limits on the number of concurrent operations of each kind
    
    170
    +is not known, or at least not reported by the kernel in advance. When trying
    
    171
    +to submit new operations, the kernel can report EAGAIN if it is out of
    
    172
    +resources. This reporting mechanism is awkward because of the two classes of
    
    173
    +operations. The way we would like to respond to hitting a limit is also
    
    174
    +different for blocking vs non-blocking operations.
    
    175
    +
    
    176
    +For non-blocking operations, we can simply wait for some operations to complete
    
    177
    +and then submit more. This works because non-blocking operations will
    
    178
    +eventually complete, and indeed typically complete fairly promptly.
    
    179
    +
    
    180
    +For blocking operations on the other hand, there's not a lot we can do if we
    
    181
    +hit a resource limit. Waiting may not help. Blocking operations can block
    
    182
    +indefinitely. It is also possible to deadlock if not all I/O readiness
    
    183
    +notifications are active simultaneously. So if we hit a resource limit for
    
    184
    +these operations then we can't do much better than throw exceptions to the
    
    185
    +Haskell threads submitting the operations. This is of course what the MIO epoll
    
    186
    +backend does.
    
    187
    +
    
    188
    +We will make the assumption that the kernel does _not_ have separate limits for
    
    189
    +these two classes of operations, but assume that it is a common limit based on
    
    190
    +the common resource of kernel memory. As noted above, there is no great benefit
    
    191
    +to having excessive concurrency of non-blocking operations. We can limit the
    
    192
    +concurrency (using a queue) with no change in semantics of the application. On
    
    193
    +the other hand an application that wants to wait on zillions of blocking
    
    194
    +operations cannot have that concurrency reduced without it having a semantic
    
    195
    +effect on the application. We either have to support it or (hopefully
    
    196
    +gracefully) fail as resources run out.
    
    197
    +
    
    198
    +So we take the approach of trying to limit the concurrency and thus resource
    
    199
    +use of the non-blocking operations so that the remaining kernel memory can
    
    200
    +maximise the number of blocking operations we can support. We do that by
    
    201
    +imposing a "reasonable" limit on the number of concurrent non-blocking
    
    202
    +operations. We also impose a separate larger limit on the number of concurrent
    
    203
    +blocking operations. The high level idea is that we treat the non-blocking
    
    204
    +limit like a semaphore (with that concurrency limit) on the threads submitting
    
    205
    +non-blocking I/O operations. This means we can handle overload transparently.
    
    206
    +On the other hand the limit on blocking operations is a hard limit and if we
    
    207
    +hit that then we have to throw exceptions (to the threads submitting the I/O).
    
    208
    +
    
    209
    +In the typical application use cases this strategy should avoid encountering
    
    210
    +EAGAIN in the first place, however it can still happen and we must handle it
    
    211
    +somehow.
    
    212
    +
    
    213
    +Before considering EAGAIN, consider a more normal scenario with lots of
    
    214
    +concurrent I/O -- both blocking and non-blocking operations -- where we do hit
    
    215
    +the limit on non-blocking operations. We track the number of blocking and
    
    216
    +non-blocking operations that are currently "in flight" using the counters
    
    217
    +iomgr->uring_inflight_{non}blocking_aiops. These are incremented by the number
    
    218
    +of prepared operations when the submission queue is successfully flushed, and
    
    219
    +decremented for each completion processed. Hitting the limit means that when a
    
    220
    +thread submits a new non-blocking operation we find the count of inflight
    
    221
    +non-blocking operations is already at the limit, and thus a new operation would
    
    222
    +be over the limit.
    
    223
    +
    
    224
    +Normally when a thread uses a primop to submit an I/O operation we would grab
    
    225
    +an existing SQE from the submission queue (SQ) and fill in the SQE. If however
    
    226
    +we would go over the inflight limit, then instead we allocate a fresh SQE (on
    
    227
    +the C heap using malloc) and then fill in the SQE as normal. Then we put the
    
    228
    +SQE and the TSO for the thread that submitted it onto (the end of) a pair of
    
    229
    +overflow queues: one for the SQEs and one for the corresponding threads. We use
    
    230
    +a pair of queues rather than a queue of pairs because the TSOs are allocated on
    
    231
    +the GC heap but the SQEs are on the C heap and there is existing infrastructure
    
    232
    +for handling TSO queues, including tracing them for GC. In the scheduler, when
    
    233
    +we process the completion of a blocking operation, if there are entries on the
    
    234
    +overflow queues then we can dequeue an SQE and corresponding TSO and copy the
    
    235
    +SQE into the SQ and reschedule the TSO. This means the SQE is ready to go and
    
    236
    +the TSO is unblocked. If we are in this overflow situation and the completions
    
    237
    +for several non-blocking operations are processed in one go then will will add
    
    238
    +several SQEs to be added to the SQ in one batch. This should naturally lead to
    
    239
    +batching in the overflow situation and amortise the syscall overheads.
    
    240
    +
    
    241
    +While all of this is going on, other threads submitting _blocking_ operations
    
    242
    +can proceed as normal, preparing their operations into the submission queue.
    
    243
    +
    
    244
    +Now if we do encounter EAGAIN, we assume that it's the blocking operations
    
    245
    +that are at fault.
    
    246
    +
    
    247
    +Approaches to batching
    
    248
    +----------------------
    
    249
    +
    
    250
    +We currently do no batching, but this section discusses plausible approaches.
    
    251
    +
    
    252
    +With this design we have the opportunity to try to accumulate multiple pending
    
    253
    +I/O operations and then submit them in one batch, which could improve
    
    254
    +performance in I/O intensive applications by reducing the number of system
    
    255
    +calls. Ordinary synchronous I/O primops like threadWaitRead# block the Haskell
    
    256
    +thread and return to the scheduler, which means we only accumulate a single
    
    257
    +pending I/O operation. There are a couple ways we could achieve batching.
    
    258
    +
    
    259
    +One approach is to allow individual Haskell threads to prepare multiple I/O
    
    260
    +operations by providing asynchronous I/O primops. This could be quite effective
    
    261
    +at generating a lot of I/O, but it would likely see relatively little use
    
    262
    +because it requires changing application designs.
    
    263
    +
    
    264
    +Another approach is by having the scheduler not always submit pending I/O, but
    
    265
    +instead let it run other threads in the hope that they will produce more
    
    266
    +pending I/O. It would wait until either a time limit or a pending count limit
    
    267
    +before submitting the I/O. Such an approach would increase I/O latency:
    
    268
    +consider the scheduler running a thread that creates a pending I/O operation,
    
    269
    +followed by running a CPU-bound thread for a whole 20 millisecond timeslice,
    
    270
    +after which the scheduler submits the I/O. Thus this approach could only be
    
    271
    +used for low priority I/O, which itself would require introducing a notion of
    
    272
    +I/O priority.
    
    273
    +
    
    274
    +One can imagine variations on this design such as a more sophisticated
    
    275
    +scheduler predicting if the next thread to run is likely to be I/O or CPU bound
    
    276
    +and using that to decide whether to flush pending I/O or to speculate on
    
    277
    +accumulating more. Or the scheduler could set a timer to interrupt CPU bound
    
    278
    +threads sooner if it's speculating on gathering more I/O. This could reduce
    
    279
    +the cost on average, and bound latency, but there would still be a latency vs
    
    280
    +throughput tradeoff, which would almost certainly require some notion of I/O
    
    281
    +priority.
    
    282
    +
    
    283
    +
    
    284
    +io_uring features we use
    
    285
    +------------------------
    
    286
    +
    
    287
    +IORING_FEAT_NODROP
    
    288
    +
    
    289
    +io_uring features we could use but don't (yet)
    
    290
    +----------------------------------------------
    
    291
    +
    
    292
    +io_uring features we cannot use
    
    293
    +-------------------------------
    
    294
    +
    
    295
    +NOTES: cannot use registered ring fd, due to multiple worker threads, even
    
    296
    +though it's protected by the lock. Same for registered/direct fds.
    
    297
    +
    
    298
    +
    
    299
    +Tracking counters
    
    300
    +-----------------
    
    301
    +
    
    302
    +We track the number of operations submitted (by Haskell threads to the I/O
    
    303
    +manager) and not yet notified of completion:
    
    304
    +
    
    305
    +> int n_submitted_b;
    
    306
    +> int n_submitted_nb;
    
    307
    +
    
    308
    +These are incremented when the primop submits I/O to the I/O manager, and are
    
    309
    +decremented when the I/O completion is processed. We track blocking and
    
    310
    +non-blocking operations separately.
    
    311
    +
    
    312
    +We track the number of operations that are prepared in the submission queue,
    
    313
    +but not yet submitted to the kernel.
    
    314
    +
    
    315
    +> int n_prepared_b;
    
    316
    +> int n_prepared_nb;
    
    317
    +
    
    318
    +These are incremented when an operation is prepared in the submission queue
    
    319
    +and decremented when operations are submitted to the kernel.
    
    320
    +
    
    321
    +We track the number of operations submitted to the kernel and where the
    
    322
    +completion has not yet been processed. We call these "in-flight" operations.
    
    323
    +
    
    324
    +> int n_inflight_b;
    
    325
    +> int n_inflight_nb;
    
    326
    +
    
    327
    +These are incremented when operations are submitted to the kernel, and
    
    328
    +decremented when I/O completions are processed.
    
    329
    +
    
    330
    +We track the limit on the number of operations the I/O manager will allow to be
    
    331
    +in-flight with the kernel.
    
    332
    +
    
    333
    +> int limit_inflight_b;
    
    334
    +> int limit_inflight_nb;
    
    335
    +
    
    336
    +These are typically set on RTS startup and then rarely changed. The
    
    337
    +limit_inflight_b can be reduced dynamically if the I/O manager encounters
    
    338
    +EAGAIN when submitting operations.
    
    339
    +
    
    340
    +We track the number of non-blocking operations that are in the overflow queue.
    
    341
    +These are operations that have been submitted, and could not be put into the
    
    342
    +submission queue (because it would exceed the limit_inflight_nb), and so go
    
    343
    +into the overflow queue instead.
    
    344
    +
    
    345
    +> int n_overflow_nb;
    
    346
    +
    
    347
    +This is incremented (instead of n_prepared_nb) if the number of n_inflight_nb
    
    348
    +plus n_prepared_nb is at or above the limit_inflight_nb, in which case the
    
    349
    +operation (and submitting thread) is put into the overflow queue. It is
    
    350
    +decremented when there is space available within the limit_inflight_nb and the
    
    351
    +operation can be put into the submission queue (and thus also incrementing the
    
    352
    +n_prepared_nb).
    
    353
    +
    
    354
    +We maintain two invariants:
    
    355
    +
    
    356
    +1. n_submitted_b  = n_prepared_b                  + n_inflight_b
    
    357
    +2. n_submitted_nb = n_prepared_nb + n_overflow_nb + n_inflight_nb
    
    358
    +
    
    359
    +The implication is that set of prepared, overflow and inflight operations are
    
    360
    +distinct from each other and their union is equal to the submitted set. Another
    
    361
    +way to look at it is that there distinct states for submitted operations:
    
    362
    +prepared, overflow (for non-blocking) and inflight.
    
    363
    +
    
    364
    +******************************************************************************/
    
    365
    +
    
    366
    +/* Forward declarations */
    
    367
    +static int  enlargeTables(Capability *cap, CapIOManager *iomgr);
    
    368
    +static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop);
    
    369
    +static void enqueueOverflowQueue(Capability *cap, CapIOManager *iomgr,
    
    370
    +                                 StgTSO *tso, struct io_uring_sqe *sqe);
    
    371
    +static void dequeueOverflowQueue(CapIOManager *iomgr,
    
    372
    +                                 StgTSO **ptso, struct io_uring_sqe **psqe);
    
    373
    +
    
    374
    +/* Constants */
    
    375
    +
    
    376
    +/* A couple tags we add to the sqe->user_data to tell us about this operation
    
    377
    + * when we process the completion. Currently we distinguish:
    
    378
    + * 1. non-blocking vs blocking operations, just so we can update our counters
    
    379
    + *    which count these separately.
    
    380
    + * 2. cancellation operations
    
    381
    + */
    
    382
    +const uint64_t AIOP_TAG_CANCEL = 0x80lu << 56;  /* bit 63 */
    
    383
    +const uint64_t AIOP_TAG_OP_NB  = 0x40lu << 56;  /* bit 62 */
    
    384
    +const uint64_t AIOP_TAG_MASK   = 0xc0lu << 56;  /* bit 62 | 63 */
    
    385
    +
    
    386
    +
    
    387
    +void initCapabilityIOManagerURing(Capability *cap, CapIOManager *iomgr)
    
    388
    +{
    
    389
    +    initClosureTable(&iomgr->aiop_table, ClosureTableCompact);
    
    390
    +    iomgr->timeout_queue = emptyTimeoutQueue();
    
    391
    +
    
    392
    +    int sq_entries = RtsFlags.MiscFlags.io_uring_sq_entries;
    
    393
    +
    
    394
    +    iomgr->n_submitted_b     = 0;
    
    395
    +    iomgr->n_submitted_nb    = 0;
    
    396
    +    iomgr->n_prepared_b      = 0;
    
    397
    +    iomgr->n_prepared_nb     = 0;
    
    398
    +    iomgr->n_inflight_b      = 0;
    
    399
    +    iomgr->n_inflight_nb     = 0;
    
    400
    +    iomgr->limit_inflight_b  = INT_MAX;
    
    401
    +    iomgr->limit_inflight_nb = 4 * sq_entries;
    
    402
    +    iomgr->n_overflow_nb     = 0;
    
    403
    +    iomgr->overflow_tso_q_hd = END_TSO_QUEUE;
    
    404
    +    iomgr->overflow_tso_q_tl = END_TSO_QUEUE;
    
    405
    +    iomgr->overflow_sqe_q_hd = NULL;
    
    406
    +    iomgr->overflow_sqe_q_tl = NULL;
    
    407
    +    
    
    408
    +    /* Set the uring params: we want to use independent sizes of submission
    
    409
    +     * and completion queues. We typically want a bigger completion queue than
    
    410
    +     * a submission queue.
    
    411
    +     */
    
    412
    +    struct io_uring_params params = {
    
    413
    +             .flags      = IORING_SETUP_CQSIZE
    
    414
    +                         | IORING_SETUP_CLAMP
    
    415
    +                         | IORING_SETUP_SUBMIT_ALL,
    
    416
    +             .cq_entries = RtsFlags.MiscFlags.io_uring_cq_entries
    
    417
    +           };
    
    418
    +    //TODO: what happens if we use flags that are not recognised by the kernel
    
    419
    +    // version, e.g. IORING_SETUP_SUBMIT_ALL prior to 5.18?
    
    420
    +
    
    421
    +    /* TODO: see if we want to support IORING_SETUP_SQPOLL. With
    
    422
    +      IORING_FEAT_NATIVE_WORKERS, it doesn't need any priviledges. */
    
    423
    +
    
    424
    +    /* Share the same kernel work-queue between the urings for each capability.
    
    425
    +     * Do this by using the IORING_SETUP_ATTACH_WQ flag for capabilities > 0,
    
    426
    +     * and pass the uring fd for cap 0 (the main capability).
    
    427
    +     */
    
    428
    +    if (cap->no > 0) {
    
    429
    +        params.flags |= IORING_SETUP_ATTACH_WQ;
    
    430
    +        params.wq_fd  = MainCapability.iomgr->uring->ring_fd;
    
    431
    +    }
    
    432
    +
    
    433
    +    /* Try to initialise the uring */
    
    434
    +    struct io_uring *uring = stgMallocBytes(sizeof (struct io_uring),
    
    435
    +                                            "initCapabilityIOManagerUring");
    
    436
    +    int res = io_uring_queue_init_params(sq_entries, uring, &params);
    
    437
    +    if (res < 0) goto fail;
    
    438
    +
    
    439
    +    /* Check for features we require. */
    
    440
    +    unsigned required =
    
    441
    +        /* Needed for simple handling of ring sizes and limits.
    
    442
    +         * TODO: we might be able to support kernels without this
    
    443
    +         * by setting the limits to be the same as the ring sizes. */
    
    444
    +        IORING_FEAT_NODROP
    
    445
    +
    
    446
    +        /* TODO: explain why */
    
    447
    +      | IORING_FEAT_SUBMIT_STABLE
    
    448
    +
    
    449
    +        /* Needed for read/write that updates the file pos. */
    
    450
    +      | IORING_FEAT_RW_CUR_POS
    
    451
    +
    
    452
    +        /* Needed for corner cases like reading from /proc/self,
    
    453
    +         * or signalfd */
    
    454
    +      | IORING_FEAT_NATIVE_WORKERS;
    
    455
    +
    
    456
    +    if ((uring->features & required) != required) goto fail;
    
    457
    +
    
    458
    +    /* Arrange for the uring (fd and mmap'ed queues) not to be inherited. */
    
    459
    +    res = fcntl(uring->ring_fd, F_SETFD, FD_CLOEXEC);
    
    460
    +    if (res < 0) goto fail;
    
    461
    +    res = io_uring_ring_dontfork(uring);
    
    462
    +    if (res < 0) goto fail;
    
    463
    +
    
    464
    +    /* Success. Save what we need. */
    
    465
    +    iomgr->uring = uring;
    
    466
    +    return;
    
    467
    +
    
    468
    +    /* Failure. Clean up. */
    
    469
    +fail:
    
    470
    +    stgFree(uring);
    
    471
    +    barf("uring iomgr: initialisation failed");
    
    472
    +    //TODO: we should add support to fail and use a fallback I/O manager
    
    473
    +}
    
    474
    +
    
    475
    +
    
    476
    +void initCapabilityIOManagerAfterForkURing(Capability *cap STG_UNUSED,
    
    477
    +                                           CapIOManager *iomgr STG_UNUSED)
    
    478
    +{
    
    479
    +    //TODO: ugg, do we need to shutdown all the other caps? Or does that
    
    480
    +    // happen automagically? Need to look into forkProcess and shutting
    
    481
    +    // down the I/O managers.
    
    482
    +}
    
    483
    +
    
    484
    +/******************************************************************************
    
    485
    + * Common prologues and epilogues for primops for I/O operations.
    
    486
    + *
    
    487
    + * There are different common prologues/epilogues depending on:
    
    488
    + *  - synchronous or asynchronous primops
    
    489
    + *  - blocking or non-blocking I/O operations
    
    490
    + */
    
    491
    +
    
    492
    +
    
    493
    +/* The common prologue for for all synchronous primops for both blocking and
    
    494
    + * non-blocking I/O operations.
    
    495
    + */
    
    496
    +static int prologueSyncIOOp(Capability *cap, StgTSO *tso,
    
    497
    +                            int why_blocked, StgAsyncIOOp **paiop)
    
    498
    +{
    
    499
    +    StgAsyncIOOp *aiop;
    
    500
    +    aiop = (StgAsyncIOOp *)allocateMightFail(cap, sizeofW(StgAsyncIOOp));
    
    501
    +    if (RTS_UNLIKELY(aiop == NULL)) { return (sizeof(StgAsyncIOOp)); }
    
    502
    +    SET_HDR(aiop, &stg_ASYNCIOOP_info, CCS_SYSTEM); //TODO: get CCCS
    
    503
    +    aiop->notify_type    = NotifyTSO;
    
    504
    +    aiop->notify.tso     = tso;
    
    505
    +    tso->why_blocked     = why_blocked;
    
    506
    +    tso->block_info.aiop = aiop;
    
    507
    +    *paiop               = aiop;
    
    508
    +    return 0;
    
    509
    +}
    
    510
    +
    
    511
    +
    
    512
    +/* The common prologue for all non-blocking I/O operations.
    
    513
    + *
    
    514
    + * Allocate a table index
    
    515
    + * Fill in some of the aiop fields
    
    516
    + * Allocate an SQE, either on the ring or on the heap if we're in overflow.
    
    517
    + */
    
    518
    +static int prologueNonBlockingIOOp(Capability *cap,
    
    519
    +                                   StgTSO *tso, StgAsyncIOOp *aiop,
    
    520
    +                                   struct io_uring_sqe **psqe,
    
    521
    +                                   bool *tso_block)
    
    522
    +{
    
    523
    +    CapIOManager *iomgr = cap->iomgr;
    
    524
    +    if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
    
    525
    +        int fail = enlargeTables(cap, iomgr);
    
    526
    +        if (RTS_UNLIKELY(fail)) return fail;
    
    527
    +    }
    
    528
    +
    
    529
    +    int index = insertClosureTable(cap, &iomgr->aiop_table, aiop);
    
    530
    +
    
    531
    +    aiop->capno = cap->no;
    
    532
    +    aiop->index = index;
    
    533
    +
    
    534
    +    struct io_uring_sqe *sqe;
    
    535
    +
    
    536
    +    if (iomgr->n_inflight_nb + iomgr->n_prepared_nb
    
    537
    +          < iomgr->limit_inflight_nb) {
    
    538
    +        /* The typical case. Allocate an SQE from the ring */
    
    539
    +        sqe = io_uring_get_sqe(iomgr->uring);
    
    540
    +        ASSERT(sqe); /* Otherwise we counted wrong */
    
    541
    +        iomgr->n_submitted_nb++;
    
    542
    +        iomgr->n_prepared_nb++;
    
    543
    +        *tso_block = false;
    
    544
    +    } else {
    
    545
    +        /* We're going to have to block the submitting thread.
    
    546
    +         * Allocate an SQE on the heap and suspend the calling TSO.
    
    547
    +         */
    
    548
    +        sqe = stgMallocBytes(sizeof (struct io_uring_sqe), "uring iomgr");
    
    549
    +        enqueueOverflowQueue(cap, iomgr, tso, sqe);
    
    550
    +        iomgr->n_submitted_nb++;
    
    551
    +        iomgr->n_overflow_nb++;
    
    552
    +        *tso_block = true;
    
    553
    +    }
    
    554
    +    io_uring_sqe_set_data64(sqe, index);
    
    555
    +    *psqe = sqe;
    
    556
    +    return 0;
    
    557
    +}
    
    558
    +
    
    559
    +
    
    560
    +/* The common prologue for all blocking I/O operations.
    
    561
    + *
    
    562
    + * Allocate a table index
    
    563
    + * Fill in some of the aiop fields
    
    564
    + * Allocate an SQE, or return failure if we're at the limit.
    
    565
    + */
    
    566
    +static int prologueBlockingIOOp(Capability *cap,
    
    567
    +                                StgTSO *tso, StgAsyncIOOp *aiop,
    
    568
    +                                struct io_uring_sqe **psqe)
    
    569
    +{
    
    570
    +    CapIOManager *iomgr = cap->iomgr;
    
    571
    +
    
    572
    +    if (iomgr->n_inflight_b + iomgr->n_prepared_b >= iomgr->limit_inflight_b) {
    
    573
    +        /* If we reach the limit we fail and throw an exception */
    
    574
    +        raiseAsync(cap, tso, (StgClosure *)blockedOnBadFD_closure
    
    575
    +                           /*TODO: use ioopResourcesExhausted_closure */,
    
    576
    +                           false, NULL);
    
    577
    +        return -1;
    
    578
    +        //TODO: review this
    
    579
    +        //TODO: current return value is for memory alloc failure, not for
    
    580
    +        // other failures. Need error result separate from alloc.
    
    581
    +    }
    
    582
    +
    
    583
    +    if (RTS_UNLIKELY(isFullClosureTable(&iomgr->aiop_table))) {
    
    584
    +        int fail = enlargeTables(cap, iomgr);
    
    585
    +        if (RTS_UNLIKELY(fail)) return fail;
    
    586
    +    }
    
    587
    +
    
    588
    +    int index = insertClosureTable(cap, &iomgr->aiop_table, aiop);
    
    589
    +
    
    590
    +    aiop->capno = cap->no;
    
    591
    +    aiop->index = index;
    
    592
    +
    
    593
    +    /* Allocate an SQE on the ring */
    
    594
    +    struct io_uring_sqe *sqe = io_uring_get_sqe(iomgr->uring);
    
    595
    +    ASSERT(sqe); /* Otherwise we counted wrong */
    
    596
    +    iomgr->n_submitted_b++;
    
    597
    +    iomgr->n_prepared_b++;
    
    598
    +    io_uring_sqe_set_data64(sqe, index);
    
    599
    +    *psqe = sqe;
    
    600
    +    return 0;
    
    601
    +}
    
    602
    +
    
    603
    +
    
    604
    +/* The common epilogue for all async non-blocking I/O operations.
    
    605
    + */
    
    606
    +static int epilogueAsyncNonBlockingIOOp(CapIOManager *iomgr,
    
    607
    +                                        StgTSO *tso, StgAsyncIOOp *aiop,
    
    608
    +                                        bool tso_block)
    
    609
    +{
    
    610
    +    if (tso_block) {
    
    611
    +        tso->why_blocked     = BlockedOnIOSubmission;
    
    612
    +        tso->block_info.aiop = aiop;
    
    613
    +        return -1;
    
    614
    +    } else if (io_uring_sq_space_left(iomgr->uring) == 0) {
    
    615
    +        return -1;
    
    616
    +    } else {
    
    617
    +      return 0;
    
    618
    +    }
    
    619
    +}
    
    620
    +
    
    621
    +
    
    622
    +/* Common epilogue for all async blocking I/O operations.
    
    623
    + */
    
    624
    +static int epilogueAsyncBlockingIOOp(CapIOManager *iomgr)
    
    625
    +{
    
    626
    +    return io_uring_sq_space_left(iomgr->uring) == 0 ? -1 : 0;
    
    627
    +}
    
    628
    +
    
    629
    +
    
    630
    +/******************************************************************************
    
    631
    + * Non-blocking I/O operations. This includes read/write on files (not sockets).
    
    632
    + *
    
    633
    + * The code is organised as common I/O preparation functions and then individual
    
    634
    + * primops (sync and async). They also rely on the common prologue and epilogue
    
    635
    + * functions above.
    
    636
    + */
    
    637
    +
    
    638
    +static int prepareIOReadWrite(Capability *cap, StgTSO *tso, bool *tso_block,
    
    639
    +                              StgAsyncIOOp *aiop,
    
    640
    +                              IOReadOrWrite rw, int fd,
    
    641
    +                              StgClosure *live, void *buf,
    
    642
    +                              size_t len, off_t off) {
    
    643
    +    struct io_uring_sqe *sqe;
    
    644
    +    int fail = prologueNonBlockingIOOp(cap, tso, aiop, &sqe, tso_block);
    
    645
    +    if (RTS_UNLIKELY(fail)) return fail;
    
    646
    +
    
    647
    +    aiop->live = live;
    
    648
    +    if (rw == IORead) {
    
    649
    +        io_uring_prep_read(sqe, fd, buf, len, off);
    
    650
    +    } else {
    
    651
    +        io_uring_prep_write(sqe, fd, buf, len, off);
    
    652
    +    }
    
    653
    +    return 0;
    
    654
    +}
    
    655
    +
    
    656
    +
    
    657
    +int asyncIOReadWriteURing(Capability *cap, StgTSO *tso, StgAsyncIOOp *aiop,
    
    658
    +                          IOReadOrWrite rw, int fd,
    
    659
    +                          StgClosure *live, void *buf,
    
    660
    +                          size_t len, off_t off)
    
    661
    +{
    
    662
    +    bool tso_block;
    
    663
    +    int fail = prepareIOReadWrite(cap, tso, &tso_block, aiop,
    
    664
    +                                  rw, fd, live, buf, len, off);
    
    665
    +    if (RTS_UNLIKELY(fail)) return fail;
    
    666
    +
    
    667
    +    return epilogueAsyncNonBlockingIOOp(cap->iomgr, tso, aiop, tso_block);
    
    668
    +}
    
    669
    +
    
    670
    +
    
    671
    +int syncIOReadWriteURing(Capability *cap, StgTSO *tso,
    
    672
    +                         IOReadOrWrite rw, int fd,
    
    673
    +                         StgClosure *live, void *buf,
    
    674
    +                         size_t len, off_t off)
    
    675
    +{
    
    676
    +    StgAsyncIOOp *aiop;
    
    677
    +    int why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
    
    678
    +    int fail = prologueSyncIOOp(cap, tso, why_blocked, &aiop);
    
    679
    +    if (RTS_UNLIKELY(fail)) return fail;
    
    680
    +
    
    681
    +    bool unused;
    
    682
    +    fail = prepareIOReadWrite(cap, tso, &unused, aiop,
    
    683
    +                              rw, fd, live, buf, len, off);
    
    684
    +    if (RTS_UNLIKELY(fail)) return fail;
    
    685
    +    return 0;
    
    686
    +}
    
    687
    +
    
    688
    +
    
    689
    +/******************************************************************************
    
    690
    + * Blocking I/O operations. This includes waiting for I/O readiness on sockets,
    
    691
    + * pipes etc.
    
    692
    + *
    
    693
    + * The code is organised as common I/O preparation functions and then individual
    
    694
    + * primops (sync and async). They also rely on the common prologue and epilogue
    
    695
    + * functions above.
    
    696
    + */
    
    697
    +
    
    698
    +
    
    699
    +static void prepareIOWaitReady(StgAsyncIOOp *aiop, struct io_uring_sqe *sqe,
    
    700
    +                               IOReadOrWrite rw, int fd) {
    
    701
    +    aiop->live = &stg_ASYNCIO_LIVE0_closure;
    
    702
    +    io_uring_prep_poll_add(sqe, fd, rw == IORead ? POLLIN : POLLOUT);
    
    703
    +}
    
    704
    +
    
    705
    +
    
    706
    +int asyncIOWaitReadyURing(Capability *cap, StgTSO *tso, StgAsyncIOOp *aiop,
    
    707
    +                          IOReadOrWrite rw, int fd)
    
    708
    +{
    
    709
    +    struct io_uring_sqe *sqe;
    
    710
    +    int fail = prologueBlockingIOOp(cap, tso, aiop, &sqe);
    
    711
    +    if (RTS_UNLIKELY(fail)) return fail;
    
    712
    +
    
    713
    +    prepareIOWaitReady(aiop,sqe, rw, fd);
    
    714
    +
    
    715
    +    return epilogueAsyncBlockingIOOp(cap->iomgr);
    
    716
    +}
    
    717
    +
    
    718
    +
    
    719
    +int syncIOWaitReadyURing(Capability *cap, StgTSO *tso,
    
    720
    +                         IOReadOrWrite rw, int fd)
    
    721
    +{
    
    722
    +    StgAsyncIOOp *aiop;
    
    723
    +    int why_blocked = rw == IORead ? BlockedOnRead : BlockedOnWrite;
    
    724
    +    int fail = prologueSyncIOOp(cap, tso, why_blocked, &aiop);
    
    725
    +    if (RTS_UNLIKELY(fail)) return fail;
    
    726
    +
    
    727
    +    struct io_uring_sqe *sqe;
    
    728
    +    fail = prologueBlockingIOOp(cap, tso, aiop, &sqe);
    
    729
    +    if (RTS_UNLIKELY(fail)) return fail;
    
    730
    +
    
    731
    +    prepareIOWaitReady(aiop, sqe, rw, fd);
    
    732
    +
    
    733
    +    return 0;
    
    734
    +}
    
    735
    +
    
    736
    +
    
    737
    +/******************************************************************************
    
    738
    + * Actions to cancel outstanding I/O operations. Also support cancelling any
    
    739
    + * outstanding I/O on an fd prior to it being closed.
    
    740
    + *
    
    741
    + * This covers both synchronous and asynchronous operations.
    
    742
    + */
    
    743
    +
    
    744
    +
    
    745
    +static void ioCancel(Capability *cap, StgAsyncIOOp *aiop);
    
    746
    +
    
    747
    +
    
    748
    +void syncIOCancelURing(Capability *cap, StgTSO *tso)
    
    749
    +{
    
    750
    +    StgAsyncIOOp *aiop  = tso->block_info.aiop;
    
    751
    +    ASSERT(aiop->notify_type == NotifyTSO);
    
    752
    +    ASSERT(indexClosureTable(&cap->iomgr->aiop_table, aiop->index) == aiop);
    
    753
    +    ioCancel(cap, aiop);
    
    754
    +    /* We cannot use the normal notifyIOCompletion here. We are in the context
    
    755
    +     * of throwTo, interrupting a thread blocked on IO via an async exception.
    
    756
    +     * We don't put the TSO back on the run queue or change the why_blocked
    
    757
    +     * status, as that is done by removeFromQueues (in the throwTo* functions).
    
    758
    +     */
    
    759
    +    tso->block_info.closure = (StgClosure *)END_TSO_QUEUE;
    
    760
    +
    
    761
    +    //TODO: Synchronous cancellation from throwTo seems to be pretty keen for
    
    762
    +    // the thread to be unblocked immediately so it can start unwinding the
    
    763
    +    // stack. Perhaps it is ok to continue the cancellation in the background.
    
    764
    +    // But if so then we will need to adjust the notify type to be none /
    
    765
    +    // cancelled!
    
    766
    +    /* Cancelling thread-synchronous I/O happens from throwTo, which is very
    
    767
    +     * keen for the thread to be unblocked immediately so it can unwind the
    
    768
    +     * stack and schedule the thread to run an exception handler. This demand
    
    769
    +     * to be synchronous is a bit tricky to arrange because cancelling the I/O
    
    770
    +     * operation the thread is blocked on is potentially asynchronous.
    
    771
    +     */
    
    772
    +    aiop->notify_type = NotifyNone;
    
    773
    +    aiop->notify.tso  = END_TSO_QUEUE;
    
    774
    +}
    
    775
    +
    
    776
    +
    
    777
    +void asyncIOCancelURing(Capability *cap, StgAsyncIOOp *aiop)
    
    778
    +{
    
    779
    +    /* We can reliably determine if the aiop is still in progress by checking
    
    780
    +     * if the aiop_table still points to this aiop object. This is reliable
    
    781
    +     * because each aiop is GC heap allocated, so cannot be recycled until it
    
    782
    +     * is no longer retained by the application.
    
    783
    +     */
    
    784
    +    ASSERT(aiop->notify_type != NotifyTSO);
    
    785
    +    if (indexClosureTable(&cap->iomgr->aiop_table, aiop->index) == aiop) {
    
    786
    +        ioCancel(cap, aiop);
    
    787
    +        notifyIOCompletion(cap, aiop);
    
    788
    +    }
    
    789
    +}
    
    790
    +
    
    791
    +
    
    792
    +static void ioCancel(Capability *cap, StgAsyncIOOp *aiop)
    
    793
    +{
    
    794
    +    barf("URing.c:ioCancel:TODO");
    
    795
    +
    
    796
    +    CapIOManager *iomgr = cap->iomgr;
    
    797
    +
    
    798
    +    int ix = aiop->index;
    
    799
    +    struct io_uring_sqe *sqe = io_uring_get_sqe(iomgr->uring);
    
    800
    +    //TODO: return status to indicate if we need to return to the scheduler
    
    801
    +    // to flush a full submission queue
    
    802
    +
    
    803
    +    /* io_uring lets us include one word into submission queue entries (SQEs),
    
    804
    +     * which come back in the corresponding completion queue entry (CQE). We
    
    805
    +     * use this feature to identify the aiop so that we will be able to
    
    806
    +     * process the completion properly, e.g. waking up the right thread.
    
    807
    +     * We can't use a direct pointer to the aiop because the aiops are heap
    
    808
    +     * allocated and GC pointers are not stable. We use the index in the
    
    809
    +     * ClosureTable, because this is stable. Indeed the raison d'être of the
    
    810
    +     * ClosureTable is to provide stable pointers for thus purpose.
    
    811
    +     //TODO: the above is a helpful comment but it belongs elsewhere, e.g.
    
    812
    +     //in the intro.
    
    813
    +     */
    
    814
    +    uint64_t sqe_data = ix;
    
    815
    +
    
    816
    +    /* Although IORING_OP_POLL_ADD has a special separate cancellation using
    
    817
    +     * IORING_OP_POLL_REMOVE, apparently it can also be cancelled using the
    
    818
    +     * generic IORING_OP_ASYNC_CANCEL, which is good so we don't need to
    
    819
    +     * distinguish.
    
    820
    +     */
    
    821
    +    io_uring_prep_cancel64(sqe, sqe_data, 0 /*flags*/);
    
    822
    +    //TODO: if we use other tags in the sqe_data we'll need to reconstruct
    
    823
    +    // them here so we can find the right item. e.g. if we use AIOP_TAG_OP_NB
    
    824
    +    // we'd need to distinguish in the aiop->flags for example.
    
    825
    +
    
    826
    +    /* Cancelling is itself a new uring I/O operation which will have a
    
    827
    +     * corresponding completion. Set a high bit to mark this as a cancel
    
    828
    +     * operation, but still knowing the index of the original operation.
    
    829
    +     */
    
    830
    +    io_uring_sqe_set_data64(sqe, sqe_data | AIOP_TAG_CANCEL);
    
    831
    +
    
    832
    +    //TODO: verify for IORING_OP_POLL_ADD, if we do a successful
    
    833
    +    // IORING_OP_POLL_REMOVE then which completions do we get?
    
    834
    +    // Do we get a completion for the IORING_OP_POLL_ADD, and if so with
    
    835
    +    // what result? Or does a successful IORING_OP_POLL_REMOVE mean we
    
    836
    +    // only get a completion for the remove and not the add?
    
    837
    +
    
    838
    +    //TODO: verify similar for normal I/O cancel, e.g. a read on a pipe.
    
    839
    +    // Which completions do we get if we 
    
    840
    +
    
    841
    +    //TODO: is it ok cancel I/O asynchronously here? Do we need to submit the
    
    842
    +    // cancel op? The cancel op will complete synchronously but the cancellation
    
    843
    +    // may only complete later. This might confuse resource cleanup, e.g. because
    
    844
    +    // a file will not get closed until the cancel finishes.
    
    845
    +    
    
    846
    +                /* This is an CQE for a cancellation. After posting a
    
    847
    +                 * cancellation SQE then we expect to get _both_
    
    848
    +                 * a cancellation CQE and a CQE for the original operation that
    
    849
    +                 * was the target of cancellation. This means that (provided
    
    850
    +                 * it's not an error) we can ignore the cancellation SQE and
    
    851
    +                 * just process the normal SQE for the target operation.
    
    852
    +                 *
    
    853
    +                 * The target operation will either be cancelled successfully
    
    854
    +                 * immediately (in which case the cancellation cqe_data->res == 0) or if the target operation
    
    855
    +                 * is in progress and cannot be cancelled, then we'll get
    
    856
    +                 * cqe_data->res == -EALREADY for the cancellation
    
    857
    +                 * the
    
    858
    +                 * result of the operation (which may still be an interrupted
    
    859
    +                 * outcome. If the cancellation fails, we 
    
    860
    +                 */
    
    861
    +                if (cqe_data->res != 0 && cqe_data->res != -EALREADY)
    
    862
    +                    sysErrorBelch("uring:processIOCompletions");
    
    863
    +                }
    
    864
    +
    
    865
    +}
    
    866
    +
    
    867
    +
    
    868
    +
    
    869
    +
    
    870
    +/* TODO: need to add support for closing properly.
    
    871
    + * Unfortunately, uring's behaviour for closing a fd when there are outstanding
    
    872
    + * poll (or other async I/O) operations on that fd is unhelpful. The poll
    
    873
    + * operation itself keeps a reference to the file open. Thus the close will
    
    874
    + * not in fact interrupt and cancel the poll.
    
    875
    + * So the I/O manager needs to be notified of fd close, so that we can do
    
    876
    + * something. Fortunately we can use io_uring_prep_cancel_fd to cancel all
    
    877
    + * operations on an fd.
    
    878
    + *
    
    879
    + * I think cancelled ops _do_ generate CQEs. So we should be able to do the
    
    880
    + * appropriate notifications by waiting for the original CQEs. We should
    
    881
    + * probably issue the cancellation
    
    882
    + * 
    
    883
    + */
    
    884
    +
    
    885
    +
    
    886
    +/******************************************************************************
    
    887
    + * The functions called from the scheduler to poll or wait for pending I/O,
    
    888
    + * and process any I/O completions.
    
    889
    + */
    
    890
    +
    
    891
    +
    
    892
    +bool anyPendingTimeoutsOrIOURing(CapIOManager *iomgr)
    
    893
    +{
    
    894
    +    return !isEmptyTimeoutQueue(iomgr->timeout_queue)
    
    895
    +        || !isEmptyClosureTable(&iomgr->aiop_table);
    
    896
    +}
    
    897
    +
    
    898
    +
    
    899
    +static void notifyIOCompletion(Capability *cap, StgAsyncIOOp *aiop)
    
    900
    +{
    
    901
    +    switch (aiop->notify_type) {
    
    902
    +        case NotifyTSO:
    
    903
    +        {
    
    904
    +            if (aiop->outcome == IOOpOutcomeFailed && aiop->error == EBADF) {
    
    905
    +                /* The fd is invalid: raise an IOError exception in the blocked
    
    906
    +                 * thread. (See bug #4934 for what happens without this.)
    
    907
    +                 */
    
    908
    +                StgTSO *tso = aiop->notify.tso;
    
    909
    +                debugTrace(DEBUG_iomanager,
    
    910
    +                           "Raising exception in thread %" FMT_StgThreadID
    
    911
    +                           " blocked on an invalid fd", tso->id);
    
    912
    +                raiseAsync(cap, tso, (StgClosure *)blockedOnBadFD_closure,
    
    913
    +                           false, NULL);
    
    914
    +                break;
    
    915
    +            } else {
    
    916
    +                /* We should be guaranteed that the tso is still on the same
    
    917
    +                 * cap because the tso was not on the run queue of any cap and
    
    918
    +                 * so is not subject to thread migration.
    
    919
    +                 */
    
    920
    +                StgTSO *tso      = aiop->notify.tso;
    
    921
    +                tso->why_blocked = NotBlocked;
    
    922
    +                tso->_link       = END_TSO_QUEUE;
    
    923
    +                pushOnRunQueue(cap, tso);
    
    924
    +            }
    
    925
    +            break;
    
    926
    +        }
    
    927
    +        case NotifyMVar:
    
    928
    +            performTryPutMVar(cap, aiop->notify.mvar, Unit_closure);
    
    929
    +            break;
    
    930
    +
    
    931
    +        case NotifyTVar:
    
    932
    +            barf("uring iomgr: TVar notification not yet supported");
    
    933
    +            break;
    
    934
    +    }
    
    935
    +}
    
    936
    +
    
    937
    +
    
    938
    +/* Process all the I/O completions that are currently available without
    
    939
    + * blocking.
    
    940
    + *
    
    941
    + * This will correctly deal with completion queue overflow: if the completions
    
    942
    + * queue is empty but has the overflow bit set then io_uring_peek_batch_cqe
    
    943
    + * will do another non-blocking uring enter to refill the completion queue.
    
    944
    + */
    
    945
    +static void processIOCompletions(Capability *cap, CapIOManager *iomgr)
    
    946
    +{
    
    947
    +    struct io_uring *uring = iomgr->uring;
    
    948
    +    while (1) {
    
    949
    +        unsigned head, count = 0;
    
    950
    +        struct io_uring_cqe *cqe;
    
    951
    +        io_uring_for_each_cqe(uring, head, cqe) {
    
    952
    +            uint64_t cqe_data = io_uring_cqe_get_data64(cqe);
    
    953
    +            if (RTS_UNLIKELY(cqe_data & AIOP_TAG_CANCEL)) {
    
    954
    +
    
    955
    +            } else {
    
    956
    +                int ix = cqe_data;
    
    957
    +                StgAsyncIOOp *aiop = indexClosureTable(&iomgr->aiop_table, ix);
    
    958
    +                removeClosureTable(cap, &iomgr->aiop_table, ix);
    
    959
    +                aiop->result = cqe->res;
    
    960
    +                //TODO: if we use these tags in the sqe_data we'll need to reconstruct
    
    961
    +                // them for cancel so we can find the right item. e.g. if we use AIOP_TAG_OP_NB
    
    962
    +                // we'd need to distinguish in the aiop->flags for example.
    
    963
    +                if (cqe_data & AIOP_TAG_OP_NB) {
    
    964
    +                    iomgr->n_inflight_nb--;
    
    965
    +                    iomgr->n_submitted_nb--;
    
    966
    +                } else {
    
    967
    +                    iomgr->n_inflight_b--;
    
    968
    +                    iomgr->n_submitted_b--;
    
    969
    +                }
    
    970
    +                notifyIOCompletion(cap, aiop);
    
    971
    +            }
    
    972
    +            count++;
    
    973
    +        }
    
    974
    +        io_uring_cq_advance(uring, count);
    
    975
    +        if (RTS_UNLIKELY(io_uring_cq_has_overflow(uring))) {
    
    976
    +            if (io_uring_get_events(uring) < 0) {
    
    977
    +                sysErrorBelch("io_uring_enter");
    
    978
    +                stg_exit(EXIT_FAILURE);
    
    979
    +            }
    
    980
    +            continue;
    
    981
    +        } else {
    
    982
    +            break;
    
    983
    +        }
    
    984
    +    }
    
    985
    +}
    
    986
    +
    
    987
    +
    
    988
    +/* Check invariants that must hold on entry to and exit from the scheduler.
    
    989
    + * Used before/after {poll,await}CompletedTimeoutsOrIOURing which are called
    
    990
    + * from the scheduler.
    
    991
    + */
    
    992
    +static void assertURingSchedulerInvariants(CapIOManager *iomgr)
    
    993
    +{
    
    994
    +    struct io_uring *uring = iomgr->uring;
    
    995
    +
    
    996
    +    // That our tracking counters are consistent.
    
    997
    +    ASSERT(iomgr->n_submitted_b == iomgr->n_prepared_b
    
    998
    +                                 + iomgr->n_inflight_b);
    
    999
    +    ASSERT(iomgr->n_submitted_nb == iomgr->n_prepared_nb
    
    1000
    +                                  + iomgr->n_overflow_nb
    
    1001
    +                                  + iomgr->n_inflight_nb);
    
    1002
    +
    
    1003
    +    // That we are within limits
    
    1004
    +    ASSERT(iomgr->n_inflight_b  <= iomgr->limit_inflight_b);
    
    1005
    +    ASSERT(iomgr->n_inflight_nb <= iomgr->limit_inflight_nb);
    
    1006
    +
    
    1007
    +    // That we correctly track the submission queue size.
    
    1008
    +    ASSERT((int)io_uring_sq_ready(uring) == iomgr->n_prepared_b
    
    1009
    +                                          + iomgr->n_prepared_nb);
    
    1010
    +
    
    1011
    +    // That our overflow queue is consistent with the overflow counter.
    
    1012
    +    ASSERT(iomgr->n_overflow_nb > 0
    
    1013
    +         ? iomgr->overflow_sqe_q_hd == NULL &&
    
    1014
    +           iomgr->overflow_sqe_q_tl == NULL &&
    
    1015
    +           iomgr->overflow_tso_q_hd == END_TSO_QUEUE &&
    
    1016
    +           iomgr->overflow_tso_q_tl == END_TSO_QUEUE
    
    1017
    +         : iomgr->n_overflow_nb == 0 &&
    
    1018
    +           iomgr->overflow_sqe_q_hd != NULL &&
    
    1019
    +           iomgr->overflow_sqe_q_tl != NULL &&
    
    1020
    +           iomgr->overflow_tso_q_hd != END_TSO_QUEUE &&
    
    1021
    +           iomgr->overflow_tso_q_tl != END_TSO_QUEUE);
    
    1022
    +}
    
    1023
    +
    
    1024
    +/* If there are any completed I/O operations or expired timers, process the
    
    1025
    + * completions as appropriate. If there are none, return without waiting.
    
    1026
    + *
    
    1027
    + * This is the non-blocking variant. See awaitCompletedTimeoutsOrIOURing
    
    1028
    + * for the potentially-blocking variant.
    
    1029
    + */
    
    1030
    +void pollCompletedTimeoutsOrIOURing(Capability *cap)
    
    1031
    +{
    
    1032
    +    CapIOManager    *iomgr = cap->iomgr;
    
    1033
    +    struct io_uring *uring = iomgr->uring;
    
    1034
    +
    
    1035
    +    assertURingSchedulerInvariants(iomgr);
    
    1036
    +
    
    1037
    +    /* Process timeouts, if any, but don't immediately return to the scheduler,
    
    1038
    +     * since we should submit I/O and reap any completions too.
    
    1039
    +     */
    
    1040
    +    if (!isEmptyTimeoutQueue(iomgr->timeout_queue)) {
    
    1041
    +        Time now = getProcessElapsedTime();
    
    1042
    +        processTimeoutCompletions(cap, now);
    
    1043
    +    }
    
    1044
    +
    
    1045
    +    /* Submit I/O if needed */
    
    1046
    +    if (io_uring_sq_ready(uring)) {
    
    1047
    +        int res = io_uring_submit_and_get_events(uring);
    
    1048
    +
    
    1049
    +        if (RTS_UNLIKELY(res < 0)) {
    
    1050
    +            if (res == -EBUSY) {
    
    1051
    +                /* This is an odd one. According to the doc:
    
    1052
    +                 * If the IORING_FEAT_NODROP feature flag is set, then EBUSY
    
    1053
    +                 * will be returned if there were overflow entries,
    
    1054
    +                 * IORING_ENTER_GETEVENTS flag is set and not all of the
    
    1055
    +                 * overflow entries were able to be flushed to the CQ ring.
    
    1056
    +                 *
    
    1057
    +                 * So it's not really an error at all. It just means we will
    
    1058
    +                 * have to do multiple iterations in processIOCompletions()
    
    1059
    +                 * to collect all the completions.
    
    1060
    +                 *
    
    1061
    +                 * Thus EBUSY should imply that there are entries in the CQ.
    
    1062
    +                 */
    
    1063
    +                ASSERT(io_uring_cq_ready(uring) > 0);
    
    1064
    +            }
    
    1065
    +        } else {
    
    1066
    +            ASSERT(res == iomgr->n_prepared_b + iomgr->n_prepared_nb);
    
    1067
    +            /* We're using IORING_SETUP_SUBMIT_ALL so we should expect to have
    
    1068
    +             * all of them submitted, or an error.
    
    1069
    +             * https://github.com/axboe/liburing/issues/186
    
    1070
    +             * Alternatively, we could loop and submit the remainder.
    
    1071
    +             */
    
    1072
    +           iomgr->n_inflight_b  += iomgr->n_prepared_b;
    
    1073
    +           iomgr->n_inflight_nb += iomgr->n_prepared_nb;
    
    1074
    +           iomgr->n_prepared_b  = 0;
    
    1075
    +           iomgr->n_prepared_nb = 0;
    
    1076
    +        }
    
    1077
    +    }
    
    1078
    +
    
    1079
    +    if (io_uring_cq_ready(uring)) {
    
    1080
    +        processIOCompletions(cap, iomgr);
    
    1081
    +    }
    
    1082
    +    //TODO: now we need to check if we have any items in our overflow queue
    
    1083
    +    //and if so, we need to copy some of those into the SQ and submit them.
    
    1084
    +    //copy in up to either the SQ limit or in-flight limit.
    
    1085
    +
    
    1086
    +    assertURingSchedulerInvariants(iomgr);
    
    1087
    +}
    
    1088
    +
    
    1089
    +
    
    1090
    +/* If there are any completed I/O operations or expired timers, process the
    
    1091
    + * completions as appropriate. If there are none, wait until I/O or a timer
    
    1092
    + * does complete (or we get a signal with a handler) and process the
    
    1093
    + * completions as appropriate.
    
    1094
    + *
    
    1095
    + * This is the potentially-blocking variant. See pollCompletedTimeoutsOrIOURing
    
    1096
    + * for the non-blocking variant.
    
    1097
    + */
    
    1098
    +void awaitCompletedTimeoutsOrIOURing(Capability *cap)
    
    1099
    +{
    
    1100
    +    CapIOManager    *iomgr = cap->iomgr;
    
    1101
    +    struct io_uring *uring = iomgr->uring;
    
    1102
    +
    
    1103
    +    assertURingSchedulerInvariants(iomgr);
    
    1104
    +
    
    1105
    +    do {
    
    1106
    +
    
    1107
    +        /* We're being asked (by the scheduler) to block if there's no
    
    1108
    +         * immediate timer or I/O completions. So there had better be
    
    1109
    +         * some pending I/O or pending timers, or we'd deadlock.
    
    1110
    +         */
    
    1111
    +        ASSERT(!isEmptyTimeoutQueue(iomgr->timeout_queue) ||
    
    1112
    +               !isEmptyClosureTable(&iomgr->aiop_table));
    
    1113
    +
    
    1114
    +        Time now = getProcessElapsedTime();
    
    1115
    +        processTimeoutCompletions(cap, now);
    
    1116
    +
    
    1117
    +        /* If we didn't wake any threads due to expiring timeouts, then we need
    
    1118
    +         * to wait on I/O. Or to put it another way, even if we did wake some
    
    1119
    +         * threads, we'll still poll (but not wait) for I/O. This is to ensure
    
    1120
    +         * we avoid starving threads blocked on I/O.
    
    1121
    +         */
    
    1122
    +        bool wait = emptyRunQueue(cap);
    
    1123
    +
    
    1124
    +        /* There are four possible cases:
    
    1125
    +         * 1. non-blocking check for I/O completion with I/O submission
    
    1126
    +         * 2. non-blocking check for I/O completion with no I/O submission
    
    1127
    +         * 3. blocking wait for I/O completion with a timeout
    
    1128
    +         * 4. blocking wait for I/O completion without a timeout
    
    1129
    +         *
    
    1130
    +         * If we woke any threads due to timeouts we're in the first or second
    
    1131
    +         * case.
    
    1132
    +         *
    
    1133
    +         * Otherwise we're in one of the blocking cases. We will use a timeout
    
    1134
    +         * if the timeout queue is non-empty.
    
    1135
    +         */
    
    1136
    +
    
    1137
    +        int res;
    
    1138
    +        if (!wait && io_uring_sq_ready(uring)) {
    
    1139
    +            /* Case 1 (as above) */
    
    1140
    +            res = io_uring_submit_and_get_events(uring);
    
    1141
    +        } else if (!wait) {
    
    1142
    +            /* Case 2 (as above) */
    
    1143
    +            res = io_uring_get_events(uring);
    
    1144
    +        } else {
    
    1145
    +            struct timespec tv;
    
    1146
    +            if (timeoutInNanoseconds(iomgr, true, now, &tv)) {
    
    1147
    +                /* Case 3 (as above) */
    
    1148
    +                /* struct timespec and struct __kernel_timespec are compatible
    
    1149
    +                 * but not exactly the same. Sigh. */
    
    1150
    +                struct __kernel_timespec ts = { .tv_sec  = tv.tv_sec,
    
    1151
    +                                                .tv_nsec = tv.tv_nsec };
    
    1152
    +                struct io_uring_cqe *cqe_unused;
    
    1153
    +                res = io_uring_submit_and_wait_timeout(uring, &cqe_unused,
    
    1154
    +                                                       1, &ts, NULL);
    
    1155
    +            } else  {
    
    1156
    +                /* Case 4 (as above) */
    
    1157
    +                res = io_uring_submit_and_wait(uring, 1);
    
    1158
    +            }
    
    1159
    +        }
    
    1160
    +        
    
    1161
    +        if (res >= 0) {
    
    1162
    +            processIOCompletions(cap, iomgr);
    
    1163
    +        } else if (errno == EINTR) {
    
    1164
    +
    
    1165
    +        } else if (errno == EBUSY || errno == EAGAIN) {
    
    1166
    +
    
    1167
    +        } else {
    
    1168
    +            sysErrorBelch("io_uring_enter");
    
    1169
    +            stg_exit(EXIT_FAILURE);
    
    1170
    +        }
    
    1171
    +    } while (emptyRunQueue(cap)
    
    1172
    +         && (!isEmptyClosureTable(&iomgr->aiop_table) ||
    
    1173
    +             !isEmptyTimeoutQueue(iomgr->timeout_queue))
    
    1174
    +         && getSchedState() == SCHED_RUNNING);
    
    1175
    +
    
    1176
    +    assertURingSchedulerInvariants(iomgr);
    
    1177
    +}
    
    1178
    +
    
    1179
    +
    
    1180
    +/******************************************************************************
    
    1181
    + * Local helper utilities
    
    1182
    + */
    
    1183
    +
    
    1184
    +static int enlargeTables(Capability *cap, CapIOManager *iomgr)
    
    1185
    +{
    
    1186
    +    int oldcapacity = capacityClosureTable(&iomgr->aiop_table);
    
    1187
    +    int newcapacity = (oldcapacity == 0) ? 1 : (oldcapacity * 2);
    
    1188
    +
    
    1189
    +    int fail = enlargeClosureTable(cap, &iomgr->aiop_table, newcapacity);
    
    1190
    +    if (RTS_UNLIKELY(fail)) return fail;
    
    1191
    +    return 0;
    
    1192
    +}
    
    1193
    +
    
    1194
    +
    
    1195
    +/* 
    
    1196
    + */
    
    1197
    +static void enqueueOverflowQueue(Capability *cap, CapIOManager *iomgr,
    
    1198
    +                                 StgTSO *tso, struct io_uring_sqe *sqe)
    
    1199
    +{
    
    1200
    +    /* Append the TSO to the tail of the overflow queue of TSOs. */
    
    1201
    +    ASSERT(tso->_link == END_TSO_QUEUE);
    
    1202
    +    if (iomgr->overflow_tso_q_hd == END_TSO_QUEUE) {
    
    1203
    +        iomgr->overflow_tso_q_hd = tso;
    
    1204
    +    } else {
    
    1205
    +        setTSOLink(cap, iomgr->overflow_tso_q_tl, tso);
    
    1206
    +    }
    
    1207
    +    iomgr->overflow_tso_q_tl = tso;
    
    1208
    +
    
    1209
    +    /* And append the SQE to the tail of the overflow queue of SQEs. */
    
    1210
    +    struct overflow_sqe_q_t *entry;
    
    1211
    +    entry = stgMallocBytes(sizeof(struct overflow_sqe_q_t), "uring iomgr");
    
    1212
    +    *entry = (struct overflow_sqe_q_t) {
    
    1213
    +               .sqe  = sqe,
    
    1214
    +               .next = NULL,
    
    1215
    +#if defined(DEBUG)
    
    1216
    +               .tid  = tso->id
    
    1217
    +#endif
    
    1218
    +             };
    
    1219
    +    if (iomgr->overflow_sqe_q_hd == NULL) {
    
    1220
    +        iomgr->overflow_sqe_q_hd = entry;
    
    1221
    +    } else {
    
    1222
    +        iomgr->overflow_sqe_q_tl->next = entry;
    
    1223
    +    }
    
    1224
    +    iomgr->overflow_sqe_q_tl = entry;
    
    1225
    +}
    
    1226
    +
    
    1227
    +
    
    1228
    +static void dequeueOverflowQueue(CapIOManager *iomgr,
    
    1229
    +                                 StgTSO **ptso, struct io_uring_sqe **psqe)
    
    1230
    +{
    
    1231
    +    /* Remove the TSO and SQE from the head of their respective queues */
    
    1232
    +    StgTSO                  *tso   = iomgr->overflow_tso_q_hd;
    
    1233
    +    struct overflow_sqe_q_t *entry = iomgr->overflow_sqe_q_hd;
    
    1234
    +
    
    1235
    +    if (tso == END_TSO_QUEUE) {
    
    1236
    +        //TODO: decide if we need this or if we should assume the queue is
    
    1237
    +        // non-empty
    
    1238
    +        ASSERT(entry == NULL);
    
    1239
    +        *ptso = END_TSO_QUEUE;
    
    1240
    +        *psqe = NULL;
    
    1241
    +    } else {
    
    1242
    +        iomgr->overflow_tso_q_hd = tso->_link;
    
    1243
    +        RELAXED_STORE(&tso->_link, END_TSO_QUEUE);
    
    1244
    +        if (iomgr->overflow_tso_q_hd == END_TSO_QUEUE) {
    
    1245
    +            iomgr->overflow_tso_q_tl = END_TSO_QUEUE;
    
    1246
    +        }
    
    1247
    +
    
    1248
    +        iomgr->overflow_sqe_q_hd = entry->next;
    
    1249
    +        if (iomgr->overflow_sqe_q_hd == NULL) {
    
    1250
    +            iomgr->overflow_sqe_q_tl = NULL;
    
    1251
    +        }
    
    1252
    +
    
    1253
    +        *ptso = tso;
    
    1254
    +        *psqe = entry->sqe;
    
    1255
    +    }
    
    1256
    +}
    
    1257
    +
    
    1258
    +#endif /* IOMGR_ENABLED_URING */
    
    1259
    +

  • rts/posix/URing.h
    1
    +/* -----------------------------------------------------------------------------
    
    2
    + *
    
    3
    + * (c) The GHC Team 2021-2023
    
    4
    + *
    
    5
    + * An I/O manager based on the Linux io_uring API.
    
    6
    + *
    
    7
    + * Prototypes for functions in URing.c
    
    8
    + *
    
    9
    + * -------------------------------------------------------------------------*/
    
    10
    +
    
    11
    +#pragma once
    
    12
    +
    
    13
    +#include "IOManager.h"
    
    14
    +
    
    15
    +#include "BeginPrivate.h"
    
    16
    +
    
    17
    +#if defined(IOMGR_ENABLED_URING)
    
    18
    +
    
    19
    +void initCapabilityIOManagerURing(Capability *cap, CapIOManager *iomgr);
    
    20
    +void initCapabilityIOManagerAfterForkURing(Capability *cap, CapIOManager *iomgr);
    
    21
    +
    
    22
    +/* Synchronous I/O and timer operations */
    
    23
    +int syncIOWaitReadyURing(Capability *cap, StgTSO *tso,
    
    24
    +                         IOReadOrWrite rw, int fd);
    
    25
    +
    
    26
    +int syncIOReadWriteURing(Capability *cap, StgTSO *tso,
    
    27
    +                         IOReadOrWrite rw, int fd,
    
    28
    +                         StgClosure *live, void *buf,
    
    29
    +                         size_t len, off_t off);
    
    30
    +
    
    31
    +void syncIOCancelURing(Capability *cap, StgTSO *tso);
    
    32
    +
    
    33
    +/* Asynchronous operations */
    
    34
    +int asyncIOWaitReadyURing(Capability *cap, StgTSO *tso, StgAsyncIOOp *aiop,
    
    35
    +                          IOReadOrWrite rw, int fd);
    
    36
    +
    
    37
    +int asyncIOReadWriteURing(Capability *cap, StgTSO *tso, StgAsyncIOOp *aiop,
    
    38
    +                          IOReadOrWrite rw, int fd,
    
    39
    +                          StgClosure *live, void *buf,
    
    40
    +                          size_t len, off_t off);
    
    41
    +
    
    42
    +void asyncIOCancelURing(Capability *cap, StgAsyncIOOp *aiop);
    
    43
    +
    
    44
    +/* Scheduler operations */
    
    45
    +bool anyPendingTimeoutsOrIOURing(CapIOManager *iomgr);
    
    46
    +void pollCompletedTimeoutsOrIOURing(Capability *cap);
    
    47
    +void awaitCompletedTimeoutsOrIOURing(Capability *cap);
    
    48
    +
    
    49
    +#endif /* IOMGR_ENABLED_URING */
    
    50
    +
    
    51
    +#include "EndPrivate.h"
    
    52
    +

  • rts/posix/URing.svg
    1
    +<?xml version="1.0" encoding="UTF-8" standalone="no"?>
    
    2
    +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/PR-SVG-20010719/DTD/svg10.dtd">
    
    3
    +<svg width="116cm" height="39cm" viewBox="-281 18 2304 768" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    
    4
    +  <g>
    
    5
    +    <rect style="fill: #ffffff" x="-200" y="20" width="158.525" height="70"/>
    
    6
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-200" y="20" width="158.525" height="70"/>
    
    7
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120.738" y="42.8812">
    
    8
    +      <tspan x="-120.738" y="42.8812">Primop: async</tspan>
    
    9
    +      <tspan x="-120.738" y="58.8812">non-blocking I/O</tspan>
    
    10
    +      <tspan x="-120.738" y="74.8812">submission</tspan>
    
    11
    +    </text>
    
    12
    +  </g>
    
    13
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120.738" y="55">
    
    14
    +    <tspan x="-120.738" y="55"></tspan>
    
    15
    +  </text>
    
    16
    +  <g>
    
    17
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120.738" y1="90" x2="-120.224" y2="112.267"/>
    
    18
    +    <polygon style="fill: #000000" points="-120.052,119.765 -125.281,109.882 -120.224,112.267 -115.283,109.652 "/>
    
    19
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120.052,119.765 -125.281,109.882 -120.224,112.267 -115.283,109.652 "/>
    
    20
    +  </g>
    
    21
    +  <g>
    
    22
    +    <rect style="fill: #ffffff" x="-200" y="122" width="160" height="54"/>
    
    23
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-200" y="122" width="160" height="54"/>
    
    24
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="144.881">
    
    25
    +      <tspan x="-120" y="144.881">Allocate AIOP</tspan>
    
    26
    +      <tspan x="-120" y="160.881">Allocate table index</tspan>
    
    27
    +    </text>
    
    28
    +  </g>
    
    29
    +  <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="-280" y1="105" x2="180" y2="105"/>
    
    30
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-280" y="98.6222">
    
    31
    +    <tspan x="-280" y="98.6222">RTS CMM</tspan>
    
    32
    +  </text>
    
    33
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-280" y="121.539">
    
    34
    +    <tspan x="-280" y="121.539">RTS C</tspan>
    
    35
    +  </text>
    
    36
    +  <g>
    
    37
    +    <rect style="fill: #ffffff" x="0" y="25" width="160" height="60"/>
    
    38
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="0" y="25" width="160" height="60"/>
    
    39
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="58.8813">
    
    40
    +      <tspan x="80" y="58.8813">Primop: GC and retry</tspan>
    
    41
    +    </text>
    
    42
    +  </g>
    
    43
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="55">
    
    44
    +    <tspan x="80" y="55"></tspan>
    
    45
    +  </text>
    
    46
    +  <g>
    
    47
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="0" y1="55" x2="-31.7389" y2="55"/>
    
    48
    +    <polygon style="fill: #000000" points="-39.2389,55 -29.2389,50 -31.7389,55 -29.2389,60 "/>
    
    49
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-39.2389,55 -29.2389,50 -31.7389,55 -29.2389,60 "/>
    
    50
    +  </g>
    
    51
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="149">
    
    52
    +    <tspan x="-120" y="149"></tspan>
    
    53
    +  </text>
    
    54
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="156.981">
    
    55
    +    <tspan x="-20" y="156.981">mem alloc failure?</tspan>
    
    56
    +  </text>
    
    57
    +  <g>
    
    58
    +    <polygon style="fill: #ffffff" points="-120,262 -40,322.81 -120,383.619 -200,322.81 "/>
    
    59
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120,262 -40,322.81 -120,383.619 -200,322.81 "/>
    
    60
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="310.691">
    
    61
    +      <tspan x="-120" y="310.691">inflight</tspan>
    
    62
    +      <tspan x="-120" y="326.691">within</tspan>
    
    63
    +      <tspan x="-120" y="342.691">limit?</tspan>
    
    64
    +    </text>
    
    65
    +  </g>
    
    66
    +  <g>
    
    67
    +    <rect style="fill: #ffffff" x="-200" y="402" width="160" height="60"/>
    
    68
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-200" y="402" width="160" height="60"/>
    
    69
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="427.881">
    
    70
    +      <tspan x="-120" y="427.881">Allocate SQE on ring</tspan>
    
    71
    +      <tspan x="-120" y="443.881">Inc prep counter</tspan>
    
    72
    +    </text>
    
    73
    +  </g>
    
    74
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-80" y="386.431">
    
    75
    +    <tspan x="-80" y="386.431">Yes</tspan>
    
    76
    +  </text>
    
    77
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="346.431">
    
    78
    +    <tspan x="-20" y="346.431">No</tspan>
    
    79
    +  </text>
    
    80
    +  <g>
    
    81
    +    <rect style="fill: #ffffff" x="-12.825" y="395" width="185.65" height="70"/>
    
    82
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-12.825" y="395" width="185.65" height="70"/>
    
    83
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="417.881">
    
    84
    +      <tspan x="80" y="417.881">Allocate SQE on heap</tspan>
    
    85
    +      <tspan x="80" y="433.881">Inc overflow counter</tspan>
    
    86
    +      <tspan x="80" y="449.881">TSO &amp; SQE on overflow Q</tspan>
    
    87
    +    </text>
    
    88
    +  </g>
    
    89
    +  <g>
    
    90
    +    <rect style="fill: #ffffff" x="-201.625" y="202" width="163.25" height="40"/>
    
    91
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-201.625" y="202" width="163.25" height="40"/>
    
    92
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="225.881">
    
    93
    +      <tspan x="-120" y="225.881">Inc submitted counter</tspan>
    
    94
    +    </text>
    
    95
    +  </g>
    
    96
    +  <g>
    
    97
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120" y1="384.619" x2="-120" y2="392.264"/>
    
    98
    +    <polygon style="fill: #000000" points="-120,399.764 -125,389.764 -120,392.264 -115,389.764 "/>
    
    99
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120,399.764 -125,389.764 -120,392.264 -115,389.764 "/>
    
    100
    +  </g>
    
    101
    +  <g>
    
    102
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120" y1="176" x2="-120" y2="192.264"/>
    
    103
    +    <polygon style="fill: #000000" points="-120,199.764 -125,189.764 -120,192.264 -115,189.764 "/>
    
    104
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120,199.764 -125,189.764 -120,192.264 -115,189.764 "/>
    
    105
    +  </g>
    
    106
    +  <g>
    
    107
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120" y1="242" x2="-120" y2="252.264"/>
    
    108
    +    <polygon style="fill: #000000" points="-120,259.764 -125,249.764 -120,252.264 -115,249.764 "/>
    
    109
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-120,259.764 -125,249.764 -120,252.264 -115,249.764 "/>
    
    110
    +  </g>
    
    111
    +  <g>
    
    112
    +    <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-40,149 80,125 80,94.7361 "/>
    
    113
    +    <polygon style="fill: #000000" points="80,87.2361 85,97.2361 80,94.7361 75,97.2361 "/>
    
    114
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="80,87.2361 85,97.2361 80,94.7361 75,97.2361 "/>
    
    115
    +  </g>
    
    116
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="430">
    
    117
    +    <tspan x="80" y="430"></tspan>
    
    118
    +  </text>
    
    119
    +  <g>
    
    120
    +    <rect style="fill: #ffffff" x="-100" y="482" width="160" height="40"/>
    
    121
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-100" y="482" width="160" height="40"/>
    
    122
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="505.881">
    
    123
    +      <tspan x="-20" y="505.881">Fill in SQE</tspan>
    
    124
    +    </text>
    
    125
    +  </g>
    
    126
    +  <g>
    
    127
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-120" y1="462" x2="-106.884" y2="475.116"/>
    
    128
    +    <polygon style="fill: #000000" points="-101.581,480.419 -112.188,476.883 -106.884,475.116 -105.117,469.812 "/>
    
    129
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-101.581,480.419 -112.188,476.883 -106.884,475.116 -105.117,469.812 "/>
    
    130
    +  </g>
    
    131
    +  <g>
    
    132
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="80" y1="465" x2="67.4183" y2="475.694"/>
    
    133
    +    <polygon style="fill: #000000" points="61.7037,480.552 66.0849,470.266 67.4183,475.694 72.5614,477.885 "/>
    
    134
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="61.7037,480.552 66.0849,470.266 67.4183,475.694 72.5614,477.885 "/>
    
    135
    +  </g>
    
    136
    +  <g>
    
    137
    +    <rect style="fill: #ffffff" x="-195.75" y="677" width="157.15" height="70"/>
    
    138
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="-195.75" y="677" width="157.15" height="70"/>
    
    139
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-117.175" y="699.881">
    
    140
    +      <tspan x="-117.175" y="699.881">Primop:</tspan>
    
    141
    +      <tspan x="-117.175" y="715.881">result is AIOP</tspan>
    
    142
    +      <tspan x="-117.175" y="731.881">control to caller</tspan>
    
    143
    +    </text>
    
    144
    +  </g>
    
    145
    +  <g>
    
    146
    +    <rect style="fill: #ffffff" x="0" y="677" width="160" height="70"/>
    
    147
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="0" y="677" width="160" height="70"/>
    
    148
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="699.881">
    
    149
    +      <tspan x="80" y="699.881">Primop:</tspan>
    
    150
    +      <tspan x="80" y="715.881">result is AIOP</tspan>
    
    151
    +      <tspan x="80" y="731.881">control to scheduler</tspan>
    
    152
    +    </text>
    
    153
    +  </g>
    
    154
    +  <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="-280" y1="662" x2="180" y2="662"/>
    
    155
    +  <g>
    
    156
    +    <polygon style="fill: #ffffff" points="-20,542 120,592 -20,642 -160,592 "/>
    
    157
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-20,542 120,592 -20,642 -160,592 "/>
    
    158
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="-20" y="587.881">
    
    159
    +      <tspan x="-20" y="587.881">inflight within limit</tspan>
    
    160
    +      <tspan x="-20" y="603.881">And SQ ring not full</tspan>
    
    161
    +    </text>
    
    162
    +  </g>
    
    163
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-280" y="682">
    
    164
    +    <tspan x="-280" y="682">RTS CMM</tspan>
    
    165
    +  </text>
    
    166
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-280" y="655.622">
    
    167
    +    <tspan x="-280" y="655.622">RTS C</tspan>
    
    168
    +  </text>
    
    169
    +  <g>
    
    170
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-20" y1="522.977" x2="-20" y2="532.264"/>
    
    171
    +    <polygon style="fill: #000000" points="-20,539.764 -25,529.764 -20,532.264 -15,529.764 "/>
    
    172
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-20,539.764 -25,529.764 -20,532.264 -15,529.764 "/>
    
    173
    +  </g>
    
    174
    +  <g>
    
    175
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="-55" y1="629.5" x2="-73.6614" y2="668.229"/>
    
    176
    +    <polygon style="fill: #000000" points="-76.917,674.986 -77.0805,663.806 -73.6614,668.229 -68.0718,668.147 "/>
    
    177
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-76.917,674.986 -77.0805,663.806 -73.6614,668.229 -68.0718,668.147 "/>
    
    178
    +  </g>
    
    179
    +  <g>
    
    180
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="15" y1="629.5" x2="35.4655" y2="668.384"/>
    
    181
    +    <polygon style="fill: #000000" points="38.9586,675.021 29.8765,668.501 35.4655,668.384 38.7257,663.843 "/>
    
    182
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="38.9586,675.021 29.8765,668.501 35.4655,668.384 38.7257,663.843 "/>
    
    183
    +  </g>
    
    184
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-100" y="646.431">
    
    185
    +    <tspan x="-100" y="646.431">Yes</tspan>
    
    186
    +  </text>
    
    187
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="40" y="646.431">
    
    188
    +    <tspan x="40" y="646.431">No</tspan>
    
    189
    +  </text>
    
    190
    +  <g>
    
    191
    +    <rect style="fill: #ffffff" x="1019.34" y="25" width="163.85" height="70"/>
    
    192
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1019.34" y="25" width="163.85" height="70"/>
    
    193
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1101.26" y="47.8812">
    
    194
    +      <tspan x="1101.26" y="47.8812">Primop: async</tspan>
    
    195
    +      <tspan x="1101.26" y="63.8812">blocking I/O</tspan>
    
    196
    +      <tspan x="1101.26" y="79.8812">submission</tspan>
    
    197
    +    </text>
    
    198
    +  </g>
    
    199
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1101.26" y="60">
    
    200
    +    <tspan x="1101.26" y="60"></tspan>
    
    201
    +  </text>
    
    202
    +  <g>
    
    203
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1101.26" y1="95" x2="1101.76" y2="115.267"/>
    
    204
    +    <polygon style="fill: #000000" points="1101.95,122.765 1096.7,112.89 1101.76,115.267 1106.7,112.645 "/>
    
    205
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1101.95,122.765 1096.7,112.89 1101.76,115.267 1106.7,112.645 "/>
    
    206
    +  </g>
    
    207
    +  <g>
    
    208
    +    <rect style="fill: #ffffff" x="1022" y="125" width="160" height="60"/>
    
    209
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1022" y="125" width="160" height="60"/>
    
    210
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="150.881">
    
    211
    +      <tspan x="1102" y="150.881">Allocate AIOP</tspan>
    
    212
    +      <tspan x="1102" y="166.881">Allocate table index</tspan>
    
    213
    +    </text>
    
    214
    +  </g>
    
    215
    +  <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="942" y1="110" x2="1402" y2="110"/>
    
    216
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="942" y="103.622">
    
    217
    +    <tspan x="942" y="103.622">RTS CMM</tspan>
    
    218
    +  </text>
    
    219
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="942" y="126.539">
    
    220
    +    <tspan x="942" y="126.539">RTS C</tspan>
    
    221
    +  </text>
    
    222
    +  <g>
    
    223
    +    <rect style="fill: #ffffff" x="1222" y="30" width="160" height="60"/>
    
    224
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1222" y="30" width="160" height="60"/>
    
    225
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1302" y="63.8813">
    
    226
    +      <tspan x="1302" y="63.8813">Primop: GC and retry</tspan>
    
    227
    +    </text>
    
    228
    +  </g>
    
    229
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1302" y="60">
    
    230
    +    <tspan x="1302" y="60"></tspan>
    
    231
    +  </text>
    
    232
    +  <g>
    
    233
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1222" y1="60" x2="1192.92" y2="60"/>
    
    234
    +    <polygon style="fill: #000000" points="1185.42,60 1195.42,55 1192.92,60 1195.42,65 "/>
    
    235
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1185.42,60 1195.42,55 1192.92,60 1195.42,65 "/>
    
    236
    +  </g>
    
    237
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="155">
    
    238
    +    <tspan x="1102" y="155"></tspan>
    
    239
    +  </text>
    
    240
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1222" y="161.981">
    
    241
    +    <tspan x="1222" y="161.981">mem alloc failure?</tspan>
    
    242
    +  </text>
    
    243
    +  <g>
    
    244
    +    <polygon style="fill: #ffffff" points="1102,285 1182,345.81 1102,406.619 1022,345.81 "/>
    
    245
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,285 1182,345.81 1102,406.619 1022,345.81 "/>
    
    246
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="333.691">
    
    247
    +      <tspan x="1102" y="333.691">inflight</tspan>
    
    248
    +      <tspan x="1102" y="349.691">within</tspan>
    
    249
    +      <tspan x="1102" y="365.691">limit?</tspan>
    
    250
    +    </text>
    
    251
    +  </g>
    
    252
    +  <g>
    
    253
    +    <rect style="fill: #ffffff" x="1022" y="440" width="160" height="60"/>
    
    254
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1022" y="440" width="160" height="60"/>
    
    255
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="465.881">
    
    256
    +      <tspan x="1102" y="465.881">Allocate SQE on ring</tspan>
    
    257
    +      <tspan x="1102" y="481.881">Inc ring prep counter</tspan>
    
    258
    +    </text>
    
    259
    +  </g>
    
    260
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1042" y="409.431">
    
    261
    +    <tspan x="1042" y="409.431">Yes</tspan>
    
    262
    +  </text>
    
    263
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1142" y="409.431">
    
    264
    +    <tspan x="1142" y="409.431">No</tspan>
    
    265
    +  </text>
    
    266
    +  <g>
    
    267
    +    <rect style="fill: #ffffff" x="1020.37" y="205" width="163.25" height="40"/>
    
    268
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1020.37" y="205" width="163.25" height="40"/>
    
    269
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="228.881">
    
    270
    +      <tspan x="1102" y="228.881">Inc submitted counter</tspan>
    
    271
    +    </text>
    
    272
    +  </g>
    
    273
    +  <g>
    
    274
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102" y1="406.62" x2="1102" y2="430.264"/>
    
    275
    +    <polygon style="fill: #000000" points="1102,437.764 1097,427.764 1102,430.264 1107,427.764 "/>
    
    276
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,437.764 1097,427.764 1102,430.264 1107,427.764 "/>
    
    277
    +  </g>
    
    278
    +  <g>
    
    279
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102" y1="185" x2="1102" y2="195.264"/>
    
    280
    +    <polygon style="fill: #000000" points="1102,202.764 1097,192.764 1102,195.264 1107,192.764 "/>
    
    281
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,202.764 1097,192.764 1102,195.264 1107,192.764 "/>
    
    282
    +  </g>
    
    283
    +  <g>
    
    284
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102" y1="245" x2="1102" y2="275.264"/>
    
    285
    +    <polygon style="fill: #000000" points="1102,282.764 1097,272.764 1102,275.264 1107,272.764 "/>
    
    286
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,282.764 1097,272.764 1102,275.264 1107,272.764 "/>
    
    287
    +  </g>
    
    288
    +  <g>
    
    289
    +    <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1182,155 1302,130 1302,99.7361 "/>
    
    290
    +    <polygon style="fill: #000000" points="1302,92.2361 1307,102.236 1302,99.7361 1297,102.236 "/>
    
    291
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1302,92.2361 1307,102.236 1302,99.7361 1297,102.236 "/>
    
    292
    +  </g>
    
    293
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1202" y="455">
    
    294
    +    <tspan x="1202" y="455"></tspan>
    
    295
    +  </text>
    
    296
    +  <g>
    
    297
    +    <rect style="fill: #ffffff" x="1022" y="525" width="160" height="40"/>
    
    298
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1022" y="525" width="160" height="40"/>
    
    299
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="548.881">
    
    300
    +      <tspan x="1102" y="548.881">Fill in SQE</tspan>
    
    301
    +    </text>
    
    302
    +  </g>
    
    303
    +  <g>
    
    304
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102" y1="500" x2="1102" y2="515.264"/>
    
    305
    +    <polygon style="fill: #000000" points="1102,522.764 1097,512.764 1102,515.264 1107,512.764 "/>
    
    306
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1102,522.764 1097,512.764 1102,515.264 1107,512.764 "/>
    
    307
    +  </g>
    
    308
    +  <g>
    
    309
    +    <rect style="fill: #ffffff" x="1022" y="716" width="160" height="70"/>
    
    310
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1022" y="716" width="160" height="70"/>
    
    311
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="738.881">
    
    312
    +      <tspan x="1102" y="738.881">Primop:</tspan>
    
    313
    +      <tspan x="1102" y="754.881">result is AIOP</tspan>
    
    314
    +      <tspan x="1102" y="770.881">control to caller</tspan>
    
    315
    +    </text>
    
    316
    +  </g>
    
    317
    +  <g>
    
    318
    +    <rect style="fill: #ffffff" x="1222" y="717" width="160" height="60"/>
    
    319
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1222" y="717" width="160" height="60"/>
    
    320
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1302" y="742.881">
    
    321
    +      <tspan x="1302" y="742.881">Primop:</tspan>
    
    322
    +      <tspan x="1302" y="758.881">throw exception</tspan>
    
    323
    +    </text>
    
    324
    +  </g>
    
    325
    +  <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="942" y1="697" x2="1402" y2="697"/>
    
    326
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="942" y="717">
    
    327
    +    <tspan x="942" y="717">RTS CMM</tspan>
    
    328
    +  </text>
    
    329
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="942" y="690.622">
    
    330
    +    <tspan x="942" y="690.622">RTS C</tspan>
    
    331
    +  </text>
    
    332
    +  <g>
    
    333
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1102.53" y1="565.994" x2="1102.86" y2="579.104"/>
    
    334
    +    <polygon style="fill: #000000" points="1103.05,586.601 1097.8,576.73 1102.86,579.104 1107.79,576.479 "/>
    
    335
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1103.05,586.601 1097.8,576.73 1102.86,579.104 1107.79,576.479 "/>
    
    336
    +  </g>
    
    337
    +  <g>
    
    338
    +    <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1182,345.81 1182,345 1302,345 1302,707.264 "/>
    
    339
    +    <polygon style="fill: #000000" points="1302,714.764 1297,704.764 1302,707.264 1307,704.764 "/>
    
    340
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1302,714.764 1297,704.764 1302,707.264 1307,704.764 "/>
    
    341
    +  </g>
    
    342
    +  <g>
    
    343
    +    <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="-40,322.81 -40,322 80,322 80,385.264 "/>
    
    344
    +    <polygon style="fill: #000000" points="80,392.764 75,382.764 80,385.264 85,382.764 "/>
    
    345
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="80,392.764 75,382.764 80,385.264 85,382.764 "/>
    
    346
    +  </g>
    
    347
    +  <g>
    
    348
    +    <rect style="fill: #ffffff" x="403" y="22" width="158.525" height="70"/>
    
    349
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="403" y="22" width="158.525" height="70"/>
    
    350
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="482.262" y="44.8813">
    
    351
    +      <tspan x="482.262" y="44.8813">Primop: sync</tspan>
    
    352
    +      <tspan x="482.262" y="60.8812">non-blocking I/O</tspan>
    
    353
    +      <tspan x="482.262" y="76.8812">submission</tspan>
    
    354
    +    </text>
    
    355
    +  </g>
    
    356
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="482.262" y="57">
    
    357
    +    <tspan x="482.262" y="57"></tspan>
    
    358
    +  </text>
    
    359
    +  <g>
    
    360
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="482.262" y1="92" x2="482.795" y2="117.266"/>
    
    361
    +    <polygon style="fill: #000000" points="482.953,124.764 477.743,114.872 482.795,117.266 487.741,114.661 "/>
    
    362
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="482.953,124.764 477.743,114.872 482.795,117.266 487.741,114.661 "/>
    
    363
    +  </g>
    
    364
    +  <g>
    
    365
    +    <rect style="fill: #ffffff" x="403" y="127" width="160" height="60"/>
    
    366
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="403" y="127" width="160" height="60"/>
    
    367
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="152.881">
    
    368
    +      <tspan x="483" y="152.881">Allocate AIOP</tspan>
    
    369
    +      <tspan x="483" y="168.881">Allocate table index</tspan>
    
    370
    +    </text>
    
    371
    +  </g>
    
    372
    +  <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="323" y1="107" x2="783" y2="107"/>
    
    373
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="323" y="100.622">
    
    374
    +    <tspan x="323" y="100.622">RTS CMM</tspan>
    
    375
    +  </text>
    
    376
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="323" y="123.539">
    
    377
    +    <tspan x="323" y="123.539">RTS C</tspan>
    
    378
    +  </text>
    
    379
    +  <g>
    
    380
    +    <rect style="fill: #ffffff" x="603" y="27" width="160" height="60"/>
    
    381
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="603" y="27" width="160" height="60"/>
    
    382
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="683" y="60.8812">
    
    383
    +      <tspan x="683" y="60.8812">Primop: GC and retry</tspan>
    
    384
    +    </text>
    
    385
    +  </g>
    
    386
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="683" y="57">
    
    387
    +    <tspan x="683" y="57"></tspan>
    
    388
    +  </text>
    
    389
    +  <g>
    
    390
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="603" y1="57" x2="571.262" y2="57"/>
    
    391
    +    <polygon style="fill: #000000" points="563.762,57 573.762,52 571.262,57 573.762,62 "/>
    
    392
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="563.762,57 573.762,52 571.262,57 573.762,62 "/>
    
    393
    +  </g>
    
    394
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="157">
    
    395
    +    <tspan x="483" y="157"></tspan>
    
    396
    +  </text>
    
    397
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="163.981">
    
    398
    +    <tspan x="583" y="163.981">mem alloc failure?</tspan>
    
    399
    +  </text>
    
    400
    +  <g>
    
    401
    +    <polygon style="fill: #ffffff" points="483,262 563,322.81 483,383.619 403,322.81 "/>
    
    402
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="483,262 563,322.81 483,383.619 403,322.81 "/>
    
    403
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="310.691">
    
    404
    +      <tspan x="483" y="310.691">inflight</tspan>
    
    405
    +      <tspan x="483" y="326.691">within</tspan>
    
    406
    +      <tspan x="483" y="342.691">limit?</tspan>
    
    407
    +    </text>
    
    408
    +  </g>
    
    409
    +  <g>
    
    410
    +    <rect style="fill: #ffffff" x="403" y="402" width="160" height="60"/>
    
    411
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="403" y="402" width="160" height="60"/>
    
    412
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="427.881">
    
    413
    +      <tspan x="483" y="427.881">Allocate SQE on ring</tspan>
    
    414
    +      <tspan x="483" y="443.881">Inc ring prep counter</tspan>
    
    415
    +    </text>
    
    416
    +  </g>
    
    417
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="523" y="386.431">
    
    418
    +    <tspan x="523" y="386.431">Yes</tspan>
    
    419
    +  </text>
    
    420
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="346.431">
    
    421
    +    <tspan x="583" y="346.431">No</tspan>
    
    422
    +  </text>
    
    423
    +  <g>
    
    424
    +    <rect style="fill: #ffffff" x="603" y="402" width="160" height="60"/>
    
    425
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="603" y="402" width="160" height="60"/>
    
    426
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="683" y="427.881">
    
    427
    +      <tspan x="683" y="427.881">Allocate SQE on heap</tspan>
    
    428
    +      <tspan x="683" y="443.881">TSO on overflow Q</tspan>
    
    429
    +    </text>
    
    430
    +  </g>
    
    431
    +  <g>
    
    432
    +    <rect style="fill: #ffffff" x="401.375" y="207" width="163.25" height="40"/>
    
    433
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="401.375" y="207" width="163.25" height="40"/>
    
    434
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="230.881">
    
    435
    +      <tspan x="483" y="230.881">Inc submitted counter</tspan>
    
    436
    +    </text>
    
    437
    +  </g>
    
    438
    +  <g>
    
    439
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="483" y1="384.619" x2="483" y2="392.264"/>
    
    440
    +    <polygon style="fill: #000000" points="483,399.764 478,389.764 483,392.264 488,389.764 "/>
    
    441
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="483,399.764 478,389.764 483,392.264 488,389.764 "/>
    
    442
    +  </g>
    
    443
    +  <g>
    
    444
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="483" y1="187" x2="483" y2="197.264"/>
    
    445
    +    <polygon style="fill: #000000" points="483,204.764 478,194.764 483,197.264 488,194.764 "/>
    
    446
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="483,204.764 478,194.764 483,197.264 488,194.764 "/>
    
    447
    +  </g>
    
    448
    +  <g>
    
    449
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="483" y1="247" x2="483" y2="252.264"/>
    
    450
    +    <polygon style="fill: #000000" points="483,259.764 478,249.764 483,252.264 488,249.764 "/>
    
    451
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="483,259.764 478,249.764 483,252.264 488,249.764 "/>
    
    452
    +  </g>
    
    453
    +  <g>
    
    454
    +    <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="563,157 683,127 683,96.7361 "/>
    
    455
    +    <polygon style="fill: #000000" points="683,89.2361 688,99.2361 683,96.7361 678,99.2361 "/>
    
    456
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="683,89.2361 688,99.2361 683,96.7361 678,99.2361 "/>
    
    457
    +  </g>
    
    458
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="683" y="432">
    
    459
    +    <tspan x="683" y="432"></tspan>
    
    460
    +  </text>
    
    461
    +  <g>
    
    462
    +    <rect style="fill: #ffffff" x="503" y="482" width="160" height="40"/>
    
    463
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="503" y="482" width="160" height="40"/>
    
    464
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="505.881">
    
    465
    +      <tspan x="583" y="505.881">Fill in SQE</tspan>
    
    466
    +    </text>
    
    467
    +  </g>
    
    468
    +  <g>
    
    469
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="483" y1="462" x2="496.116" y2="475.116"/>
    
    470
    +    <polygon style="fill: #000000" points="501.419,480.419 490.812,476.883 496.116,475.116 497.883,469.812 "/>
    
    471
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="501.419,480.419 490.812,476.883 496.116,475.116 497.883,469.812 "/>
    
    472
    +  </g>
    
    473
    +  <g>
    
    474
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="683" y1="462" x2="669.884" y2="475.116"/>
    
    475
    +    <polygon style="fill: #000000" points="664.581,480.419 668.117,469.812 669.884,475.116 675.188,476.883 "/>
    
    476
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="664.581,480.419 668.117,469.812 669.884,475.116 675.188,476.883 "/>
    
    477
    +  </g>
    
    478
    +  <g>
    
    479
    +    <rect style="fill: #ffffff" x="503" y="562" width="160" height="60"/>
    
    480
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="503" y="562" width="160" height="60"/>
    
    481
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="587.881">
    
    482
    +      <tspan x="583" y="587.881">Primop:</tspan>
    
    483
    +      <tspan x="583" y="603.881">control to scheduler</tspan>
    
    484
    +    </text>
    
    485
    +  </g>
    
    486
    +  <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="323" y1="542" x2="783" y2="542"/>
    
    487
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="323" y="562">
    
    488
    +    <tspan x="323" y="562">RTS CMM</tspan>
    
    489
    +  </text>
    
    490
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="323" y="535.622">
    
    491
    +    <tspan x="323" y="535.622">RTS C</tspan>
    
    492
    +  </text>
    
    493
    +  <g>
    
    494
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="583" y1="522.991" x2="583" y2="552.264"/>
    
    495
    +    <polygon style="fill: #000000" points="583,559.764 578,549.764 583,552.264 588,549.764 "/>
    
    496
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="583,559.764 578,549.764 583,552.264 588,549.764 "/>
    
    497
    +  </g>
    
    498
    +  <g>
    
    499
    +    <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="563,322.81 563,322 683,322 683,392.264 "/>
    
    500
    +    <polygon style="fill: #000000" points="683,399.764 678,389.764 683,392.264 688,389.764 "/>
    
    501
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="683,399.764 678,389.764 683,392.264 688,389.764 "/>
    
    502
    +  </g>
    
    503
    +  <g>
    
    504
    +    <rect style="fill: #ffffff" x="1638.85" y="38.6" width="163.85" height="70"/>
    
    505
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1638.85" y="38.6" width="163.85" height="70"/>
    
    506
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1720.77" y="61.4813">
    
    507
    +      <tspan x="1720.77" y="61.4813">Primop: sync</tspan>
    
    508
    +      <tspan x="1720.77" y="77.4813">blocking I/O</tspan>
    
    509
    +      <tspan x="1720.77" y="93.4812">submission</tspan>
    
    510
    +    </text>
    
    511
    +  </g>
    
    512
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1720.77" y="73.6">
    
    513
    +    <tspan x="1720.77" y="73.6"></tspan>
    
    514
    +  </text>
    
    515
    +  <g>
    
    516
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1720.77" y1="108.6" x2="1721.27" y2="128.867"/>
    
    517
    +    <polygon style="fill: #000000" points="1721.46,136.365 1716.21,126.491 1721.27,128.867 1726.21,126.245 "/>
    
    518
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.46,136.365 1716.21,126.491 1721.27,128.867 1726.21,126.245 "/>
    
    519
    +  </g>
    
    520
    +  <g>
    
    521
    +    <rect style="fill: #ffffff" x="1641.51" y="138.6" width="160" height="60"/>
    
    522
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1641.51" y="138.6" width="160" height="60"/>
    
    523
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="164.481">
    
    524
    +      <tspan x="1721.51" y="164.481">Allocate AIOP</tspan>
    
    525
    +      <tspan x="1721.51" y="180.481">Allocate table index</tspan>
    
    526
    +    </text>
    
    527
    +  </g>
    
    528
    +  <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="1561.51" y1="123.6" x2="2021.52" y2="123.6"/>
    
    529
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1561.51" y="117.222">
    
    530
    +    <tspan x="1561.51" y="117.222">RTS CMM</tspan>
    
    531
    +  </text>
    
    532
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1561.51" y="140.139">
    
    533
    +    <tspan x="1561.51" y="140.139">RTS C</tspan>
    
    534
    +  </text>
    
    535
    +  <g>
    
    536
    +    <rect style="fill: #ffffff" x="1841.51" y="43.6" width="160" height="60"/>
    
    537
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1841.51" y="43.6" width="160" height="60"/>
    
    538
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1921.51" y="77.4813">
    
    539
    +      <tspan x="1921.51" y="77.4813">Primop: GC and retry</tspan>
    
    540
    +    </text>
    
    541
    +  </g>
    
    542
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1921.51" y="73.6">
    
    543
    +    <tspan x="1921.51" y="73.6"></tspan>
    
    544
    +  </text>
    
    545
    +  <g>
    
    546
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1841.51" y1="73.6" x2="1812.44" y2="73.6"/>
    
    547
    +    <polygon style="fill: #000000" points="1804.94,73.6 1814.94,68.6 1812.44,73.6 1814.94,78.6 "/>
    
    548
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1804.94,73.6 1814.94,68.6 1812.44,73.6 1814.94,78.6 "/>
    
    549
    +  </g>
    
    550
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="168.6">
    
    551
    +    <tspan x="1721.51" y="168.6"></tspan>
    
    552
    +  </text>
    
    553
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1841.51" y="175.581">
    
    554
    +    <tspan x="1841.51" y="175.581">mem alloc failure?</tspan>
    
    555
    +  </text>
    
    556
    +  <g>
    
    557
    +    <polygon style="fill: #ffffff" points="1721.51,298.6 1801.51,359.41 1721.51,420.219 1641.51,359.41 "/>
    
    558
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,298.6 1801.51,359.41 1721.51,420.219 1641.51,359.41 "/>
    
    559
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="347.291">
    
    560
    +      <tspan x="1721.51" y="347.291">inflight</tspan>
    
    561
    +      <tspan x="1721.51" y="363.291">within</tspan>
    
    562
    +      <tspan x="1721.51" y="379.291">limit?</tspan>
    
    563
    +    </text>
    
    564
    +  </g>
    
    565
    +  <g>
    
    566
    +    <rect style="fill: #ffffff" x="1641.51" y="453.6" width="160" height="60"/>
    
    567
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1641.51" y="453.6" width="160" height="60"/>
    
    568
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="479.481">
    
    569
    +      <tspan x="1721.51" y="479.481">Allocate SQE on ring</tspan>
    
    570
    +      <tspan x="1721.51" y="495.481">Inc ring prep counter</tspan>
    
    571
    +    </text>
    
    572
    +  </g>
    
    573
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1661.51" y="423.031">
    
    574
    +    <tspan x="1661.51" y="423.031">Yes</tspan>
    
    575
    +  </text>
    
    576
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1761.51" y="423.031">
    
    577
    +    <tspan x="1761.51" y="423.031">No</tspan>
    
    578
    +  </text>
    
    579
    +  <g>
    
    580
    +    <rect style="fill: #ffffff" x="1639.89" y="218.6" width="163.25" height="40"/>
    
    581
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1639.89" y="218.6" width="163.25" height="40"/>
    
    582
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="242.481">
    
    583
    +      <tspan x="1721.51" y="242.481">Inc submitted counter</tspan>
    
    584
    +    </text>
    
    585
    +  </g>
    
    586
    +  <g>
    
    587
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="420.22" x2="1721.51" y2="443.864"/>
    
    588
    +    <polygon style="fill: #000000" points="1721.51,451.364 1716.51,441.364 1721.51,443.864 1726.51,441.364 "/>
    
    589
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,451.364 1716.51,441.364 1721.51,443.864 1726.51,441.364 "/>
    
    590
    +  </g>
    
    591
    +  <g>
    
    592
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="198.6" x2="1721.51" y2="208.864"/>
    
    593
    +    <polygon style="fill: #000000" points="1721.51,216.364 1716.51,206.364 1721.51,208.864 1726.51,206.364 "/>
    
    594
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,216.364 1716.51,206.364 1721.51,208.864 1726.51,206.364 "/>
    
    595
    +  </g>
    
    596
    +  <g>
    
    597
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="258.6" x2="1721.51" y2="288.864"/>
    
    598
    +    <polygon style="fill: #000000" points="1721.51,296.364 1716.51,286.364 1721.51,288.864 1726.51,286.364 "/>
    
    599
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,296.364 1716.51,286.364 1721.51,288.864 1726.51,286.364 "/>
    
    600
    +  </g>
    
    601
    +  <g>
    
    602
    +    <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1801.51,168.6 1921.51,143.6 1921.51,113.336 "/>
    
    603
    +    <polygon style="fill: #000000" points="1921.51,105.836 1926.51,115.836 1921.51,113.336 1916.51,115.836 "/>
    
    604
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1921.51,105.836 1926.51,115.836 1921.51,113.336 1916.51,115.836 "/>
    
    605
    +  </g>
    
    606
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1821.51" y="468.6">
    
    607
    +    <tspan x="1821.51" y="468.6"></tspan>
    
    608
    +  </text>
    
    609
    +  <g>
    
    610
    +    <rect style="fill: #ffffff" x="1641.51" y="538.6" width="160" height="40"/>
    
    611
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1641.51" y="538.6" width="160" height="40"/>
    
    612
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="562.481">
    
    613
    +      <tspan x="1721.51" y="562.481">Fill in SQE</tspan>
    
    614
    +    </text>
    
    615
    +  </g>
    
    616
    +  <g>
    
    617
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="513.6" x2="1721.51" y2="528.864"/>
    
    618
    +    <polygon style="fill: #000000" points="1721.51,536.364 1716.51,526.364 1721.51,528.864 1726.51,526.364 "/>
    
    619
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,536.364 1716.51,526.364 1721.51,528.864 1726.51,526.364 "/>
    
    620
    +  </g>
    
    621
    +  <g>
    
    622
    +    <rect style="fill: #ffffff" x="1641.51" y="618.6" width="160" height="60"/>
    
    623
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1641.51" y="618.6" width="160" height="60"/>
    
    624
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1721.51" y="644.481">
    
    625
    +      <tspan x="1721.51" y="644.481">Primop:</tspan>
    
    626
    +      <tspan x="1721.51" y="660.481">return to scheduler</tspan>
    
    627
    +    </text>
    
    628
    +  </g>
    
    629
    +  <g>
    
    630
    +    <rect style="fill: #ffffff" x="1841.51" y="618.6" width="160" height="60"/>
    
    631
    +    <rect style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x="1841.51" y="618.6" width="160" height="60"/>
    
    632
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1921.51" y="644.481">
    
    633
    +      <tspan x="1921.51" y="644.481">Primop:</tspan>
    
    634
    +      <tspan x="1921.51" y="660.481">throw exception</tspan>
    
    635
    +    </text>
    
    636
    +  </g>
    
    637
    +  <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke-dasharray: 4; stroke: #000000" x1="1561.51" y1="598.6" x2="2021.52" y2="598.6"/>
    
    638
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1561.51" y="618.6">
    
    639
    +    <tspan x="1561.51" y="618.6">RTS CMM</tspan>
    
    640
    +  </text>
    
    641
    +  <text font-size="14.6756" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1561.51" y="592.222">
    
    642
    +    <tspan x="1561.51" y="592.222">RTS C</tspan>
    
    643
    +  </text>
    
    644
    +  <g>
    
    645
    +    <line style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" x1="1721.51" y1="579.591" x2="1721.51" y2="608.864"/>
    
    646
    +    <polygon style="fill: #000000" points="1721.51,616.364 1716.51,606.364 1721.51,608.864 1726.51,606.364 "/>
    
    647
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1721.51,616.364 1716.51,606.364 1721.51,608.864 1726.51,606.364 "/>
    
    648
    +  </g>
    
    649
    +  <g>
    
    650
    +    <polyline style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1801.51,359.41 1801.51,358.6 1921.51,358.6 1921.51,608.864 "/>
    
    651
    +    <polygon style="fill: #000000" points="1921.51,616.364 1916.51,606.364 1921.51,608.864 1926.51,606.364 "/>
    
    652
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1921.51,616.364 1916.51,606.364 1921.51,608.864 1926.51,606.364 "/>
    
    653
    +  </g>
    
    654
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120.738" y="55">
    
    655
    +    <tspan x="-120.738" y="55"></tspan>
    
    656
    +  </text>
    
    657
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-120" y="149">
    
    658
    +    <tspan x="-120" y="149"></tspan>
    
    659
    +  </text>
    
    660
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-117.175" y="712">
    
    661
    +    <tspan x="-117.175" y="712"></tspan>
    
    662
    +  </text>
    
    663
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="-117.175" y="712">
    
    664
    +    <tspan x="-117.175" y="712"></tspan>
    
    665
    +  </text>
    
    666
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="583" y="592">
    
    667
    +    <tspan x="583" y="592"></tspan>
    
    668
    +  </text>
    
    669
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="80" y="712">
    
    670
    +    <tspan x="80" y="712"></tspan>
    
    671
    +  </text>
    
    672
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1102" y="751">
    
    673
    +    <tspan x="1102" y="751"></tspan>
    
    674
    +  </text>
    
    675
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1720.77" y="73.6">
    
    676
    +    <tspan x="1720.77" y="73.6"></tspan>
    
    677
    +  </text>
    
    678
    +  <g>
    
    679
    +    <polygon style="fill: #ffffff" points="1104,589.546 1208.51,624.256 1104,658.967 999.486,624.256 "/>
    
    680
    +    <polygon style="fill: none; fill-opacity:0; stroke-width: 2; stroke: #000000" points="1104,589.546 1208.51,624.256 1104,658.967 999.486,624.256 "/>
    
    681
    +    <text font-size="12.8" style="fill: #000000;text-anchor:middle;font-family:sans-serif;font-style:normal;font-weight:normal" x="1104" y="628.138">
    
    682
    +      <tspan x="1104" y="628.138">SQ ring not full</tspan>
    
    683
    +    </text>
    
    684
    +  </g>
    
    685
    +  <text font-size="12.8" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="1104" y="624.256">
    
    686
    +    <tspan x="1104" y="624.256"></tspan>
    
    687
    +  </text>
    
    688
    +  <text font-size="12.7998" style="fill: #000000;text-anchor:start;font-family:sans-serif;font-style:normal;font-weight:normal" x="483" y="227">
    
    689
    +    <tspan x="483" y="227"></tspan>
    
    690
    +  </text>
    
    691
    +</svg>

  • rts/rts.cabal
    ... ... @@ -48,6 +48,8 @@ flag libdw
    48 48
       default: False
    
    49 49
     flag libnuma
    
    50 50
       default: False
    
    51
    +flag liburing
    
    52
    +  default: True
    
    51 53
     flag libzstd
    
    52 54
       default: False
    
    53 55
     flag static-libzstd
    
    ... ... @@ -231,6 +233,8 @@ library
    231 233
              extra-libraries: elf dw
    
    232 234
           if flag(libnuma)
    
    233 235
              extra-libraries: numa
    
    236
    +      if flag(liburing)
    
    237
    +         extra-libraries: uring
    
    234 238
           if flag(libzstd)
    
    235 239
              if flag(static-libzstd)
    
    236 240
                 if os(darwin)
    
    ... ... @@ -536,6 +540,7 @@ library
    536 540
                         posix/Signals.c
    
    537 541
                         posix/Timeout.c
    
    538 542
                         posix/TTY.c
    
    543
    +                    posix/URing.c
    
    539 544
                         -- ticker/*.c
    
    540 545
                         -- We don't want to compile posix/ticker/*.c, these will be #included
    
    541 546
                         -- from Ticker.c