From 85b0f87a298c8e54b06a8f8c6ce88669c5cad3bc Mon Sep 17 00:00:00 2001
From: Duncan Coutts <duncan@well-typed.com>
Date: Sun, 13 Nov 2022 11:28:56 +0000
Subject: [PATCH] Change the handling of the RTS flag --io-manager=

Now instead of it being just used on Windows to select between the WinIO
vs the MIO or Win32-legacy I/O managers, it is now used on all platforms
for selecting the I/O manager to use.

Right now it remains the case that there is only an actual choice on
Windows, but that will change later.

Document the --io-manager flag in the user guide.

This change is also reflected in the RTS flags types in the base
library. Deprecate the export of IoSubSystem from GHC.RTS.Flags with a
message to import it from GHC.IO.Subsystem.

The way the 'IoSubSystem' is detected also changes. Instead of looking
at the RTS flag, there is now a C bool global var in the RTS which gets
set on startup when the I/O manager is selected. This bool var says
whether the selected I/O manager classifies as "native" on Windows,
which in practice means the WinIO I/O manager has been selected.

Similarly, the is_io_mng_native_p RTS helper function is re-implemented
in terms of the selected I/O manager, rather than based on the RTS
flags.

We do however remove the ./configure --native-io-manager flag because
we're bringing the WinIO/MIO/Win32-legacy choice under the new general
scheme for selecting I/O managers, and that new scheme involves no
./configure time user choices, just runtime RTS flag choices.
---
 docs/users_guide/runtime_control.rst          |  17 ++
 libraries/base/src/GHC/RTS/Flags.hs           |   6 +-
 .../src/GHC/Internal/IO/SubSystem.hs          |  46 ++++-
 .../src/GHC/Internal/RTS/Flags.hsc            |  69 +------
 rts/IOManager.c                               | 184 ++++++++++++++++++
 rts/IOManager.h                               |  54 +++++
 rts/RtsFlags.c                                |  46 ++---
 rts/RtsFlags.h                                |   1 -
 rts/RtsSymbols.c                              |   2 +
 rts/configure.ac                              |  10 -
 rts/include/rts/Flags.h                       |  20 +-
 11 files changed, 352 insertions(+), 103 deletions(-)

diff --git a/docs/users_guide/runtime_control.rst b/docs/users_guide/runtime_control.rst
index 8ab6b2c8ea1a..370593d207e1 100644
--- a/docs/users_guide/runtime_control.rst
+++ b/docs/users_guide/runtime_control.rst
@@ -308,6 +308,23 @@ Miscellaneous RTS options
     undue memory usage shown in reporting tools, so with this flag it can
     be turned off.
 
+.. rts-flag:: --io-manager=(name)
+
+    Select the I/O manager to use. On some combinations of platform and
+    threaded/non-threaded RTS way there is a choice of more than one
+    implementation of I/O manager. This flag lets you override the default
+    and select one by name.
+
+    Currently the available I/O managers are:
+
+    ================ ========= ============
+     Name            Platforms RTS way
+    ================ ========= ============
+    ``select``       Posix     Non-threaded
+    ``mio``          All       Threaded
+    ``win32-legacy`` Windows   Non-threaded
+    ``winio``        Windows   All
+    ================ ========= ============
 
 .. rts-flag:: -xp
 
diff --git a/libraries/base/src/GHC/RTS/Flags.hs b/libraries/base/src/GHC/RTS/Flags.hs
index e9e2f00f185c..24dd2203de74 100644
--- a/libraries/base/src/GHC/RTS/Flags.hs
+++ b/libraries/base/src/GHC/RTS/Flags.hs
@@ -25,6 +25,7 @@ module GHC.RTS.Flags
   , GCFlags (..)
   , ConcFlags (..)
   , MiscFlags (..)
+  , IoManagerFlag (..)
   , DebugFlags (..)
   , DoCostCentres (..)
   , CCFlags (..)
@@ -35,12 +36,12 @@ module GHC.RTS.Flags
   , TickyFlags (..)
   , ParFlags (..)
   , HpcFlags (..)
-  , IoSubSystem (..)
+  , {-# DEPRECATED "import GHC.IO.SubSystem (IoSubSystem (..))" #-}
+    IoSubSystem (..)
   , getRTSFlags
   , getGCFlags
   , getConcFlags
   , getMiscFlags
-  , getIoManagerFlag
   , getDebugFlags
   , getCCFlags
   , getProfFlags
@@ -51,3 +52,4 @@ module GHC.RTS.Flags
   ) where
 
 import GHC.Internal.RTS.Flags
+import GHC.Internal.IO.SubSystem (IoSubSystem(..))
diff --git a/libraries/ghc-internal/src/GHC/Internal/IO/SubSystem.hs b/libraries/ghc-internal/src/GHC/Internal/IO/SubSystem.hs
index e62e3e947332..747e5a48b48c 100644
--- a/libraries/ghc-internal/src/GHC/Internal/IO/SubSystem.hs
+++ b/libraries/ghc-internal/src/GHC/Internal/IO/SubSystem.hs
@@ -34,14 +34,27 @@ module GHC.Internal.IO.SubSystem (
  ) where
 
 import GHC.Internal.Base
-import GHC.Internal.RTS.Flags
 
 #if defined(mingw32_HOST_OS)
 import GHC.Internal.IO.Unsafe
+import GHC.Internal.Foreign.Ptr
+import GHC.Internal.Foreign.Storable
+import GHC.Internal.Foreign.C.Types
+import GHC.Internal.Foreign.Marshal.Utils
 #endif
 
 infixl 7 <!>
 
+-- | The I/O SubSystem to use in the program.
+--
+-- @since base-4.9.0.0
+data IoSubSystem
+  = IoPOSIX   -- ^ Use a POSIX I/O Sub-System
+  | IoNative  -- ^ Use platform native Sub-System. For unix OSes this is the
+              --   same as IoPOSIX, but on Windows this means use the Windows
+              --   native APIs for I/O, including IOCP and RIO.
+  deriving (Eq)
+
 -- | Conditionally execute an action depending on the configured I/O subsystem.
 -- On POSIX systems always execute the first action.
 -- On Windows execute the second action if WINIO as active, otherwise fall back to
@@ -64,10 +77,37 @@ conditional posix _       = posix
 isWindowsNativeIO :: Bool
 isWindowsNativeIO = False <!> True
 
+-- | The 'IoSubSystem' in use.
+--
+-- This is needed to optimize support for different IO Managers on Windows.
+-- GHC supports both the new WinIO manager as well as the old MIO (threaded),
+-- and ancient win32-legacy (non-threaded) ones. The WinIO manager uses native
+-- Win32 HANDLEs, whereas the other two use posix style FDs (via translation
+-- layers).
+--
+-- In many places in the I\/O base library code, for correctness or performance
+-- on Windows, we have to take different code paths depending on which style of
+-- IO manager is in use. The IO manager is set on RTS startup (and the default
+-- choice can be overridden using RTS flags). On Windows this value is obtained
+-- by reading a global variable that is set by the RTS IOManager on startup.
+--
+-- On non-Windows systems this value is always 'IoPOSIX'.
+--
 ioSubSystem :: IoSubSystem
 #if defined(mingw32_HOST_OS)
-{-# NOINLINE ioSubSystem #-}
-ioSubSystem = unsafeDupablePerformIO getIoManagerFlag
+{-# INLINE ioSubSystem #-}
+ioSubSystem =
+  case toBool ioManagerIsWin32NativeCBool of
+    False -> IoPOSIX
+    True  -> IoNative
+
+{-# NOINLINE ioManagerIsWin32NativeCBool #-}
+ioManagerIsWin32NativeCBool :: CBool
+ioManagerIsWin32NativeCBool =
+  unsafeDupablePerformIO $ peek ioManagerIsWin32NativePtr
+
+foreign import ccall "&rts_IOManagerIsWin32Native"
+  ioManagerIsWin32NativePtr :: Ptr CBool
 #else
 ioSubSystem = IoPOSIX
 #endif
diff --git a/libraries/ghc-internal/src/GHC/Internal/RTS/Flags.hsc b/libraries/ghc-internal/src/GHC/Internal/RTS/Flags.hsc
index 4a3e94176289..c11346659a98 100644
--- a/libraries/ghc-internal/src/GHC/Internal/RTS/Flags.hsc
+++ b/libraries/ghc-internal/src/GHC/Internal/RTS/Flags.hsc
@@ -27,6 +27,7 @@ module GHC.Internal.RTS.Flags
   , GCFlags (..)
   , ConcFlags (..)
   , MiscFlags (..)
+  , IoManagerFlag (..)
   , DebugFlags (..)
   , DoCostCentres (..)
   , CCFlags (..)
@@ -37,12 +38,10 @@ module GHC.Internal.RTS.Flags
   , TickyFlags (..)
   , ParFlags (..)
   , HpcFlags (..)
-  , IoSubSystem (..)
   , getRTSFlags
   , getGCFlags
   , getConcFlags
   , getMiscFlags
-  , getIoManagerFlag
   , getDebugFlags
   , getCCFlags
   , getProfFlags
@@ -103,32 +102,6 @@ instance Enum GiveGCStats where
     toEnum #{const VERBOSE_GC_STATS} = VerboseGCStats
     toEnum e = errorWithoutStackTrace ("invalid enum for GiveGCStats: " ++ show e)
 
--- | The I/O SubSystem to use in the program.
---
--- @since base-4.9.0.0
-data IoSubSystem
-  = IoPOSIX   -- ^ Use a POSIX I/O Sub-System
-  | IoNative  -- ^ Use platform native Sub-System. For unix OSes this is the
-              --   same as IoPOSIX, but on Windows this means use the Windows
-              --   native APIs for I/O, including IOCP and RIO.
-  deriving (Eq, Show)
-
--- | @since base-4.9.0.0
-instance Enum IoSubSystem where
-    fromEnum IoPOSIX  = #{const IO_MNGR_POSIX}
-    fromEnum IoNative = #{const IO_MNGR_NATIVE}
-
-    toEnum #{const IO_MNGR_POSIX}  = IoPOSIX
-    toEnum #{const IO_MNGR_NATIVE} = IoNative
-    toEnum e = errorWithoutStackTrace ("invalid enum for IoSubSystem: " ++ show e)
-
--- | @since base-4.9.0.0
-instance Storable IoSubSystem where
-    sizeOf = sizeOf . fromEnum
-    alignment = sizeOf . fromEnum
-    peek ptr = fmap toEnum $ peek (castPtr ptr)
-    poke ptr v = poke (castPtr ptr) (fromEnum v)
-
 -- | Parameters of the garbage collector.
 --
 -- @since base-4.8.0.0
@@ -191,12 +164,20 @@ data MiscFlags = MiscFlags
     , linkerAlwaysPic       :: Bool
     , linkerMemBase         :: Word
       -- ^ address to ask the OS for memory for the linker, 0 ==> off
-    , ioManager             :: IoSubSystem
+    , ioManager             :: IoManagerFlag
     , numIoWorkerThreads    :: Word32
     } deriving ( Show -- ^ @since base-4.8.0.0
                , Generic -- ^ @since base-4.15.0.0
                )
 
+data IoManagerFlag =
+       IoManagerFlagAuto
+     | IoManagerFlagSelect        -- ^ Unix only, non-threaded RTS only
+     | IoManagerFlagMIO           -- ^ cross-platform, threaded RTS only
+     | IoManagerFlagWinIO         -- ^ Windows only
+     | IoManagerFlagWin32Legacy   -- ^ Windows only, non-threaded RTS only
+  deriving (Eq, Enum, Show)
+
 -- | Flags to control debugging output & extra checking in various
 -- subsystems.
 --
@@ -552,36 +533,6 @@ getMiscFlags = do
             <*> (fromIntegral
                  <$> (#{peek MISC_FLAGS, numIoWorkerThreads} ptr :: IO Word32))
 
-{- Note [The need for getIoManagerFlag]
-   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-   GHC supports both the new WINIO manager
-   as well as the old MIO one. In order to
-   decide which code path to take we often
-   have to inspect what the user selected at
-   RTS startup.
-
-   We could use getMiscFlags but then we end up with core containing
-   reads for all MiscFlags. These won't be eliminated at the core level
-   even if it's obvious we will only look at the ioManager part of the
-   ADT.
-
-   We could add a INLINE pragma, but that just means whatever we inline
-   into is likely to be inlined. So rather than adding a dozen pragmas
-   we expose a lean way to query this particular flag. It's not satisfying
-   but it works well enough and allows these checks to be inlined nicely.
-
--}
-
-{-# INLINE getIoManagerFlag #-}
--- | Needed to optimize support for different IO Managers on Windows.
--- See Note [The need for getIoManagerFlag]
-getIoManagerFlag :: IO IoSubSystem
-getIoManagerFlag = do
-      let ptr = (#ptr RTS_FLAGS, MiscFlags) rtsFlagsPtr
-      mgrFlag <- (#{peek MISC_FLAGS, ioManager} ptr :: IO Word32)
-      return $ (toEnum . fromIntegral) mgrFlag
-
 getDebugFlags :: IO DebugFlags
 getDebugFlags = do
   let ptr = (#ptr RTS_FLAGS, DebugFlags) rtsFlagsPtr
diff --git a/rts/IOManager.c b/rts/IOManager.c
index 74d48ed5414e..2592ea4826de 100644
--- a/rts/IOManager.c
+++ b/rts/IOManager.c
@@ -33,6 +33,174 @@
 #include "win32/AsyncWinIO.h"
 #endif
 
+#include <string.h>
+
+
+/* Global var to tell us which I/O manager impl we are using */
+IOManagerType iomgr_type;
+
+#if defined(mingw32_HOST_OS)
+/* Global var (only on Windows) that is exported to be shared with the I/O code
+ * in the base library to tell us which style of I/O manager we are using: one
+ * that uses the Windows native API HANDLEs, or one that uses Posix style fds.
+ */
+bool rts_IOManagerIsWin32Native = false;
+#endif
+
+enum IOManagerAvailability
+parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag)
+{
+    if (strcmp("select", iomgrstr) == 0) {
+#if defined(IOMGR_ENABLED_SELECT)
+        *flag = IO_MNGR_FLAG_SELECT;
+        return IOManagerAvailable;
+#else
+        return IOManagerUnavailable;
+#endif
+    }
+    else if (strcmp("mio", iomgrstr) == 0) {
+#if defined(IOMGR_ENABLED_MIO_POSIX) || defined(IOMGR_ENABLED_MIO_WIN32)
+        *flag = IO_MNGR_FLAG_MIO;
+        return IOManagerAvailable;
+#else
+        return IOManagerUnavailable;
+#endif
+        *flag = IO_MNGR_FLAG_MIO;
+    }
+    else if (strcmp("winio", iomgrstr) == 0) {
+#if defined(IOMGR_ENABLED_WINIO)
+        *flag = IO_MNGR_FLAG_WINIO;
+        return IOManagerAvailable;
+#else
+        return IOManagerUnavailable;
+#endif
+    }
+    else if (strcmp("win32-legacy", iomgrstr) == 0) {
+#if defined(IOMGR_ENABLED_WIN32_LEGACY)
+        *flag = IO_MNGR_FLAG_WIN32_LEGACY;
+        return IOManagerAvailable;
+#else
+        return IOManagerUnavailable;
+#endif
+    }
+    else if (strcmp("auto", iomgrstr) == 0) {
+        *flag = IO_MNGR_FLAG_AUTO;
+        return IOManagerAvailable;
+    }
+    /* Two deprecated aliases. These aliases only had any effect on Windows,
+     * but were available as RTS flags on all platforms. The "native" flag
+     * refers to the newer Windows WinIO IO manager (threaded or non-threaded),
+     * while (somewhat confusingly) the "posix" flag refers to the older
+     * Windows I/O managers (win32-legacy and mio). On non-Windows, we now make
+     * these flags equivalent to IO_MNGR_FLAG_AUTO.
+     */
+    else if (strcmp("native", iomgrstr) == 0) {
+#if defined(mingw32_HOST_OS)
+    /* On windows "native" is now an alias for IO_MNGR_FLAG_WINIO */
+#if defined(IOMGR_ENABLED_WINIO)
+        *flag = IO_MNGR_FLAG_WINIO;
+        return IOManagerAvailable;
+#else
+        return IOManagerUnavailable;
+#endif
+#else // !defined(mingw32_HOST_OS)
+        *flag = IO_MNGR_FLAG_AUTO;
+        return IOManagerAvailable;
+#endif
+    }
+    else if (strcmp("posix", iomgrstr) == 0) {
+#if defined(mingw32_HOST_OS)
+        /* On Windows "posix" is now an alias for either IO_MNGR_FLAG_MIO or
+         * IO_MNGR_FLAG_WIN32_LEGACY */
+#if defined(IOMGR_ENABLED_MIO_WIN32)
+        *flag = IO_MNGR_FLAG_MIO;
+        return IOManagerAvailable;
+#elif defined(IOMGR_ENABLED_WIN32_LEGACY)
+        *flag = IO_MNGR_FLAG_WIN32_LEGACY;
+        return IOManagerAvailable;
+#else
+        return IOManagerUnavailable;
+#endif
+#else // !defined(mingw32_HOST_OS)
+        *flag = IO_MNGR_FLAG_AUTO;
+        return IOManagerAvailable;
+#endif
+    }
+    else {
+        return IOManagerUnrecognised;
+    }
+}
+
+/* Based on the I/O manager RTS flag, select an I/O manager to use.
+ *
+ * This fills in the iomgr_type and rts_IOManagerIsWin32Native globals.
+ * Must be called before the I/O manager is started.
+ */
+static void selectIOManager(void)
+{
+    switch (RtsFlags.MiscFlags.ioManager) {
+        case IO_MNGR_FLAG_AUTO:
+#if defined(THREADED_RTS)
+#if   defined(IOMGR_DEFAULT_THREADED_MIO)
+#if defined(mingw32_HOST_OS)
+            iomgr_type = IO_MANAGER_MIO_WIN32;
+#else
+            iomgr_type = IO_MANAGER_MIO_POSIX;
+#endif
+#elif defined(IOMGR_DEFAULT_THREADED_WINIO)
+            iomgr_type = IO_MANAGER_WINIO;
+#else
+#error No I/O default manager. See IOMGR_DEFAULT_THREADED_ flags
+#endif
+#else // !defined(THREADED_RTS)
+#if   defined(IOMGR_DEFAULT_NON_THREADED_SELECT)
+            iomgr_type = IO_MANAGER_SELECT;
+#elif defined(IOMGR_DEFAULT_NON_THREADED_WINIO)
+            iomgr_type = IO_MANAGER_WINIO;
+#elif defined(IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY)
+            iomgr_type = IO_MANAGER_WIN32_LEGACY;
+#else
+#error No I/O default manager. See IOMGR_DEFAULT_NON_THREADED_ flags
+#endif
+#endif
+            break;
+
+#if defined(IOMGR_ENABLED_SELECT)
+        case IO_MNGR_FLAG_SELECT:
+            iomgr_type = IO_MANAGER_SELECT;
+            break;
+#endif
+
+#if defined(IOMGR_ENABLED_MIO_POSIX)
+        case IO_MNGR_FLAG_MIO:
+            iomgr_type = IO_MANAGER_MIO_POSIX;
+            break;
+#endif
+
+#if defined(IOMGR_ENABLED_MIO_WIN32)
+        case IO_MNGR_FLAG_MIO:
+            iomgr_type = IO_MANAGER_MIO_WIN32;
+            break;
+#endif
+
+#if defined(IOMGR_ENABLED_WINIO)
+        case IO_MNGR_FLAG_WINIO:
+            iomgr_type = IO_MANAGER_WINIO;
+            rts_IOManagerIsWin32Native = true;
+            break;
+#endif
+
+#if defined(IOMGR_ENABLED_WIN32_LEGACY)
+        case IO_MNGR_FLAG_WIN32_LEGACY:
+            iomgr_type = IO_MANAGER_WIN32_LEGACY;
+            break;
+#endif
+
+        default:
+          barf("selectIOManager: %d", RtsFlags.MiscFlags.ioManager);
+    }
+}
+
 
 /* Allocate and initialise the per-capability CapIOManager that lives in each
  * Capability. Called early in the RTS initialisation.
@@ -62,6 +230,7 @@ void initCapabilityIOManager(CapIOManager **piomgr)
 void
 initIOManager(void)
 {
+    selectIOManager();
 
 #if defined(THREADED_RTS)
     /* Posix implementation in posix/Signals.c
@@ -217,3 +386,18 @@ void insertIntoSleepingQueue(Capability *cap, StgTSO *tso, LowResTime target)
     }
 }
 #endif
+
+/* Temporary compat helper function used in the Win32 I/O managers.
+ * TODO: replace by consulting the iomgr_type global instead.
+ */
+bool is_io_mng_native_p (void)
+{
+    switch (iomgr_type) {
+#if defined(IOMGR_ENABLED_WINIO)
+        case IO_MANAGER_WINIO:
+            return true;
+#endif
+        default:
+            return false;
+    }
+}
diff --git a/rts/IOManager.h b/rts/IOManager.h
index df5f72cdd840..1e34d6b16650 100644
--- a/rts/IOManager.h
+++ b/rts/IOManager.h
@@ -122,6 +122,60 @@
           IOMGR_ENABLED_STR_WINIO \
           IOMGR_ENABLED_STR_WIN32_LEGACY
 
+/* An enumeration of all the available I/O managers. We use conditional
+ * compilation to help us optimise out unavailable choices. To help us
+ * do that correctly, we only define choices that are available.
+ */
+typedef enum {
+#if defined(IOMGR_ENABLED_SELECT)
+    IO_MANAGER_SELECT,
+#endif
+#if defined(IOMGR_ENABLED_MIO_POSIX)
+    IO_MANAGER_MIO_POSIX,
+#endif
+#if defined(IOMGR_ENABLED_MIO_WIN32)
+    IO_MANAGER_MIO_WIN32,
+#endif
+#if defined(IOMGR_ENABLED_WINIO)
+    IO_MANAGER_WINIO,
+#endif
+#if defined(IOMGR_ENABLED_WIN32_LEGACY)
+    IO_MANAGER_WIN32_LEGACY,
+#endif
+} IOManagerType;
+
+/* Global var to tell us which I/O manager impl we are using */
+extern IOManagerType iomgr_type;
+
+#if defined(mingw32_HOST_OS)
+/* Global var (only on Windows) that is exported to be shared with the I/O code
+ * in the base library to tell us which style of I/O manager we are using: one
+ * that uses the Windows native API HANDLEs, or one that uses Posix style fds.
+ */
+extern bool rts_IOManagerIsWin32Native;
+#endif
+
+
+/* Parse the I/O manager flag value, returning if is available, unavailable or
+ * unrecognised.
+ *
+ * If it is available, the passed-in IO_MANAGER_FLAG value will be filled in
+ * to record what was requested.
+ *
+ * Called in the RTS flag processing by procRtsOpts.
+ */
+enum IOManagerAvailability {
+    IOManagerAvailable,
+    IOManagerUnavailable,
+    IOManagerUnrecognised
+};
+enum IOManagerAvailability
+parseIOManagerFlag(const char *iomgrstr, IO_MANAGER_FLAG *flag);
+
+/* Temporary compat helper function used in the Win32 I/O managers.
+ * TODO: replace by consulting the iomgr_type global instead.
+ */
+bool is_io_mng_native_p (void);
 
 /* The per-capability data structures belonging to the I/O manager.
  *
diff --git a/rts/RtsFlags.c b/rts/RtsFlags.c
index 9e26df76f5e0..e74499954bad 100644
--- a/rts/RtsFlags.c
+++ b/rts/RtsFlags.c
@@ -269,11 +269,7 @@ void initRtsFlagsDefaults(void)
     RtsFlags.MiscFlags.internalCounters        = false;
     RtsFlags.MiscFlags.linkerAlwaysPic         = DEFAULT_LINKER_ALWAYS_PIC;
     RtsFlags.MiscFlags.linkerMemBase           = 0;
-#if defined(DEFAULT_NATIVE_IO_MANAGER)
-    RtsFlags.MiscFlags.ioManager               = IO_MNGR_NATIVE;
-#else
-    RtsFlags.MiscFlags.ioManager               = IO_MNGR_POSIX;
-#endif
+    RtsFlags.MiscFlags.ioManager               = IO_MNGR_FLAG_AUTO;
 #if defined(THREADED_RTS) && defined(mingw32_HOST_OS)
     RtsFlags.MiscFlags.numIoWorkerThreads      = getNumberOfProcessors();
 #else
@@ -533,8 +529,10 @@ usage_text[] = {
 "             fatal error. When symbols are available an attempt will be",
 "             made to resolve addresses to names. (default: yes)",
 #endif
-"  --io-manager=<native|posix>",
-"             The I/O manager subsystem to use. (default: posix)",
+"  --io-manager=<name>",
+"             The I/O manager to use.",
+"             Options available: auto" IOMGRS_ENABLED_STR
+              " (default: " IOMGR_DEFAULT_STR ")",
 #if defined(THREADED_RTS)
 #if defined(mingw32_HOST_OS)
 "  --io-manager-threads=<num>",
@@ -1013,15 +1011,23 @@ error = true;
                       OPTION_SAFE;
                       RtsFlags.MiscFlags.internalCounters = true;
                   }
-                  else if (strequal("io-manager=native",
-                               &rts_argv[arg][2])) {
-                      OPTION_UNSAFE;
-                      RtsFlags.MiscFlags.ioManager = IO_MNGR_NATIVE;
-                  }
-                  else if (strequal("io-manager=posix",
-                               &rts_argv[arg][2])) {
+                  else if (!strncmp("io-manager=",
+                               &rts_argv[arg][2], 11)) {
                       OPTION_UNSAFE;
-                      RtsFlags.MiscFlags.ioManager = IO_MNGR_POSIX;
+                      char *iomgrstr = &rts_argv[arg][13];
+                      IO_MANAGER_FLAG iomgrflag;
+                      enum IOManagerAvailability availability;
+                      availability = parseIOManagerFlag(iomgrstr, &iomgrflag);
+                      if (availability == IOManagerAvailable) {
+                          RtsFlags.MiscFlags.ioManager = iomgrflag;
+                      } else {
+                          errorBelch("%s choice '%s' for --io-manager=\n"
+                                     "The choices are: auto%s",
+                                     availability == IOManagerUnavailable ?
+                                     "unavailable" : "unrecognised",
+                                     iomgrstr, IOMGRS_ENABLED_STR);
+                          stg_exit(EXIT_FAILURE);
+                      }
                   }
                   else if (strequal("info",
                                &rts_argv[arg][2])) {
@@ -2722,16 +2728,6 @@ files like <progname>.eventlog, not arbitrary files.
    Helper utilities to query state.
    ------------------------------------------------------------------------- */
 
-bool is_io_mng_native_p (void)
-{
-#if defined(mingw32_HOST_OS)
-  return RtsFlags.MiscFlags.ioManager == IO_MNGR_NATIVE;
-#else
-  return false;
-#endif
-}
-
-
 #if defined(PROFILING)
 bool
 doingLDVProfiling( void )
diff --git a/rts/RtsFlags.h b/rts/RtsFlags.h
index 05a00af4e773..2395a03aaa07 100644
--- a/rts/RtsFlags.h
+++ b/rts/RtsFlags.h
@@ -23,7 +23,6 @@ char** getUTF8Args(int* argc);
 void initRtsFlagsDefaults (void);
 void setupRtsFlags        (int *argc, char *argv[], RtsConfig rtsConfig);
 void freeRtsArgs          (void);
-bool is_io_mng_native_p   (void);
 #if defined(PROFILING)
 bool doingLDVProfiling (void);
 bool doingRetainerProfiling(void);
diff --git a/rts/RtsSymbols.c b/rts/RtsSymbols.c
index 9b908ee227c0..285049a3066c 100644
--- a/rts/RtsSymbols.c
+++ b/rts/RtsSymbols.c
@@ -28,6 +28,7 @@
 #include <io.h>
 #include <windows.h>
 #include <shfolder.h> /* SHGetFolderPathW */
+#include "IOManager.h"
 #include "win32/AsyncWinIO.h"
 #endif
 
@@ -166,6 +167,7 @@ extern char **environ;
       SymI_HasProto(stg_asyncWritezh)                    \
       SymI_HasProto(stg_asyncDoProczh)                   \
       SymI_HasProto(rts_InstallConsoleEvent)             \
+      SymI_HasProto(rts_IOManagerIsWin32Native)          \
       SymI_HasProto(rts_ConsoleHandlerDone)              \
       SymI_NeedsProto(__mingw_module_is_dll)             \
       RTS_WIN32_ONLY(SymI_NeedsProto(___chkstk_ms))      \
diff --git a/rts/configure.ac b/rts/configure.ac
index d1ae16fe407c..5819e68d29b8 100644
--- a/rts/configure.ac
+++ b/rts/configure.ac
@@ -37,16 +37,6 @@ if test "$enable_asserts_all_ways" = "yes" ; then
    AC_DEFINE([USE_ASSERTS_ALL_WAYS], [1], [Compile-in ASSERTs in all ways.])
 fi
 
-AC_ARG_ENABLE(native-io-manager,
-[AS_HELP_STRING([--enable-native-io-manager],
-                [Enable the native I/O manager by default.])],
-  [FP_CAPITALIZE_YES_NO(["$enableval"], [EnableNativeIOManager])],
-  [EnableNativeIOManager=NO]
-)
-if test "$EnableNativeIOManager" = "YES"; then
-  AC_DEFINE_UNQUOTED([DEFAULT_NATIVE_IO_MANAGER], [1], [Enable Native I/O manager as default.])
-fi
-
 # We have to run these unconditionally, but we may discard their
 # results in the following code
 AC_CANONICAL_BUILD
diff --git a/rts/include/rts/Flags.h b/rts/include/rts/Flags.h
index 66f8fa568ecf..ca9520ef8a88 100644
--- a/rts/include/rts/Flags.h
+++ b/rts/include/rts/Flags.h
@@ -233,8 +233,22 @@ typedef struct _CONCURRENT_FLAGS {
 #define DEFAULT_LINKER_ALWAYS_PIC false
 #endif
 
-/* Which I/O Manager to use in the target program.  */
-typedef enum _IO_MANAGER { IO_MNGR_NATIVE, IO_MNGR_POSIX } IO_MANAGER;
+/* Which I/O Manager to use in the target program. */
+typedef enum _IO_MANAGER_FLAG {
+
+    /* Select an I/O manager automatically. This will pick the one determined
+     * at configure time, for the RTS way. This can also fall back to other
+     * available I/O managers if the first choice cannot be initialised,
+     * if platform support turns out to be unavailable (e.g. too old a kernel).
+     */
+    IO_MNGR_FLAG_AUTO,
+
+    /* All other choices pick only the requested one, with no fallback. */
+    IO_MNGR_FLAG_SELECT,          /* Unix only,    non-threaded RTS only */
+    IO_MNGR_FLAG_MIO,             /* cross-platform,   threaded RTS only */
+    IO_MNGR_FLAG_WINIO,           /* Windows only                        */
+    IO_MNGR_FLAG_WIN32_LEGACY,    /* Windows only, non-threaded RTS only */
+  } IO_MANAGER_FLAG;
 
 /* See Note [Synchronization of flags and base APIs] */
 typedef struct _MISC_FLAGS {
@@ -254,7 +268,7 @@ typedef struct _MISC_FLAGS {
     bool linkerAlwaysPic;        /* Assume the object code is always PIC */
     StgWord linkerMemBase;       /* address to ask the OS for memory
                                   * for the linker, NULL ==> off */
-    IO_MANAGER ioManager;        /* The I/O manager to use.  */
+    IO_MANAGER_FLAG ioManager;   /* The I/O manager to use.  */
     uint32_t numIoWorkerThreads; /* Number of I/O worker threads to use.  */
 } MISC_FLAGS;
 
-- 
GitLab