diff --git a/m4/ghc_iomanagers.m4 b/m4/ghc_iomanagers.m4
new file mode 100644
index 0000000000000000000000000000000000000000..dd15758b8d268bcbaca986d02518cabc5872f5ca
--- /dev/null
+++ b/m4/ghc_iomanagers.m4
@@ -0,0 +1,40 @@
+# GHC_IOMANAGER_ENABLE(iomgr_name, enable_var, output_cpp_var, action_detect)
+# ---------------------------------------------------------------------
+AC_DEFUN([GHC_IOMANAGER_ENABLE], [
+  $4
+  AC_MSG_CHECKING( if the the $1 I/O manager should be built)
+  if test "${$2}" = "YES"; then
+    AC_MSG_RESULT(yes)
+    AC_DEFINE([$3], [1], [Define to 1 if the $1 I/O manager should be built])
+  else
+    AC_MSG_RESULT(no)
+  fi
+])
+
+
+# GHC_IOMANAGER_DEFAULT_SELECT(default_var, iomgr_name, enable_var)
+# ---------------------------------------------------------------------
+AC_DEFUN([GHC_IOMANAGER_DEFAULT_SELECT], [
+  if test "${$3}" = "YES"; then
+    $1=$2
+  fi
+])
+
+
+# GHC_IOMANAGER_DEFAULT_CHECK_NOT_EMPTY(default_var, way)
+# ---------------------------------------------------------------------
+AC_DEFUN([GHC_IOMANAGER_DEFAULT_CHECK_NOT_EMPTY], [
+  if test "${$1}" = ""; then
+    AC_MSG_ERROR([no suitable I/O manager enabled for the $2 RTS])
+  fi
+])
+
+# GHC_IOMANAGER_DEFAULT_AC_DEFINE(default_var, way, iomgr_name, output_cpp_var)
+# ---------------------------------------------------------------------
+AC_DEFUN([GHC_IOMANAGER_DEFAULT_AC_DEFINE], [
+  if test "${$1}" = "$3"; then
+    AC_DEFINE([$4], [1],
+             [Define to 1 if the $3 I/O manager is the default for the $2 RTS])
+  fi
+])
+
diff --git a/rts/IOManager.h b/rts/IOManager.h
index bd7ce35ff753f037fe8605ca8a2334a4041d982e..df5f72cdd840c0135673541f56b82d7fc0a026b9 100644
--- a/rts/IOManager.h
+++ b/rts/IOManager.h
@@ -24,6 +24,104 @@
 #include "sm/GC.h" // for evac_fn
 #include "posix/Select.h" // for LowResTime TODO: switch to normal Time
 
+/* The ./configure gives us a set of CPP flags, one for each named I/O manager:
+ * IOMGR_BUILD_<name>                : which ones should be built (some)
+ * IOMGR_DEFAULT_NON_THREADED_<name> : which one is default (exactly one)
+ * IOMGR_DEFAULT_THREADED_<name>     : which one is default (exactly one)
+ *
+ * The IOMGR_BUILD_<name> flags just says that an I/O manager should be built
+ * for _some_ RTS way (i.e. threaded or non-threaded). What we need however are
+ * flags to use for conditional compilation of I/O manager code. These flags
+ * must take into account whether the particular I/O manager is enabled for the
+ * RTS way we're currently building, in particular taking into account if we're
+ * building for a threaded or non-threaded RTS.
+ *
+ * So here we define a set of derived flags IOMGR_ENABLED_<name> which says if
+ * each I/O manager is enabled in the RTS way we're building now. We'll then
+ * use these flags everywhere else for conditional compilation.
+ */
+
+#if defined(IOMGR_BUILD_SELECT) && !defined(THREADED_RTS)
+    #define IOMGR_ENABLED_SELECT
+#endif
+#if defined(IOMGR_BUILD_MIO) && defined(THREADED_RTS)
+/* For MIO, it is really two separate I/O manager implementations: one for
+ * Windows and one for non-Windows. This is clear from both the C code on the
+ * RTS side and the Haskell code in the base library. By treating them as
+ * such leads to simpler I/O manager dispatch code.
+ *
+ * These two implementations do share a common architecture, and so we still
+ * use a single name in public interfaces like ./configure and the RTS flags.
+ */
+#if defined(mingw32_HOST_OS)
+    #define IOMGR_ENABLED_MIO_WIN32
+#else
+    #define IOMGR_ENABLED_MIO_POSIX
+#endif
+#endif
+#if defined(IOMGR_BUILD_WINIO)
+    #define IOMGR_ENABLED_WINIO
+#endif
+#if defined(IOMGR_BUILD_WIN32_LEGACY) && !defined(THREADED_RTS)
+    #define IOMGR_ENABLED_WIN32_LEGACY
+#endif
+
+/* To provide a string to use for output of +RTS -? we use the
+ * IOMGR_DEFAULT_{NON_}THREADED_<name> flags to derived a CPP variable
+ * IOMGR_DEFAULT_STR with the string name of the default I/O manager for the
+ * _current_ RTS way. At the same time we can do a sanity check that there is
+ * actually a default.
+ */
+#if defined(THREADED_RTS)
+#if   defined(IOMGR_DEFAULT_THREADED_MIO)
+    #define IOMGR_DEFAULT_STR "mio"
+#elif defined(IOMGR_DEFAULT_THREADED_WINIO)
+    #define IOMGR_DEFAULT_STR "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)
+    #define IOMGR_DEFAULT_STR "select"
+#elif defined(IOMGR_DEFAULT_NON_THREADED_WINIO)
+    #define IOMGR_DEFAULT_STR "winio"
+#elif defined(IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY)
+    #define IOMGR_DEFAULT_STR "win32-legacy"
+#else
+#error No I/O default manager. See IOMGR_DEFAULT_NON_THREADED_ flags
+#endif
+#endif
+
+/* To help with error messages we provide a macro IOMGRS_ENABLED_STR that is
+ * the stringy list of all enabled I/O managers (with leading and separating
+ * spaces)
+ */
+#if defined(IOMGR_ENABLED_SELECT)
+    #define IOMGR_ENABLED_STR_SELECT " select"
+#else
+    #define IOMGR_ENABLED_STR_SELECT ""
+#endif
+#if defined(IOMGR_ENABLED_MIO_POSIX) || defined(IOMGR_ENABLED_MIO_WIN32)
+    #define IOMGR_ENABLED_STR_MIO " mio"
+#else
+    #define IOMGR_ENABLED_STR_MIO ""
+#endif
+#if defined(IOMGR_ENABLED_WINIO)
+    #define IOMGR_ENABLED_STR_WINIO " winio"
+#else
+    #define IOMGR_ENABLED_STR_WINIO ""
+#endif
+#if defined(IOMGR_ENABLED_WIN32_LEGACY)
+    #define IOMGR_ENABLED_STR_WIN32_LEGACY " win32-legacy"
+#else
+    #define IOMGR_ENABLED_STR_WIN32_LEGACY ""
+#endif
+#define IOMGRS_ENABLED_STR \
+          IOMGR_ENABLED_STR_SELECT \
+          IOMGR_ENABLED_STR_MIO \
+          IOMGR_ENABLED_STR_WINIO \
+          IOMGR_ENABLED_STR_WIN32_LEGACY
+
 
 /* The per-capability data structures belonging to the I/O manager.
  *
diff --git a/rts/configure.ac b/rts/configure.ac
index 76d599ec3eab81462d70a292f9badafbebf9428b..d1ae16fe407c638edf7369ef60d8dfb31fd43f13 100644
--- a/rts/configure.ac
+++ b/rts/configure.ac
@@ -351,6 +351,93 @@ AS_IF(
   [test "$CABAL_FLAG_libnuma" = 1],
   [AC_CHECK_HEADERS([numa.h numaif.h])])
 
+
+dnl ** I/O managers
+dnl --------------------------------------------------------------
+dnl
+dnl The scheme here is that every I/O manager can be enabled/disabled
+dnl at GHC build time (subject to some constraints). More than one I/O
+dnl manager can be enabled to be built. At least one I/O manager
+dnl supporting the threaded RTS must be enabled as well as at least
+dnl one supporting the non-threaded RTS. The I/O managers enabled here
+dnl become the choices available at runtime at RTS startup. The choice
+dnl can be made with RTS flags. There are separate choices for the
+dnl threaded and non-threaded RTS ways, because most I/O managers are
+dnl specific to these ways. Furthermore we must establish a default I/O
+dnl manager for the threaded and non-threaded RTS.
+dnl
+dnl Most I/O managers are platform-specific so there are checks to
+dnl ensure each one can be enabled on the platform. Such checks are
+dnl also where any system dependencies (e.g. libraries) can be checked.
+dnl
+dnl The output is a set of CPP flags, with one flag per named I/O manager:
+dnl * IOMGR_BUILD_<name>                : which ones should be built (some)
+dnl * IOMGR_DEFAULT_NON_THREADED_<name> : which one is default (exactly one)
+dnl * IOMGR_DEFAULT_THREADED_<name>     : which one is default (exactly one)
+dnl
+dnl Note that IOMGR_BUILD_<name> just says that an I/O manager will be
+dnl built for _some_ RTS way (i.e. threaded or non-threaded). There is
+dnl a set of derived flags IOMGR_ENABLED_<name> in IOManager.h which says
+dnl if each I/O manager is enabled in the "current" RTS way. These are
+dnl the ones used for conditional compilation of the I/O manager code.
+dnl -------------------------------------------------------------------
+
+dnl Here we check for platform constraints. The result is the CPP flag
+dnl IOMGR_BUILD_<name> and a EnableIOManager<Name> var for use here later.
+
+GHC_IOMANAGER_ENABLE([select], [EnableIOManagerSelect], [IOMGR_BUILD_SELECT],
+  [if test "$HostOS" = "mingw32"; then
+       EnableIOManagerSelect=NO
+   else
+       AC_CHECK_HEADER([sys/select.h],
+           [EnableIOManagerSelect=YES],
+           [AC_MSG_ERROR([sys/select.h required by select I/O manager])],[])
+   fi])
+
+GHC_IOMANAGER_ENABLE([mio], [EnableIOManagerMIO], [IOMGR_BUILD_MIO],
+  [EnableIOManagerMIO=YES])
+
+GHC_IOMANAGER_ENABLE([win32-legacy], [EnableIOManagerWin32Legacy], [IOMGR_BUILD_WIN32_LEGACY],
+  [if test "$HostOS" = "mingw32"; then EnableIOManagerWin32Legacy=YES; fi])
+
+GHC_IOMANAGER_ENABLE([winio], [EnableIOManagerWinIO], [IOMGR_BUILD_WINIO],
+  [if test "$HostOS" = "mingw32"; then EnableIOManagerWinIO=YES; fi])
+
+dnl Now we establish a default I/O manager for the threaded and non-threaded
+dnl RTS. We select the default based on which I/O managers are enabled. They
+dnl are checked in reverse order of priority, the last enabled one wins:
+if test "$HostOS" = "mingw32"; then
+  GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [winio], [EnableIOManagerWinIO],)
+  GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [win32-legacy], [EnableIOManagerWin32Legacy])
+  GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [winio], [EnableIOManagerWinIO])
+  GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [mio], [EnableIOManagerMIO])
+else
+  GHC_IOMANAGER_DEFAULT_SELECT([IOManagerNonThreadedDefault], [select], [EnableIOManagerSelect])
+  GHC_IOMANAGER_DEFAULT_SELECT([IOManagerThreadedDefault], [mio], [EnableIOManagerMIO])
+fi
+GHC_IOMANAGER_DEFAULT_CHECK_NOT_EMPTY([IOManagerNonThreadedDefault],[non-threaded])
+GHC_IOMANAGER_DEFAULT_CHECK_NOT_EMPTY([IOManagerThreadedDefault],[threaded])
+
+AC_MSG_NOTICE(default I/O manager for the non-threaded RTS: ${IOManagerNonThreadedDefault})
+AC_MSG_NOTICE(default I/O manager for the threaded RTS: ${IOManagerThreadedDefault})
+
+dnl Now define CPP vars for the default ones (threaded and non-threaded)
+GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
+                                [select], [IOMGR_DEFAULT_NON_THREADED_SELECT])
+
+GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
+                                [winio], [IOMGR_DEFAULT_NON_THREADED_WINIO])
+
+GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerNonThreadedDefault], [non-threaded],
+                                [win32-legacy], [IOMGR_DEFAULT_NON_THREADED_WIN32_LEGACY])
+
+GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerThreadedDefault], [threaded],
+                                [mio], [IOMGR_DEFAULT_THREADED_MIO])
+
+GHC_IOMANAGER_DEFAULT_AC_DEFINE([IOManagerThreadedDefault], [threaded],
+                                [winio], [IOMGR_DEFAULT_THREADED_WINIO])
+
+
 dnl ** Write config files
 dnl --------------------------------------------------------------