summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Gamari <ben@smart-cactus.org>2020-07-16 10:56:54 -0400
committerBen Gamari <ben@smart-cactus.org>2020-07-16 10:56:54 -0400
commitc0979cc53442b3a6202acab9cf164f0a4beea0b7 (patch)
treed08b956887e69f9bd2959f1ac75cc2a2182f9a32
parentae11bdfd98a10266bfc7de9e16b500be220307ac (diff)
parent2143c49273d7d87ee2f3ef1211856d60b1427af1 (diff)
downloadhaskell-c0979cc53442b3a6202acab9cf164f0a4beea0b7.tar.gz
Merge remote-tracking branch 'origin/wip/winio'
-rw-r--r--Makefile6
-rw-r--r--compiler/GHC/Builtin/Names.hs13
-rw-r--r--compiler/GHC/Builtin/Types/Prim.hs20
-rw-r--r--compiler/GHC/Builtin/primops.txt.pp39
-rw-r--r--compiler/GHC/Driver/Pipeline.hs6
-rw-r--r--compiler/GHC/StgToCmm/Prim.hs4
-rw-r--r--compiler/GHC/SysTools/Info.hs2
-rw-r--r--configure.ac12
-rw-r--r--hadrian/src/Settings/Packages.hs10
-rw-r--r--includes/rts/Constants.h8
-rw-r--r--includes/rts/FileLock.h22
-rw-r--r--includes/rts/Flags.h5
-rw-r--r--includes/rts/IOManager.h19
-rw-r--r--includes/rts/OSThreads.h77
-rw-r--r--includes/rts/storage/TSO.h1
-rw-r--r--includes/stg/MiscClosures.h4
m---------libraries/Cabal0
-rw-r--r--libraries/base/Control/Concurrent.hs-boot30
-rw-r--r--libraries/base/GHC/Conc/IO.hs28
-rw-r--r--libraries/base/GHC/Conc/POSIX.hs305
-rw-r--r--libraries/base/GHC/Conc/POSIX/Const.hsc29
-rw-r--r--libraries/base/GHC/Conc/Sync.hs10
-rw-r--r--libraries/base/GHC/Conc/Sync.hs-boot72
-rw-r--r--libraries/base/GHC/Conc/WinIO.hs28
-rw-r--r--libraries/base/GHC/Conc/Windows.hs263
-rw-r--r--libraries/base/GHC/ConsoleHandler.hsc (renamed from libraries/base/GHC/ConsoleHandler.hs)32
-rw-r--r--libraries/base/GHC/Event/Array.hs11
-rw-r--r--libraries/base/GHC/Event/IntTable.hs1
-rw-r--r--libraries/base/GHC/Event/Internal.hs138
-rw-r--r--libraries/base/GHC/Event/Internal/Types.hs160
-rw-r--r--libraries/base/GHC/Event/Thread.hs2
-rw-r--r--libraries/base/GHC/Event/TimeOut.hs40
-rw-r--r--libraries/base/GHC/Event/TimerManager.hs18
-rw-r--r--libraries/base/GHC/Event/Windows.hsc1324
-rw-r--r--libraries/base/GHC/Event/Windows/Clock.hs55
-rw-r--r--libraries/base/GHC/Event/Windows/ConsoleEvent.hsc72
-rw-r--r--libraries/base/GHC/Event/Windows/FFI.hsc395
-rw-r--r--libraries/base/GHC/Event/Windows/ManagedThreadPool.hs93
-rw-r--r--libraries/base/GHC/Event/Windows/Thread.hs35
-rw-r--r--libraries/base/GHC/IO/Buffer.hs59
-rw-r--r--libraries/base/GHC/IO/BufferedIO.hs24
-rw-r--r--libraries/base/GHC/IO/Device.hs35
-rw-r--r--libraries/base/GHC/IO/Encoding/CodePage.hs19
-rw-r--r--libraries/base/GHC/IO/Encoding/CodePage/API.hs16
-rw-r--r--libraries/base/GHC/IO/FD.hs92
-rw-r--r--libraries/base/GHC/IO/Handle.hs53
-rw-r--r--libraries/base/GHC/IO/Handle/Internals.hs107
-rw-r--r--libraries/base/GHC/IO/Handle/Lock/Windows.hsc83
-rw-r--r--libraries/base/GHC/IO/Handle/Text.hs233
-rw-r--r--libraries/base/GHC/IO/Handle/Types.hs9
-rw-r--r--libraries/base/GHC/IO/Handle/Windows.hs235
-rw-r--r--libraries/base/GHC/IO/StdHandles.hs73
-rw-r--r--libraries/base/GHC/IO/StdHandles.hs-boot23
-rw-r--r--libraries/base/GHC/IO/SubSystem.hs79
-rw-r--r--libraries/base/GHC/IO/Windows/Encoding.hs218
-rw-r--r--libraries/base/GHC/IO/Windows/Handle.hsc966
-rw-r--r--libraries/base/GHC/IO/Windows/Paths.hs49
-rw-r--r--libraries/base/GHC/IOPort.hs122
-rw-r--r--libraries/base/GHC/RTS/Flags.hsc68
-rw-r--r--libraries/base/GHC/TopHandler.hs2
-rw-r--r--libraries/base/GHC/Windows.hs77
-rw-r--r--libraries/base/System/IO.hs51
-rw-r--r--libraries/base/System/Timeout.hs2
-rw-r--r--libraries/base/base.cabal48
-rw-r--r--libraries/base/cbits/IOutils.c484
-rw-r--r--libraries/base/cbits/Win32Utils.c102
-rw-r--r--libraries/base/cbits/consUtils.c32
-rw-r--r--libraries/base/include/alignment.h3
-rw-r--r--libraries/base/include/consUtils.h3
-rw-r--r--libraries/base/include/windows_cconv.h12
-rw-r--r--libraries/base/include/winio_structs.h40
-rw-r--r--libraries/base/tests/Concurrent/ThreadDelay001.hs25
-rw-r--r--libraries/base/tests/IO/T4144.hs23
-rw-r--r--libraries/base/tests/IO/all.T15
-rw-r--r--libraries/base/tests/IO/hClose002.stdout-mingw324
-rw-r--r--libraries/base/tests/IO/openFile002.stderr-mingw321
-rw-r--r--libraries/base/tests/IO/openFile002.stderr-mingw32-21
-rw-r--r--libraries/base/tests/IO/openFile003.stdout-mingw328
-rw-r--r--libraries/base/tests/IO/openFile009.hs19
-rw-r--r--libraries/base/tests/IO/openFile009.stdout1
-rw-r--r--libraries/base/tests/Numeric/all.T2
-rw-r--r--libraries/base/tests/tempfiles.stdout-mingw3212
-rw-r--r--libraries/ghc-heap/GHC/Exts/Heap/Closures.hs11
-rw-r--r--libraries/ghc-prim/ghc-prim.cabal2
m---------libraries/haskeline0
m---------libraries/process0
-rw-r--r--rts/FileLock.c24
-rw-r--r--rts/HeapStackCheck.cmm4
-rw-r--r--rts/Linker.c8
-rw-r--r--rts/Prelude.h11
-rw-r--r--rts/PrimOps.cmm244
-rw-r--r--rts/RaiseAsync.c16
-rw-r--r--rts/RtsFlags.c66
-rw-r--r--rts/RtsFlags.h2
-rw-r--r--rts/RtsStartup.c38
-rw-r--r--rts/RtsSymbols.c24
-rw-r--r--rts/Schedule.c42
-rw-r--r--rts/Schedule.h6
-rw-r--r--rts/StgMiscClosures.cmm4
-rw-r--r--rts/Threads.c5
-rw-r--r--rts/Trace.c43
-rw-r--r--rts/TraverseHeap.c1
-rw-r--r--rts/eventlog/EventLog.c1
-rw-r--r--rts/eventlog/EventLogWriter.c2
-rw-r--r--rts/ghc.mk4
-rw-r--r--rts/linker/PEi386.c2
-rw-r--r--rts/package.conf.in10
-rw-r--r--rts/rts.cabal.in27
-rw-r--r--rts/sm/Compact.c1
-rw-r--r--rts/sm/Sanity.c10
-rw-r--r--rts/sm/Scav.c1
-rw-r--r--rts/win32/AsyncMIO.c (renamed from rts/win32/AsyncIO.c)41
-rw-r--r--rts/win32/AsyncMIO.h (renamed from rts/win32/AsyncIO.h)5
-rw-r--r--rts/win32/AsyncWinIO.c545
-rw-r--r--rts/win32/AsyncWinIO.h25
-rw-r--r--rts/win32/AwaitEvent.c16
-rw-r--r--rts/win32/ConsoleHandler.c7
-rw-r--r--rts/win32/ConsoleHandler.h14
-rw-r--r--rts/win32/IOManager.c63
-rw-r--r--rts/win32/IOManager.h5
-rw-r--r--rts/win32/OSMem.c19
-rw-r--r--rts/win32/OSThreads.c172
-rw-r--r--rts/win32/ThrIOManager.c83
-rw-r--r--rts/win32/WorkQueue.c14
-rw-r--r--rts/win32/WorkQueue.h4
-rw-r--r--rts/win32/libHSbase.def4
-rw-r--r--testsuite/config/ghc12
-rw-r--r--testsuite/mk/test.mk2
-rw-r--r--testsuite/tests/backpack/cabal/T16219/backpack-issue.cabal4
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal01/bkpcabal01.cabal4
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal02/bkpcabal02.cabal4
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal03/asig1/asig1.cabal4
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal03/asig2/asig2.cabal4
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in14
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in24
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in14
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in24
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal05/bkpcabal05.cabal4
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal06/bkpcabal06.cabal4
-rw-r--r--testsuite/tests/backpack/cabal/bkpcabal07/bkpcabal07.cabal4
-rw-r--r--testsuite/tests/cabal/cabal01/test.cabal1
-rw-r--r--testsuite/tests/cabal/cabal04/Makefile2
-rw-r--r--testsuite/tests/cabal/cabal06/p-1.0/p.cabal2
-rw-r--r--testsuite/tests/cabal/cabal06/p-1.1/p.cabal2
-rw-r--r--testsuite/tests/cabal/cabal06/q/q.cabal2
-rw-r--r--testsuite/tests/cabal/cabal06/r/r.cabal2
-rw-r--r--testsuite/tests/driver/T4437.hs3
-rw-r--r--testsuite/tests/ghci/linking/dyn/Makefile2
-rw-r--r--testsuite/tests/ghci/linking/dyn/T10955dyn.stderr4
-rw-r--r--testsuite/tests/ghci/linking/dyn/load_short_name.stderr2
-rw-r--r--utils/fs/fs.c2
-rw-r--r--utils/fs/fs.h1
-rw-r--r--utils/genprimopcode/Main.hs2
-rw-r--r--utils/ghc-cabal/ghc.mk6
-rw-r--r--utils/hp2ps/Main.h2
m---------utils/hsc2hs0
156 files changed, 7450 insertions, 1192 deletions
diff --git a/Makefile b/Makefile
index d7885995d0..cf15d1c086 100644
--- a/Makefile
+++ b/Makefile
@@ -217,15 +217,15 @@ endif
# test`, runs each test at least once.
.PHONY: fasttest
fasttest:
- $(MAKE) -C testsuite/tests CLEANUP=1 SUMMARY_FILE=../../testsuite_summary.txt fast
+ $(MAKE) -C testsuite/tests SUMMARY_FILE=../../testsuite_summary.txt fast
.PHONY: test
test:
- $(MAKE) -C testsuite/tests CLEANUP=1 SUMMARY_FILE=../../testsuite_summary.txt
+ $(MAKE) -C testsuite/tests SUMMARY_FILE=../../testsuite_summary.txt
.PHONY: slowtest fulltest
slowtest fulltest:
- $(MAKE) -C testsuite/tests CLEANUP=1 SUMMARY_FILE=../../testsuite_summary.txt slow
+ $(MAKE) -C testsuite/tests SUMMARY_FILE=../../testsuite_summary.txt slow
.PHONY: fast
fast:
diff --git a/compiler/GHC/Builtin/Names.hs b/compiler/GHC/Builtin/Names.hs
index 02a10d4b35..b9ef184923 100644
--- a/compiler/GHC/Builtin/Names.hs
+++ b/compiler/GHC/Builtin/Names.hs
@@ -1747,7 +1747,7 @@ addrPrimTyConKey, arrayPrimTyConKey, arrayArrayPrimTyConKey, boolTyConKey,
weakPrimTyConKey, mutableArrayPrimTyConKey, mutableArrayArrayPrimTyConKey,
mutableByteArrayPrimTyConKey, orderingTyConKey, mVarPrimTyConKey,
ratioTyConKey, rationalTyConKey, realWorldTyConKey, stablePtrPrimTyConKey,
- stablePtrTyConKey, eqTyConKey, heqTyConKey,
+ stablePtrTyConKey, eqTyConKey, heqTyConKey, ioPortPrimTyConKey,
smallArrayPrimTyConKey, smallMutableArrayPrimTyConKey,
stringTyConKey :: Unique
addrPrimTyConKey = mkPreludeTyConUnique 1
@@ -1783,11 +1783,12 @@ mutableArrayPrimTyConKey = mkPreludeTyConUnique 30
mutableByteArrayPrimTyConKey = mkPreludeTyConUnique 31
orderingTyConKey = mkPreludeTyConUnique 32
mVarPrimTyConKey = mkPreludeTyConUnique 33
-ratioTyConKey = mkPreludeTyConUnique 34
-rationalTyConKey = mkPreludeTyConUnique 35
-realWorldTyConKey = mkPreludeTyConUnique 36
-stablePtrPrimTyConKey = mkPreludeTyConUnique 37
-stablePtrTyConKey = mkPreludeTyConUnique 38
+ioPortPrimTyConKey = mkPreludeTyConUnique 34
+ratioTyConKey = mkPreludeTyConUnique 35
+rationalTyConKey = mkPreludeTyConUnique 36
+realWorldTyConKey = mkPreludeTyConUnique 37
+stablePtrPrimTyConKey = mkPreludeTyConUnique 38
+stablePtrTyConKey = mkPreludeTyConUnique 39
eqTyConKey = mkPreludeTyConUnique 40
heqTyConKey = mkPreludeTyConUnique 41
arrayArrayPrimTyConKey = mkPreludeTyConUnique 42
diff --git a/compiler/GHC/Builtin/Types/Prim.hs b/compiler/GHC/Builtin/Types/Prim.hs
index 88ef943a64..13f08739d0 100644
--- a/compiler/GHC/Builtin/Types/Prim.hs
+++ b/compiler/GHC/Builtin/Types/Prim.hs
@@ -62,6 +62,7 @@ module GHC.Builtin.Types.Prim(
mutVarPrimTyCon, mkMutVarPrimTy,
mVarPrimTyCon, mkMVarPrimTy,
+ ioPortPrimTyCon, mkIOPortPrimTy,
tVarPrimTyCon, mkTVarPrimTy,
stablePtrPrimTyCon, mkStablePtrPrimTy,
stableNamePrimTyCon, mkStableNamePrimTy,
@@ -171,6 +172,7 @@ exposedPrimTyCons
, mutableArrayArrayPrimTyCon
, smallMutableArrayPrimTyCon
, mVarPrimTyCon
+ , ioPortPrimTyCon
, tVarPrimTyCon
, mutVarPrimTyCon
, realWorldTyCon
@@ -207,7 +209,7 @@ mkBuiltInPrimTc fs unique tycon
BuiltInSyntax
-charPrimTyConName, intPrimTyConName, int8PrimTyConName, int16PrimTyConName, int32PrimTyConName, int64PrimTyConName, wordPrimTyConName, word32PrimTyConName, word8PrimTyConName, word16PrimTyConName, word64PrimTyConName, addrPrimTyConName, floatPrimTyConName, doublePrimTyConName, statePrimTyConName, proxyPrimTyConName, realWorldTyConName, arrayPrimTyConName, arrayArrayPrimTyConName, smallArrayPrimTyConName, byteArrayPrimTyConName, mutableArrayPrimTyConName, mutableByteArrayPrimTyConName, mutableArrayArrayPrimTyConName, smallMutableArrayPrimTyConName, mutVarPrimTyConName, mVarPrimTyConName, tVarPrimTyConName, stablePtrPrimTyConName, stableNamePrimTyConName, compactPrimTyConName, bcoPrimTyConName, weakPrimTyConName, threadIdPrimTyConName, eqPrimTyConName, eqReprPrimTyConName, eqPhantPrimTyConName, voidPrimTyConName :: Name
+charPrimTyConName, intPrimTyConName, int8PrimTyConName, int16PrimTyConName, int32PrimTyConName, int64PrimTyConName, wordPrimTyConName, word32PrimTyConName, word8PrimTyConName, word16PrimTyConName, word64PrimTyConName, addrPrimTyConName, floatPrimTyConName, doublePrimTyConName, statePrimTyConName, proxyPrimTyConName, realWorldTyConName, arrayPrimTyConName, arrayArrayPrimTyConName, smallArrayPrimTyConName, byteArrayPrimTyConName, mutableArrayPrimTyConName, mutableByteArrayPrimTyConName, mutableArrayArrayPrimTyConName, smallMutableArrayPrimTyConName, mutVarPrimTyConName, mVarPrimTyConName, ioPortPrimTyConName, tVarPrimTyConName, stablePtrPrimTyConName, stableNamePrimTyConName, compactPrimTyConName, bcoPrimTyConName, weakPrimTyConName, threadIdPrimTyConName, eqPrimTyConName, eqReprPrimTyConName, eqPhantPrimTyConName, voidPrimTyConName :: Name
charPrimTyConName = mkPrimTc (fsLit "Char#") charPrimTyConKey charPrimTyCon
intPrimTyConName = mkPrimTc (fsLit "Int#") intPrimTyConKey intPrimTyCon
int8PrimTyConName = mkPrimTc (fsLit "Int8#") int8PrimTyConKey int8PrimTyCon
@@ -238,6 +240,7 @@ mutableByteArrayPrimTyConName = mkPrimTc (fsLit "MutableByteArray#") mutableByte
mutableArrayArrayPrimTyConName= mkPrimTc (fsLit "MutableArrayArray#") mutableArrayArrayPrimTyConKey mutableArrayArrayPrimTyCon
smallMutableArrayPrimTyConName= mkPrimTc (fsLit "SmallMutableArray#") smallMutableArrayPrimTyConKey smallMutableArrayPrimTyCon
mutVarPrimTyConName = mkPrimTc (fsLit "MutVar#") mutVarPrimTyConKey mutVarPrimTyCon
+ioPortPrimTyConName = mkPrimTc (fsLit "IOPort#") ioPortPrimTyConKey ioPortPrimTyCon
mVarPrimTyConName = mkPrimTc (fsLit "MVar#") mVarPrimTyConKey mVarPrimTyCon
tVarPrimTyConName = mkPrimTc (fsLit "TVar#") tVarPrimTyConKey tVarPrimTyCon
stablePtrPrimTyConName = mkPrimTc (fsLit "StablePtr#") stablePtrPrimTyConKey stablePtrPrimTyCon
@@ -1006,7 +1009,22 @@ mkMutVarPrimTy s elt = TyConApp mutVarPrimTyCon [s, elt]
{-
************************************************************************
* *
+\subsection[TysPrim-io-port-var]{The synchronizing I/O Port type}
+* *
+************************************************************************
+-}
+
+ioPortPrimTyCon :: TyCon
+ioPortPrimTyCon = pcPrimTyCon ioPortPrimTyConName [Nominal, Representational] UnliftedRep
+
+mkIOPortPrimTy :: Type -> Type -> Type
+mkIOPortPrimTy s elt = TyConApp ioPortPrimTyCon [s, elt]
+
+{-
+************************************************************************
+* *
The synchronizing variable type
+\subsection[TysPrim-synch-var]{The synchronizing variable type}
* *
************************************************************************
-}
diff --git a/compiler/GHC/Builtin/primops.txt.pp b/compiler/GHC/Builtin/primops.txt.pp
index a12ac1f29c..261d02aa67 100644
--- a/compiler/GHC/Builtin/primops.txt.pp
+++ b/compiler/GHC/Builtin/primops.txt.pp
@@ -2827,6 +2827,45 @@ primop IsEmptyMVarOp "isEmptyMVar#" GenPrimOp
out_of_line = True
has_side_effects = True
+
+------------------------------------------------------------------------
+section "Synchronized I/O Ports"
+ {Operations on {\tt IOPort\#}s. }
+------------------------------------------------------------------------
+
+primtype IOPort# s a
+ { A shared I/O port is almost the same as a {\tt MVar\#}!).
+ The main difference is that IOPort has no deadlock detection or
+ deadlock breaking code that forcibly releases the lock. }
+
+primop NewIOPortrOp "newIOPort#" GenPrimOp
+ State# s -> (# State# s, IOPort# s a #)
+ {Create new {\tt IOPort\#}; initially empty.}
+ with
+ out_of_line = True
+ has_side_effects = True
+
+primop ReadIOPortOp "readIOPort#" GenPrimOp
+ IOPort# s a -> State# s -> (# State# s, a #)
+ {If {\tt IOPort\#} is empty, block until it becomes full.
+ Then remove and return its contents, and set it empty.}
+ with
+ out_of_line = True
+ has_side_effects = True
+
+primop WriteIOPortOp "writeIOPort#" GenPrimOp
+ IOPort# s a -> a -> State# s -> (# State# s, Int# #)
+ {If {\tt IOPort\#} is full, immediately return with integer 0.
+ Otherwise, store value arg as {\tt IOPort\#}'s new contents,
+ and return with integer 1. }
+ with
+ out_of_line = True
+ has_side_effects = True
+
+primop SameIOPortOp "sameIOPort#" GenPrimOp
+ IOPort# s a -> IOPort# s a -> Int#
+
+
------------------------------------------------------------------------
section "Delay/wait operations"
------------------------------------------------------------------------
diff --git a/compiler/GHC/Driver/Pipeline.hs b/compiler/GHC/Driver/Pipeline.hs
index 83e637401e..81a141afee 100644
--- a/compiler/GHC/Driver/Pipeline.hs
+++ b/compiler/GHC/Driver/Pipeline.hs
@@ -1980,6 +1980,7 @@ doCpp dflags raw input_fn output_fn = do
let targetArch = stringEncodeArch $ platformArch $ targetPlatform dflags
targetOS = stringEncodeOS $ platformOS $ targetPlatform dflags
+ isWindows = (platformOS $ targetPlatform dflags) == OSMinGW32
let target_defs =
[ "-D" ++ HOST_OS ++ "_BUILD_OS",
"-D" ++ HOST_ARCH ++ "_BUILD_ARCH",
@@ -1988,6 +1989,10 @@ doCpp dflags raw input_fn output_fn = do
-- remember, in code we *compile*, the HOST is the same our TARGET,
-- and BUILD is the same as our HOST.
+ let io_manager_defs =
+ [ "-D__IO_MANAGER_WINIO__=1" | isWindows ] ++
+ [ "-D__IO_MANAGER_MIO__=1" ]
+
let sse_defs =
[ "-D__SSE__" | isSseEnabled dflags ] ++
[ "-D__SSE2__" | isSse2Enabled dflags ] ++
@@ -2033,6 +2038,7 @@ doCpp dflags raw input_fn output_fn = do
++ map GHC.SysTools.Option hscpp_opts
++ map GHC.SysTools.Option sse_defs
++ map GHC.SysTools.Option avx_defs
+ ++ map GHC.SysTools.Option io_manager_defs
++ mb_macro_include
-- Set the language mode to assembler-with-cpp when preprocessing. This
-- alleviates some of the C99 macro rules relating to whitespace and the hash
diff --git a/compiler/GHC/StgToCmm/Prim.hs b/compiler/GHC/StgToCmm/Prim.hs
index ef5e376be8..afbcc34836 100644
--- a/compiler/GHC/StgToCmm/Prim.hs
+++ b/compiler/GHC/StgToCmm/Prim.hs
@@ -1320,6 +1320,7 @@ emitPrimOp dflags primop = case primop of
SameMutVarOp -> \args -> opTranslate args (mo_wordEq platform)
SameMVarOp -> \args -> opTranslate args (mo_wordEq platform)
+ SameIOPortOp -> \args -> opTranslate args (mo_wordEq platform)
SameMutableArrayOp -> \args -> opTranslate args (mo_wordEq platform)
SameMutableByteArrayOp -> \args -> opTranslate args (mo_wordEq platform)
SameMutableArrayArrayOp -> \args -> opTranslate args (mo_wordEq platform)
@@ -1467,6 +1468,9 @@ emitPrimOp dflags primop = case primop of
ReadMVarOp -> alwaysExternal
TryReadMVarOp -> alwaysExternal
IsEmptyMVarOp -> alwaysExternal
+ NewIOPortrOp -> alwaysExternal
+ ReadIOPortOp -> alwaysExternal
+ WriteIOPortOp -> alwaysExternal
DelayOp -> alwaysExternal
WaitReadOp -> alwaysExternal
WaitWriteOp -> alwaysExternal
diff --git a/compiler/GHC/SysTools/Info.hs b/compiler/GHC/SysTools/Info.hs
index 039c1d12aa..fec6ecff15 100644
--- a/compiler/GHC/SysTools/Info.hs
+++ b/compiler/GHC/SysTools/Info.hs
@@ -241,7 +241,7 @@ getCompilerInfo' dflags = do
| any ("Apple clang version" `isPrefixOf`) stde =
return AppleClang
-- Unknown linker.
- | otherwise = fail "invalid -v output, or compiler is unsupported"
+ | otherwise = fail $ "invalid -v output, or compiler is unsupported: " ++ unlines stde
-- Process the executable call
info <- catchIO (do
diff --git a/configure.ac b/configure.ac
index 6e710c7f8d..d3ef6eee62 100644
--- a/configure.ac
+++ b/configure.ac
@@ -117,6 +117,18 @@ if test "$EnableDistroToolchain" = "YES"; then
TarballsAutodownload=NO
fi
+AC_ARG_ENABLE(native-io-manager,
+[AC_HELP_STRING([--enable-native-io-manager],
+ [Enable the native I/O manager by default.])],
+ EnableNativeIOManager=YES,
+ EnableNativeIOManager=NO
+)
+
+if test "$EnableNativeIOManager" = "YES"; then
+ AC_DEFINE_UNQUOTED([DEFAULT_NATIVE_IO_MANAGER], [1], [Enable Native I/O manager as default.])
+
+fi
+
dnl CC_STAGE0, LD_STAGE0, AR_STAGE0 are like the "previous" variable
dnl CC, LD, AR (inherited by CC_STAGE[123], etc.)
dnl but instead used by stage0 for bootstrapping stage1
diff --git a/hadrian/src/Settings/Packages.hs b/hadrian/src/Settings/Packages.hs
index 8c9d7875d4..42e95f6664 100644
--- a/hadrian/src/Settings/Packages.hs
+++ b/hadrian/src/Settings/Packages.hs
@@ -368,7 +368,6 @@ rtsPackageArgs = package rts ? do
, input "**/RetainerProfile.c" ? flag CcLlvmBackend ?
arg "-Wno-incompatible-pointer-types"
- , windowsHost ? arg ("-DWINVER=" ++ windowsVersion)
-- libffi's ffi.h triggers various warnings
, inputs [ "**/Interpreter.c", "**/Storage.c", "**/Adjustor.c" ] ?
@@ -455,12 +454,3 @@ rtsWarnings = mconcat
, arg "-Wredundant-decls"
, arg "-Wundef"
, arg "-fno-strict-aliasing" ]
-
--- These numbers can be found at:
--- https://msdn.microsoft.com/en-us/library/windows/desktop/aa383745(v=vs.85).aspx
--- If we're compiling on windows, enforce that we only support Vista SP1+
--- Adding this here means it doesn't have to be done in individual .c files
--- and also centralizes the versioning.
--- | Minimum supported Windows version.
-windowsVersion :: String
-windowsVersion = "0x06010000"
diff --git a/includes/rts/Constants.h b/includes/rts/Constants.h
index c2cad8fc80..043099bd1a 100644
--- a/includes/rts/Constants.h
+++ b/includes/rts/Constants.h
@@ -256,7 +256,13 @@
by tryWakeupThread() */
#define ThreadMigrating 13
-/* WARNING WARNING top number is BlockedOnMVarRead 14, not 13!! */
+/* Lightweight non-deadlock checked version of MVar. Used for the why_blocked
+ field of a TSO. Threads blocked for this reason are not forcibly release by
+ the GC, as we expect them to be unblocked in the future based on outstanding
+ IO events. */
+#define BlockedOnIOCompletion 15
+
+/* Next number is 16. */
/*
* These constants are returned to the scheduler by a thread that has
diff --git a/includes/rts/FileLock.h b/includes/rts/FileLock.h
index 978ccf86b6..69df911595 100644
--- a/includes/rts/FileLock.h
+++ b/includes/rts/FileLock.h
@@ -11,9 +11,27 @@
*
* ---------------------------------------------------------------------------*/
+/* Note [RTS File locking]
+ * ~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * The Haskell report dictates certain file locking behaviour.
+ * This is specified in the Haskell98 report under: 21.2.3 File locking
+ *
+ * GHC does not rely on the platform it's on to implement this.
+ * Instead we keep track of locked files in a data structure in
+ * the RTS. This file provides the interface to this data structure.
+ *
+ * In the base librarie we then use this interface to "lock" files.
+ * This means it's very much still possible for users outside of the
+ * rts/base library to open the files in question even if they are
+ * locked.
+ * */
+
#pragma once
#include "Stg.h"
-int lockFile(int fd, StgWord64 dev, StgWord64 ino, int for_writing);
-int unlockFile(int fd);
+/* No valid FD would be negative, so use a word instead of int so the value
+ is compatible with a Windows handle. */
+int lockFile(StgWord64 id, StgWord64 dev, StgWord64 ino, int for_writing);
+int unlockFile(StgWord64 id);
diff --git a/includes/rts/Flags.h b/includes/rts/Flags.h
index d0c41a1576..bf84c5dc96 100644
--- a/includes/rts/Flags.h
+++ b/includes/rts/Flags.h
@@ -206,6 +206,9 @@ 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;
+
/* See Note [Synchronization of flags and base APIs] */
typedef struct _MISC_FLAGS {
Time tickInterval; /* units: TIME_RESOLUTION */
@@ -224,6 +227,8 @@ 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. */
+ uint32_t numIoWorkerThreads; /* Number of I/O worker threads to use. */
} MISC_FLAGS;
/* See Note [Synchronization of flags and base APIs] */
diff --git a/includes/rts/IOManager.h b/includes/rts/IOManager.h
index 603cb3f578..4c392e2058 100644
--- a/includes/rts/IOManager.h
+++ b/includes/rts/IOManager.h
@@ -15,6 +15,11 @@
#if defined(mingw32_HOST_OS)
+#define IO_MANAGER_WAKEUP 0xffffffff
+#define IO_MANAGER_DIE 0xfffffffe
+/* spurious wakeups are returned as zero. */
+/* console events are ((event<<1) | 1). */
+
int rts_InstallConsoleEvent ( int action, StgStablePtr *handler );
void rts_ConsoleHandlerDone ( int ev );
extern StgInt console_handler;
@@ -31,13 +36,15 @@ void setIOManagerWakeupFd (int fd);
#endif
-//
-// Communicating with the IO manager thread (see GHC.Conc).
-// Posix implementation in posix/Signals.c
-// Win32 implementation in win32/ThrIOManager.c
-//
+/*
+ * Communicating with the IO manager thread (see GHC.Conc).
+ * Posix implementation in posix/Signals.c
+ * Win32 implementation in win32/ThrIOManager.c, Windows's WINIO has the same
+ * interfaces for Threaded and Non-threaded I/O, so these methods are always
+ * available for WINIO.
+*/
void ioManagerWakeup (void);
-#if defined(THREADED_RTS)
+#if defined(THREADED_RTS) || defined(mingw32_HOST_OS)
void ioManagerDie (void);
void ioManagerStart (void);
#endif
diff --git a/includes/rts/OSThreads.h b/includes/rts/OSThreads.h
index ada2a9a787..a68f1ea140 100644
--- a/includes/rts/OSThreads.h
+++ b/includes/rts/OSThreads.h
@@ -77,18 +77,22 @@ EXTERN_INLINE int TRY_ACQUIRE_LOCK(pthread_mutex_t *mutex)
#if defined(CMINUSMINUS)
-/* We jump through a hoop here to get a CCall EnterCriticalSection
- and LeaveCriticalSection, as that's what C-- wants. */
+/* We jump through a hoop here to get a CCall AcquireSRWLockExclusive
+ and ReleaseSRWLockExclusive, as that's what C-- wants. */
-#define OS_ACQUIRE_LOCK(mutex) foreign "stdcall" EnterCriticalSection(mutex)
-#define OS_RELEASE_LOCK(mutex) foreign "stdcall" LeaveCriticalSection(mutex)
+#define OS_ACQUIRE_LOCK(mutex) foreign "stdcall" AcquireSRWLockExclusive(mutex)
+#define OS_RELEASE_LOCK(mutex) foreign "stdcall" ReleaseSRWLockExclusive(mutex)
#define OS_ASSERT_LOCK_HELD(mutex) /* nothing */
-#else
+#else // CMINUSMINUS
#include <windows.h>
+#include <synchapi.h>
-typedef HANDLE Condition;
+/* Use native conditional variables coupled with SRW locks, these are more
+ efficient and occur a smaller overhead then emulating them with events.
+ See Note [SRW locks]. */
+typedef CONDITION_VARIABLE Condition;
typedef DWORD OSThreadId;
// don't be tempted to use HANDLE as the OSThreadId: there can be
// many HANDLES to a given thread, so comparison would not work.
@@ -98,58 +102,47 @@ typedef DWORD ThreadLocalKey;
#define INIT_COND_VAR 0
-// We have a choice for implementing Mutexes on Windows. Standard
-// Mutexes are kernel objects that require kernel calls to
-// acquire/release, whereas CriticalSections are spin-locks that block
-// in the kernel after spinning for a configurable number of times.
-// CriticalSections are *much* faster, so we use those. The Mutex
-// implementation is left here for posterity.
-#define USE_CRITICAL_SECTIONS 1
-
-#if USE_CRITICAL_SECTIONS
-
-typedef CRITICAL_SECTION Mutex;
+/* Note [SRW locks]
+ We have a choice for implementing Mutexes on Windows. Standard
+ Mutexes are kernel objects that require kernel calls to
+ acquire/release, whereas CriticalSections are spin-locks that block
+ in the kernel after spinning for a configurable number of times.
+ CriticalSections are *much* faster than Mutexes, however not as fast as
+ slim reader/writer locks. CriticalSections also require a 48 byte structure
+ to provide lock re-entrancy. We don't need that because the other primitives
+ used for other platforms don't have this, as such locks are used defensively
+ in the RTS in a way that we don't need re-entrancy. This means that SRW's
+ 8 byte size is much more appropriate. With an 8 byte payload there's a
+ higher chance of it being in your cache line. They're also a lot faster than
+ CriticalSections when multiple threads are involved. CS requires setup and
+ teardown via kernel calls while SRWL is zero-initialized via
+ SRWLOCK_INIT assignment. */
+
+typedef SRWLOCK Mutex;
#if defined(LOCK_DEBUG)
#define OS_ACQUIRE_LOCK(mutex) \
debugBelch("ACQUIRE_LOCK(0x%p) %s %d\n", mutex,__FILE__,__LINE__); \
- EnterCriticalSection(mutex)
+ AcquireSRWLockExclusive(mutex)
#define OS_RELEASE_LOCK(mutex) \
debugBelch("RELEASE_LOCK(0x%p) %s %d\n", mutex,__FILE__,__LINE__); \
- LeaveCriticalSection(mutex)
+ ReleaseSRWLockExclusive(mutex)
#define OS_ASSERT_LOCK_HELD(mutex) /* nothing */
#else
-#define OS_ACQUIRE_LOCK(mutex) EnterCriticalSection(mutex)
-#define TRY_ACQUIRE_LOCK(mutex) (TryEnterCriticalSection(mutex) == 0)
-#define OS_RELEASE_LOCK(mutex) LeaveCriticalSection(mutex)
+#define OS_ACQUIRE_LOCK(mutex) AcquireSRWLockExclusive(mutex)
+#define TRY_ACQUIRE_LOCK(mutex) (TryAcquireSRWLockExclusive(mutex) == 0)
+#define OS_RELEASE_LOCK(mutex) ReleaseSRWLockExclusive(mutex)
+#define OS_INIT_LOCK(mutex) InitializeSRWLock(mutex)
+#define OS_CLOSE_LOCK(mutex)
// I don't know how to do this. TryEnterCriticalSection() doesn't do
// the right thing.
#define OS_ASSERT_LOCK_HELD(mutex) /* nothing */
-#endif
-
-#else
-
-typedef HANDLE Mutex;
-
-// casting to (Mutex *) here required due to use in .cmm files where
-// the argument has (void *) type.
-#define OS_ACQUIRE_LOCK(mutex) \
- if (WaitForSingleObject(*((Mutex *)mutex),INFINITE) == WAIT_FAILED) { \
- barf("WaitForSingleObject: %d", GetLastError()); \
- }
-
-#define OS_RELEASE_LOCK(mutex) \
- if (ReleaseMutex(*((Mutex *)mutex)) == 0) { \
- barf("ReleaseMutex: %d", GetLastError()); \
- }
-
-#define OS_ASSERT_LOCK_HELD(mutex) /* nothing */
-#endif
+#endif // LOCK_DEBUG
#endif // CMINUSMINUS
diff --git a/includes/rts/storage/TSO.h b/includes/rts/storage/TSO.h
index 3a488d97b5..33eebffc7c 100644
--- a/includes/rts/storage/TSO.h
+++ b/includes/rts/storage/TSO.h
@@ -288,6 +288,7 @@ void dirty_STACK (Capability *cap, StgStack *stack);
BlockedOnBlackHole MessageBlackHole * TSO->bq
BlockedOnMVar the MVAR the MVAR's queue
+ BlockedOnIOCompletion the PortEVent the IOCP's queue
BlockedOnSTM END_TSO_QUEUE STM wait queue(s)
BlockedOnSTM STM_AWOKEN run queue
diff --git a/includes/stg/MiscClosures.h b/includes/stg/MiscClosures.h
index dc2b0715ca..5ffdd5cd7b 100644
--- a/includes/stg/MiscClosures.h
+++ b/includes/stg/MiscClosures.h
@@ -337,6 +337,10 @@ RTS_FUN_DECL(stg_block_stmwait);
RTS_FUN_DECL(stg_block_throwto);
RTS_RET(stg_block_throwto);
+RTS_FUN_DECL(stg_readIOPortzh);
+RTS_FUN_DECL(stg_writeIOPortzh);
+RTS_FUN_DECL(stg_newIOPortzh);
+
/* Entry/exit points from StgStartup.cmm */
RTS_RET(stg_stop_thread);
diff --git a/libraries/Cabal b/libraries/Cabal
-Subproject 8dc7f0db292ff1a5b1316127e3652d06ab51f3a
+Subproject 32dad5c1cf70d65ecb93b0ec214445cf9c9f661
diff --git a/libraries/base/Control/Concurrent.hs-boot b/libraries/base/Control/Concurrent.hs-boot
new file mode 100644
index 0000000000..213340432e
--- /dev/null
+++ b/libraries/base/Control/Concurrent.hs-boot
@@ -0,0 +1,30 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : Control.Concurrent
+-- Copyright : (c) The University of Glasgow 2018-2019
+-- License : BSD-style (see the file libraries/base/LICENSE)
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : experimental
+-- Portability : non-portable (concurrency)
+--
+-- A common interface to a collection of useful concurrency
+-- abstractions.
+--
+-----------------------------------------------------------------------------
+module Control.Concurrent (
+ -- * Bound Threads
+ rtsSupportsBoundThreads,
+ forkOS
+ ) where
+
+import Data.Bool
+
+import GHC.IO
+import GHC.Conc.Sync
+
+rtsSupportsBoundThreads :: Bool
+forkOS :: IO () -> IO ThreadId
diff --git a/libraries/base/GHC/Conc/IO.hs b/libraries/base/GHC/Conc/IO.hs
index 7b87adc7ea..d65f9c0acf 100644
--- a/libraries/base/GHC/Conc/IO.hs
+++ b/libraries/base/GHC/Conc/IO.hs
@@ -4,7 +4,6 @@
, MagicHash
, UnboxedTuples
#-}
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# OPTIONS_HADDOCK not-home #-}
-----------------------------------------------------------------------------
@@ -29,6 +28,7 @@
module GHC.Conc.IO
( ensureIOManagerIsRunning
, ioManagerCapabilitiesChanged
+ , interruptIOManager
-- * Waiting
, threadDelay
@@ -61,6 +61,7 @@ import System.Posix.Types
#if defined(mingw32_HOST_OS)
import qualified GHC.Conc.Windows as Windows
+import GHC.IO.SubSystem
import GHC.Conc.Windows (asyncRead, asyncWrite, asyncDoProc, asyncReadBA,
asyncWriteBA, ConsoleEvent(..), win32ConsoleHandler,
toWin32ConsoleEvent)
@@ -75,6 +76,17 @@ ensureIOManagerIsRunning = Event.ensureIOManagerIsRunning
ensureIOManagerIsRunning = Windows.ensureIOManagerIsRunning
#endif
+-- | Interrupts the current wait of the I/O manager if it is currently blocked.
+-- This instructs it to re-read how much it should wait and to process any
+-- pending events.
+-- @since 4.15
+interruptIOManager :: IO ()
+#if !defined(mingw32_HOST_OS)
+interruptIOManager = return ()
+#else
+interruptIOManager = Windows.interruptIOManager
+#endif
+
ioManagerCapabilitiesChanged :: IO ()
#if !defined(mingw32_HOST_OS)
ioManagerCapabilitiesChanged = Event.ioManagerCapabilitiesChanged
@@ -179,11 +191,12 @@ closeFdWith close fd
threadDelay :: Int -> IO ()
threadDelay time
#if defined(mingw32_HOST_OS)
- | threaded = Windows.threadDelay time
+ | isWindowsNativeIO = Windows.threadDelay time
+ | threaded = Windows.threadDelay time
#else
- | threaded = Event.threadDelay time
+ | threaded = Event.threadDelay time
#endif
- | otherwise = IO $ \s ->
+ | otherwise = IO $ \s ->
case time of { I# time# ->
case delay# time# s of { s' -> (# s', () #)
}}
@@ -195,10 +208,11 @@ threadDelay time
registerDelay :: Int -> IO (TVar Bool)
registerDelay usecs
#if defined(mingw32_HOST_OS)
- | threaded = Windows.registerDelay usecs
+ | isWindowsNativeIO = Windows.registerDelay usecs
+ | threaded = Windows.registerDelay usecs
#else
- | threaded = Event.registerDelay usecs
+ | threaded = Event.registerDelay usecs
#endif
- | otherwise = errorWithoutStackTrace "registerDelay: requires -threaded"
+ | otherwise = errorWithoutStackTrace "registerDelay: requires -threaded"
foreign import ccall unsafe "rtsSupportsBoundThreads" threaded :: Bool
diff --git a/libraries/base/GHC/Conc/POSIX.hs b/libraries/base/GHC/Conc/POSIX.hs
new file mode 100644
index 0000000000..84dc68fc30
--- /dev/null
+++ b/libraries/base/GHC/Conc/POSIX.hs
@@ -0,0 +1,305 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE CPP, NoImplicitPrelude, MagicHash, UnboxedTuples #-}
+{-# OPTIONS_GHC -Wno-missing-signatures #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.Conc.POSIX
+-- Copyright : (c) The University of Glasgow, 1994-2002
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : cvs-ghc@haskell.org
+-- Stability : internal
+-- Portability : non-portable (GHC extensions)
+--
+-- Windows I/O manager
+--
+-- This is the I/O manager based on posix FDs for windows.
+-- When using the winio manager these functions may not
+-- be used as they will behave in unexpected ways.
+--
+-- TODO: This manager is currently the default. But we will eventually
+-- switch to use winio instead.
+--
+-----------------------------------------------------------------------------
+
+-- #not-home
+module GHC.Conc.POSIX
+ ( ensureIOManagerIsRunning
+ , interruptIOManager
+
+ -- * Waiting
+ , threadDelay
+ , registerDelay
+
+ -- * Miscellaneous
+ , asyncRead
+ , asyncWrite
+ , asyncDoProc
+
+ , asyncReadBA
+ , asyncWriteBA
+
+ , module GHC.Event.Windows.ConsoleEvent
+ ) where
+
+
+#include "windows_cconv.h"
+
+import Data.Bits (shiftR)
+import GHC.Base
+import GHC.Conc.Sync
+import GHC.Conc.POSIX.Const
+import GHC.Event.Windows.ConsoleEvent
+import GHC.IO (unsafePerformIO)
+import GHC.IORef
+import GHC.MVar
+import GHC.Num (Num(..))
+import GHC.Ptr
+import GHC.Real (div, fromIntegral)
+import GHC.Word (Word32, Word64)
+import GHC.Windows
+import Unsafe.Coerce ( unsafeCoerceUnlifted )
+
+-- ----------------------------------------------------------------------------
+-- Thread waiting
+
+-- Note: threadWaitRead and threadWaitWrite aren't really functional
+-- on Win32, but left in there because lib code (still) uses them (the manner
+-- in which they're used doesn't cause problems on a Win32 platform though.)
+
+asyncRead :: Int -> Int -> Int -> Ptr a -> IO (Int, Int)
+asyncRead (I# fd) (I# isSock) (I# len) (Ptr buf) =
+ IO $ \s -> case asyncRead# fd isSock len buf s of
+ (# s', len#, err# #) -> (# s', (I# len#, I# err#) #)
+
+asyncWrite :: Int -> Int -> Int -> Ptr a -> IO (Int, Int)
+asyncWrite (I# fd) (I# isSock) (I# len) (Ptr buf) =
+ IO $ \s -> case asyncWrite# fd isSock len buf s of
+ (# s', len#, err# #) -> (# s', (I# len#, I# err#) #)
+
+asyncDoProc :: FunPtr (Ptr a -> IO Int) -> Ptr a -> IO Int
+asyncDoProc (FunPtr proc) (Ptr param) =
+ -- the 'length' value is ignored; simplifies implementation of
+ -- the async*# primops to have them all return the same result.
+ IO $ \s -> case asyncDoProc# proc param s of
+ (# s', _len#, err# #) -> (# s', I# err# #)
+
+-- to aid the use of these primops by the IO Handle implementation,
+-- provide the following convenience funs:
+
+-- this better be a pinned byte array!
+asyncReadBA :: Int -> Int -> Int -> Int -> MutableByteArray# RealWorld -> IO (Int,Int)
+asyncReadBA fd isSock len off bufB =
+ asyncRead fd isSock len ((Ptr (byteArrayContents# (unsafeCoerceUnlifted bufB))) `plusPtr` off)
+
+asyncWriteBA :: Int -> Int -> Int -> Int -> MutableByteArray# RealWorld -> IO (Int,Int)
+asyncWriteBA fd isSock len off bufB =
+ asyncWrite fd isSock len ((Ptr (byteArrayContents# (unsafeCoerceUnlifted bufB))) `plusPtr` off)
+
+-- ----------------------------------------------------------------------------
+-- Threaded RTS implementation of threadDelay
+
+-- | Suspends the current thread for a given number of microseconds
+-- (GHC only).
+--
+-- There is no guarantee that the thread will be rescheduled promptly
+-- when the delay has expired, but the thread will never continue to
+-- run /earlier/ than specified.
+--
+threadDelay :: Int -> IO ()
+threadDelay time
+ | threaded = waitForDelayEvent time
+ | otherwise = IO $ \s ->
+ case time of { I# time# ->
+ case delay# time# s of { s' -> (# s', () #)
+ }}
+
+-- | Set the value of returned TVar to True after a given number of
+-- microseconds. The caveats associated with threadDelay also apply.
+--
+registerDelay :: Int -> IO (TVar Bool)
+registerDelay usecs
+ | threaded = waitForDelayEventSTM usecs
+ | otherwise = errorWithoutStackTrace "registerDelay: requires -threaded"
+
+foreign import ccall unsafe "rtsSupportsBoundThreads" threaded :: Bool
+
+waitForDelayEvent :: Int -> IO ()
+waitForDelayEvent usecs = do
+ m <- newEmptyMVar
+ target <- calculateTarget usecs
+ _ <- atomicModifyIORef'_ pendingDelays (\xs -> Delay target m : xs)
+ prodServiceThread
+ takeMVar m
+
+-- Delays for use in STM
+waitForDelayEventSTM :: Int -> IO (TVar Bool)
+waitForDelayEventSTM usecs = do
+ t <- atomically $ newTVar False
+ target <- calculateTarget usecs
+ _ <- atomicModifyIORef'_ pendingDelays (\xs -> DelaySTM target t : xs)
+ prodServiceThread
+ return t
+
+calculateTarget :: Int -> IO USecs
+calculateTarget usecs = do
+ now <- getMonotonicUSec
+ return $ now + (fromIntegral usecs)
+
+data DelayReq
+ = Delay {-# UNPACK #-} !USecs {-# UNPACK #-} !(MVar ())
+ | DelaySTM {-# UNPACK #-} !USecs {-# UNPACK #-} !(TVar Bool)
+
+{-# NOINLINE pendingDelays #-}
+pendingDelays :: IORef [DelayReq]
+pendingDelays = unsafePerformIO $ do
+ m <- newIORef []
+ sharedCAF m getOrSetGHCConcWindowsPendingDelaysStore
+
+foreign import ccall unsafe "getOrSetGHCConcWindowsPendingDelaysStore"
+ getOrSetGHCConcWindowsPendingDelaysStore :: Ptr a -> IO (Ptr a)
+
+{-# NOINLINE ioManagerThread #-}
+ioManagerThread :: MVar (Maybe ThreadId)
+ioManagerThread = unsafePerformIO $ do
+ m <- newMVar Nothing
+ sharedCAF m getOrSetGHCConcWindowsIOManagerThreadStore
+
+foreign import ccall unsafe "getOrSetGHCConcWindowsIOManagerThreadStore"
+ getOrSetGHCConcWindowsIOManagerThreadStore :: Ptr a -> IO (Ptr a)
+
+ensureIOManagerIsRunning :: IO ()
+ensureIOManagerIsRunning
+ | threaded = startIOManagerThread
+ | otherwise = return ()
+
+interruptIOManager :: IO ()
+interruptIOManager = return ()
+
+startIOManagerThread :: IO ()
+startIOManagerThread = do
+ modifyMVar_ ioManagerThread $ \old -> do
+ let create = do t <- forkIO ioManager;
+ labelThread t "IOManagerThread";
+ return (Just t)
+ case old of
+ Nothing -> create
+ Just t -> do
+ s <- threadStatus t
+ case s of
+ ThreadFinished -> create
+ ThreadDied -> create
+ _other -> return (Just t)
+
+insertDelay :: DelayReq -> [DelayReq] -> [DelayReq]
+insertDelay d [] = [d]
+insertDelay d1 ds@(d2 : rest)
+ | delayTime d1 <= delayTime d2 = d1 : ds
+ | otherwise = d2 : insertDelay d1 rest
+
+delayTime :: DelayReq -> USecs
+delayTime (Delay t _) = t
+delayTime (DelaySTM t _) = t
+
+type USecs = Word64
+type NSecs = Word64
+
+foreign import ccall unsafe "getMonotonicNSec"
+ getMonotonicNSec :: IO NSecs
+
+getMonotonicUSec :: IO USecs
+getMonotonicUSec = fmap (`div` 1000) getMonotonicNSec
+
+{-# NOINLINE prodding #-}
+prodding :: IORef Bool
+prodding = unsafePerformIO $ do
+ r <- newIORef False
+ sharedCAF r getOrSetGHCConcWindowsProddingStore
+
+foreign import ccall unsafe "getOrSetGHCConcWindowsProddingStore"
+ getOrSetGHCConcWindowsProddingStore :: Ptr a -> IO (Ptr a)
+
+prodServiceThread :: IO ()
+prodServiceThread = do
+ -- NB. use atomicSwapIORef here, otherwise there are race
+ -- conditions in which prodding is left at True but the server is
+ -- blocked in select().
+ was_set <- atomicSwapIORef prodding True
+ when (not was_set) wakeupIOManager
+
+-- ----------------------------------------------------------------------------
+-- Windows IO manager thread
+
+ioManager :: IO ()
+ioManager = do
+ wakeup <- c_getIOManagerEvent
+ service_loop wakeup []
+
+service_loop :: HANDLE -- read end of pipe
+ -> [DelayReq] -- current delay requests
+ -> IO ()
+
+service_loop wakeup old_delays = do
+ -- pick up new delay requests
+ new_delays <- atomicSwapIORef pendingDelays []
+ let delays = foldr insertDelay old_delays new_delays
+
+ now <- getMonotonicUSec
+ (delays', timeout) <- getDelay now delays
+
+ r <- c_WaitForSingleObject wakeup timeout
+ case r of
+ 0xffffffff -> do throwGetLastError "service_loop"
+ 0 -> do
+ r2 <- c_readIOManagerEvent
+ exit <-
+ case r2 of
+ _ | r2 == io_MANAGER_WAKEUP -> return False
+ _ | r2 == io_MANAGER_DIE -> return True
+ 0 -> return False -- spurious wakeup
+ _ -> do start_console_handler (r2 `shiftR` 1); return False
+ when (not exit) $ service_cont wakeup delays'
+
+ _other -> service_cont wakeup delays' -- probably timeout
+
+service_cont :: HANDLE -> [DelayReq] -> IO ()
+service_cont wakeup delays = do
+ _ <- atomicSwapIORef prodding False
+ service_loop wakeup delays
+
+wakeupIOManager :: IO ()
+wakeupIOManager = c_sendIOManagerEvent io_MANAGER_WAKEUP
+
+-- Walk the queue of pending delays, waking up any that have passed
+-- and return the smallest delay to wait for. The queue of pending
+-- delays is kept ordered.
+getDelay :: USecs -> [DelayReq] -> IO ([DelayReq], DWORD)
+getDelay _ [] = return ([], iNFINITE)
+getDelay now all@(d : rest)
+ = case d of
+ Delay time m | now >= time -> do
+ putMVar m ()
+ getDelay now rest
+ DelaySTM time t | now >= time -> do
+ atomically $ writeTVar t True
+ getDelay now rest
+ _otherwise ->
+ -- delay is in millisecs for WaitForSingleObject
+ let micro_seconds = delayTime d - now
+ milli_seconds = (micro_seconds + 999) `div` 1000
+ in return (all, fromIntegral milli_seconds)
+
+foreign import ccall unsafe "getIOManagerEvent" -- in the RTS (ThrIOManager.c)
+ c_getIOManagerEvent :: IO HANDLE
+
+foreign import ccall unsafe "readIOManagerEvent" -- in the RTS (ThrIOManager.c)
+ c_readIOManagerEvent :: IO Word32
+
+foreign import ccall unsafe "sendIOManagerEvent" -- in the RTS (ThrIOManager.c)
+ c_sendIOManagerEvent :: Word32 -> IO ()
+
+foreign import WINDOWS_CCONV "WaitForSingleObject"
+ c_WaitForSingleObject :: HANDLE -> DWORD -> IO DWORD
+
diff --git a/libraries/base/GHC/Conc/POSIX/Const.hsc b/libraries/base/GHC/Conc/POSIX/Const.hsc
new file mode 100644
index 0000000000..b9c59bb439
--- /dev/null
+++ b/libraries/base/GHC/Conc/POSIX/Const.hsc
@@ -0,0 +1,29 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.Conc.POSIX.Const
+-- Copyright : (c) The University of Glasgow, 2019
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : cvs-ghc@haskell.org
+-- Stability : internal
+-- Portability : non-portable (GHC extensions)
+--
+-- Constants shared with the rts, GHC.Conc.POSIX uses MagicHash which confuses
+-- hsc2hs so these are moved to a new module.
+--
+-----------------------------------------------------------------------------
+
+-- #not-home
+module GHC.Conc.POSIX.Const where
+
+import Data.Word
+
+#include <Rts.h>
+
+io_MANAGER_WAKEUP, io_MANAGER_DIE :: Word32
+io_MANAGER_WAKEUP = #{const IO_MANAGER_WAKEUP}
+io_MANAGER_DIE = #{const IO_MANAGER_DIE}
diff --git a/libraries/base/GHC/Conc/Sync.hs b/libraries/base/GHC/Conc/Sync.hs
index d6ffbc2de9..a15e91f956 100644
--- a/libraries/base/GHC/Conc/Sync.hs
+++ b/libraries/base/GHC/Conc/Sync.hs
@@ -8,7 +8,6 @@
, StandaloneDeriving
, RankNTypes
#-}
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# OPTIONS_HADDOCK not-home #-}
-----------------------------------------------------------------------------
@@ -33,6 +32,7 @@
-- #not-home
module GHC.Conc.Sync
( ThreadId(..)
+ , showThreadId
-- * Forking and suchlike
, forkIO
@@ -102,7 +102,7 @@ import Data.Maybe
import GHC.Base
import {-# SOURCE #-} GHC.IO.Handle ( hFlush )
-import {-# SOURCE #-} GHC.IO.Handle.FD ( stdout )
+import {-# SOURCE #-} GHC.IO.StdHandles ( stdout )
import GHC.Int
import GHC.IO
import GHC.IO.Encoding.UTF8
@@ -151,6 +151,9 @@ instance Show ThreadId where
showString "ThreadId " .
showsPrec d (getThreadId (id2TSO t))
+showThreadId :: ThreadId -> String
+showThreadId = show
+
foreign import ccall unsafe "rts_getThreadId" getThreadId :: ThreadId# -> CInt
id2TSO :: ThreadId -> ThreadId#
@@ -538,6 +541,8 @@ data BlockReason
-- ^blocked in 'retry' in an STM transaction
| BlockedOnForeignCall
-- ^currently in a foreign call
+ | BlockedOnIOCompletion
+ -- ^currently blocked on an I/O Completion port
| BlockedOnOther
-- ^blocked on some other resource. Without @-threaded@,
-- I\/O and 'Control.Concurrent.threadDelay' show up as
@@ -576,6 +581,7 @@ threadStatus (ThreadId t) = IO $ \s ->
mk_stat 11 = ThreadBlocked BlockedOnForeignCall
mk_stat 12 = ThreadBlocked BlockedOnException
mk_stat 14 = ThreadBlocked BlockedOnMVar -- possibly: BlockedOnMVarRead
+ mk_stat 15 = ThreadBlocked BlockedOnIOCompletion
-- NB. these are hardcoded in rts/PrimOps.cmm
mk_stat 16 = ThreadFinished
mk_stat 17 = ThreadDied
diff --git a/libraries/base/GHC/Conc/Sync.hs-boot b/libraries/base/GHC/Conc/Sync.hs-boot
new file mode 100644
index 0000000000..07b4ef05ab
--- /dev/null
+++ b/libraries/base/GHC/Conc/Sync.hs-boot
@@ -0,0 +1,72 @@
+{-# LANGUAGE MagicHash, NoImplicitPrelude #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.Conc.Sync [boot]
+-- Copyright : (c) The University of Glasgow, 1994-2002
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : cvs-ghc@haskell.org
+-- Stability : internal
+-- Portability : non-portable (GHC extensions)
+--
+-- Basic concurrency stuff.
+--
+-----------------------------------------------------------------------------
+
+module GHC.Conc.Sync
+ ( forkIO,
+ TVar(..),
+ ThreadId(..),
+ myThreadId,
+ showThreadId,
+ ThreadStatus(..),
+ threadStatus,
+ sharedCAF
+ ) where
+
+import GHC.Base
+import GHC.Ptr
+
+forkIO :: IO () -> IO ThreadId
+
+data ThreadId = ThreadId ThreadId#
+data TVar a = TVar (TVar# RealWorld a)
+
+data BlockReason
+ = BlockedOnMVar
+ -- ^blocked on 'MVar'
+ {- possibly (see 'threadstatus' below):
+ | BlockedOnMVarRead
+ -- ^blocked on reading an empty 'MVar'
+ -}
+ | BlockedOnBlackHole
+ -- ^blocked on a computation in progress by another thread
+ | BlockedOnException
+ -- ^blocked in 'throwTo'
+ | BlockedOnSTM
+ -- ^blocked in 'retry' in an STM transaction
+ | BlockedOnForeignCall
+ -- ^currently in a foreign call
+ | BlockedOnIOCompletion
+ -- ^currently blocked on an I/O Completion port
+ | BlockedOnOther
+ -- ^blocked on some other resource. Without @-threaded@,
+ -- I\/O and 'threadDelay' show up as 'BlockedOnOther', with @-threaded@
+ -- they show up as 'BlockedOnMVar'.
+
+data ThreadStatus
+ = ThreadRunning
+ -- ^the thread is currently runnable or running
+ | ThreadFinished
+ -- ^the thread has finished
+ | ThreadBlocked BlockReason
+ -- ^the thread is blocked on some resource
+ | ThreadDied
+ -- ^the thread received an uncaught exception
+
+myThreadId :: IO ThreadId
+showThreadId :: ThreadId -> String
+threadStatus :: ThreadId -> IO ThreadStatus
+sharedCAF :: a -> (Ptr a -> IO (Ptr a)) -> IO a
diff --git a/libraries/base/GHC/Conc/WinIO.hs b/libraries/base/GHC/Conc/WinIO.hs
new file mode 100644
index 0000000000..d0325910b1
--- /dev/null
+++ b/libraries/base/GHC/Conc/WinIO.hs
@@ -0,0 +1,28 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE CPP, NoImplicitPrelude, MagicHash, UnboxedTuples #-}
+{-# OPTIONS_HADDOCK not-home #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.Conc.WinIO
+-- Copyright : (c) The University of Glasgow, 1994-2002
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : cvs-ghc@haskell.org
+-- Stability : internal
+-- Portability : non-portable (GHC extensions)
+--
+-- Windows I/O Completion Port interface to the one defined in
+-- GHC.Event.Windows.
+--
+-- This module is an indirection to keep things in the same structure as before
+-- but also to keep the new code where the actual I/O manager is. As such it
+-- just re-exports GHC.Event.Windows.Thread
+--
+-----------------------------------------------------------------------------
+
+-- #not-home
+module GHC.Conc.WinIO
+ ( module GHC.Event.Windows.Thread ) where
+
+import GHC.Event.Windows.Thread
diff --git a/libraries/base/GHC/Conc/Windows.hs b/libraries/base/GHC/Conc/Windows.hs
index 53f22d6d50..34131cc416 100644
--- a/libraries/base/GHC/Conc/Windows.hs
+++ b/libraries/base/GHC/Conc/Windows.hs
@@ -1,6 +1,5 @@
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE CPP, NoImplicitPrelude, MagicHash, UnboxedTuples #-}
-{-# OPTIONS_GHC -Wno-missing-signatures #-}
{-# OPTIONS_HADDOCK not-home #-}
-----------------------------------------------------------------------------
@@ -13,13 +12,15 @@
-- Stability : internal
-- Portability : non-portable (GHC extensions)
--
--- Windows I/O manager
+-- Windows I/O manager interfaces. Depending on which I/O Subsystem is used
+-- requests will be routed to different places.
--
-----------------------------------------------------------------------------
-- #not-home
module GHC.Conc.Windows
( ensureIOManagerIsRunning
+ , interruptIOManager
-- * Waiting
, threadDelay
@@ -33,37 +34,22 @@ module GHC.Conc.Windows
, asyncReadBA
, asyncWriteBA
- , ConsoleEvent(..)
- , win32ConsoleHandler
- , toWin32ConsoleEvent
+ -- * Console event handler
+ , module GHC.Event.Windows.ConsoleEvent
) where
-import Data.Bits (shiftR)
+
+#include "windows_cconv.h"
+
import GHC.Base
import GHC.Conc.Sync
-import GHC.Enum (Enum)
-import GHC.IO (unsafePerformIO)
-import GHC.IORef
-import GHC.MVar
-import GHC.Num (Num(..))
+import qualified GHC.Conc.POSIX as POSIX
+import qualified GHC.Conc.WinIO as WINIO
+import GHC.Event.Windows.ConsoleEvent
+import GHC.IO.SubSystem ((<!>))
import GHC.Ptr
-import GHC.Read (Read)
-import GHC.Real (div, fromIntegral)
-import GHC.Show (Show)
-import GHC.Word (Word32, Word64)
-import GHC.Windows
import Unsafe.Coerce ( unsafeCoerceUnlifted )
-#if defined(mingw32_HOST_OS)
-# if defined(i386_HOST_ARCH)
-# define WINDOWS_CCONV stdcall
-# elif defined(x86_64_HOST_ARCH)
-# define WINDOWS_CCONV ccall
-# else
-# error Unknown mingw32 arch
-# endif
-#endif
-
-- ----------------------------------------------------------------------------
-- Thread waiting
@@ -111,232 +97,19 @@ asyncWriteBA fd isSock len off bufB =
-- run /earlier/ than specified.
--
threadDelay :: Int -> IO ()
-threadDelay time
- | threaded = waitForDelayEvent time
- | otherwise = IO $ \s ->
- case time of { I# time# ->
- case delay# time# s of { s' -> (# s', () #)
- }}
+threadDelay = POSIX.threadDelay <!> WINIO.threadDelay
-- | Set the value of returned TVar to True after a given number of
-- microseconds. The caveats associated with threadDelay also apply.
--
registerDelay :: Int -> IO (TVar Bool)
-registerDelay usecs
- | threaded = waitForDelayEventSTM usecs
- | otherwise = errorWithoutStackTrace "registerDelay: requires -threaded"
-
-foreign import ccall unsafe "rtsSupportsBoundThreads" threaded :: Bool
-
-waitForDelayEvent :: Int -> IO ()
-waitForDelayEvent usecs = do
- m <- newEmptyMVar
- target <- calculateTarget usecs
- _ <- atomicModifyIORef'_ pendingDelays (\xs -> Delay target m : xs)
- prodServiceThread
- takeMVar m
-
--- Delays for use in STM
-waitForDelayEventSTM :: Int -> IO (TVar Bool)
-waitForDelayEventSTM usecs = do
- t <- atomically $ newTVar False
- target <- calculateTarget usecs
- _ <- atomicModifyIORef'_ pendingDelays (\xs -> DelaySTM target t : xs)
- prodServiceThread
- return t
-
-calculateTarget :: Int -> IO USecs
-calculateTarget usecs = do
- now <- getMonotonicUSec
- return $ now + (fromIntegral usecs)
-
-data DelayReq
- = Delay {-# UNPACK #-} !USecs {-# UNPACK #-} !(MVar ())
- | DelaySTM {-# UNPACK #-} !USecs {-# UNPACK #-} !(TVar Bool)
-
-{-# NOINLINE pendingDelays #-}
-pendingDelays :: IORef [DelayReq]
-pendingDelays = unsafePerformIO $ do
- m <- newIORef []
- sharedCAF m getOrSetGHCConcWindowsPendingDelaysStore
-
-foreign import ccall unsafe "getOrSetGHCConcWindowsPendingDelaysStore"
- getOrSetGHCConcWindowsPendingDelaysStore :: Ptr a -> IO (Ptr a)
-
-{-# NOINLINE ioManagerThread #-}
-ioManagerThread :: MVar (Maybe ThreadId)
-ioManagerThread = unsafePerformIO $ do
- m <- newMVar Nothing
- sharedCAF m getOrSetGHCConcWindowsIOManagerThreadStore
-
-foreign import ccall unsafe "getOrSetGHCConcWindowsIOManagerThreadStore"
- getOrSetGHCConcWindowsIOManagerThreadStore :: Ptr a -> IO (Ptr a)
+registerDelay = POSIX.registerDelay <!> WINIO.registerDelay
ensureIOManagerIsRunning :: IO ()
-ensureIOManagerIsRunning
- | threaded = startIOManagerThread
- | otherwise = return ()
-
-startIOManagerThread :: IO ()
-startIOManagerThread = do
- modifyMVar_ ioManagerThread $ \old -> do
- let create = do t <- forkIO ioManager; return (Just t)
- case old of
- Nothing -> create
- Just t -> do
- s <- threadStatus t
- case s of
- ThreadFinished -> create
- ThreadDied -> create
- _other -> return (Just t)
-
-insertDelay :: DelayReq -> [DelayReq] -> [DelayReq]
-insertDelay d [] = [d]
-insertDelay d1 ds@(d2 : rest)
- | delayTime d1 <= delayTime d2 = d1 : ds
- | otherwise = d2 : insertDelay d1 rest
-
-delayTime :: DelayReq -> USecs
-delayTime (Delay t _) = t
-delayTime (DelaySTM t _) = t
-
-type USecs = Word64
-type NSecs = Word64
-
-foreign import ccall unsafe "getMonotonicNSec"
- getMonotonicNSec :: IO NSecs
-
-getMonotonicUSec :: IO USecs
-getMonotonicUSec = fmap (`div` 1000) getMonotonicNSec
-
-{-# NOINLINE prodding #-}
-prodding :: IORef Bool
-prodding = unsafePerformIO $ do
- r <- newIORef False
- sharedCAF r getOrSetGHCConcWindowsProddingStore
-
-foreign import ccall unsafe "getOrSetGHCConcWindowsProddingStore"
- getOrSetGHCConcWindowsProddingStore :: Ptr a -> IO (Ptr a)
-
-prodServiceThread :: IO ()
-prodServiceThread = do
- -- NB. use atomicSwapIORef here, otherwise there are race
- -- conditions in which prodding is left at True but the server is
- -- blocked in select().
- was_set <- atomicSwapIORef prodding True
- when (not was_set) wakeupIOManager
-
--- ----------------------------------------------------------------------------
--- Windows IO manager thread
-
-ioManager :: IO ()
-ioManager = do
- wakeup <- c_getIOManagerEvent
- service_loop wakeup []
-
-service_loop :: HANDLE -- read end of pipe
- -> [DelayReq] -- current delay requests
- -> IO ()
-
-service_loop wakeup old_delays = do
- -- pick up new delay requests
- new_delays <- atomicSwapIORef pendingDelays []
- let delays = foldr insertDelay old_delays new_delays
-
- now <- getMonotonicUSec
- (delays', timeout) <- getDelay now delays
-
- r <- c_WaitForSingleObject wakeup timeout
- case r of
- 0xffffffff -> do throwGetLastError "service_loop"
- 0 -> do
- r2 <- c_readIOManagerEvent
- exit <-
- case r2 of
- _ | r2 == io_MANAGER_WAKEUP -> return False
- _ | r2 == io_MANAGER_DIE -> return True
- 0 -> return False -- spurious wakeup
- _ -> do start_console_handler (r2 `shiftR` 1); return False
- when (not exit) $ service_cont wakeup delays'
-
- _other -> service_cont wakeup delays' -- probably timeout
-
-service_cont :: HANDLE -> [DelayReq] -> IO ()
-service_cont wakeup delays = do
- _ <- atomicSwapIORef prodding False
- service_loop wakeup delays
-
--- must agree with rts/win32/ThrIOManager.c
-io_MANAGER_WAKEUP, io_MANAGER_DIE :: Word32
-io_MANAGER_WAKEUP = 0xffffffff
-io_MANAGER_DIE = 0xfffffffe
-
-data ConsoleEvent
- = ControlC
- | Break
- | Close
- -- these are sent to Services only.
- | Logoff
- | Shutdown
- deriving ( Eq -- ^ @since 4.3.0.0
- , Ord -- ^ @since 4.3.0.0
- , Enum -- ^ @since 4.3.0.0
- , Show -- ^ @since 4.3.0.0
- , Read -- ^ @since 4.3.0.0
- )
-
-start_console_handler :: Word32 -> IO ()
-start_console_handler r =
- case toWin32ConsoleEvent r of
- Just x -> withMVar win32ConsoleHandler $ \handler -> do
- _ <- forkIO (handler x)
- return ()
- Nothing -> return ()
-
-toWin32ConsoleEvent :: (Eq a, Num a) => a -> Maybe ConsoleEvent
-toWin32ConsoleEvent ev =
- case ev of
- 0 {- CTRL_C_EVENT-} -> Just ControlC
- 1 {- CTRL_BREAK_EVENT-} -> Just Break
- 2 {- CTRL_CLOSE_EVENT-} -> Just Close
- 5 {- CTRL_LOGOFF_EVENT-} -> Just Logoff
- 6 {- CTRL_SHUTDOWN_EVENT-} -> Just Shutdown
- _ -> Nothing
-
-win32ConsoleHandler :: MVar (ConsoleEvent -> IO ())
-win32ConsoleHandler = unsafePerformIO (newMVar (errorWithoutStackTrace "win32ConsoleHandler"))
-
-wakeupIOManager :: IO ()
-wakeupIOManager = c_sendIOManagerEvent io_MANAGER_WAKEUP
-
--- Walk the queue of pending delays, waking up any that have passed
--- and return the smallest delay to wait for. The queue of pending
--- delays is kept ordered.
-getDelay :: USecs -> [DelayReq] -> IO ([DelayReq], DWORD)
-getDelay _ [] = return ([], iNFINITE)
-getDelay now all@(d : rest)
- = case d of
- Delay time m | now >= time -> do
- putMVar m ()
- getDelay now rest
- DelaySTM time t | now >= time -> do
- atomically $ writeTVar t True
- getDelay now rest
- _otherwise ->
- -- delay is in millisecs for WaitForSingleObject
- let micro_seconds = delayTime d - now
- milli_seconds = (micro_seconds + 999) `div` 1000
- in return (all, fromIntegral milli_seconds)
-
-foreign import ccall unsafe "getIOManagerEvent" -- in the RTS (ThrIOManager.c)
- c_getIOManagerEvent :: IO HANDLE
-
-foreign import ccall unsafe "readIOManagerEvent" -- in the RTS (ThrIOManager.c)
- c_readIOManagerEvent :: IO Word32
+ensureIOManagerIsRunning = POSIX.ensureIOManagerIsRunning
+ <!> WINIO.ensureIOManagerIsRunning
-foreign import ccall unsafe "sendIOManagerEvent" -- in the RTS (ThrIOManager.c)
- c_sendIOManagerEvent :: Word32 -> IO ()
+interruptIOManager :: IO ()
+interruptIOManager = POSIX.interruptIOManager <!> WINIO.interruptIOManager
-foreign import WINDOWS_CCONV "WaitForSingleObject"
- c_WaitForSingleObject :: HANDLE -> DWORD -> IO DWORD
diff --git a/libraries/base/GHC/ConsoleHandler.hs b/libraries/base/GHC/ConsoleHandler.hsc
index 8579c22739..1fc26f0563 100644
--- a/libraries/base/GHC/ConsoleHandler.hs
+++ b/libraries/base/GHC/ConsoleHandler.hsc
@@ -27,9 +27,9 @@ import GHC.Base () -- dummy dependency
( Handler(..)
, installHandler
, ConsoleEvent(..)
- , flushConsole
) where
+#include <windows.h>
{-
#include "rts/Signals.h"
@@ -44,13 +44,8 @@ Note: this #include is inside a Haskell comment
import GHC.Base
import Foreign
import Foreign.C
-import GHC.IO.FD
-import GHC.IO.Exception
-import GHC.IO.Handle.Types
-import GHC.IO.Handle.Internals
import GHC.Conc
import Control.Concurrent.MVar
-import Data.Typeable
data Handler
= Default
@@ -122,11 +117,11 @@ installHandler handler
where
fromConsoleEvent ev =
case ev of
- ControlC -> 0 {- CTRL_C_EVENT-}
- Break -> 1 {- CTRL_BREAK_EVENT-}
- Close -> 2 {- CTRL_CLOSE_EVENT-}
- Logoff -> 5 {- CTRL_LOGOFF_EVENT-}
- Shutdown -> 6 {- CTRL_SHUTDOWN_EVENT-}
+ ControlC -> #{const CTRL_C_EVENT }
+ Break -> #{const CTRL_BREAK_EVENT }
+ Close -> #{const CTRL_CLOSE_EVENT }
+ Logoff -> #{const CTRL_LOGOFF_EVENT }
+ Shutdown -> #{const CTRL_SHUTDOWN_EVENT}
toHandler hdlr ev = do
case toWin32ConsoleEvent ev of
@@ -144,19 +139,4 @@ foreign import ccall unsafe "RtsExternal.h rts_InstallConsoleEvent"
foreign import ccall unsafe "RtsExternal.h rts_ConsoleHandlerDone"
rts_ConsoleHandlerDone :: CInt -> IO ()
-
-flushConsole :: Handle -> IO ()
-flushConsole h =
- wantReadableHandle_ "flushConsole" h $ \ Handle__{haDevice=dev} ->
- case cast dev of
- Nothing -> ioException $
- IOError (Just h) IllegalOperation "flushConsole"
- "handle is not a file descriptor" Nothing Nothing
- Just fd -> do
- throwErrnoIfMinus1Retry_ "flushConsole" $
- flush_console_fd (fdFD fd)
-
-foreign import ccall unsafe "consUtils.h flush_input_console__"
- flush_console_fd :: CInt -> IO CInt
-
#endif /* mingw32_HOST_OS */
diff --git a/libraries/base/GHC/Event/Array.hs b/libraries/base/GHC/Event/Array.hs
index 903f7c0c23..9558ece576 100644
--- a/libraries/base/GHC/Event/Array.hs
+++ b/libraries/base/GHC/Event/Array.hs
@@ -19,6 +19,7 @@ module GHC.Event.Array
, removeAt
, snoc
, unsafeLoad
+ , unsafeCopyFromBuffer
, unsafeRead
, unsafeWrite
, useAsPtr
@@ -139,6 +140,16 @@ unsafeLoad (Array ref) load = do
writeIORef ref (AC es len' cap)
return len'
+-- | Reads n elements from the pointer and copies them
+-- into the array.
+unsafeCopyFromBuffer :: Array a -> Ptr a -> Int -> IO ()
+unsafeCopyFromBuffer (Array ref) sptr n =
+ readIORef ref >>= \(AC es _ cap) ->
+ CHECK_BOUNDS("unsafeCopyFromBuffer", cap, n-1)
+ withForeignPtr es $ \pdest -> do
+ _ <- memcpy pdest sptr (fromIntegral n)
+ writeIORef ref (AC es n cap)
+
ensureCapacity :: Storable a => Array a -> Int -> IO ()
ensureCapacity (Array ref) c = do
ac@(AC _ _ cap) <- readIORef ref
diff --git a/libraries/base/GHC/Event/IntTable.hs b/libraries/base/GHC/Event/IntTable.hs
index 56993d18b3..a821cfdf07 100644
--- a/libraries/base/GHC/Event/IntTable.hs
+++ b/libraries/base/GHC/Event/IntTable.hs
@@ -143,3 +143,4 @@ updateWith f k (IntTable ref) = do
size <- peek ptr
poke ptr (size - 1)
return oldVal
+
diff --git a/libraries/base/GHC/Event/Internal.hs b/libraries/base/GHC/Event/Internal.hs
index 5778c6f3fe..2ed8d2e66c 100644
--- a/libraries/base/GHC/Event/Internal.hs
+++ b/libraries/base/GHC/Event/Internal.hs
@@ -10,150 +10,16 @@ module GHC.Event.Internal
, poll
, modifyFd
, modifyFdOnce
- -- * Event type
- , Event
- , evtRead
- , evtWrite
- , evtClose
- , eventIs
- -- * Lifetimes
- , Lifetime(..)
- , EventLifetime
- , eventLifetime
- , elLifetime
- , elEvent
- -- * Timeout type
- , Timeout(..)
+ , module GHC.Event.Internal.Types
-- * Helpers
, throwErrnoIfMinus1NoRetry
) where
-import Data.Bits ((.|.), (.&.))
-import Data.OldList (foldl', filter, intercalate, null)
import Foreign.C.Error (eINTR, getErrno, throwErrno)
import System.Posix.Types (Fd)
import GHC.Base
-import GHC.Word (Word64)
import GHC.Num (Num(..))
-import GHC.Show (Show(..))
-import Data.Semigroup.Internal (stimesMonoid)
-
--- | An I\/O event.
-newtype Event = Event Int
- deriving Eq -- ^ @since 4.4.0.0
-
-evtNothing :: Event
-evtNothing = Event 0
-{-# INLINE evtNothing #-}
-
--- | Data is available to be read.
-evtRead :: Event
-evtRead = Event 1
-{-# INLINE evtRead #-}
-
--- | The file descriptor is ready to accept a write.
-evtWrite :: Event
-evtWrite = Event 2
-{-# INLINE evtWrite #-}
-
--- | Another thread closed the file descriptor.
-evtClose :: Event
-evtClose = Event 4
-{-# INLINE evtClose #-}
-
-eventIs :: Event -> Event -> Bool
-eventIs (Event a) (Event b) = a .&. b /= 0
-
--- | @since 4.4.0.0
-instance Show Event where
- show e = '[' : (intercalate "," . filter (not . null) $
- [evtRead `so` "evtRead",
- evtWrite `so` "evtWrite",
- evtClose `so` "evtClose"]) ++ "]"
- where ev `so` disp | e `eventIs` ev = disp
- | otherwise = ""
-
--- | @since 4.10.0.0
-instance Semigroup Event where
- (<>) = evtCombine
- stimes = stimesMonoid
-
--- | @since 4.4.0.0
-instance Monoid Event where
- mempty = evtNothing
- mconcat = evtConcat
-
-evtCombine :: Event -> Event -> Event
-evtCombine (Event a) (Event b) = Event (a .|. b)
-{-# INLINE evtCombine #-}
-
-evtConcat :: [Event] -> Event
-evtConcat = foldl' evtCombine evtNothing
-{-# INLINE evtConcat #-}
-
--- | The lifetime of an event registration.
---
--- @since 4.8.1.0
-data Lifetime = OneShot -- ^ the registration will be active for only one
- -- event
- | MultiShot -- ^ the registration will trigger multiple times
- deriving ( Show -- ^ @since 4.8.1.0
- , Eq -- ^ @since 4.8.1.0
- )
-
--- | The longer of two lifetimes.
-elSupremum :: Lifetime -> Lifetime -> Lifetime
-elSupremum OneShot OneShot = OneShot
-elSupremum _ _ = MultiShot
-{-# INLINE elSupremum #-}
-
--- | @since 4.10.0.0
-instance Semigroup Lifetime where
- (<>) = elSupremum
- stimes = stimesMonoid
-
--- | @mappend@ takes the longer of two lifetimes.
---
--- @since 4.8.0.0
-instance Monoid Lifetime where
- mempty = OneShot
-
--- | A pair of an event and lifetime
---
--- Here we encode the event in the bottom three bits and the lifetime
--- in the fourth bit.
-newtype EventLifetime = EL Int
- deriving ( Show -- ^ @since 4.8.0.0
- , Eq -- ^ @since 4.8.0.0
- )
-
--- | @since 4.11.0.0
-instance Semigroup EventLifetime where
- EL a <> EL b = EL (a .|. b)
-
--- | @since 4.8.0.0
-instance Monoid EventLifetime where
- mempty = EL 0
-
-eventLifetime :: Event -> Lifetime -> EventLifetime
-eventLifetime (Event e) l = EL (e .|. lifetimeBit l)
- where
- lifetimeBit OneShot = 0
- lifetimeBit MultiShot = 8
-{-# INLINE eventLifetime #-}
-
-elLifetime :: EventLifetime -> Lifetime
-elLifetime (EL x) = if x .&. 8 == 0 then OneShot else MultiShot
-{-# INLINE elLifetime #-}
-
-elEvent :: EventLifetime -> Event
-elEvent (EL x) = Event (x .&. 0x7)
-{-# INLINE elEvent #-}
-
--- | A type alias for timeouts, specified in nanoseconds.
-data Timeout = Timeout {-# UNPACK #-} !Word64
- | Forever
- deriving Show -- ^ @since 4.4.0.0
+import GHC.Event.Internal.Types
-- | Event notification backend.
data Backend = forall a. Backend {
diff --git a/libraries/base/GHC/Event/Internal/Types.hs b/libraries/base/GHC/Event/Internal/Types.hs
new file mode 100644
index 0000000000..e02ff36b61
--- /dev/null
+++ b/libraries/base/GHC/Event/Internal/Types.hs
@@ -0,0 +1,160 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+-------------------------------------------------------------------------------
+-- |
+-- Module : GHC.IO.Types
+-- Copyright : (c) Tamar Christina 2018
+-- License : BSD-style (see the file libraries/base/LICENSE)
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : experimental
+-- Portability : non-portable
+--
+-- Abstraction over C Handle types for GHC, Unix wants FD (CInt) while Windows
+-- Wants Handle (CIntPtr), so we abstract over them here.
+--
+-------------------------------------------------------------------------------
+
+module GHC.Event.Internal.Types
+ (
+ -- * Event type
+ Event
+ , evtRead
+ , evtWrite
+ , evtClose
+ , evtNothing
+ , eventIs
+ -- * Lifetimes
+ , Lifetime(..)
+ , EventLifetime
+ , eventLifetime
+ , elLifetime
+ , elEvent
+ -- * Timeout type
+ , Timeout(..)
+ ) where
+
+import Data.OldList (foldl', filter, intercalate, null)
+
+import Data.Bits ((.|.), (.&.))
+import Data.Semigroup.Internal (stimesMonoid)
+
+import GHC.Base
+import GHC.Show (Show(..))
+import GHC.Word (Word64)
+
+-- | An I\/O event.
+newtype Event = Event Int
+ deriving Eq -- ^ @since 4.4.0.0
+
+evtNothing :: Event
+evtNothing = Event 0
+{-# INLINE evtNothing #-}
+
+-- | Data is available to be read.
+evtRead :: Event
+evtRead = Event 1
+{-# INLINE evtRead #-}
+
+-- | The file descriptor is ready to accept a write.
+evtWrite :: Event
+evtWrite = Event 2
+{-# INLINE evtWrite #-}
+
+-- | Another thread closed the file descriptor.
+evtClose :: Event
+evtClose = Event 4
+{-# INLINE evtClose #-}
+
+eventIs :: Event -> Event -> Bool
+eventIs (Event a) (Event b) = a .&. b /= 0
+
+-- | @since 4.4.0.0
+instance Show Event where
+ show e = '[' : (intercalate "," . filter (not . null) $
+ [evtRead `so` "evtRead",
+ evtWrite `so` "evtWrite",
+ evtClose `so` "evtClose"]) ++ "]"
+ where ev `so` disp | e `eventIs` ev = disp
+ | otherwise = ""
+
+-- | @since 4.10.0.0
+instance Semigroup Event where
+ (<>) = evtCombine
+ stimes = stimesMonoid
+
+-- | @since 4.4.0.0
+instance Monoid Event where
+ mempty = evtNothing
+ mconcat = evtConcat
+
+evtCombine :: Event -> Event -> Event
+evtCombine (Event a) (Event b) = Event (a .|. b)
+{-# INLINE evtCombine #-}
+
+evtConcat :: [Event] -> Event
+evtConcat = foldl' evtCombine evtNothing
+{-# INLINE evtConcat #-}
+
+-- | The lifetime of an event registration.
+--
+-- @since 4.8.1.0
+data Lifetime = OneShot -- ^ the registration will be active for only one
+ -- event
+ | MultiShot -- ^ the registration will trigger multiple times
+ deriving ( Show -- ^ @since 4.8.1.0
+ , Eq -- ^ @since 4.8.1.0
+ )
+
+-- | The longer of two lifetimes.
+elSupremum :: Lifetime -> Lifetime -> Lifetime
+elSupremum OneShot OneShot = OneShot
+elSupremum _ _ = MultiShot
+{-# INLINE elSupremum #-}
+
+-- | @since 4.10.0.0
+instance Semigroup Lifetime where
+ (<>) = elSupremum
+ stimes = stimesMonoid
+
+-- | @mappend@ takes the longer of two lifetimes.
+--
+-- @since 4.8.0.0
+instance Monoid Lifetime where
+ mempty = OneShot
+
+-- | A pair of an event and lifetime
+--
+-- Here we encode the event in the bottom three bits and the lifetime
+-- in the fourth bit.
+newtype EventLifetime = EL Int
+ deriving ( Show -- ^ @since 4.8.0.0
+ , Eq -- ^ @since 4.8.0.0
+ )
+
+-- | @since 4.11.0.0
+instance Semigroup EventLifetime where
+ EL a <> EL b = EL (a .|. b)
+
+-- | @since 4.8.0.0
+instance Monoid EventLifetime where
+ mempty = EL 0
+
+eventLifetime :: Event -> Lifetime -> EventLifetime
+eventLifetime (Event e) l = EL (e .|. lifetimeBit l)
+ where
+ lifetimeBit OneShot = 0
+ lifetimeBit MultiShot = 8
+{-# INLINE eventLifetime #-}
+
+elLifetime :: EventLifetime -> Lifetime
+elLifetime (EL x) = if x .&. 8 == 0 then OneShot else MultiShot
+{-# INLINE elLifetime #-}
+
+elEvent :: EventLifetime -> Event
+elEvent (EL x) = Event (x .&. 0x7)
+{-# INLINE elEvent #-}
+
+-- | A type alias for timeouts, specified in nanoseconds.
+data Timeout = Timeout {-# UNPACK #-} !Word64
+ | Forever
+ deriving Show -- ^ @since 4.4.0.0
diff --git a/libraries/base/GHC/Event/Thread.hs b/libraries/base/GHC/Event/Thread.hs
index ad922d73f2..19b6cd4117 100644
--- a/libraries/base/GHC/Event/Thread.hs
+++ b/libraries/base/GHC/Event/Thread.hs
@@ -15,7 +15,7 @@ module GHC.Event.Thread
, registerDelay
, blockedOnBadFD -- used by RTS
) where
-
+-- TODO: Use new Windows I/O manager
import Control.Exception (finally, SomeException, toException)
import Data.Foldable (forM_, mapM_, sequence_)
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
diff --git a/libraries/base/GHC/Event/TimeOut.hs b/libraries/base/GHC/Event/TimeOut.hs
new file mode 100644
index 0000000000..7be0a4ebc4
--- /dev/null
+++ b/libraries/base/GHC/Event/TimeOut.hs
@@ -0,0 +1,40 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+-------------------------------------------------------------------------------
+-- |
+-- Module : GHC.Event.TimeOut
+-- Copyright : (c) Tamar Christina 2018
+-- License : BSD-style (see the file libraries/base/LICENSE)
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : experimental
+-- Portability : non-portable
+--
+-- Common Timer definitions shared between WinIO and RIO.
+--
+-------------------------------------------------------------------------------
+
+module GHC.Event.TimeOut where
+
+import GHC.IO
+import GHC.Base
+
+import qualified GHC.Event.PSQ as Q
+import GHC.Event.Unique (Unique)
+
+-- | A priority search queue, with timeouts as priorities.
+type TimeoutQueue = Q.PSQ TimeoutCallback
+
+-- |
+-- Warning: since the 'TimeoutCallback' is called from the I/O manager, it must
+-- not throw an exception or block for a long period of time. In particular,
+-- be wary of 'Control.Exception.throwTo' and 'Control.Concurrent.killThread':
+-- if the target thread is making a foreign call, these functions will block
+-- until the call completes.
+type TimeoutCallback = IO ()
+
+-- | An edit to apply to a 'TimeoutQueue'.
+type TimeoutEdit = TimeoutQueue -> TimeoutQueue
+
+-- | A timeout registration cookie.
+newtype TimeoutKey = TK Unique
+ deriving (Eq, Ord)
diff --git a/libraries/base/GHC/Event/TimerManager.hs b/libraries/base/GHC/Event/TimerManager.hs
index 946f2333bf..c6518d8cba 100644
--- a/libraries/base/GHC/Event/TimerManager.hs
+++ b/libraries/base/GHC/Event/TimerManager.hs
@@ -6,7 +6,7 @@
, TypeSynonymInstances
, FlexibleInstances
#-}
-
+-- TODO: use the new Windows IO manager
module GHC.Event.TimerManager
( -- * Types
TimerManager
@@ -51,7 +51,8 @@ import GHC.Real (quot, fromIntegral)
import GHC.Show (Show(..))
import GHC.Event.Control
import GHC.Event.Internal (Backend, Event, evtRead, Timeout(..))
-import GHC.Event.Unique (Unique, UniqueSource, newSource, newUnique)
+import GHC.Event.Unique (UniqueSource, newSource, newUnique)
+import GHC.Event.TimeOut
import System.Posix.Types (Fd)
import qualified GHC.Event.Internal as I
@@ -66,13 +67,6 @@ import qualified GHC.Event.Poll as Poll
------------------------------------------------------------------------
-- Types
--- | A timeout registration cookie.
-newtype TimeoutKey = TK Unique
- deriving Eq -- ^ @since 4.7.0.0
-
--- | Callback invoked on timeout events.
-type TimeoutCallback = IO ()
-
data State = Created
| Running
| Dying
@@ -81,12 +75,6 @@ data State = Created
, Show -- ^ @since 4.7.0.0
)
--- | A priority search queue, with timeouts as priorities.
-type TimeoutQueue = Q.PSQ TimeoutCallback
-
--- | An edit to apply to a 'TimeoutQueue'.
-type TimeoutEdit = TimeoutQueue -> TimeoutQueue
-
-- | The event manager state.
data TimerManager = TimerManager
{ emBackend :: !Backend
diff --git a/libraries/base/GHC/Event/Windows.hsc b/libraries/base/GHC/Event/Windows.hsc
new file mode 100644
index 0000000000..d074a300b3
--- /dev/null
+++ b/libraries/base/GHC/Event/Windows.hsc
@@ -0,0 +1,1324 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE DoAndIfThenElse #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+-------------------------------------------------------------------------------
+-- |
+-- Module : GHC.Event.Windows
+-- Copyright : (c) Tamar Christina 2018
+-- License : BSD-style (see the file libraries/base/LICENSE)
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : experimental
+-- Portability : non-portable
+--
+-- WinIO Windows event manager.
+--
+-------------------------------------------------------------------------------
+
+module GHC.Event.Windows (
+ -- * Manager
+ Manager,
+ getSystemManager,
+ interruptSystemManager,
+ wakeupIOManager,
+ processRemoteCompletion,
+
+ -- * Overlapped I/O
+ associateHandle,
+ associateHandle',
+ withOverlapped,
+ withOverlappedEx,
+ StartCallback,
+ StartIOCallback,
+ CbResult(..),
+ CompletionCallback,
+ LPOVERLAPPED,
+
+ -- * Timeouts
+ TimeoutCallback,
+ TimeoutKey,
+ Seconds,
+ registerTimeout,
+ updateTimeout,
+ unregisterTimeout,
+
+ -- * Utilities
+ withException,
+ ioSuccess,
+ ioFailed,
+ ioFailedAny,
+ getLastError,
+
+ -- * I/O Result type
+ IOResult(..),
+
+ -- * I/O Event notifications
+ HandleData (..), -- seal for release
+ HandleKey (handleValue),
+ registerHandle,
+ unregisterHandle,
+
+ -- * Console events
+ module GHC.Event.Windows.ConsoleEvent
+) where
+
+-- define DEBUG 1
+
+-- #define DEBUG_TRACE 1
+
+##include "windows_cconv.h"
+#include <windows.h>
+#include <ntstatus.h>
+#include <Rts.h>
+#include "winio_structs.h"
+
+-- There doesn't seem to be GHC.* import for these
+import Control.Concurrent.MVar (modifyMVar)
+import {-# SOURCE #-} Control.Concurrent (forkOS)
+import Data.Semigroup.Internal (stimesMonoid)
+import Data.Foldable (mapM_, length, forM_)
+import Data.Maybe (isJust, maybe)
+
+import GHC.Event.Windows.Clock (Clock, Seconds, getClock, getTime)
+import GHC.Event.Windows.FFI (LPOVERLAPPED, OVERLAPPED_ENTRY(..))
+import GHC.Event.Windows.ManagedThreadPool
+import GHC.Event.Internal.Types
+import GHC.Event.Unique
+import GHC.Event.TimeOut
+import GHC.Event.Windows.ConsoleEvent
+import qualified GHC.Event.Windows.FFI as FFI
+import qualified GHC.Event.PSQ as Q
+import qualified GHC.Event.IntTable as IT
+import qualified GHC.Event.Internal as I
+
+import GHC.MVar
+import GHC.Exception as E
+import GHC.IORef
+import GHC.Maybe
+import GHC.Word
+import GHC.OldList (deleteBy)
+import Foreign
+import qualified GHC.Event.Array as A
+import GHC.Base
+import GHC.Conc.Sync
+import GHC.IO
+import GHC.IOPort
+import GHC.Num
+import GHC.Real
+import GHC.Enum (maxBound)
+import GHC.Windows
+import GHC.List (null)
+import GHC.Ptr
+import Text.Show
+
+#if defined(DEBUG)
+import Foreign.C
+import System.Posix.Internals (c_write)
+import GHC.Conc.Sync (myThreadId)
+#endif
+
+import qualified GHC.Windows as Win32
+
+#if defined(DEBUG_TRACE)
+import {-# SOURCE #-} Debug.Trace (traceEventIO)
+#endif
+
+-- Note [WINIO Manager design]
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- This file contains the Windows I//O manager. Windows's IO subsystem is by
+-- design fully asynchronous, however there are multiple ways and interfaces
+-- to the async methods.
+--
+-- The chosen Async interface for this implementation is using Completion Ports
+-- See also Note [Completion Ports]. The I/O manager uses a new interface added
+-- in Windows Vista called `GetQueuedCompletionStatusEx` which allows us to
+-- service multiple requests in one go.
+--
+-- See https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/overview-of-the-windows-i-o-model
+-- and https://www.microsoftpressstore.com/articles/article.aspx?p=2201309&seqNum=3
+--
+-- In order to understand this file, here is what you should know:
+-- We're using relatively new APIs that allow us to service multiple requests at
+-- the same time using one OS thread. This happens using so called Completion
+-- ports. All I/O actions get associated with one and the same completion port.
+--
+-- The I/O manager itself has two mode of operation:
+-- 1) Threaded: We have N dedicated OS threads in the Haskell world that service
+-- completion requests. Everything is Handled 100% in view of the runtime.
+-- Whenever the OS has completions that need to be serviced it wakes up one
+-- one of the OS threads that are blocked in GetQueuedCompletionStatusEx and
+-- lets it proceed with the list of completions that are finished. If more
+-- completions finish before the first list is done being processed then
+-- another thread is woken up. These threads are associated with the I/O
+-- manager through the completion port. If a thread blocks for any reason the
+-- OS I/O manager will wake up another thread blocked in GetQueuedCompletionStatusEx
+-- from the pool to finish processing the remaining entries. This worker thread
+-- must be able to handle the
+-- fact that something else has finished the remainder of their queue or must
+-- have a guarantee to never block. In this implementation we strive to
+-- never block. This is achieved by not having the worker threads call out
+-- to any user code, and to have the IOPort synchronization primitive never
+-- block. This means if the port is full the message is lost, however we
+-- have an invariant that the port can never be full and have a waiting
+-- receiver. As such, dropping the message does not change anything as there
+-- will never be anyone to receive it. e.g. it is an impossible situation to
+-- land in.
+-- Note that it is valid (and perhaps expected) that at times two workers
+-- will receive the same requests to handle. We deal with this by using
+-- atomic operations to prevent race conditions. See processCompletion
+-- for details.
+-- 2) Non-threaded: We don't have any dedicated Haskell threads servicing
+-- I/O Requests. Instead we have an OS thread inside the RTS that gets
+-- notified of new requests and does the servicing. When a request completes
+-- a Haskell thread is scheduled to run to finish off the processing of any
+-- completed requests. See Note [Non-Threaded WINIO design].
+--
+-- These two modes of operations share the majority of the code and so they both
+-- support the same operations and fixing one will fix the other.
+-- Unlike MIO, we don't threat network I/O any differently than file I/O. Hence
+-- any network specific code is now only in the network package.
+--
+-- See also Note [Completion Ports] which has some of the details which
+-- informed this design.
+--
+-- Note [Threaded WINIO design]
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+-- The threaded WiNIO is designed around a simple blocking call that's called in
+-- a service loop in a dedicated thread: `GetQueuedCompletionStatusEx`.
+-- as such the loop is reasonably simple. We're either servicing finished
+-- requests or blocking in `getQueuedCompletionStatusEx` waiting for new
+-- requests to arrive.
+--
+-- Each time a Handle is made three important things happen that affect the I/O
+-- manager design:
+-- 1) Files are opened with the `FILE_FLAG_OVERLAPPED` flag, which instructs the
+-- OS that we will be doing purely asynchronous requests. See
+-- `GHC.IO.Windows.Handle.openFile`. They are also opened with
+-- `FILE_FLAG_SEQUENTIAL_SCAN` to indicate to the OS that we want to optimize
+-- the access of the file for sequential access. (e.g. equivalent to MADVISE)
+-- 2) The created handle is associated with the I/O manager's completion port.
+-- This allows the I/O manager to be able to service I/O events from this
+-- handle. See `associateHandle`.
+-- 3) File handles are additionally modified with two optimization flags:
+--
+-- FILE_SKIP_COMPLETION_PORT_ON_SUCCESS: If the request can be serviced
+-- immediately, then do not queue the IRP (IO Request Packet) into the I/O
+-- manager waiting for us to service it later. Instead service it
+-- immediately in the same call. This is beneficial for two reasons:
+-- 1) We don't have to block in the Haskell RTS.
+-- 2) We save a bunch of work in the OS's I/O subsystem.
+-- The downside is though that we have to do a bunch of work to handle these
+-- cases. This is abstracted away from the user by the `withOverlapped`
+-- function.
+-- This together with the buffering strategy mentioned above means we
+-- actually skip the I/O manager on quite a lot of I/O requests due to the
+-- value being in the cache. Because of the Lazy I/O in Haskell, the time
+-- to read and decode the buffer of bytes is usually longer than the OS needs
+-- to read the next chunk, so we hit the FAST_IO IRP quite often.
+--
+-- FILE_SKIP_SET_EVENT_ON_HANDLE: Since we will not be using an event object
+-- to monitor asynchronous completions, don't bother updating or checking for
+-- one. This saves some precious cycles, especially on operations with very
+-- high number of I/O operations (e.g. servers.)
+--
+-- So what does servicing a request actually mean. As mentioned before the
+-- I/O manager will be blocked or servicing a request. In reality it doesn't
+-- always block till an I/O request has completed. In cases where we have event
+-- timers, we block till the next timer's timeout. This allows us to also
+-- service timers in the same loop. The side effect of this is that we will
+-- exit the I/O wait sometimes without any completions. Not really a problem
+-- but it's an important design decision.
+--
+-- Every time we wait, we give a pre-allocated buffer of `n`
+-- `OVERLAPPED_ENTRIES` to the OS. This means that in a single call we can
+-- service up to `n` I/O requests at a time. The size of `n` is not fixed,
+-- anytime we dequeue `n` I/O requests in a single operation we double the
+-- buffer size, allowing the I/O manager to be able to scale up depending
+-- on the workload. This buffer is kept alive throughout the lifetime of the
+-- program and is never freed until the I/O manager is shutting down.
+--
+-- One very important property of the I/O subsystem is that each I/O request
+-- now requires an `OVERLAPPED` structure be given to the I/O manager. See
+-- `withOverlappedEx`. This buffer is used by the OS to fill in various state
+-- information. Throughout the duration of I/O call, this buffer MUST
+-- remain live. The address is pinned by the kernel, which means that the
+-- pointer must remain accessible until `GetQueuedCompletionStatusEx` returns
+-- the completion associated with the handle and not just until the call to what
+-- ever I/O operation was used to initialize the I/O request returns.
+-- The only exception to this is when the request has hit the FAST_IO path, in
+-- which case it has skipped the I/O queue and so can be freed immediately after
+-- reading the results from it.
+--
+-- To prevent having to lookup the Haskell payload in a shared state after the
+-- request completes we attach it as part of the I/O request by extending the
+-- `OVERLAPPED` structure. Instead of passing an `OVERLAPPED` structure to the
+-- Windows API calls we instead pass a `HASKELL_OVERLAPPED` struct which has
+-- as the first element an `OVERLAPPED structure. This means when a request is
+-- done all we need to do is cast the pointer back to `HASKELL_OVERLAPPED` and
+-- read the accompanying data. This also means we don't have a global lock and
+-- so can scale much easier.
+--
+
+-- ---------------------------------------------------------------------------
+-- I/O manager global thread
+
+-- When running GHCi we still want to ensure we still only have one
+-- io manager thread, even if base is loaded twice. See the docs for
+-- sharedCAF for how this is done.
+
+{-# NOINLINE ioManagerThread #-}
+ioManagerThread :: MVar (Maybe ThreadId)
+ioManagerThread = unsafePerformIO $ do
+ m <- newMVar Nothing
+ sharedCAF m getOrSetGHCConcWindowsIOManagerThreadStore
+
+foreign import ccall unsafe "getOrSetGHCConcWindowsIOManagerThreadStore"
+ getOrSetGHCConcWindowsIOManagerThreadStore :: Ptr a -> IO (Ptr a)
+
+-- ---------------------------------------------------------------------------
+-- Non-threaded I/O manager callback hooks. See `ASyncWinIO.c`
+
+foreign import ccall safe "registerIOCPHandle"
+ registerIOCPHandle :: FFI.IOCP -> IO ()
+
+foreign import ccall safe "registerAlertableWait"
+-- (bool has_timeout, DWORD mssec, uint64_t num_req, bool pending_service);
+ c_registerAlertableWait :: Bool -> DWORD -> Word64 -> Bool -> IO ()
+
+foreign import ccall safe "getOverlappedEntries"
+ getOverlappedEntries :: Ptr DWORD -> IO (Ptr OVERLAPPED_ENTRY)
+
+foreign import ccall safe "completeSynchronousRequest"
+ completeSynchronousRequest :: IO ()
+
+------------------------------------------------------------------------
+-- Manager structures
+
+-- | Callback type that will be called when an I/O operation completes.
+type IOCallback = CompletionCallback ()
+
+-- | Wrap the IOCallback type into a FunPtr.
+foreign import ccall "wrapper"
+ wrapIOCallback :: IOCallback -> IO (FunPtr IOCallback)
+
+-- | Unwrap a FunPtr IOCallback to a normal Haskell function.
+foreign import ccall "dynamic"
+ mkIOCallback :: FunPtr IOCallback -> IOCallback
+
+-- | Structure that the I/O manager uses to associate callbacks with
+-- additional payload such as their OVERLAPPED structure and Win32 handle
+-- etc. *Must* be kept in sync with that in `winio_structs.h` or horrible things
+-- happen.
+--
+-- We keep the handle around for the benefit of ghc-external libraries making
+-- use of the manager.
+data CompletionData = CompletionData { cdHandle :: !HANDLE
+ , cdCallback :: !IOCallback
+ }
+
+instance Storable CompletionData where
+ sizeOf _ = #{size CompletionData}
+ alignment _ = #{alignment CompletionData}
+
+ peek ptr = do
+ cdCallback <- mkIOCallback `fmap` #{peek CompletionData, cdCallback} ptr
+ cdHandle <- #{peek CompletionData, cdHandle} ptr
+ let !cd = CompletionData{..}
+ return cd
+
+ poke ptr CompletionData{..} = do
+ cb <- wrapIOCallback cdCallback
+ #{poke CompletionData, cdCallback} ptr cb
+ #{poke CompletionData, cdHandle} ptr cdHandle
+
+-- | Pointer offset in bytes to the location of hoData in HASKELL_OVERLAPPPED
+cdOffset :: Int
+cdOffset = #{const __builtin_offsetof (HASKELL_OVERLAPPED, hoData)}
+
+-- | Terminator symbol for IOCP request
+nullReq :: Ptr (Ptr a)
+nullReq = castPtr $ unsafePerformIO $ new $ (nullPtr :: Ptr ())
+
+-- I don't expect a lot of events, so a simple linked lists should be enough.
+type EventElements = [(Event, HandleData)]
+data EventData = EventData { evtTopLevel :: !Event, evtElems :: !EventElements }
+
+instance Monoid EventData where
+ mempty = EventData evtNothing []
+ mappend = (<>)
+
+instance Semigroup EventData where
+ (<>) = \a b -> EventData (evtTopLevel a <> evtTopLevel b)
+ (evtElems a ++ evtElems b)
+ stimes = stimesMonoid
+
+data IOResult a
+ = IOSuccess { ioValue :: a }
+ | IOFailed { ioErrCode :: Maybe Int }
+
+-- | The state object for the I/O manager. This structure is available for both
+-- the threaded and the non-threaded RTS.
+data Manager = Manager
+ { mgrIOCP :: {-# UNPACK #-} !FFI.IOCP
+ , mgrClock :: !Clock
+ , mgrUniqueSource :: {-# UNPACK #-} !UniqueSource
+ , mgrTimeouts :: {-# UNPACK #-} !(IORef TimeoutQueue)
+ , mgrEvntHandlers :: {-# UNPACK #-}
+ !(MVar (IT.IntTable EventData))
+ , mgrOverlappedEntries
+ :: {-#UNPACK #-} !(A.Array OVERLAPPED_ENTRY)
+ , mgrThreadPool :: Maybe ThreadPool
+ }
+
+{-# INLINE startIOManagerThread #-}
+-- | Starts a new I/O manager thread.
+-- For the threaded runtime it creates a pool of OS threads which stays alive
+-- until they are instructed to die.
+-- For the non-threaded runtime we have a single worker thread in
+-- the C runtime which we force to wake up instead.
+--
+-- TODO: Threadpools are not yet implemented.
+startIOManagerThread :: IO () -> IO ()
+startIOManagerThread loop
+ | not threadedIOMgr
+ = debugIO "startIOManagerThread:NonThreaded" >>
+ interruptSystemManager
+ | otherwise = do
+ modifyMVar_ ioManagerThread $ \old -> do
+ let create = do debugIO "spawning worker threads.."
+ t <- forkOS loop
+ debugIO $ "created io-manager threads."
+ labelThread t "IOManagerThread"
+ return (Just t)
+ debugIO $ "startIOManagerThread old=" ++ show old
+ case old of
+ Nothing -> create
+ Just t -> do
+ s <- threadStatus t
+ case s of
+ ThreadFinished -> create
+ ThreadDied -> create
+ _other -> do interruptSystemManager
+ return (Just t)
+
+requests :: MVar Word64
+requests = unsafePerformIO $ newMVar 0
+
+addRequest :: IO Word64
+addRequest = modifyMVar requests (\x -> return (x + 1, x + 1))
+
+removeRequest :: IO Word64
+removeRequest = modifyMVar requests (\x -> return (x - 1, x - 1))
+
+outstandingRequests :: IO Word64
+outstandingRequests = withMVar requests return
+
+getSystemManager :: IO Manager
+getSystemManager = readMVar managerRef
+
+-- | Mutable reference to the IO manager
+managerRef :: MVar Manager
+managerRef = unsafePerformIO $ createManager >>= newMVar
+ where
+ -- | Create the I/O manager. In the Threaded I/O manager this call doesn't
+ -- have any side effects, but in the Non-Threaded I/O manager the newly
+ -- created IOCP handle will be registered with the RTS. Users should never
+ -- call this.
+ -- It's only used to create the single global manager which is stored
+ -- in an MVar.
+ --
+ -- NOTE: This needs to finish without making any calls to anything requiring the
+ -- I/O manager otherwise we'll get into some weird synchronization issues.
+ -- Essentially this means avoid using long running operations here.
+ createManager :: IO Manager
+ createManager = do
+ debugIO "Starting io-manager..."
+ mgrIOCP <- FFI.newIOCP
+ when (not threadedIOMgr) $
+ registerIOCPHandle mgrIOCP
+ debugIO $ "iocp: " ++ show mgrIOCP
+ mgrClock <- getClock
+ mgrUniqueSource <- newSource
+ mgrTimeouts <- newIORef Q.empty
+ mgrOverlappedEntries <- A.new 64
+ mgrEvntHandlers <- newMVar =<< IT.new callbackArraySize
+ let mgrThreadPool = Nothing
+
+ let !mgr = Manager{..}
+ return mgr
+{-# NOINLINE managerRef #-}
+
+-- | Interrupts an I/O manager Wait. This will force the I/O manager to process
+-- any outstanding events and timers. Also called when console events such as
+-- ctrl+c are used to break abort an I/O request.
+interruptSystemManager :: IO ()
+interruptSystemManager = do
+ mgr <- getSystemManager
+ debugIO "interrupt received.."
+ FFI.postQueuedCompletionStatus (mgrIOCP mgr) 0 0 nullPtr
+
+-- | The initial number of I/O requests we can service at the same time.
+-- Must be power of 2. This number is used as the starting point to scale
+-- the number of concurrent requests. It will be doubled every time we are
+-- saturated.
+callbackArraySize :: Int
+callbackArraySize = 32
+
+-----------------------------------------------------------------------
+-- Time utilities
+
+secondsToNanoSeconds :: Seconds -> Q.Prio
+secondsToNanoSeconds s = ceiling $ s * 1000000000
+
+secondsToMilliSeconds :: Seconds -> Word32
+secondsToMilliSeconds s = ceiling $ s * 1000
+
+nanoSecondsToSeconds :: Q.Prio -> Seconds
+nanoSecondsToSeconds n = fromIntegral n / 1000000000.0
+
+------------------------------------------------------------------------
+-- Overlapped I/O
+
+-- | Callback that starts the overlapped I/O operation.
+-- It must return successfully if and only if an I/O completion has been
+-- queued. Otherwise, it must throw an exception, which 'withOverlapped'
+-- will rethrow.
+type StartCallback a = LPOVERLAPPED -> IO a
+
+-- | Specialized callback type for I/O Completion Ports calls using
+-- withOverlapped.
+type StartIOCallback a = StartCallback (CbResult a)
+
+-- | CallBack result type to disambiguate between the different states
+-- an I/O Completion call could be in.
+data CbResult a
+ = CbDone (Maybe DWORD) -- ^ Request was handled immediately, no queue.
+ | CbPending -- ^ Queued and to be handled by I/O manager
+ | CbIncomplete -- ^ I/O request is incomplete but not enqueued, handle
+ -- it synchronously.
+ | CbError a -- ^ I/O request abort, return failure immediately
+ | CbNone Bool -- ^ The caller did not do any checking, the I/O
+ -- manager will perform additional checks.
+ deriving Show
+
+-- | Called when the completion is delivered.
+type CompletionCallback a = ErrCode -- ^ 0 indicates success
+ -> DWORD -- ^ Number of bytes transferred
+ -> IO a
+
+-- | Associate a 'HANDLE' with the current I/O manager's completion port.
+-- This must be done before using the handle with 'withOverlapped'.
+associateHandle' :: HANDLE -> IO ()
+associateHandle' hwnd
+ = do mngr <- getSystemManager
+ associateHandle mngr hwnd
+
+-- | A handle value representing an invalid handle.
+invalidHandle :: HANDLE
+invalidHandle = intPtrToPtr (#{const INVALID_HANDLE_VALUE})
+
+-- | Associate a 'HANDLE' with the I/O manager's completion port. This must be
+-- done before using the handle with 'withOverlapped'.
+associateHandle :: Manager -> HANDLE -> IO ()
+associateHandle Manager{..} h =
+ -- Don't try to if the handle is invalid. This can happen with i.e a closed
+ -- std handle.
+ when (h /= invalidHandle) $
+ -- Use as completion key the file handle itself, so we can track
+ -- completion
+ FFI.associateHandleWithIOCP mgrIOCP h (fromIntegral $ ptrToWordPtr h)
+
+
+{- Note [Why use non-waiting getOverlappedResult requests.]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ When waiting for a request that is bound to be done soon
+ we spin inside waitForCompletion. There are multiple reasons
+ for this.
+
+ In the non-threaded RTS we can't perform blocking calls to
+ C functions without blocking the whole RTS so immediately
+ a blocking call is not an option there.
+
+ In the threaded RTS we don't use a blocking wait for different
+ reasons. In particular performing a waiting request using
+ getOverlappedResult uses the hEvent object embedded in the
+ OVERLAPPED structure to wait for a signal.
+ However we do not provide such an object as their creation
+ would incur to much overhead. Making a waiting request a
+ less useful operation as it doesn't guarantee that the
+ operation we were waiting one finished. Only that some
+ operation on the handle did.
+
+-}
+
+-- | Start an overlapped I/O operation, and wait for its completion. If
+-- 'withOverlapped' is interrupted by an asynchronous exception, the operation
+-- will be canceled using @CancelIoEx@.
+--
+-- 'withOverlapped' waits for a completion to arrive before returning or
+-- throwing an exception. This means you can use functions like
+-- 'Foreign.Marshal.Alloc.alloca' to allocate buffers for the operation.
+withOverlappedEx :: forall a.
+ Manager
+ -> String -- ^ Handle name
+ -> HANDLE -- ^ Windows handle associated with the operation.
+ -> Word64 -- ^ Value to use for the @OVERLAPPED@
+ -- structure's Offset/OffsetHigh members.
+ -> StartIOCallback Int
+ -> CompletionCallback (IOResult a)
+ -> IO (IOResult a)
+withOverlappedEx mgr fname h offset startCB completionCB = do
+ signal <- newEmptyIOPort :: IO (IOPort (IOResult a))
+ let signalReturn a = failIfFalse_ (dbgMsg "signalReturn") $
+ writeIOPort signal (IOSuccess a)
+ signalThrow ex = failIfFalse_ (dbgMsg "signalThrow") $
+ writeIOPort signal (IOFailed ex)
+ mask_ $ do
+ let completionCB' e b = completionCB e b >>= \result ->
+ case result of
+ IOSuccess val -> signalReturn val
+ IOFailed err -> signalThrow err
+ hs_lpol <- FFI.allocOverlapped offset
+ -- Create the completion record and store it.
+ -- We only need the record when we enqueue a request, however if we
+ -- delay creating it then we will run into a race condition where the
+ -- driver may have finished servicing the request before we were ready
+ -- and so the request won't have the book keeping information to know
+ -- what to do. So because of that we always create the payload, If we
+ -- need it ok, if we don't that's no problem. This approach prevents
+ -- expensive lookups in hash-tables.
+ --
+ -- Todo: Use a memory pool for this so we don't have to hit malloc every
+ -- time. This would allow us to scale better.
+ cdData <- new (CompletionData h completionCB') :: IO (Ptr CompletionData)
+ let ptr_lpol = hs_lpol `plusPtr` cdOffset
+ let lpol = castPtr hs_lpol
+ debugIO $ "hs_lpol:" ++ show hs_lpol
+ ++ " cdData:" ++ show cdData
+ ++ " ptr_lpol:" ++ show ptr_lpol
+
+ startCBResult <- startCB lpol `onException`
+ (CbError `fmap` Win32.getLastError) >>= \result -> do
+ -- Check to see if the operation was completed on a
+ -- non-overlapping handle or was completed immediately.
+ -- e.g. stdio redirection or data in cache, FAST I/O.
+ success <- FFI.overlappedIOStatus lpol
+ err <- getLastError
+ -- Determine if the caller has done any checking. If not then check
+ -- to see if the request was completed synchronously. We have to
+ -- in order to prevent deadlocks since if it has completed
+ -- synchronously we've requested to not have the completion queued.
+ let result' =
+ case result of
+ CbNone ret -- Start by checking some flags which indicates we
+ -- are done.
+ | success == #{const STATUS_SUCCESS} -> CbDone Nothing
+ | success == #{const STATUS_END_OF_FILE} -> CbDone Nothing
+ -- Buffer was too small.. not sure what to do, so I'll just
+ -- complete the read request
+ | err == #{const ERROR_MORE_DATA} -> CbDone Nothing
+ | err == #{const ERROR_SUCCESS} -> CbDone Nothing
+ | err == #{const ERROR_IO_PENDING} -> CbPending
+ | err == #{const ERROR_IO_INCOMPLETE} -> CbIncomplete
+ | err == #{const ERROR_HANDLE_EOF} -> CbDone Nothing
+ | err == #{const ERROR_BROKEN_PIPE} -> CbDone Nothing
+ | err == #{const ERROR_NO_MORE_ITEMS} -> CbDone Nothing
+ | err == #{const ERROR_OPERATION_ABORTED} -> CbDone Nothing
+ -- This is currently mapping all non-complete requests we don't know
+ -- about as an error. I wonder if this isn't too strict..
+ | not ret -> CbError $ fromIntegral err
+ -- We check success codes after checking error as
+ -- errors are much more indicative
+ | success == #{const STATUS_PENDING} -> CbPending
+ -- If not just assume we can complete. If we can't this will
+ -- hang because we don't know how to properly deal with it.
+ -- I don't know what the best default here is...
+ | otherwise -> CbPending
+ _ -> result
+ case result' of
+ CbNone _ -> error "withOverlappedEx: CbNone shouldn't happen."
+ CbIncomplete -> do
+ debugIO $ "handling incomplete request synchronously " ++ show (h, lpol)
+ res <- waitForCompletion h lpol
+ debugIO $ "done blocking request 2: " ++ show (h, lpol) ++ " - " ++ show res
+ return res
+ CbPending -> do
+ -- Before we enqueue check see if operation finished in the
+ -- mean time, since caller may not have done this.
+ -- Normally we'd have to clear lpol with 0 before this call,
+ -- however the statuses we're interested in would not get to here
+ -- so we can save the memset call.
+ finished <- FFI.getOverlappedResult h lpol False
+ debugIO $ "== " ++ show (finished)
+ status <- FFI.overlappedIOStatus lpol
+ debugIO $ "== >< " ++ show (status)
+ lasterr <- getLastError
+ -- This status indicated that we have finished early and so we
+ -- won't have a request enqueued. Handle it inline.
+ let done_early = status == #{const STATUS_SUCCESS}
+ || status == #{const STATUS_END_OF_FILE}
+ || errorIsCompleted lasterr
+ -- This status indicates that the request hasn't finished early,
+ -- but it will finish shortly. The I/O manager will not be
+ -- enqueuing this either. Also needs to be handled inline.
+ -- Sadly named pipes will always return this error, so in practice
+ -- we end up always handling them synchronously. There is no good
+ -- documentation on this.
+ let will_finish_sync = lasterr == #{const ERROR_IO_INCOMPLETE}
+
+ debugIO $ "== >*< " ++ show (finished, done_early, will_finish_sync, h, lpol, lasterr)
+ case (finished, done_early, will_finish_sync) of
+ -- Still pending
+ (Nothing, False, False) -> do
+ -- Since FILE_SKIP_COMPLETION_PORT_ON_SUCCESS can't be
+ -- relied on for non-file handles we need a way to prevent
+ -- us from handling a request inline and handle a completion
+ -- event handled without a queued I/O operation. We can do
+ -- this by deferring the setting data pointer until we know
+ -- the request will be handled async.
+ poke ptr_lpol cdData
+ reqs <- addRequest
+ debugIO $ "+1.. " ++ show reqs ++ " requests queued. | " ++ show lpol
+ -- If we should add back support to suspend the IO Manager thread
+ -- then we will need to make sure it's running at this point.
+ return result'
+ -- In progress, we will wait for completion.
+ (Nothing, False, True) -> do
+ debugIO $ "handling incomplete request synchronously " ++ show (h, lpol)
+ res <- waitForCompletion h lpol
+ debugIO $ "done blocking request 1: " ++ show (h, lpol) ++ " - " ++ show res
+ return res
+ _ -> do
+ debugIO "request handled immediately (o/b), not queued."
+ return $ CbDone finished
+ CbError err' -> signalThrow (Just err') >> return result'
+ CbDone _ -> do
+ debugIO "request handled immediately (o), not queued." >> return result'
+
+ -- If an exception was received while waiting for IO to complete
+ -- we try to cancel the request here.
+ let cancel e = do
+ debugIO $ "## Exception occurred. Cancelling request... "
+ debugIO $ show (e :: SomeException)
+ _ <- uninterruptibleMask_ $ FFI.cancelIoEx' h lpol
+ -- we need to wait for the cancellation before removing
+ -- the pointer.
+ debugIO $ "## Waiting for cancellation record... "
+ _ <- FFI.getOverlappedResult h lpol True
+ oldDataPtr <- exchangePtr ptr_lpol nullReq
+ -- Check if we have to free and cleanup pointer
+ when (oldDataPtr == cdData) $
+ do free oldDataPtr
+ free hs_lpol
+ reqs <- removeRequest
+ debugIO $ "-1.. " ++ show reqs ++ " requests queued after error."
+ status <- fmap fromIntegral getLastError
+ completionCB' status 0
+ when (not threadedIOMgr) $
+ do num_remaining <- outstandingRequests
+ -- Run timeouts. This way if we canceled the last
+ -- IO Request and have no timer events waiting we
+ -- can go into an unbounded alertable wait.
+ delay <- runExpiredTimeouts mgr
+ registerAlertableWait delay num_remaining True
+ return $ IOFailed Nothing
+ let runner = do debugIO $ (dbgMsg ":: waiting ") ++ " | " ++ show lpol
+ res <- readIOPort signal `catch` cancel
+ debugIO $ dbgMsg ":: signaled "
+ case res of
+ IOFailed err -> FFI.throwWinErr fname (maybe 0 fromIntegral err)
+ _ -> return res
+
+ -- Sometimes we shouldn't bother with the I/O manager as the call has
+ -- failed or is done.
+ case startCBResult of
+ CbPending -> runner
+ CbDone rdata -> do
+ free cdData
+ debugIO $ dbgMsg $ ":: done " ++ show lpol ++ " - " ++ show rdata
+ bytes <- if isJust rdata
+ then return rdata
+ -- Make sure it's safe to free the OVERLAPPED buffer
+ else FFI.getOverlappedResult h lpol False
+ debugIO $ dbgMsg $ ":: done bytes: " ++ show bytes
+ case bytes of
+ Just res -> free hs_lpol >> completionCB 0 res
+ Nothing -> do err <- FFI.overlappedIOStatus lpol
+ numBytes <- FFI.overlappedIONumBytes lpol
+ -- TODO: Remap between STATUS_ and ERROR_ instead
+ -- of re-interpret here. But for now, don't care.
+ let err' = fromIntegral err
+ free hs_lpol
+ debugIO $ dbgMsg $ ":: done callback: " ++ show err' ++ " - " ++ show numBytes
+ completionCB err' (fromIntegral numBytes)
+ CbError err -> do
+ free cdData
+ free hs_lpol
+ let err' = fromIntegral err
+ completionCB err' 0
+ _ -> do
+ free cdData
+ free hs_lpol
+ error "unexpected case in `startCBResult'"
+ where dbgMsg s = s ++ " (" ++ show h ++ ":" ++ show offset ++ ")"
+ -- Wait for .25ms (threaded) and 1ms (non-threaded)
+ -- Yields in the threaded case allowing other work.
+ -- Blocks all haskell execution in the non-threaded case.
+ -- We might want to reconsider the non-threaded handling
+ -- at some point.
+ doShortWait :: IO ()
+ doShortWait
+ | threadedIOMgr = do
+ -- Uses an inline definition of threadDelay to prevent an import
+ -- cycle.
+ let usecs = 250 -- 0.25ms
+ m <- newEmptyIOPort
+ reg <- registerTimeout mgr usecs $
+ writeIOPort m () >> return ()
+ readIOPort m `onException` unregisterTimeout mgr reg
+ | otherwise = sleepBlock 1 -- 1 ms
+ waitForCompletion :: HANDLE -> Ptr FFI.OVERLAPPED -> IO (CbResult Int)
+ waitForCompletion fhndl lpol = do
+ -- Wait for the request to finish as it was running before and
+ -- The I/O manager won't enqueue it due to our optimizations to
+ -- prevent context switches in such cases.
+ -- In the non-threaded case we must use a non-waiting query here
+ -- otherwise the RTS will lock up until we get a result back.
+ -- In the threaded case it can be beneficial to spin on the haskell
+ -- side versus
+ -- See also Note [Why use non-waiting getOverlappedResult requests.]
+ res <- FFI.getOverlappedResult fhndl lpol False
+ status <- FFI.overlappedIOStatus lpol
+ case res of
+ Nothing | status == #{const STATUS_END_OF_FILE}
+ -> do
+ when (not threadedIOMgr) completeSynchronousRequest
+ return $ CbDone res
+ | otherwise ->
+ do lasterr <- getLastError
+ let done = errorIsCompleted lasterr
+ -- debugIO $ ":: loop - " ++ show lasterr ++ " :" ++ show done
+ -- We will complete quite soon, in the threaded RTS we
+ -- probably don't really want to wait for it while we could
+ -- have done something else. In particular this is because
+ -- of sockets which make take slightly longer.
+ -- There's a trade-off. Using the timer would allow it do
+ -- to continue running other Haskell threads, but also
+ -- means it may take longer to complete the wait.
+ unless done doShortWait
+ if done
+ then do when (not threadedIOMgr)
+ completeSynchronousRequest
+ return $ CbDone Nothing
+ else waitForCompletion fhndl lpol
+ Just _ -> do
+ when (not threadedIOMgr) completeSynchronousRequest
+ return $ CbDone res
+ unless :: Bool -> IO () -> IO ()
+ unless p a = if p then a else return ()
+
+-- Safe version of function
+withOverlapped :: String
+ -> HANDLE
+ -> Word64 -- ^ Value to use for the @OVERLAPPED@
+ -- structure's Offset/OffsetHigh members.
+ -> StartIOCallback Int
+ -> CompletionCallback (IOResult a)
+ -> IO (IOResult a)
+withOverlapped fname h offset startCB completionCB = do
+ mngr <- getSystemManager
+ withOverlappedEx mngr fname h offset startCB completionCB
+
+------------------------------------------------------------------------
+-- Helper to check if an error code implies an operation has completed.
+
+errorIsCompleted :: ErrCode -> Bool
+errorIsCompleted lasterr =
+ lasterr == #{const ERROR_HANDLE_EOF}
+ || lasterr == #{const ERROR_SUCCESS}
+ || lasterr == #{const ERROR_BROKEN_PIPE}
+ || lasterr == #{const ERROR_NO_MORE_ITEMS}
+ || lasterr == #{const ERROR_OPERATION_ABORTED}
+
+------------------------------------------------------------------------
+-- I/O Utilities
+
+-- | Process an IOResult and throw an exception back to the user if the action
+-- has failed, or return the result.
+withException :: String -> IO (IOResult a) -> IO a
+withException name fn
+ = do res <- fn
+ case res of
+ IOSuccess a -> return a
+ IOFailed (Just err) -> FFI.throwWinErr name $ fromIntegral err
+ IOFailed Nothing -> FFI.throwWinErr name 0
+
+-- | Signal that the I/O action was successful.
+ioSuccess :: a -> IO (IOResult a)
+ioSuccess = return . IOSuccess
+
+-- | Signal that the I/O action has failed with the given reason.
+ioFailed :: Integral a => a -> IO (IOResult a)
+ioFailed = return . IOFailed . Just . fromIntegral
+
+-- | Signal that the I/O action has failed with the given reason.
+-- Polymorphic in successful result type.
+ioFailedAny :: Integral a => a -> IO (IOResult b)
+ioFailedAny = return . IOFailed . Just . fromIntegral
+
+------------------------------------------------------------------------
+-- Timeouts
+
+-- | Convert uS(Int) to nS(Word64/Q.Prio) capping at maxBound
+expirationTime :: Clock -> Int -> IO Q.Prio
+expirationTime mgr us = do
+ now <- getTime mgr :: IO Seconds -- Double
+ let now_ns = ceiling $ now * 1000 * 1000 * 1000 :: Word64
+ let expTime
+ -- Currently we treat overflows by clamping to maxBound. If humanity
+ -- still exists in 2500 CE we will ned to be a bit more careful here.
+ -- See #15158.
+ | (maxBound - now_ns) `quot` 1000 < fromIntegral us = maxBound :: Q.Prio
+ | otherwise = now_ns + ns
+ where ns = 1000 * fromIntegral us
+ return expTime
+
+-- | Register an action to be performed in the given number of seconds. The
+-- returned 'TimeoutKey' can be used to later un-register or update the timeout.
+-- The timeout is automatically unregistered when it fires.
+--
+-- The 'TimeoutCallback' will not be called more than once.
+{-# NOINLINE registerTimeout #-}
+registerTimeout :: Manager -> Int -> TimeoutCallback -> IO TimeoutKey
+registerTimeout mgr@Manager{..} uSrelTime cb = do
+ key <- newUnique mgrUniqueSource
+ if uSrelTime <= 0 then cb
+ else do
+ !expTime <- expirationTime mgrClock uSrelTime :: IO Q.Prio
+ editTimeouts mgr (Q.unsafeInsertNew key expTime cb)
+ return $ TK key
+
+-- | Update an active timeout to fire in the given number of seconds (from the
+-- time 'updateTimeout' is called), instead of when it was going to fire.
+-- This has no effect if the timeout has already fired.
+updateTimeout :: Manager -> TimeoutKey -> Seconds -> IO ()
+updateTimeout mgr (TK key) relTime = do
+ now <- getTime (mgrClock mgr)
+ let !expTime = secondsToNanoSeconds $ now + relTime
+ -- Note: editTimeouts unconditionally wakes the IO Manager
+ -- but that is not required if the new time is after
+ -- the current time.
+ editTimeouts mgr (Q.adjust (const expTime) key)
+
+-- | Unregister an active timeout. This is a harmless no-op if the timeout is
+-- already unregistered or has already fired.
+--
+-- Warning: the timeout callback may fire even after
+-- 'unregisterTimeout' completes.
+unregisterTimeout :: Manager -> TimeoutKey -> IO ()
+unregisterTimeout mgr (TK key) = do
+ editTimeouts mgr (Q.delete key)
+
+-- | Modify an existing timeout. This isn't thread safe and so if the time to
+-- elapse the timer was close it may fire anyway.
+editTimeouts :: Manager -> TimeoutEdit -> IO ()
+editTimeouts mgr g = do
+ atomicModifyIORef' (mgrTimeouts mgr) $ \tq -> (g tq, ())
+ interruptSystemManager
+
+------------------------------------------------------------------------
+-- I/O manager loop
+
+-- | Call all expired timeouts, and return how much time until the next
+-- | expiration.
+runExpiredTimeouts :: Manager -> IO (Maybe Seconds)
+runExpiredTimeouts Manager{..} = do
+ now <- getTime mgrClock
+ (expired, delay) <- atomicModifyIORef' mgrTimeouts (mkTimeout now)
+ -- Execute timeout callbacks.
+ mapM_ Q.value expired
+ when (not threadedIOMgr && not (null expired))
+ completeSynchronousRequest
+ debugIO $ "expired calls: " ++ show (length expired)
+ return delay
+ where
+ mkTimeout :: Seconds -> TimeoutQueue ->
+ (TimeoutQueue, ([Q.Elem TimeoutCallback], Maybe Seconds))
+ mkTimeout now tq =
+ let (tq', (expired, sec)) = mkTimeout' (secondsToNanoSeconds now) tq
+ in (tq', (expired, fmap nanoSecondsToSeconds sec))
+ mkTimeout' :: Q.Prio -> TimeoutQueue ->
+ (TimeoutQueue, ([Q.Elem TimeoutCallback], Maybe Q.Prio))
+ mkTimeout' now tq =
+ -- Remove timeouts with expiration <= now.
+ let (expired, tq') = Q.atMost now tq in
+ -- See how soon the next timeout expires.
+ case Q.prio `fmap` Q.findMin tq' of
+ Nothing ->
+ (tq', (expired, Nothing))
+ Just t ->
+ -- This value will always be positive since the call
+ -- to 'atMost' above removed any timeouts <= 'now'
+ let !t' = t - now
+ in (tq', (expired, Just t'))
+
+-- | Return the delay argument to pass to GetQueuedCompletionStatus.
+-- Return value is in ms
+fromTimeout :: Maybe Seconds -> Word32
+fromTimeout Nothing = 120000
+fromTimeout (Just sec) | sec > 120 = 120000
+ | sec > 0 = ceiling (sec * 1000)
+ | otherwise = 0
+
+-- | Perform one full evaluation step of the I/O manager's service loop.
+-- This means process timeouts and completed completions and calculate the time
+-- for the next timeout.
+--
+-- The I/O manager is then notified of how long it should block again based on
+-- the queued I/O requests and timers. If the I/O manager was given a command
+-- to block, shutdown or suspend than that request is honored at the end of the
+-- loop.
+--
+-- This function can be safely executed multiple times in parallel and is only
+-- used by the threaded manager.
+step :: Bool -> Manager -> IO (Bool, Maybe Seconds)
+step maxDelay mgr@Manager{..} = do
+ -- Determine how long to wait the next time we block in an alertable state.
+ delay <- runExpiredTimeouts mgr
+ let !timer = if maxDelay && delay == Nothing
+ then #{const INFINITE}
+ else fromTimeout delay
+ debugIO $ "next timer: " ++ show timer -- todo: print as hex
+ if isJust delay
+ then debugIO $ "I/O manager waiting: delay=" ++ show delay
+ else debugIO $ "I/O manager pausing: maxDelay=" ++ show maxDelay
+
+ -- Inform the threadpool that a thread is now
+ -- entering a kernel mode wait and thus is ready for new work.
+ notifyWaiting mgrThreadPool
+
+ -- To quote Matt Godbolts:
+ -- There are some unusual edge cases you need to deal with. The
+ -- GetQueuedCompletionStatus function blocks a thread until there's
+ -- work for it to do. Based on the return value, the number of bytes
+ -- and the overlapped structure, there’s a lot of possible "reasons"
+ -- for the function to have returned. Deciphering all the possible
+ -- cases:
+ --
+ -- ------------------------------------------------------------------------
+ -- Ret value | OVERLAPPED | # of bytes | Description
+ -- ------------------------------------------------------------------------
+ -- zero | NULL | n/a | Call to GetQueuedCompletionStatus
+ -- failed, and no data was dequeued from the IO port. This usually
+ -- indicates an error in the parameters to GetQueuedCompletionStatus.
+ --
+ -- zero | non-NULL | n/a | Call to GetQueuedCompletionStatus
+ -- failed, but data was read or written. The thread must deal with the
+ -- data (possibly freeing any associated buffers), but there is an error
+ -- condition on the underlying HANDLE. Usually seen when the other end of
+ -- a network connection has been forcibly closed but there's still data in
+ -- the send or receive queue.
+ --
+ -- non-zero | NULL | n/a | This condition doesn't happen due
+ -- to IO requests, but is useful to use in combination with
+ -- PostQueuedCompletionStatus as a way of indicating to threads that they
+ -- should terminate.
+ --
+ -- non-zero | non-NULL | zero | End of file for a file HANDLE, or
+ -- the connection has been gracefully closed (for network connections).
+ -- The OVERLAPPED buffer has still been used; and must be deallocated if
+ -- necessary.
+ --
+ -- non-zero | non-NULL | non-zero | "num bytes" of data have been
+ -- transferred into the block pointed by the OVERLAPPED structure. The
+ -- direction of the transfer is dependant on the call made to the IO
+ -- port, it's up to the user to remember if it was a read or a write
+ -- (usually by stashing extra data in the OVERLAPPED structure). The
+ -- thread must deallocate the structure as necessary.
+ --
+ -- The getQueuedCompletionStatusEx call will remove entries queued by the OS
+ -- and returns the finished ones in mgrOverlappedEntries and the number of
+ -- entries removed.
+ n <- FFI.getQueuedCompletionStatusEx mgrIOCP mgrOverlappedEntries timer
+ debugIO "WinIORunning"
+ -- If threaded this call informs the threadpool manager that a thread is
+ -- busy. If all threads are busy and we have not reached the maximum amount
+ -- of allowed threads then the threadpool manager will spawn a new thread to
+ -- allow us to scale under load.
+ notifyRunning mgrThreadPool
+ processCompletion mgr n delay
+
+-- | Process the results at the end of an evaluation loop. This function will
+-- read all the completions, unblock up all the Haskell threads, clean up the book
+-- keeping of the I/O manager.
+-- It returns whether there is outstanding work (request or timer) to be
+-- done and how long it expects to have to wait till it can take action again.
+--
+-- Note that this method can do less work than there are entries in the
+-- completion table. This is because some completion entries may have been
+-- created due to calls to interruptIOManager which will enqueue a faux
+-- completion.
+--
+-- NOTE: In Threaded mode things get a bit complicated the operation may have
+-- been completed even before we even got around to put the request in the
+-- waiting callback table. These events are handled by having a separate queue
+-- for orphaned callback instances that the calling thread is supposed to check
+-- before adding something to the work queue.
+--
+-- Thread safety: This function atomically replaces outstanding events with
+-- a pointer to nullReq. This means it's safe (but potentially wastefull) to
+-- have two concurrent or parallel invocations on the same array.
+processCompletion :: Manager -> Int -> Maybe Seconds -> IO (Bool, Maybe Seconds)
+processCompletion Manager{..} n delay = do
+ -- If some completions are done, we need to process them and call their
+ -- callbacks. We then remove the callbacks from the bookkeeping and resize
+ -- the array if required.
+ when (n > 0) $ do
+ forM_ [0..(n-1)] $ \idx -> do
+ oe <- A.unsafeRead mgrOverlappedEntries idx :: IO OVERLAPPED_ENTRY
+ let lpol = lpOverlapped oe
+ when (lpol /= nullPtr) $ do
+ let hs_lpol = castPtr lpol :: Ptr FFI.HASKELL_OVERLAPPED
+ let ptr_lpol = castPtr (hs_lpol `plusPtr` cdOffset) :: Ptr (Ptr CompletionData)
+ cdDataCheck <- peek ptr_lpol
+ debugIO $ " $ checking " ++ show lpol
+ ++ " -en ptr_lpol: " ++ show ptr_lpol
+ ++ " offset: " ++ show cdOffset
+ ++ " cdData: " ++ show cdDataCheck
+ ++ " at idx " ++ show idx
+ oldDataPtr <- exchangePtr ptr_lpol nullReq :: IO (Ptr CompletionData)
+ debugIO $ ":: oldDataPtr " ++ show oldDataPtr
+ when (oldDataPtr /= nullPtr) $
+ do debugIO $ "exchanged: " ++ show oldDataPtr
+ payload <- peek oldDataPtr :: IO CompletionData
+ let !cb = cdCallback payload
+ free oldDataPtr
+ reqs <- removeRequest
+ debugIO $ "-1.. " ++ show reqs ++ " requests queued."
+ status <- FFI.overlappedIOStatus (lpOverlapped oe)
+ -- TODO: Remap between STATUS_ and ERROR_ instead
+ -- of re-interpret here. But for now, don't care.
+ let status' = fromIntegral status
+ cb status' (dwNumberOfBytesTransferred oe)
+ free hs_lpol
+
+ -- clear the array so we don't erroneously interpret the output, in
+ -- certain circumstances like lockFileEx the code could return 1 entry
+ -- removed but the file data not been filled in.
+ -- TODO: Maybe not needed..
+ A.clear mgrOverlappedEntries
+
+ -- Check to see if we received the maximum amount of entries we could
+ -- this likely indicates a high number of I/O requests have been queued.
+ -- In which case we should process more at a time.
+ cap <- A.capacity mgrOverlappedEntries
+ when (cap == n) $ A.ensureCapacity mgrOverlappedEntries (2*cap)
+
+ -- Keep running if we still have some work queued or
+ -- if we have a pending delay.
+ reqs <- outstandingRequests
+ debugIO $ "outstanding requests: " ++ show reqs
+ let more = reqs > 0
+ debugIO $ "has more: " ++ show more ++ " - removed: " ++ show n
+ return (more || (isJust delay && threadedIOMgr), delay)
+
+-- | Entry point for the non-threaded I/O manager to be able to process
+-- completed completions. It is mostly a wrapper around processCompletion
+-- and invoked by the C thread via the scheduler.
+processRemoteCompletion :: IO ()
+processRemoteCompletion = do
+#if defined(DEBUG) || defined(DEBUG_TRACE)
+ tid <- myThreadId
+ labelThread tid $ "IOManagerThread-PRC" ++ show tid
+#endif
+ alloca $ \ptr_n -> do
+ debugIO "processRemoteCompletion :: start ()"
+ -- First figure out how much work we have to do.
+ entries <- getOverlappedEntries ptr_n
+ n <- fromIntegral `fmap` peek ptr_n
+ -- This call will unmarshal data from the C buffer but pointers inside of
+ -- this have not been read yet.
+ _ <- peekArray n entries
+ mngr <- getSystemManager
+ let arr = mgrOverlappedEntries mngr
+ A.unsafeCopyFromBuffer arr entries n
+
+ -- Process timeouts
+ delay <- runExpiredTimeouts mngr :: IO (Maybe Seconds)
+
+ -- Process available completions
+ _ <- processCompletion mngr n delay
+
+ num_left <- outstandingRequests
+
+ -- Update and potentially wake up IO Manager
+ -- This call will unblock the non-threaded I/O manager. After this it is no
+ -- longer safe to use `entries` nor `completed` as they can now be modified
+ -- by the C thread.
+ registerAlertableWait delay num_left False
+
+ debugIO "processRemoteCompletion :: done ()"
+ return ()
+
+registerAlertableWait :: Maybe Seconds -> Word64 -> Bool -> IO ()
+registerAlertableWait Nothing num_reqs pending_service =
+ c_registerAlertableWait False 0 num_reqs pending_service
+registerAlertableWait (Just delay) num_reqs pending_service =
+ c_registerAlertableWait True (secondsToMilliSeconds delay)
+ num_reqs pending_service
+
+-- | Event loop for the Threaded I/O manager. The one for the non-threaded
+-- I/O manager is in AsyncWinIO.c in the rts.
+io_mngr_loop :: HANDLE -> Manager -> IO ()
+io_mngr_loop _event _mgr
+ | not threadedIOMgr
+ = do debugIO "io_mngr_loop:no-op:called in non-threaded case"
+ return ()
+io_mngr_loop _event mgr = go False
+ where
+ go maxDelay =
+ do debugIO "io_mngr_loop:WinIORunning"
+ -- Step will process IO events, or block if none are outstanding.
+ (more, delay) <- step maxDelay mgr
+ let !use_max_delay = not (isJust delay || more)
+ debugIO "I/O manager stepping."
+ event_id <- c_readIOManagerEvent
+ exit <-
+ case event_id of
+ _ | event_id == io_MANAGER_WAKEUP -> return False
+ _ | event_id == io_MANAGER_DIE -> return True
+ 0 -> return False -- spurious wakeup
+ _ -> do debugIO $ "handling console event: " ++ show (event_id `shiftR` 1)
+ start_console_handler (event_id `shiftR` 1)
+ return False
+
+ -- If we have no more work to do, or something from the outside
+ -- told us to stop then we let the thread die and stop the I/O
+ -- manager. It will be woken up again when there is more to do.
+ case () of
+ _ | exit -> debugIO "I/O manager shutting down."
+ _ -> go use_max_delay
+
+
+io_MANAGER_WAKEUP, io_MANAGER_DIE :: Word32
+io_MANAGER_WAKEUP = #{const IO_MANAGER_WAKEUP}
+io_MANAGER_DIE = #{const IO_MANAGER_DIE}
+
+-- | Wake up a single thread from the I/O Manager's worker queue. This will
+-- unblock a thread blocked in `processCompletion` and allows the I/O manager to
+-- react accordingly to changes in timers or to process console signals.
+-- No-op if the io-manager is already running.
+wakeupIOManager :: IO ()
+wakeupIOManager
+ = do mngr <- getSystemManager
+ -- We don't care about the event handle here, only that it exists.
+ _event <- c_getIOManagerEvent
+ debugIO "waking up I/O manager."
+ startIOManagerThread (io_mngr_loop (error "IOManagerEvent used") mngr)
+
+-- | Returns the signaling event for the IO Manager.
+foreign import ccall unsafe "getIOManagerEvent" -- in the RTS (ThrIOManager.c)
+ c_getIOManagerEvent :: IO HANDLE
+
+-- | Reads one IO Manager event. For WINIO we distinguish:
+-- * Shutdown events, sent from the RTS
+-- * Console events, sent from the default console handler.
+-- * Wakeup events, which are not used by WINIO and will be ignored
+foreign import ccall unsafe "readIOManagerEvent" -- in the RTS (ThrIOManager.c)
+ c_readIOManagerEvent :: IO Word32
+
+foreign import ccall unsafe "rtsSupportsBoundThreads" threadedIOMgr :: Bool
+
+-- | Sleep for n ms
+foreign import WINDOWS_CCONV unsafe "Sleep" sleepBlock :: Int -> IO ()
+
+-- ---------------------------------------------------------------------------
+-- I/O manager event notifications
+
+
+data HandleData = HandleData {
+ tokenKey :: {-# UNPACK #-} !HandleKey
+ , tokenEvents :: {-# UNPACK #-} !EventLifetime
+ , _handleCallback :: !EventCallback
+ }
+
+-- | A file handle registration cookie.
+data HandleKey = HandleKey {
+ handleValue :: {-# UNPACK #-} !HANDLE
+ , handleUnique :: {-# UNPACK #-} !Unique
+ } deriving ( Eq -- ^ @since 4.4.0.0
+ , Show -- ^ @since 4.4.0.0
+ )
+
+-- | Callback invoked on I/O events.
+type EventCallback = HandleKey -> Event -> IO ()
+
+registerHandle :: Manager -> EventCallback -> HANDLE -> Event -> Lifetime
+ -> IO HandleKey
+registerHandle (Manager{..}) cb hwnd evs lt = do
+ u <- newUnique mgrUniqueSource
+ let reg = HandleKey hwnd u
+ hwnd' = fromIntegral $ ptrToIntPtr hwnd
+ el = I.eventLifetime evs lt
+ !hwdd = HandleData reg el cb
+ event = EventData evs [(evs, hwdd)]
+ _ <- withMVar mgrEvntHandlers $ \evts -> do
+ IT.insertWith mappend hwnd' event evts
+ wakeupIOManager
+ return reg
+
+unregisterHandle :: Manager -> HandleKey -> IO ()
+unregisterHandle (Manager{..}) key@HandleKey{..} = do
+ withMVar mgrEvntHandlers $ \evts -> do
+ let hwnd' = fromIntegral $ ptrToIntPtr handleValue
+ val <- IT.lookup hwnd' evts
+ case val of
+ Nothing -> return ()
+ Just (EventData evs lst) -> do
+ let cmp (_, a) (_, b) = tokenKey a == tokenKey b
+ key' = (undefined, HandleData key undefined undefined)
+ updated = deleteBy cmp key' lst
+ new_lst = EventData evs updated
+ _ <- IT.updateWith (\_ -> return new_lst) hwnd' evts
+ return ()
+
+-- ---------------------------------------------------------------------------
+-- debugging
+
+#if defined(DEBUG)
+c_DEBUG_DUMP :: IO Bool
+c_DEBUG_DUMP = return True -- scheduler `fmap` getDebugFlags
+#endif
+
+debugIO :: String -> IO ()
+#if defined(DEBUG_TRACE)
+debugIO s = traceEventIO ( "winIO :: " ++ s)
+#elif defined(DEBUG)
+debugIO s
+ = do debug <- c_DEBUG_DUMP
+ if debug
+ then do tid <- myThreadId
+ let pref = if threadedIOMgr then "\t" else ""
+ _ <- withCStringLen (pref ++ "winio: " ++ s ++ " (" ++
+ showThreadId tid ++ ")\n") $
+ \(p, len) -> c_write 2 (castPtr p) (fromIntegral len)
+ return ()
+ else do return ()
+#else
+debugIO _ = return ()
+#endif
+
+-- dbxIO :: String -> IO ()
+-- dbxIO s = do tid <- myThreadId
+-- let pref = if threadedIOMgr then "\t" else ""
+-- _ <- withCStringLen (pref ++ "winio: " ++ s ++ " (" ++
+-- showThreadId tid ++ ")\n") $
+-- \(p, len) -> c_write 2 (castPtr p) (fromIntegral len)
+-- return () \ No newline at end of file
diff --git a/libraries/base/GHC/Event/Windows/Clock.hs b/libraries/base/GHC/Event/Windows/Clock.hs
new file mode 100644
index 0000000000..34728248c0
--- /dev/null
+++ b/libraries/base/GHC/Event/Windows/Clock.hs
@@ -0,0 +1,55 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+module GHC.Event.Windows.Clock (
+ Clock,
+ Seconds,
+ getTime,
+ getClock,
+
+ -- * Specific implementations
+ queryPerformanceCounter,
+ getTickCount64
+) where
+
+import qualified GHC.Event.Windows.FFI as FFI
+
+import Data.Maybe
+import GHC.Base
+import GHC.Real
+
+-- | Monotonic clock
+newtype Clock = Clock (IO Seconds)
+
+type Seconds = Double
+
+-- | Get the current time, in seconds since some fixed time in the past.
+getTime :: Clock -> IO Seconds
+getTime (Clock io) = io
+
+-- | Figure out what time API to use, and return a 'Clock' for accessing it.
+getClock :: IO Clock
+getClock = tryInOrder
+ [ queryPerformanceCounter
+ , fmap Just getTickCount64
+ ]
+
+tryInOrder :: Monad m => [m (Maybe a)] -> m a
+tryInOrder (x:xs) = x >>= maybe (tryInOrder xs) return
+tryInOrder [] = undefined
+
+mapJust :: Monad m => m (Maybe a) -> (a -> b) -> m (Maybe b)
+mapJust m f = liftM (fmap f) m
+
+queryPerformanceCounter :: IO (Maybe Clock)
+queryPerformanceCounter =
+ FFI.queryPerformanceFrequency `mapJust` \freq ->
+ Clock $! do
+ count <- FFI.queryPerformanceCounter
+ let !secs = fromIntegral count / fromIntegral freq
+ return secs
+
+getTickCount64 :: IO Clock
+getTickCount64 =
+ return $! Clock $! do
+ msecs <- FFI.getTickCount64
+ return $! fromIntegral msecs / 1000
diff --git a/libraries/base/GHC/Event/Windows/ConsoleEvent.hsc b/libraries/base/GHC/Event/Windows/ConsoleEvent.hsc
new file mode 100644
index 0000000000..fd6f790d3b
--- /dev/null
+++ b/libraries/base/GHC/Event/Windows/ConsoleEvent.hsc
@@ -0,0 +1,72 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE CPP, NoImplicitPrelude, MagicHash, UnboxedTuples #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.Event.Windows.ConsoleEvent
+-- Copyright : (c) The University of Glasgow, 1994-2002
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : cvs-ghc@haskell.org
+-- Stability : internal
+-- Portability : non-portable (GHC extensions)
+--
+-- Windows I/O manager interfaces. Depending on which I/O Subsystem is used
+-- requests will be routed to different places.
+--
+-----------------------------------------------------------------------------
+
+module GHC.Event.Windows.ConsoleEvent (
+ ConsoleEvent (..),
+ start_console_handler,
+ toWin32ConsoleEvent,
+ win32ConsoleHandler
+) where
+
+import GHC.Base
+import GHC.Conc.Sync
+import GHC.Enum (Enum)
+import GHC.IO (unsafePerformIO)
+import GHC.MVar
+import GHC.Num (Num(..))
+import GHC.Read (Read)
+import GHC.Word (Word32)
+import GHC.Show (Show)
+
+#include <windows.h>
+
+data ConsoleEvent
+ = ControlC
+ | Break
+ | Close
+ -- these are sent to Services only.
+ | Logoff
+ | Shutdown
+ deriving ( Eq -- ^ @since 4.3.0.0
+ , Ord -- ^ @since 4.3.0.0
+ , Enum -- ^ @since 4.3.0.0
+ , Show -- ^ @since 4.3.0.0
+ , Read -- ^ @since 4.3.0.0
+ )
+
+start_console_handler :: Word32 -> IO ()
+start_console_handler r =
+ case toWin32ConsoleEvent r of
+ Just x -> withMVar win32ConsoleHandler $ \handler -> do
+ _ <- forkIO (handler x)
+ return ()
+ Nothing -> return ()
+
+toWin32ConsoleEvent :: (Eq a, Num a) => a -> Maybe ConsoleEvent
+toWin32ConsoleEvent ev =
+ case ev of
+ #{const CTRL_C_EVENT } -> Just ControlC
+ #{const CTRL_BREAK_EVENT } -> Just Break
+ #{const CTRL_CLOSE_EVENT } -> Just Close
+ #{const CTRL_LOGOFF_EVENT } -> Just Logoff
+ #{const CTRL_SHUTDOWN_EVENT } -> Just Shutdown
+ _ -> Nothing
+
+win32ConsoleHandler :: MVar (ConsoleEvent -> IO ())
+win32ConsoleHandler =
+ unsafePerformIO (newMVar (errorWithoutStackTrace "win32ConsoleHandler"))
diff --git a/libraries/base/GHC/Event/Windows/FFI.hsc b/libraries/base/GHC/Event/Windows/FFI.hsc
new file mode 100644
index 0000000000..b9c766c977
--- /dev/null
+++ b/libraries/base/GHC/Event/Windows/FFI.hsc
@@ -0,0 +1,395 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DoAndIfThenElse #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE EmptyDataDecls #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-------------------------------------------------------------------------------
+-- |
+-- Module : GHC.Event.Windows.FFI
+-- Copyright : (c) Tamar Christina 2019
+-- License : BSD-style (see the file libraries/base/LICENSE)
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : experimental
+-- Portability : non-portable
+--
+-- WinIO Windows API Foreign Function imports
+--
+-------------------------------------------------------------------------------
+
+module GHC.Event.Windows.FFI (
+ -- * IOCP
+ IOCP(..),
+ CompletionKey,
+ newIOCP,
+ associateHandleWithIOCP,
+ getQueuedCompletionStatusEx,
+ postQueuedCompletionStatus,
+ getOverlappedResult,
+
+ -- * Overlapped
+ OVERLAPPED,
+ LPOVERLAPPED,
+ OVERLAPPED_ENTRY(..),
+ LPOVERLAPPED_ENTRY,
+ HASKELL_OVERLAPPED,
+ LPHASKELL_OVERLAPPED,
+ allocOverlapped,
+ zeroOverlapped,
+ pokeOffsetOverlapped,
+ overlappedIOStatus,
+ overlappedIONumBytes,
+
+ -- * Cancel pending I/O
+ cancelIoEx,
+ cancelIoEx',
+
+ -- * Monotonic time
+
+ -- ** GetTickCount
+ getTickCount64,
+
+ -- ** QueryPerformanceCounter
+ queryPerformanceCounter,
+ queryPerformanceFrequency,
+
+ -- ** Miscellaneous
+ throwWinErr,
+ setLastError
+) where
+
+#include <ntstatus.h>
+#include <windows.h>
+#include "winio_structs.h"
+
+##include "windows_cconv.h"
+
+import Data.Maybe
+import Foreign
+import GHC.Base
+import GHC.Num ((*))
+import GHC.Real (fromIntegral)
+import GHC.Show
+import GHC.Windows
+import qualified GHC.Event.Array as A
+import qualified GHC.Windows as Win32
+import GHC.IO.Handle.Internals (debugIO)
+
+------------------------------------------------------------------------
+-- IOCP
+
+-- | An I/O completion port.
+newtype IOCP = IOCP HANDLE
+ deriving (Eq, Ord, Show)
+
+type CompletionKey = ULONG_PTR
+
+-- | This function has two distinct purposes depending on the value of
+-- The completion port handle:
+--
+-- - When the IOCP port is NULL then the function creates a new I/O completion
+-- port. See `newIOCP`.
+--
+-- - When The port contains a valid handle then the given handle is
+-- associated with he given completion port handle. Once associated it
+-- cannot be easily changed. Associating a Handle with a Completion Port
+-- allows the I/O manager's worker threads to handle requests to the given
+-- handle.
+foreign import WINDOWS_CCONV unsafe "windows.h CreateIoCompletionPort"
+ c_CreateIoCompletionPort :: HANDLE -> IOCP -> ULONG_PTR -> DWORD
+ -> IO IOCP
+
+-- | Create a new I/O completion port.
+newIOCP :: IO IOCP
+newIOCP = failIf (== IOCP nullPtr) "newIOCP" $
+ c_CreateIoCompletionPort iNVALID_HANDLE_VALUE (IOCP nullPtr) 0 0
+
+-- | Associate a HANDLE with an I/O completion port.
+associateHandleWithIOCP :: IOCP -> HANDLE -> CompletionKey -> IO ()
+associateHandleWithIOCP iocp handle completionKey =
+ failIf_ (/= iocp) "associateHandleWithIOCP" $
+ c_CreateIoCompletionPort handle iocp completionKey 0
+
+foreign import WINDOWS_CCONV safe "windows.h GetOverlappedResult"
+ c_GetOverlappedResult :: HANDLE -> LPOVERLAPPED -> Ptr DWORD -> BOOL
+ -> IO BOOL
+
+-- | Get the result of a single overlap operation without the IO manager
+getOverlappedResult :: HANDLE -> Ptr OVERLAPPED -> BOOL -> IO (Maybe DWORD)
+getOverlappedResult handle lp block
+ = alloca $ \bytes ->
+ do res <- c_GetOverlappedResult handle lp bytes block
+ if res
+ then fmap Just $ peek bytes
+ else return Nothing
+
+foreign import WINDOWS_CCONV safe "windows.h GetQueuedCompletionStatusEx"
+ c_GetQueuedCompletionStatusEx :: IOCP -> LPOVERLAPPED_ENTRY -> Word32
+ -> Ptr ULONG -> DWORD -> BOOL -> IO BOOL
+
+-- | Note [Completion Ports]
+-- When an I/O operation has been queued by an operation
+-- (ReadFile/WriteFile/etc) it is placed in a queue that the driver uses when
+-- servicing IRQs. This queue has some important properties:
+--
+-- 1.) It is not an ordered queue. Requests may be performed out of order as
+-- as the OS's native I/O manager may try to re-order requests such that as
+-- few random seeks as possible are needed to complete the pending
+-- operations. As such do not assume a fixed order between something being
+-- queued and dequeued.
+--
+-- 2.) Operations may skip the queue entirely. In which case they do not end in
+-- in this function. (This is an optimization flag we have turned on. See
+-- `openFile`.)
+--
+-- 3.) Across this call the specified OVERLAPPED_ENTRY buffer MUST remain live,
+-- and the buffer for an I/O operation cannot be freed or moved until
+-- `getOverlappedResult` says it's done. The reason is the kernel may not
+-- have fully released the buffer, or finished writing to it when this
+-- operation returns. Failure to adhere to this will cause your IRQs to be
+-- silently dropped and your program will never receive a completion for it.
+-- This means that the OVERLAPPED buffer must also remain valid for the
+-- duration of the call and as such must be allocated on the unmanaged heap.
+--
+-- 4.) When a thread calls this method it is associated with the I/O manager's
+-- worker threads pool. You should always use dedicated threads for this
+-- since the OS I/O manager will now monitor the threads. If the thread
+-- becomes blocked for whatever reason, the Haskell I/O manager will wake up
+-- another threads from it's pool to service the remaining results.
+-- A new thread will also be woken up from the pool when the previous thread
+-- is busy servicing requests and new requests have finished. For this
+-- reason the Haskell I/O manager multiplexes I/O operations from N haskell
+-- threads into 1 completion port, which is serviced by M native threads in
+-- an asynchronous method. This allows it to scale efficiently.
+getQueuedCompletionStatusEx :: IOCP
+ -> A.Array OVERLAPPED_ENTRY
+ -> DWORD -- ^ Timeout in milliseconds (or
+ -- 'GHC.Windows.iNFINITE')
+ -> IO Int
+getQueuedCompletionStatusEx iocp arr timeout =
+ alloca $ \num_removed_ptr ->do
+ A.unsafeLoad arr $ \oes cap -> do
+ -- TODO: remove after debugging
+ fillBytes oes 0 (cap * (sizeOf (undefined :: OVERLAPPED_ENTRY)))
+ debugIO $ "-- call getQueuedCompletionStatusEx "
+ -- don't block the call if the rts is not supporting threads.
+ -- this would block the entire program.
+ let alertable = False -- not rtsSupportsBoundThreads
+ ok <- c_GetQueuedCompletionStatusEx iocp oes (fromIntegral cap)
+ num_removed_ptr timeout alertable
+ debugIO $ "-- call getQueuedCompletionStatusEx: " ++ show ok
+ err <- getLastError
+ nc <- (peek num_removed_ptr)
+ debugIO $ "-- getQueuedCompletionStatusEx: n=" ++ show nc ++ " ,err=" ++ show err
+ if ok then fromIntegral `fmap` peek num_removed_ptr
+ else do debugIO $ "failed getQueuedCompletionStatusEx: " ++ show err
+ if err == #{const WAIT_TIMEOUT} || alertable then return 0
+ else failWith "GetQueuedCompletionStatusEx" err
+
+overlappedIOStatus :: LPOVERLAPPED -> IO NTSTATUS
+overlappedIOStatus lpol = do
+ status <- #{peek OVERLAPPED, Internal} lpol
+ -- TODO: Map NTSTATUS to ErrCode?
+ -- See https://github.com/libuv/libuv/blob/b12624c13693c4d29ca84b3556eadc9e9c0936a4/src/win/winsock.c#L153
+ return status
+{-# INLINE overlappedIOStatus #-}
+
+overlappedIONumBytes :: LPOVERLAPPED -> IO ULONG_PTR
+overlappedIONumBytes lpol = do
+ bytes <- #{peek OVERLAPPED, InternalHigh} lpol
+ return bytes
+{-# INLINE overlappedIONumBytes #-}
+
+foreign import WINDOWS_CCONV unsafe "windows.h PostQueuedCompletionStatus"
+ c_PostQueuedCompletionStatus :: IOCP -> DWORD -> ULONG_PTR -> LPOVERLAPPED
+ -> IO BOOL
+
+-- | Manually post a completion to the specified I/O port. This will wake up
+-- a thread waiting `GetQueuedCompletionStatusEx`.
+postQueuedCompletionStatus :: IOCP -> DWORD -> CompletionKey -> LPOVERLAPPED
+ -> IO ()
+postQueuedCompletionStatus iocp numBytes completionKey lpol =
+ failIfFalse_ "PostQueuedCompletionStatus" $
+ c_PostQueuedCompletionStatus iocp numBytes completionKey lpol
+
+------------------------------------------------------------------------
+-- Overlapped
+
+-- | Tag type for @LPOVERLAPPED@.
+data OVERLAPPED
+
+-- | Tag type for the extended version of @OVERLAPPED@ containg some book
+-- keeping information.
+data HASKELL_OVERLAPPED
+
+-- | Identifies an I/O operation. Used as the @LPOVERLAPPED@ parameter
+-- for overlapped I/O functions (e.g. @ReadFile@, @WSASend@).
+type LPOVERLAPPED = Ptr OVERLAPPED
+
+-- | Pointer to the extended HASKELL_OVERLAPPED function.
+type LPHASKELL_OVERLAPPED = Ptr HASKELL_OVERLAPPED
+
+-- | An array of these is passed to GetQueuedCompletionStatusEx as an output
+-- argument.
+data OVERLAPPED_ENTRY = OVERLAPPED_ENTRY {
+ lpCompletionKey :: ULONG_PTR,
+ lpOverlapped :: LPOVERLAPPED,
+ dwNumberOfBytesTransferred :: DWORD
+ }
+
+type LPOVERLAPPED_ENTRY = Ptr OVERLAPPED_ENTRY
+
+instance Storable OVERLAPPED_ENTRY where
+ sizeOf _ = #{size OVERLAPPED_ENTRY}
+ alignment _ = #{alignment OVERLAPPED_ENTRY}
+
+ peek ptr = do
+ lpCompletionKey <- #{peek OVERLAPPED_ENTRY, lpCompletionKey} ptr
+ lpOverlapped <- #{peek OVERLAPPED_ENTRY, lpOverlapped} ptr
+ dwNumberOfBytesTransferred <-
+ #{peek OVERLAPPED_ENTRY, dwNumberOfBytesTransferred} ptr
+ let !oe = OVERLAPPED_ENTRY{..}
+ return oe
+
+ poke ptr OVERLAPPED_ENTRY{..} = do
+ #{poke OVERLAPPED_ENTRY, lpCompletionKey} ptr lpCompletionKey
+ #{poke OVERLAPPED_ENTRY, lpOverlapped} ptr lpOverlapped
+ #{poke OVERLAPPED_ENTRY, dwNumberOfBytesTransferred}
+ ptr dwNumberOfBytesTransferred
+
+-- | Allocate a new
+-- <http://msdn.microsoft.com/en-us/library/windows/desktop/ms684342%28v=vs.85%29.aspx
+-- OVERLAPPED> structure on the unmanaged heap. This also zeros the memory to
+-- prevent the values inside the struct to be incorrectlt interpreted as data
+-- payload.
+--
+-- We extend the overlapped structure with some extra book keeping information
+-- such that we don't have to do a lookup on the Haskell side.
+--
+-- Future: We can gain some performance here by using a pool instead of calling
+-- malloc for each request. A simple block allocator would be very
+-- useful here, especially when we implement sockets support.
+allocOverlapped :: Word64 -- ^ Offset/OffsetHigh
+ -> IO (Ptr HASKELL_OVERLAPPED)
+allocOverlapped offset = do
+ lpol <- mallocBytes #{size HASKELL_OVERLAPPED}
+ zeroOverlapped lpol
+ pokeOffsetOverlapped (castPtr lpol) offset
+ return lpol
+
+-- | Zero-fill an HASKELL_OVERLAPPED structure.
+zeroOverlapped :: LPHASKELL_OVERLAPPED -> IO ()
+zeroOverlapped lpol = fillBytes lpol 0 #{size HASKELL_OVERLAPPED}
+{-# INLINE zeroOverlapped #-}
+
+-- | Set the offset field in an OVERLAPPED structure.
+pokeOffsetOverlapped :: LPOVERLAPPED -> Word64 -> IO ()
+pokeOffsetOverlapped lpol offset = do
+ let (offsetHigh, offsetLow) = Win32.ddwordToDwords offset
+ #{poke OVERLAPPED, Offset} lpol offsetLow
+ #{poke OVERLAPPED, OffsetHigh} lpol offsetHigh
+{-# INLINE pokeOffsetOverlapped #-}
+
+------------------------------------------------------------------------
+-- Cancel pending I/O
+
+-- | CancelIo shouldn't block, but cancellation happens infrequently,
+-- so we might as well be on the safe side.
+foreign import WINDOWS_CCONV unsafe "windows.h CancelIoEx"
+ c_CancelIoEx :: HANDLE -> LPOVERLAPPED -> IO BOOL
+
+-- | Cancel all pending overlapped I/O for the given file that was initiated by
+-- the current OS thread. Cancelling is just a request for cancellation and
+-- before the OVERLAPPED struct is freed we must make sure that the IRQ has been
+-- removed from the queue. See `getOverlappedResult`.
+cancelIoEx :: HANDLE -> LPOVERLAPPED -> IO ()
+cancelIoEx h o = failIfFalse_ "CancelIoEx" . c_CancelIoEx h $ o
+
+cancelIoEx' :: HANDLE -> LPOVERLAPPED -> IO Bool
+cancelIoEx' = c_CancelIoEx
+
+------------------------------------------------------------------------
+-- Monotonic time
+
+foreign import WINDOWS_CCONV "windows.h GetTickCount64"
+ c_GetTickCount64 :: IO #{type ULONGLONG}
+
+-- | Call the @GetTickCount64@ function, which returns a monotonic time in
+-- milliseconds.
+--
+-- Problems:
+--
+-- * Low resolution (10 to 16 milliseconds).
+--
+-- <http://msdn.microsoft.com/en-us/library/windows/desktop/ms724408%28v=vs.85%29.aspx>
+getTickCount64 :: IO Word64
+getTickCount64 = c_GetTickCount64
+
+-- | Call the @QueryPerformanceCounter@ function.
+--
+-- Problems:
+--
+-- * Might not be available on some hardware. Use 'queryPerformanceFrequency'
+-- to test for availability before calling this function.
+--
+-- * On a multiprocessor computer, may produce different results on
+-- different processors due to hardware bugs.
+--
+-- To get a monotonic time in seconds, divide the result of
+-- 'queryPerformanceCounter' by that of 'queryPerformanceFrequency'.
+--
+-- <http://msdn.microsoft.com/en-us/library/windows/desktop/ms644904%28v=vs.85%29.aspx>
+queryPerformanceCounter :: IO Int64
+queryPerformanceCounter =
+ callQP c_QueryPerformanceCounter
+ >>= maybe (throwGetLastError "QueryPerformanceCounter") return
+
+-- | Call the @QueryPerformanceFrequency@ function. Return 'Nothing' if the
+-- hardware does not provide a high-resolution performance counter.
+--
+-- <http://msdn.microsoft.com/en-us/library/windows/desktop/ms644905%28v=vs.85%29.aspx>
+queryPerformanceFrequency :: IO (Maybe Int64)
+queryPerformanceFrequency = do
+ m <- callQP c_QueryPerformanceFrequency
+ case m of
+ Nothing -> return Nothing
+ Just 0 -> return Nothing -- Shouldn't happen; just a safeguard to
+ -- avoid a zero denominator.
+ Just freq -> return (Just freq)
+
+type QPFunc = Ptr Int64 -> IO BOOL
+
+foreign import WINDOWS_CCONV "Windows.h QueryPerformanceCounter"
+ c_QueryPerformanceCounter :: QPFunc
+
+foreign import WINDOWS_CCONV "Windows.h QueryPerformanceFrequency"
+ c_QueryPerformanceFrequency :: QPFunc
+
+callQP :: QPFunc -> IO (Maybe Int64)
+callQP qpfunc =
+ allocaBytes #{size LARGE_INTEGER} $ \ptr -> do
+ ok <- qpfunc ptr
+ if ok then do
+ n <- #{peek LARGE_INTEGER, QuadPart} ptr
+ return (Just n)
+ else
+ return Nothing
+
+------------------------------------------------------------------------
+-- Miscellaneous
+
+type ULONG_PTR = #type ULONG_PTR
+
+throwWinErr :: String -> ErrCode -> IO a
+throwWinErr loc err = do
+ c_SetLastError err
+ Win32.failWith loc err
+
+setLastError :: ErrCode -> IO ()
+setLastError = c_SetLastError
+
+foreign import WINDOWS_CCONV unsafe "windows.h SetLastError"
+ c_SetLastError :: ErrCode -> IO ()
diff --git a/libraries/base/GHC/Event/Windows/ManagedThreadPool.hs b/libraries/base/GHC/Event/Windows/ManagedThreadPool.hs
new file mode 100644
index 0000000000..94e498b58e
--- /dev/null
+++ b/libraries/base/GHC/Event/Windows/ManagedThreadPool.hs
@@ -0,0 +1,93 @@
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DoAndIfThenElse #-}
+{-# LANGUAGE ForeignFunctionInterface #-}
+{-# LANGUAGE EmptyDataDecls #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+
+-------------------------------------------------------------------------------
+-- |
+-- Module : GHC.Event.Windows.ManagedThreadPool
+-- Copyright : (c) Tamar Christina 2019
+-- License : BSD-style (see the file libraries/base/LICENSE)
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : experimental
+-- Portability : non-portable
+--
+-- WinIO Windows Managed Thread pool API. This thread pool scales dynamically
+-- based on demand.
+--
+-------------------------------------------------------------------------------
+
+module GHC.Event.Windows.ManagedThreadPool
+ ( ThreadPool(..)
+ , startThreadPool
+ , notifyRunning
+ , notifyWaiting
+ , monitorThreadPool
+ ) where
+
+import Control.Concurrent.MVar
+import Data.Maybe
+import Foreign
+import GHC.Base
+import GHC.Num ((-), (+))
+import GHC.Real (fromIntegral)
+import qualified GHC.Event.Array as A
+import GHC.IO.Handle.Internals (debugIO)
+import GHC.Conc.Sync (ThreadId(..))
+import GHC.RTS.Flags
+
+------------------------------------------------------------------------
+-- Thread spool manager
+
+type WorkerJob = IO ()
+
+-- | Thread pool manager state
+data ThreadPool = ThreadPool
+ { thrMainThread :: Maybe ThreadId
+ , thrMaxThreads :: {-# UNPACK #-} !Int
+ , thrMinThreads :: {-# UNPACK #-} !Int
+ , thrCurThreads :: {-# UNPACK #-} !Int
+ , thrCallBack :: WorkerJob
+ , thrActiveThreads :: MVar Int
+ , thrMonitor :: MVar ()
+ , thrThreadIds :: {-#UNPACK #-} !(A.Array ThreadId)
+ }
+
+startThreadPool :: WorkerJob -> IO ThreadPool
+startThreadPool job = do
+ debugIO "Starting I/O manager threadpool..."
+ let thrMinThreads = 2
+ let thrCurThreads = 0
+ let thrCallBack = job
+ thrMaxThreads <- (fromIntegral . numIoWorkerThreads) `fmap` getMiscFlags
+ thrActiveThreads <- newMVar 0
+ thrMonitor <- newEmptyMVar
+ thrThreadIds <- undefined -- A.new thrMaxThreads
+ let thrMainThread = Nothing
+
+ let !pool = ThreadPool{..}
+ return pool
+
+monitorThreadPool :: MVar () -> IO ()
+monitorThreadPool monitor = do
+ _active <- takeMVar monitor
+
+ return ()
+
+notifyRunning :: Maybe ThreadPool -> IO ()
+notifyRunning Nothing = return ()
+notifyRunning (Just pool) = do
+ modifyMVar_ (thrActiveThreads pool) (\x -> return $ x + 1)
+ _ <- tryPutMVar (thrMonitor pool) ()
+ return ()
+
+notifyWaiting :: Maybe ThreadPool -> IO ()
+notifyWaiting Nothing = return ()
+notifyWaiting (Just pool) = do
+ modifyMVar_ (thrActiveThreads pool) (\x -> return $ x - 1)
+ _ <- tryPutMVar (thrMonitor pool) ()
+ return ()
diff --git a/libraries/base/GHC/Event/Windows/Thread.hs b/libraries/base/GHC/Event/Windows/Thread.hs
new file mode 100644
index 0000000000..57faa9de80
--- /dev/null
+++ b/libraries/base/GHC/Event/Windows/Thread.hs
@@ -0,0 +1,35 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+
+module GHC.Event.Windows.Thread (
+ ensureIOManagerIsRunning,
+ interruptIOManager,
+ threadDelay,
+ registerDelay,
+) where
+
+import GHC.Conc.Sync
+import GHC.Base
+import GHC.Event.Windows
+import GHC.IO
+import GHC.IOPort
+
+ensureIOManagerIsRunning :: IO ()
+ensureIOManagerIsRunning = wakeupIOManager
+
+interruptIOManager :: IO ()
+interruptIOManager = interruptSystemManager
+
+threadDelay :: Int -> IO ()
+threadDelay usecs = mask_ $ do
+ m <- newEmptyIOPort
+ mgr <- getSystemManager
+ reg <- registerTimeout mgr usecs $ writeIOPort m () >> return ()
+ readIOPort m `onException` unregisterTimeout mgr reg
+
+registerDelay :: Int -> IO (TVar Bool)
+registerDelay usecs = do
+ t <- newTVarIO False
+ mgr <- getSystemManager
+ _ <- registerTimeout mgr usecs $ atomically $ writeTVar t True
+ return t
+
diff --git a/libraries/base/GHC/IO/Buffer.hs b/libraries/base/GHC/IO/Buffer.hs
index 447c574e2b..167bc2a346 100644
--- a/libraries/base/GHC/IO/Buffer.hs
+++ b/libraries/base/GHC/IO/Buffer.hs
@@ -31,6 +31,8 @@ module GHC.IO.Buffer (
bufferAdd,
slideContents,
bufferAdjustL,
+ bufferAddOffset,
+ bufferAdjustOffset,
-- ** Inspecting
isEmptyBuffer,
@@ -39,6 +41,7 @@ module GHC.IO.Buffer (
isWriteBuffer,
bufferElems,
bufferAvailable,
+ bufferOffset,
summaryBuffer,
-- ** Operating on the raw buffer as a Ptr
@@ -68,6 +71,7 @@ import GHC.Ptr
import GHC.Word
import GHC.Show
import GHC.Real
+import GHC.List
import Foreign.C.Types
import Foreign.ForeignPtr
import Foreign.Storable
@@ -89,6 +93,9 @@ import Foreign.Storable
-- broken. In particular, the built-in codecs
-- e.g. GHC.IO.Encoding.UTF{8,16,32} need to use isFullCharBuffer or
-- similar in place of the ow >= os comparisons.
+--
+-- Tamar: We need to do this eventually for Windows, as we have to re-encode
+-- the text as UTF-16 anyway, so if we can avoid it it would be great.
-- ---------------------------------------------------------------------------
-- Raw blocks of data
@@ -177,13 +184,27 @@ charSize = 4
-- a memory-mapped file and in which case 'bufL' will point to the
-- next location to be written, which is not necessarily the beginning
-- of the file.
+--
+-- On Posix systems the I/O manager has an implicit reliance on doing a file
+-- read moving the file pointer. However on Windows async operations the kernel
+-- object representing a file does not use the file pointer offset. Logically
+-- this makes sense since operations can be performed in any arbitrary order.
+-- OVERLAPPED operations don't respect the file pointer offset as their
+-- intention is to support arbitrary async reads to anywhere at a much lower
+-- level. As such we should explicitly keep track of the file offsets of the
+-- target in the buffer. Any operation to seek should also update this entry.
+--
+-- In order to keep us sane we try to uphold the invariant that any function
+-- being passed a Handle is responsible for updating the handles offset unless
+-- other behaviour is documented.
data Buffer e
= Buffer {
- bufRaw :: !(RawBuffer e),
- bufState :: BufferState,
- bufSize :: !Int, -- in elements, not bytes
- bufL :: !Int, -- offset of first item in the buffer
- bufR :: !Int -- offset of last item + 1
+ bufRaw :: !(RawBuffer e),
+ bufState :: BufferState,
+ bufSize :: !Int, -- in elements, not bytes
+ bufOffset :: !Word64, -- start location for next read/write
+ bufL :: !Int, -- offset of first item in the buffer
+ bufR :: !Int -- offset of last item + 1
}
#if defined(CHARBUF_UTF16)
@@ -237,9 +258,22 @@ bufferAdjustL l buf@Buffer{ bufR=w }
bufferAdd :: Int -> Buffer e -> Buffer e
bufferAdd i buf@Buffer{ bufR=w } = buf{ bufR=w+i }
+bufferOffset :: Buffer e -> Word64
+bufferOffset Buffer{ bufOffset=off } = off
+
+bufferAdjustOffset :: Word64 -> Buffer e -> Buffer e
+bufferAdjustOffset offs buf = buf{ bufOffset=offs }
+
+-- The adjustment to the offset can be 32bit int on 32 platforms.
+-- This is fine, we only use this after reading into/writing from
+-- the buffer so we will never overflow here.
+bufferAddOffset :: Int -> Buffer e -> Buffer e
+bufferAddOffset offs buf@Buffer{ bufOffset=w } =
+ buf{ bufOffset=w+(fromIntegral offs) }
+
emptyBuffer :: RawBuffer e -> Int -> BufferState -> Buffer e
emptyBuffer raw sz state =
- Buffer{ bufRaw=raw, bufState=state, bufR=0, bufL=0, bufSize=sz }
+ Buffer{ bufRaw=raw, bufState=state, bufOffset=0, bufR=0, bufL=0, bufSize=sz }
newByteBuffer :: Int -> BufferState -> IO (Buffer Word8)
newByteBuffer c st = newBuffer c c st
@@ -266,9 +300,16 @@ foreign import ccall unsafe "memmove"
summaryBuffer :: Buffer a -> String
summaryBuffer !buf -- Strict => slightly better code
- = "buf" ++ show (bufSize buf) ++ "(" ++ show (bufL buf) ++ "-" ++ show (bufR buf) ++ ")"
-
--- INVARIANTS on Buffers:
+ = ppr (show $ bufRaw buf) ++ "@buf" ++ show (bufSize buf)
+ ++ "(" ++ show (bufL buf) ++ "-" ++ show (bufR buf) ++ ")"
+ ++ " (>=" ++ show (bufOffset buf) ++ ")"
+ where ppr :: String -> String
+ ppr ('0':'x':xs) = let p = dropWhile (=='0') xs
+ in if null p then "0x0" else '0':'x':p
+ ppr x = x
+
+-- Note [INVARIANTS on Buffers]
+-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-- * r <= w
-- * if r == w, and the buffer is for reading, then r == 0 && w == 0
-- * a write buffer is never full. If an operation
diff --git a/libraries/base/GHC/IO/BufferedIO.hs b/libraries/base/GHC/IO/BufferedIO.hs
index cd38cefe07..c6f4cde477 100644
--- a/libraries/base/GHC/IO/BufferedIO.hs
+++ b/libraries/base/GHC/IO/BufferedIO.hs
@@ -92,9 +92,11 @@ class BufferedIO dev where
readBuf :: RawIO dev => dev -> Buffer Word8 -> IO (Int, Buffer Word8)
readBuf dev bbuf = do
let bytes = bufferAvailable bbuf
+ let offset = bufferOffset bbuf
res <- withBuffer bbuf $ \ptr ->
- RawIO.read dev (ptr `plusPtr` bufR bbuf) bytes
- return (res, bbuf{ bufR = bufR bbuf + res })
+ RawIO.read dev (ptr `plusPtr` bufR bbuf) offset bytes
+ let bbuf' = bufferAddOffset res bbuf
+ return (res, bbuf'{ bufR = bufR bbuf' + res })
-- zero indicates end of file
readBufNonBlocking :: RawIO dev => dev -> Buffer Word8
@@ -103,24 +105,30 @@ readBufNonBlocking :: RawIO dev => dev -> Buffer Word8
Buffer Word8)
readBufNonBlocking dev bbuf = do
let bytes = bufferAvailable bbuf
+ let offset = bufferOffset bbuf
res <- withBuffer bbuf $ \ptr ->
- IODevice.readNonBlocking dev (ptr `plusPtr` bufR bbuf) bytes
+ IODevice.readNonBlocking dev (ptr `plusPtr` bufR bbuf) offset bytes
case res of
Nothing -> return (Nothing, bbuf)
- Just n -> return (Just n, bbuf{ bufR = bufR bbuf + n })
+ Just n -> do let bbuf' = bufferAddOffset n bbuf
+ return (Just n, bbuf'{ bufR = bufR bbuf' + n })
writeBuf :: RawIO dev => dev -> Buffer Word8 -> IO (Buffer Word8)
writeBuf dev bbuf = do
let bytes = bufferElems bbuf
+ let offset = bufferOffset bbuf
withBuffer bbuf $ \ptr ->
- IODevice.write dev (ptr `plusPtr` bufL bbuf) bytes
- return bbuf{ bufL=0, bufR=0 }
+ IODevice.write dev (ptr `plusPtr` bufL bbuf) offset bytes
+ let bbuf' = bufferAddOffset bytes bbuf
+ return bbuf'{ bufL=0, bufR=0 }
-- XXX ToDo
writeBufNonBlocking :: RawIO dev => dev -> Buffer Word8 -> IO (Int, Buffer Word8)
writeBufNonBlocking dev bbuf = do
let bytes = bufferElems bbuf
+ let offset = bufferOffset bbuf
res <- withBuffer bbuf $ \ptr ->
- IODevice.writeNonBlocking dev (ptr `plusPtr` bufL bbuf) bytes
- return (res, bufferAdjustL (bufL bbuf + res) bbuf)
+ IODevice.writeNonBlocking dev (ptr `plusPtr` bufL bbuf) offset bytes
+ let bbuf' = bufferAddOffset bytes bbuf
+ return (res, bufferAdjustL (bufL bbuf + res) bbuf')
diff --git a/libraries/base/GHC/IO/Device.hs b/libraries/base/GHC/IO/Device.hs
index 024ff7bbbb..0f244ae626 100644
--- a/libraries/base/GHC/IO/Device.hs
+++ b/libraries/base/GHC/IO/Device.hs
@@ -34,26 +34,29 @@ import GHC.IO
import {-# SOURCE #-} GHC.IO.Exception ( unsupportedOperation )
-- | A low-level I/O provider where the data is bytes in memory.
+-- The Word64 offsets currently have no effect on POSIX system or consoles
+-- where the implicit behaviour of the C runtime is assume to move the file
+-- pointer on every read/write without needing an explicit seek.
class RawIO a where
- -- | Read up to the specified number of bytes, returning the number
- -- of bytes actually read. This function should only block if there
- -- is no data available. If there is not enough data available,
- -- then the function should just return the available data. A return
- -- value of zero indicates that the end of the data stream (e.g. end
+ -- | Read up to the specified number of bytes starting from a specified
+ -- offset, returning the number of bytes actually read. This function
+ -- should only block if there is no data available. If there is not enough
+ -- data available, then the function should just return the available data.
+ -- A return value of zero indicates that the end of the data stream (e.g. end
-- of file) has been reached.
- read :: a -> Ptr Word8 -> Int -> IO Int
+ read :: a -> Ptr Word8 -> Word64 -> Int -> IO Int
- -- | Read up to the specified number of bytes, returning the number
- -- of bytes actually read, or 'Nothing' if the end of the stream has
- -- been reached.
- readNonBlocking :: a -> Ptr Word8 -> Int -> IO (Maybe Int)
+ -- | Read up to the specified number of bytes starting from a specified
+ -- offset, returning the number of bytes actually read, or 'Nothing' if
+ -- the end of the stream has been reached.
+ readNonBlocking :: a -> Ptr Word8 -> Word64 -> Int -> IO (Maybe Int)
- -- | Write the specified number of bytes.
- write :: a -> Ptr Word8 -> Int -> IO ()
+ -- | Write the specified number of bytes starting at a given offset.
+ write :: a -> Ptr Word8 -> Word64 -> Int -> IO ()
- -- | Write up to the specified number of bytes without blocking. Returns
- -- the actual number of bytes written.
- writeNonBlocking :: a -> Ptr Word8 -> Int -> IO Int
+ -- | Write up to the specified number of bytes without blocking starting at a
+ -- given offset. Returns the actual number of bytes written.
+ writeNonBlocking :: a -> Ptr Word8 -> Word64 -> Int -> IO Int
-- | I/O operations required for implementing a 'System.IO.Handle'.
@@ -78,7 +81,7 @@ class IODevice a where
isSeekable _ = return False
-- | seek to the specified position in the data.
- seek :: a -> SeekMode -> Integer -> IO ()
+ seek :: a -> SeekMode -> Integer -> IO Integer
seek _ _ _ = ioe_unsupportedOperation
-- | return the current position in the data.
diff --git a/libraries/base/GHC/IO/Encoding/CodePage.hs b/libraries/base/GHC/IO/Encoding/CodePage.hs
index 42980b59bc..ef03e985fa 100644
--- a/libraries/base/GHC/IO/Encoding/CodePage.hs
+++ b/libraries/base/GHC/IO/Encoding/CodePage.hs
@@ -5,7 +5,8 @@
module GHC.IO.Encoding.CodePage(
#if defined(mingw32_HOST_OS)
codePageEncoding, mkCodePageEncoding,
- localeEncoding, mkLocaleEncoding
+ localeEncoding, mkLocaleEncoding, CodePage,
+ getCurrentCodePage
#endif
) where
@@ -32,19 +33,15 @@ import GHC.IO.Encoding.UTF8 (mkUTF8)
import GHC.IO.Encoding.UTF16 (mkUTF16le, mkUTF16be)
import GHC.IO.Encoding.UTF32 (mkUTF32le, mkUTF32be)
-#if defined(mingw32_HOST_OS)
-# if defined(i386_HOST_ARCH)
-# define WINDOWS_CCONV stdcall
-# elif defined(x86_64_HOST_ARCH)
-# define WINDOWS_CCONV ccall
-# else
-# error Unknown mingw32 arch
-# endif
-#endif
+import GHC.Windows (DWORD)
+
+#include "windows_cconv.h"
+
+type CodePage = DWORD
-- note CodePage = UInt which might not work on Win64. But the Win32 package
-- also has this issue.
-getCurrentCodePage :: IO Word32
+getCurrentCodePage :: IO CodePage
getCurrentCodePage = do
conCP <- getConsoleCP
if conCP > 0
diff --git a/libraries/base/GHC/IO/Encoding/CodePage/API.hs b/libraries/base/GHC/IO/Encoding/CodePage/API.hs
index 9c2dc0e85c..41bc8d0f07 100644
--- a/libraries/base/GHC/IO/Encoding/CodePage/API.hs
+++ b/libraries/base/GHC/IO/Encoding/CodePage/API.hs
@@ -27,7 +27,7 @@ import GHC.IO.Encoding.UTF16
import GHC.Num
import GHC.Show
import GHC.Real
-import GHC.Windows
+import GHC.Windows hiding (LPCSTR)
import GHC.ForeignPtr (castForeignPtr)
import System.Posix.Internals
@@ -41,15 +41,7 @@ debugIO s
| c_DEBUG_DUMP = puts s
| otherwise = return ()
-
-#if defined(i386_HOST_ARCH)
-# define WINDOWS_CCONV stdcall
-#elif defined(x86_64_HOST_ARCH)
-# define WINDOWS_CCONV ccall
-#else
-# error Unknown mingw32 arch
-#endif
-
+#include "windows_cconv.h"
type LPCSTR = Ptr Word8
@@ -188,10 +180,10 @@ saner code ibuf obuf = do
else return (why, bufL ibuf' - bufL ibuf, ibuf', obuf')
byteView :: Buffer CWchar -> Buffer Word8
-byteView (Buffer {..}) = Buffer { bufState = bufState, bufRaw = castForeignPtr bufRaw, bufSize = bufSize * 2, bufL = bufL * 2, bufR = bufR * 2 }
+byteView (Buffer {..}) = Buffer { bufState = bufState, bufRaw = castForeignPtr bufRaw, bufSize = bufSize * 2, bufOffset = bufOffset, bufL = bufL * 2, bufR = bufR * 2 }
cwcharView :: Buffer Word8 -> Buffer CWchar
-cwcharView (Buffer {..}) = Buffer { bufState = bufState, bufRaw = castForeignPtr bufRaw, bufSize = half bufSize, bufL = half bufL, bufR = half bufR }
+cwcharView (Buffer {..}) = Buffer { bufState = bufState, bufRaw = castForeignPtr bufRaw, bufSize = half bufSize, bufOffset = bufOffset, bufL = half bufL, bufR = half bufR }
where half x = case x `divMod` 2 of (y, 0) -> y
_ -> errorWithoutStackTrace "cwcharView: utf16_(encode|decode) (wrote out|consumed) non multiple-of-2 number of bytes"
diff --git a/libraries/base/GHC/IO/FD.hs b/libraries/base/GHC/IO/FD.hs
index ad9b11564a..4245bf0b26 100644
--- a/libraries/base/GHC/IO/FD.hs
+++ b/libraries/base/GHC/IO/FD.hs
@@ -6,7 +6,6 @@
{-# OPTIONS_GHC -Wno-identities #-}
-- Whether there are identities depends on the platform
{-# OPTIONS_HADDOCK not-home #-}
-
-----------------------------------------------------------------------------
-- |
-- Module : GHC.IO.FD
@@ -46,6 +45,7 @@ import GHC.IO.Exception
#if defined(mingw32_HOST_OS)
import GHC.Windows
import Data.Bool
+import GHC.IO.SubSystem ((<!>))
#endif
import Foreign
@@ -101,29 +101,37 @@ fdIsSocket fd = fdIsSocket_ fd /= 0
instance Show FD where
show fd = show (fdFD fd)
+{-# INLINE ifSupported #-}
+ifSupported :: String -> a -> a
+#if defined(mingw32_HOST_OS)
+ifSupported s a = a <!> (error $ "FD:" ++ s ++ " not supported")
+#else
+ifSupported _ = id
+#endif
+
-- | @since 4.1.0.0
instance GHC.IO.Device.RawIO FD where
- read = fdRead
- readNonBlocking = fdReadNonBlocking
- write = fdWrite
- writeNonBlocking = fdWriteNonBlocking
+ read = ifSupported "fdRead" fdRead
+ readNonBlocking = ifSupported "fdReadNonBlocking" fdReadNonBlocking
+ write = ifSupported "fdWrite" fdWrite
+ writeNonBlocking = ifSupported "fdWriteNonBlocking" fdWriteNonBlocking
-- | @since 4.1.0.0
instance GHC.IO.Device.IODevice FD where
- ready = ready
- close = close
- isTerminal = isTerminal
- isSeekable = isSeekable
- seek = seek
- tell = tell
- getSize = getSize
- setSize = setSize
- setEcho = setEcho
- getEcho = getEcho
- setRaw = setRaw
- devType = devType
- dup = dup
- dup2 = dup2
+ ready = ifSupported "ready" ready
+ close = ifSupported "close" close
+ isTerminal = ifSupported "isTerm" isTerminal
+ isSeekable = ifSupported "isSeek" isSeekable
+ seek = ifSupported "seek" seek
+ tell = ifSupported "tell" tell
+ getSize = ifSupported "getSize" getSize
+ setSize = ifSupported "setSize" setSize
+ setEcho = ifSupported "setEcho" setEcho
+ getEcho = ifSupported "getEcho" getEcho
+ setRaw = ifSupported "setRaw" setRaw
+ devType = ifSupported "devType" devType
+ dup = ifSupported "dup" dup
+ dup2 = ifSupported "dup2" dup2
-- We used to use System.Posix.Internals.dEFAULT_BUFFER_SIZE, which is
-- taken from the value of BUFSIZ on the current platform. This value
@@ -134,11 +142,11 @@ dEFAULT_FD_BUFFER_SIZE = 8192
-- | @since 4.1.0.0
instance BufferedIO FD where
- newBuffer _dev state = newByteBuffer dEFAULT_FD_BUFFER_SIZE state
- fillReadBuffer fd buf = readBuf' fd buf
- fillReadBuffer0 fd buf = readBufNonBlocking fd buf
- flushWriteBuffer fd buf = writeBuf' fd buf
- flushWriteBuffer0 fd buf = writeBufNonBlocking fd buf
+ newBuffer _dev state = ifSupported "newBuf" $ newByteBuffer dEFAULT_FD_BUFFER_SIZE state
+ fillReadBuffer fd buf = ifSupported "readBuf" $ readBuf' fd buf
+ fillReadBuffer0 fd buf = ifSupported "readBufNonBlock" $ readBufNonBlocking fd buf
+ flushWriteBuffer fd buf = ifSupported "writeBuf" $ writeBuf' fd buf
+ flushWriteBuffer0 fd buf = ifSupported "writeBufNonBlock" $ writeBufNonBlocking fd buf
readBuf' :: FD -> Buffer Word8 -> IO (Int, Buffer Word8)
readBuf' fd buf = do
@@ -256,8 +264,10 @@ mkFD fd iomode mb_stat is_socket is_nonblock = do
RegularFile -> do
-- On Windows we need an additional call to get a unique device id
-- and inode, since fstat just returns 0 for both.
+ -- See also Note [RTS File locking]
(unique_dev, unique_ino) <- getUniqueFileInfo fd dev ino
- r <- lockFile fd unique_dev unique_ino (fromBool write)
+ r <- lockFile (fromIntegral fd) unique_dev unique_ino
+ (fromBool write)
when (r == -1) $
ioException (IOError Nothing ResourceBusy "openFile"
"file is locked" Nothing Nothing)
@@ -335,7 +345,7 @@ close fd =
closeFdWith closer (fromIntegral (fdFD fd))
release :: FD -> IO ()
-release fd = do _ <- unlockFile (fdFD fd)
+release fd = do _ <- unlockFile (fromIntegral $ fdFD fd)
return ()
#if defined(mingw32_HOST_OS)
@@ -348,10 +358,10 @@ isSeekable fd = do
t <- devType fd
return (t == RegularFile || t == RawDevice)
-seek :: FD -> SeekMode -> Integer -> IO ()
-seek fd mode off = do
- throwErrnoIfMinus1Retry_ "seek" $
- c_lseek (fdFD fd) (fromIntegral off) seektype
+seek :: FD -> SeekMode -> Integer -> IO Integer
+seek fd mode off = fromIntegral `fmap`
+ (throwErrnoIfMinus1Retry "seek" $
+ c_lseek (fdFD fd) (fromIntegral off) seektype)
where
seektype :: CInt
seektype = case mode of
@@ -436,14 +446,14 @@ setRaw fd raw = System.Posix.Internals.setCooked (fdFD fd) (not raw)
-- -----------------------------------------------------------------------------
-- Reading and Writing
-fdRead :: FD -> Ptr Word8 -> Int -> IO Int
-fdRead fd ptr bytes
+fdRead :: FD -> Ptr Word8 -> Word64 -> Int -> IO Int
+fdRead fd ptr _offset bytes
= do { r <- readRawBufferPtr "GHC.IO.FD.fdRead" fd ptr 0
(fromIntegral $ clampReadSize bytes)
; return (fromIntegral r) }
-fdReadNonBlocking :: FD -> Ptr Word8 -> Int -> IO (Maybe Int)
-fdReadNonBlocking fd ptr bytes = do
+fdReadNonBlocking :: FD -> Ptr Word8 -> Word64 -> Int -> IO (Maybe Int)
+fdReadNonBlocking fd ptr _offset bytes = do
r <- readRawBufferPtrNoBlock "GHC.IO.FD.fdReadNonBlocking" fd ptr
0 (fromIntegral $ clampReadSize bytes)
case fromIntegral r of
@@ -451,18 +461,18 @@ fdReadNonBlocking fd ptr bytes = do
n -> return (Just n)
-fdWrite :: FD -> Ptr Word8 -> Int -> IO ()
-fdWrite fd ptr bytes = do
+fdWrite :: FD -> Ptr Word8 -> Word64 -> Int -> IO ()
+fdWrite fd ptr _offset bytes = do
res <- writeRawBufferPtr "GHC.IO.FD.fdWrite" fd ptr 0
(fromIntegral $ clampWriteSize bytes)
let res' = fromIntegral res
if res' < bytes
- then fdWrite fd (ptr `plusPtr` res') (bytes - res')
+ then fdWrite fd (ptr `plusPtr` res') (_offset + fromIntegral res') (bytes - res')
else return ()
-- XXX ToDo: this isn't non-blocking
-fdWriteNonBlocking :: FD -> Ptr Word8 -> Int -> IO Int
-fdWriteNonBlocking fd ptr bytes = do
+fdWriteNonBlocking :: FD -> Ptr Word8 -> Word64 -> Int -> IO Int
+fdWriteNonBlocking fd ptr _offset bytes = do
res <- writeRawBufferPtrNoBlock "GHC.IO.FD.fdWriteNonBlocking" fd ptr 0
(fromIntegral $ clampWriteSize bytes)
return (fromIntegral res)
@@ -688,10 +698,10 @@ throwErrnoIfMinus1RetryOnBlock loc f on_block =
-- Locking/unlocking
foreign import ccall unsafe "lockFile"
- lockFile :: CInt -> Word64 -> Word64 -> CInt -> IO CInt
+ lockFile :: Word64 -> Word64 -> Word64 -> CInt -> IO CInt
foreign import ccall unsafe "unlockFile"
- unlockFile :: CInt -> IO CInt
+ unlockFile :: Word64 -> IO CInt
#if defined(mingw32_HOST_OS)
foreign import ccall unsafe "get_unique_file_info"
diff --git a/libraries/base/GHC/IO/Handle.hs b/libraries/base/GHC/IO/Handle.hs
index a847bcffca..f62acc1510 100644
--- a/libraries/base/GHC/IO/Handle.hs
+++ b/libraries/base/GHC/IO/Handle.hs
@@ -56,7 +56,8 @@ import GHC.IO.Encoding
import GHC.IO.Buffer
import GHC.IO.BufferedIO ( BufferedIO )
import GHC.IO.Device as IODevice
-import GHC.IO.Handle.FD
+import GHC.IO.StdHandles
+import GHC.IO.SubSystem
import GHC.IO.Handle.Lock
import GHC.IO.Handle.Types
import GHC.IO.Handle.Internals
@@ -120,10 +121,12 @@ hFileSize handle =
SemiClosedHandle -> ioe_semiclosedHandle
_ -> do flushWriteBuffer handle_
r <- IODevice.getSize dev
+ debugIO $ "hFileSize: " ++ show r ++ " " ++ show handle
if r /= -1
- then return r
- else ioException (IOError Nothing InappropriateType "hFileSize"
- "not a regular file" Nothing Nothing)
+ then return r
+ else ioException (IOError Nothing InappropriateType "hFileSize"
+ "not a regular file" Nothing Nothing)
+
-- | 'hSetFileSize' @hdl@ @size@ truncates the physical file with handle @hdl@ to @size@ bytes.
@@ -234,10 +237,11 @@ hSetBuffering handle mode =
case mode of
#if !defined(mingw32_HOST_OS)
-- 'raw' mode under win32 is a bit too specialised (and troublesome
- -- for most common uses), so simply disable its use here.
+ -- for most common uses), so simply disable its use here when not using
+ -- WinIO.
NoBuffering -> IODevice.setRaw haDevice True
#else
- NoBuffering -> return ()
+ NoBuffering -> return () <!> IODevice.setRaw haDevice True
#endif
_ -> IODevice.setRaw haDevice False
@@ -402,22 +406,36 @@ hSeek :: Handle -> SeekMode -> Integer -> IO ()
hSeek handle mode offset =
wantSeekableHandle "hSeek" handle $ \ handle_@Handle__{..} -> do
debugIO ("hSeek " ++ show (mode,offset))
- buf <- readIORef haCharBuffer
+ cbuf <- readIORef haCharBuffer
+ bbuf <- readIORef haByteBuffer
+ debugIO $ "hSeek - bbuf:" ++ summaryBuffer bbuf
+ debugIO $ "hSeek - cbuf:" ++ summaryBuffer cbuf
- if isWriteBuffer buf
+ if isWriteBuffer cbuf
then do flushWriteBuffer handle_
- IODevice.seek haDevice mode offset
+ new_offset <- IODevice.seek haDevice mode offset
+ -- buffer has been updated, need to re-read it
+ bbuf1 <- readIORef haByteBuffer
+ let bbuf2 = bbuf1{ bufOffset = fromIntegral new_offset }
+ debugIO $ "hSeek - seek:: " ++ show offset ++
+ " - " ++ show new_offset
+ debugIO $ "hSeek - wr flush bbuf1:" ++ summaryBuffer bbuf2
+ writeIORef haByteBuffer bbuf2
else do
- let r = bufL buf; w = bufR buf
+ let r = bufL cbuf; w = bufR cbuf
if mode == RelativeSeek && isNothing haDecoder &&
offset >= 0 && offset < fromIntegral (w - r)
- then writeIORef haCharBuffer buf{ bufL = r + fromIntegral offset }
+ then writeIORef haCharBuffer cbuf{ bufL = r + fromIntegral offset }
else do
flushCharReadBuffer handle_
flushByteReadBuffer handle_
- IODevice.seek haDevice mode offset
+ -- read the updated values
+ bbuf2 <- readIORef haByteBuffer
+ new_offset <- IODevice.seek haDevice mode offset
+ debugIO $ "hSeek after: " ++ show new_offset
+ writeIORef haByteBuffer bbuf2{ bufOffset = fromIntegral new_offset }
-- | Computation 'hTell' @hdl@ returns the current position of the
@@ -433,13 +451,18 @@ hTell :: Handle -> IO Integer
hTell handle =
wantSeekableHandle "hGetPosn" handle $ \ handle_@Handle__{..} -> do
- posn <- IODevice.tell haDevice
+ -- TODO: Guard these on Windows
+ posn <- if ioSubSystem == IoNative
+ then (fromIntegral . bufOffset) `fmap` readIORef haByteBuffer
+ else IODevice.tell haDevice
-- we can't tell the real byte offset if there are buffered
-- Chars, so must flush first:
flushCharBuffer handle_
bbuf <- readIORef haByteBuffer
+ debugIO ("hTell bbuf (elems=" ++ show (bufferElems bbuf) ++ ")"
+ ++ summaryBuffer bbuf)
let real_posn
| isWriteBuffer bbuf = posn + fromIntegral (bufferElems bbuf)
@@ -448,7 +471,7 @@ hTell handle =
cbuf <- readIORef haCharBuffer
debugIO ("\nhGetPosn: (posn, real_posn) = " ++ show (posn, real_posn))
debugIO (" cbuf: " ++ summaryBuffer cbuf ++
- " bbuf: " ++ summaryBuffer bbuf)
+ " bbuf: " ++ summaryBuffer bbuf)
return real_posn
@@ -647,7 +670,7 @@ dupHandle filepath h other_side h_@Handle__{..} mb_finalizer = do
withHandle_' "dupHandle" h r $ \Handle__{haDevice=dev} -> do
dupHandle_ dev filepath other_side h_ mb_finalizer
-dupHandle_ :: (IODevice dev, BufferedIO dev, Typeable dev) => dev
+dupHandle_ :: (RawIO dev, IODevice dev, BufferedIO dev, Typeable dev) => dev
-> FilePath
-> Maybe (MVar Handle__)
-> Handle__
diff --git a/libraries/base/GHC/IO/Handle/Internals.hs b/libraries/base/GHC/IO/Handle/Internals.hs
index c0b7e35a11..120ae0ea66 100644
--- a/libraries/base/GHC/IO/Handle/Internals.hs
+++ b/libraries/base/GHC/IO/Handle/Internals.hs
@@ -51,7 +51,7 @@ module GHC.IO.Handle.Internals (
HandleFinalizer, handleFinalizer,
- debugIO,
+ debugIO, traceIO
) where
import GHC.IO
@@ -62,7 +62,8 @@ import GHC.IO.Handle.Types
import GHC.IO.Buffer
import GHC.IO.BufferedIO (BufferedIO)
import GHC.IO.Exception
-import GHC.IO.Device (IODevice, SeekMode(..))
+import GHC.IO.Device (IODevice, RawIO, SeekMode(..))
+import GHC.IO.SubSystem ((<!>), isWindowsNativeIO)
import qualified GHC.IO.Device as IODevice
import qualified GHC.IO.BufferedIO as Buffered
@@ -93,8 +94,10 @@ newFileHandle :: FilePath -> Maybe HandleFinalizer -> Handle__ -> IO Handle
newFileHandle filepath mb_finalizer hc = do
m <- newMVar hc
case mb_finalizer of
- Just finalizer -> addMVarFinalizer m (finalizer filepath m)
- Nothing -> return ()
+ Just finalizer -> do debugIO $ "Registering finalizer: " ++ show filepath
+ addMVarFinalizer m (finalizer filepath m)
+ Nothing -> do debugIO $ "No finalizer: " ++ show filepath
+ return ()
return (FileHandle filepath m)
-- ---------------------------------------------------------------------------
@@ -222,6 +225,11 @@ augmentIOError ioe@IOError{ ioe_filename = fp } fun h
-- ---------------------------------------------------------------------------
-- Wrapper for write operations.
+-- If we already have a writeable handle just run the action.
+-- If we have a read only handle we throw an exception.
+-- If we have a read/write handle in read mode we:
+-- * Seek to the unread (from the users PoV) position and
+-- change the handles buffer to a write buffer.
wantWritableHandle :: String -> Handle -> (Handle__ -> IO a) -> IO a
wantWritableHandle fun h@(FileHandle _ m) act
= wantWritableHandle' fun h m act
@@ -253,13 +261,15 @@ checkWritableHandle act h_@Handle__{..}
buf' <- Buffered.emptyWriteBuffer haDevice buf
writeIORef haByteBuffer buf'
act h_
- _other -> act h_
+ AppendHandle -> act h_
+ WriteHandle -> act h_
-- ---------------------------------------------------------------------------
-- Wrapper for read operations.
wantReadableHandle :: String -> Handle -> (Handle__ -> IO (Handle__,a)) -> IO a
-wantReadableHandle fun h act = withHandle fun h (checkReadableHandle act)
+wantReadableHandle fun h act =
+ withHandle fun h (checkReadableHandle act)
wantReadableHandle_ :: String -> Handle -> (Handle__ -> IO a) -> IO a
wantReadableHandle_ fun h@(FileHandle _ m) act
@@ -504,11 +514,13 @@ flushByteWriteBuffer h_@Handle__{..} = do
bbuf <- readIORef haByteBuffer
when (not (isEmptyBuffer bbuf)) $ do
bbuf' <- Buffered.flushWriteBuffer haDevice bbuf
+ debugIO ("flushByteWriteBuffer: bbuf=" ++ summaryBuffer bbuf')
writeIORef haByteBuffer bbuf'
-- write the contents of the CharBuffer to the Handle__.
-- The data will be encoded and pushed to the byte buffer,
-- flushing if the buffer becomes full.
+-- Data is written to the handles current buffer offset.
writeCharBuffer :: Handle__ -> CharBuffer -> IO ()
writeCharBuffer h_@Handle__{..} !cbuf = do
--
@@ -536,6 +548,7 @@ writeCharBuffer h_@Handle__{..} !cbuf = do
then do
bbuf'' <- Buffered.flushWriteBuffer haDevice bbuf'
writeIORef haByteBuffer bbuf''
+ debugIO ("writeCharBuffer after flushing: cbuf=" ++ summaryBuffer bbuf'')
else
writeIORef haByteBuffer bbuf'
@@ -583,8 +596,12 @@ flushCharReadBuffer Handle__{..} = do
(bbuf1,cbuf1) <- (streamEncode decoder) bbuf0
cbuf0{ bufL=0, bufR=0, bufSize = bufL cbuf0 }
- debugIO ("finished, bbuf=" ++ summaryBuffer bbuf1 ++
- " cbuf=" ++ summaryBuffer cbuf1)
+ -- We should not need to update the offset here. The bytebuffer contains the
+ -- offset for the next read after it's used up. But this function only flushes
+ -- the char buffer.
+ -- let bbuf2 = bbuf1 -- {bufOffset = bufOffset bbuf1 - fromIntegral (bufL bbuf1)}
+ -- debugIO ("finished, bbuf=" ++ summaryBuffer bbuf2 ++
+ -- " cbuf=" ++ summaryBuffer cbuf1)
writeIORef haByteBuffer bbuf1
@@ -604,30 +621,51 @@ flushByteReadBuffer h_@Handle__{..} = do
when (not seekable) $ ioe_cannotFlushNotSeekable
let seek = negate (bufR bbuf - bufL bbuf)
+ let offset = bufOffset bbuf - fromIntegral (bufR bbuf - bufL bbuf)
debugIO ("flushByteReadBuffer: new file offset = " ++ show seek)
- IODevice.seek haDevice RelativeSeek (fromIntegral seek)
+ debugIO ("flushByteReadBuffer: " ++ summaryBuffer bbuf)
+
+ let mIOSeek = IODevice.seek haDevice RelativeSeek (fromIntegral seek)
+ -- win-io doesn't need this, but it allows us to error out on invalid offsets
+ let winIOSeek = IODevice.seek haDevice AbsoluteSeek (fromIntegral offset)
+
+ _ <- mIOSeek <!> winIOSeek -- execute one of these two seek functions
- writeIORef haByteBuffer bbuf{ bufL=0, bufR=0 }
+ writeIORef haByteBuffer bbuf{ bufL=0, bufR=0, bufOffset=offset }
-- ----------------------------------------------------------------------------
-- Making Handles
-mkHandle :: (IODevice dev, BufferedIO dev, Typeable dev) => dev
- -> FilePath
- -> HandleType
- -> Bool -- buffered?
- -> Maybe TextEncoding
- -> NewlineMode
- -> Maybe HandleFinalizer
- -> Maybe (MVar Handle__)
- -> IO Handle
-
-mkHandle dev filepath ha_type buffered mb_codec nl finalizer other_side = do
+{- Note [Making offsets for append]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The WINIO subysstem keeps track of offsets for handles
+ on the Haskell side of things instead of letting the OS
+ handle it. This requires us to establish the correct offset
+ for a handle on creation. This is usually zero but slightly
+ more tedious for append modes. There we fall back on IODevice
+ functionality to establish the size of the file and then set
+ the offset accordingly. This is only required for WINIO.
+-}
+
+mkHandle :: (RawIO dev, IODevice dev, BufferedIO dev, Typeable dev) => dev
+ -> FilePath
+ -> HandleType
+ -> Bool -- buffered?
+ -> Maybe TextEncoding
+ -> NewlineMode
+ -> Maybe HandleFinalizer
+ -> Maybe (MVar Handle__)
+ -> IO Handle
+mkHandle dev filepath ha_type buffered mb_codec nl finalizer other_side =
openTextEncoding mb_codec ha_type $ \ mb_encoder mb_decoder -> do
- let buf_state = initBufferState ha_type
- bbuf <- Buffered.newBuffer dev buf_state
+ let !buf_state = initBufferState ha_type
+ !bbuf_no_offset <- (Buffered.newBuffer dev buf_state)
+ !buf_offset <- initHandleOffset
+ let !bbuf = bbuf_no_offset { bufOffset = buf_offset}
+
bbufref <- newIORef bbuf
last_decode <- newIORef (errorWithoutStackTrace "codec_state", bbuf)
@@ -636,6 +674,7 @@ mkHandle dev filepath ha_type buffered mb_codec nl finalizer other_side = do
else mkUnBuffer buf_state
spares <- newIORef BufferListNil
+ debugIO $ "making handle for " ++ filepath
newFileHandle filepath finalizer
(Handle__ { haDevice = dev,
haType = ha_type,
@@ -651,9 +690,17 @@ mkHandle dev filepath ha_type buffered mb_codec nl finalizer other_side = do
haOutputNL = outputNL nl,
haOtherSide = other_side
})
+ where
+ -- See Note [Making offsets for append]
+ initHandleOffset
+ | isAppendHandleType ha_type
+ , isWindowsNativeIO = do
+ size <- IODevice.getSize dev
+ return (fromIntegral size :: Word64)
+ | otherwise = return 0
-- | makes a new 'Handle'
-mkFileHandle :: (IODevice dev, BufferedIO dev, Typeable dev)
+mkFileHandle :: (RawIO dev, IODevice dev, BufferedIO dev, Typeable dev)
=> dev -- ^ the underlying IO device, which must support
-- 'IODevice', 'BufferedIO' and 'Typeable'
-> FilePath
@@ -674,7 +721,7 @@ mkFileHandle dev filepath iomode mb_codec tr_newlines = do
-- | like 'mkFileHandle', except that a 'Handle' is created with two
-- independent buffers, one for reading and one for writing. Used for
-- full-duplex streams, such as network sockets.
-mkDuplexHandle :: (IODevice dev, BufferedIO dev, Typeable dev) => dev
+mkDuplexHandle :: (RawIO dev, IODevice dev, BufferedIO dev, Typeable dev) => dev
-> FilePath -> Maybe TextEncoding -> NewlineMode -> IO Handle
mkDuplexHandle dev filepath mb_codec tr_newlines = do
@@ -806,6 +853,7 @@ hLookAhead_ handle_@Handle__{..} = do
-- debugging
debugIO :: String -> IO ()
+-- debugIO s = traceEventIO s
debugIO s
| c_DEBUG_DUMP
= do _ <- withCStringLen (s ++ "\n") $
@@ -813,6 +861,13 @@ debugIO s
return ()
| otherwise = return ()
+-- For development, like debugIO but always on.
+traceIO :: String -> IO ()
+traceIO s = do
+ _ <- withCStringLen (s ++ "\n") $
+ \(p, len) -> c_write 1 (castPtr p) (fromIntegral len)
+ return ()
+
-- ----------------------------------------------------------------------------
-- Text input/output
@@ -840,7 +895,9 @@ readTextDevice h_@Handle__{..} cbuf = do
bbuf1 <- if not (isEmptyBuffer bbuf0)
then return bbuf0
else do
+ debugIO $ "readBuf at " ++ show (bufferOffset bbuf0)
(r,bbuf1) <- Buffered.fillReadBuffer haDevice bbuf0
+ debugIO $ "readBuf after " ++ show (bufferOffset bbuf1)
if r == 0 then ioe_EOF else do -- raise EOF
return bbuf1
diff --git a/libraries/base/GHC/IO/Handle/Lock/Windows.hsc b/libraries/base/GHC/IO/Handle/Lock/Windows.hsc
index 1118e523ec..f1e54125bb 100644
--- a/libraries/base/GHC/IO/Handle/Lock/Windows.hsc
+++ b/libraries/base/GHC/IO/Handle/Lock/Windows.hsc
@@ -13,32 +13,60 @@ module GHC.IO.Handle.Lock.Windows where
import GHC.Base () -- Make implicit dependency known to build system
#else
-#if defined(i386_HOST_ARCH)
-## define WINDOWS_CCONV stdcall
-#elif defined(x86_64_HOST_ARCH)
-## define WINDOWS_CCONV ccall
-#else
-# error Unknown mingw32 arch
-#endif
-
+##include <windows_cconv.h>
#include <windows.h>
import Data.Bits
import Data.Function
+import GHC.IO.Handle.Windows (handleToHANDLE)
import Foreign.C.Error
import Foreign.C.Types
import Foreign.Marshal.Alloc
import Foreign.Marshal.Utils
import GHC.Base
+import qualified GHC.Event.Windows as Mgr
+import GHC.Event.Windows (LPOVERLAPPED, withOverlapped)
import GHC.IO.FD
import GHC.IO.Handle.FD
import GHC.IO.Handle.Types (Handle)
import GHC.IO.Handle.Lock.Common (LockMode(..))
-import GHC.Ptr
+import GHC.IO.SubSystem
import GHC.Windows
lockImpl :: Handle -> String -> LockMode -> Bool -> IO Bool
-lockImpl h ctx mode block = do
+lockImpl = lockImplPOSIX <!> lockImplWinIO
+
+lockImplWinIO :: Handle -> String -> LockMode -> Bool -> IO Bool
+lockImplWinIO h ctx mode block = do
+ wh <- handleToHANDLE h
+ fix $ \retry ->
+ do retcode <- Mgr.withException ctx $
+ withOverlapped ctx wh 0 (startCB wh) completionCB
+ case () of
+ _ | retcode == #{const ERROR_OPERATION_ABORTED} -> retry
+ | retcode == #{const ERROR_SUCCESS} -> return True
+ | retcode == #{const ERROR_LOCK_VIOLATION} && not block
+ -> return False
+ | otherwise -> failWith ctx retcode
+ where
+ cmode = case mode of
+ SharedLock -> 0
+ ExclusiveLock -> #{const LOCKFILE_EXCLUSIVE_LOCK}
+ flags = if block
+ then cmode
+ else cmode .|. #{const LOCKFILE_FAIL_IMMEDIATELY}
+
+ startCB wh lpOverlapped = do
+ ret <- c_LockFileEx wh flags 0 #{const INFINITE} #{const INFINITE}
+ lpOverlapped
+ return $ Mgr.CbNone ret
+
+ completionCB err _dwBytes
+ | err == #{const ERROR_SUCCESS} = Mgr.ioSuccess 0
+ | otherwise = Mgr.ioFailed err
+
+lockImplPOSIX :: Handle -> String -> LockMode -> Bool -> IO Bool
+lockImplPOSIX h ctx mode block = do
FD{fdFD = fd} <- handleToFd h
wh <- throwErrnoIf (== iNVALID_HANDLE_VALUE) ctx $ c_get_osfhandle fd
allocaBytes sizeof_OVERLAPPED $ \ovrlpd -> do
@@ -49,12 +77,13 @@ lockImpl h ctx mode block = do
-- "locking a region that goes beyond the current end-of-file position is
-- not an error", hence we pass maximum value as the number of bytes to
-- lock.
- fix $ \retry -> c_LockFileEx wh flags 0 0xffffffff 0xffffffff ovrlpd >>= \case
+ fix $ \retry -> c_LockFileEx wh flags 0 #{const INFINITE} #{const INFINITE}
+ ovrlpd >>= \case
True -> return True
False -> getLastError >>= \err -> if
| not block && err == #{const ERROR_LOCK_VIOLATION} -> return False
- | err == #{const ERROR_OPERATION_ABORTED} -> retry
- | otherwise -> failWith ctx err
+ | err == #{const ERROR_OPERATION_ABORTED} -> retry
+ | otherwise -> failWith ctx err
where
sizeof_OVERLAPPED = #{size OVERLAPPED}
@@ -63,12 +92,31 @@ lockImpl h ctx mode block = do
ExclusiveLock -> #{const LOCKFILE_EXCLUSIVE_LOCK}
unlockImpl :: Handle -> IO ()
-unlockImpl h = do
+unlockImpl = unlockImplPOSIX <!> unlockImplWinIO
+
+unlockImplWinIO :: Handle -> IO ()
+unlockImplWinIO h = do
+ wh <- handleToHANDLE h
+ _ <- Mgr.withException "unlockImpl" $
+ withOverlapped "unlockImpl" wh 0 (startCB wh) completionCB
+ return ()
+ where
+ startCB wh lpOverlapped = do
+ ret <- c_UnlockFileEx wh 0 #{const INFINITE} #{const INFINITE}
+ lpOverlapped
+ return $ Mgr.CbNone ret
+
+ completionCB err _dwBytes
+ | err == #{const ERROR_SUCCESS} = Mgr.ioSuccess 0
+ | otherwise = Mgr.ioFailed err
+
+unlockImplPOSIX :: Handle -> IO ()
+unlockImplPOSIX h = do
FD{fdFD = fd} <- handleToFd h
wh <- throwErrnoIf (== iNVALID_HANDLE_VALUE) "hUnlock" $ c_get_osfhandle fd
allocaBytes sizeof_OVERLAPPED $ \ovrlpd -> do
fillBytes ovrlpd 0 sizeof_OVERLAPPED
- c_UnlockFileEx wh 0 0xffffffff 0xffffffff ovrlpd >>= \case
+ c_UnlockFileEx wh 0 #{const INFINITE} #{const INFINITE} ovrlpd >>= \case
True -> return ()
False -> getLastError >>= failWith "hUnlock"
where
@@ -80,10 +128,11 @@ foreign import ccall unsafe "_get_osfhandle"
-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203.aspx
foreign import WINDOWS_CCONV interruptible "LockFileEx"
- c_LockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> DWORD -> Ptr () -> IO BOOL
+ c_LockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> DWORD -> LPOVERLAPPED
+ -> IO BOOL
-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365716.aspx
foreign import WINDOWS_CCONV interruptible "UnlockFileEx"
- c_UnlockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> Ptr () -> IO BOOL
+ c_UnlockFileEx :: HANDLE -> DWORD -> DWORD -> DWORD -> LPOVERLAPPED -> IO BOOL
#endif
diff --git a/libraries/base/GHC/IO/Handle/Text.hs b/libraries/base/GHC/IO/Handle/Text.hs
index 20a449f39d..6d63bb0d54 100644
--- a/libraries/base/GHC/IO/Handle/Text.hs
+++ b/libraries/base/GHC/IO/Handle/Text.hs
@@ -6,7 +6,6 @@
, NondecreasingIndentation
, MagicHash
#-}
-{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-unused-matches #-}
{-# OPTIONS_HADDOCK not-home #-}
@@ -32,7 +31,6 @@ module GHC.IO.Handle.Text (
) where
import GHC.IO
-import GHC.IO.FD
import GHC.IO.Buffer
import qualified GHC.IO.BufferedIO as Buffered
import GHC.IO.Exception
@@ -46,7 +44,6 @@ import Foreign
import Foreign.C
import qualified Control.Exception as Exception
-import Data.Typeable
import System.IO.Error
import Data.Either (Either(..))
import Data.Maybe
@@ -578,9 +575,9 @@ hPutcBuffered handle_@Handle__{..} c = do
LineBuffering -> True
_ -> False
- putc buf@Buffer{ bufRaw=raw, bufR=w } c = do
+ putc buf@Buffer{ bufRaw=raw, bufR=w } c' = do
debugIO ("putc: " ++ summaryBuffer buf)
- w' <- writeCharBuf raw w c
+ w' <- writeCharBuf raw w c'
return buf{ bufR = w' }
-- ---------------------------------------------------------------------------
@@ -644,6 +641,7 @@ hPutChars :: Handle -> [Char] -> IO ()
hPutChars _ [] = return ()
hPutChars handle (c:cs) = hPutChar handle c >> hPutChars handle cs
+-- Buffer offset is always zero.
getSpareBuffer :: Handle__ -> IO (BufferMode, CharBuffer)
getSpareBuffer Handle__{haCharBuffer=ref,
haBuffers=spare_ref,
@@ -703,7 +701,6 @@ writeBlocks hdl line_buffered add_nl nl
--
-- Write the contents of the buffer 'buf' ('sz' bytes long, containing
-- 'count' bytes of data) to handle (handle must be block or line buffered).
-
commitBuffer
:: Handle -- handle to commit to
-> RawCharBuffer -> Int -- address and size (in bytes) of buffer
@@ -715,9 +712,10 @@ commitBuffer
commitBuffer hdl !raw !sz !count flush release =
wantWritableHandle "commitBuffer" hdl $ \h_@Handle__{..} -> do
debugIO ("commitBuffer: sz=" ++ show sz ++ ", count=" ++ show count
- ++ ", flush=" ++ show flush ++ ", release=" ++ show release)
+ ++ ", flush=" ++ show flush ++ ", release=" ++ show release ++ ", handle=" ++ show hdl)
- writeCharBuffer h_ Buffer{ bufRaw=raw, bufState=WriteBuffer,
+ -- Offset taken from handle
+ writeCharBuffer h_ Buffer{ bufRaw=raw, bufState=WriteBuffer, bufOffset=0,
bufL=0, bufR=count, bufSize=sz }
when flush $ flushByteWriteBuffer h_
@@ -730,6 +728,8 @@ commitBuffer hdl !raw !sz !count flush release =
spare_bufs <- readIORef haBuffers
writeIORef haBuffers (BufferListCons raw spare_bufs)
+ -- bb <- readIORef haByteBuffer
+ -- debugIO ("commitBuffer: buffer=" ++ summaryBuffer bb ++ ", handle=" ++ show hdl)
return ()
-- backwards compatibility; the text package uses this
@@ -741,7 +741,7 @@ commitBuffer' raw sz@(I# _) count@(I# _) flush release h_@Handle__{..}
++ ", flush=" ++ show flush ++ ", release=" ++ show release)
let this_buf = Buffer{ bufRaw=raw, bufState=WriteBuffer,
- bufL=0, bufR=count, bufSize=sz }
+ bufL=0, bufR=count, bufSize=sz, bufOffset=0 }
writeCharBuffer h_ this_buf
@@ -816,63 +816,80 @@ hPutBuf' handle ptr count can_block
_line_or_no_buffering -> do flushWriteBuffer h_
return r
+-- TODO: Possible optimisation:
+-- If we know that `w + count > size`, we should write both the
+-- handle buffer and the `ptr` in a single `writev()` syscall.
bufWrite :: Handle__-> Ptr Word8 -> Int -> Bool -> IO Int
-bufWrite h_@Handle__{..} ptr count can_block =
- seq count $ do -- strictness hack
- old_buf@Buffer{ bufRaw=old_raw, bufR=w, bufSize=size }
- <- readIORef haByteBuffer
+bufWrite h_@Handle__{..} ptr !count can_block = do
+ -- Get buffer to determine size and free space in buffer
+ old_buf@Buffer{ bufR=w, bufSize=size }
+ <- readIORef haByteBuffer
- -- TODO: Possible optimisation:
- -- If we know that `w + count > size`, we should write both the
- -- handle buffer and the `ptr` in a single `writev()` syscall.
-
- -- Need to buffer and enough room in handle buffer?
- -- There's no need to buffer if the data to be written is larger than
+ -- There's no need to buffer if the incoming data is larger than
-- the handle buffer (`count >= size`).
- if (count < size && count <= size - w)
- -- We need to buffer and there's enough room in the buffer:
- -- just copy the data in and update bufR.
- then do debugIO ("hPutBuf: copying to buffer, w=" ++ show w)
- copyToRawBuffer old_raw w ptr count
- let copied_buf = old_buf{ bufR = w + count }
- -- If the write filled the buffer completely, we need to flush,
- -- to maintain the "INVARIANTS on Buffers" from
- -- GHC.IO.Buffer.checkBuffer: "a write buffer is never full".
- if (count == size - w)
- then do
- debugIO "hPutBuf: flushing full buffer after writing"
- flushed_buf <- Buffered.flushWriteBuffer haDevice copied_buf
- -- TODO: we should do a non-blocking flush here
- writeIORef haByteBuffer flushed_buf
- else do
- writeIORef haByteBuffer copied_buf
- return count
-
- -- else, we have to flush any existing handle buffer data
- -- and can then write out the data in `ptr` directly.
- else do -- No point flushing when there's nothing in the buffer.
- when (w > 0) $ do
- debugIO "hPutBuf: flushing first"
- flushed_buf <- Buffered.flushWriteBuffer haDevice old_buf
- -- TODO: we should do a non-blocking flush here
- writeIORef haByteBuffer flushed_buf
- -- if we can fit in the buffer, then just loop
- if count < size
- then bufWrite h_ ptr count can_block
- else if can_block
- then do writeChunk h_ (castPtr ptr) count
- return count
- else writeChunkNonBlocking h_ (castPtr ptr) count
-
-writeChunk :: Handle__ -> Ptr Word8 -> Int -> IO ()
-writeChunk h_@Handle__{..} ptr bytes
- | Just fd <- cast haDevice = RawIO.write (fd::FD) ptr bytes
- | otherwise = error "Todo: hPutBuf"
-
-writeChunkNonBlocking :: Handle__ -> Ptr Word8 -> Int -> IO Int
-writeChunkNonBlocking h_@Handle__{..} ptr bytes
- | Just fd <- cast haDevice = RawIO.writeNonBlocking (fd::FD) ptr bytes
- | otherwise = error "Todo: hPutBuf"
+ -- Check if we can try to buffer the given chunk of data.
+ b <- if (count < size && count <= size - w)
+ then bufferChunk h_ old_buf ptr count
+ else do
+ -- The given data does not fit into the buffer.
+ -- Either because it's too large for the buffer
+ -- or the buffer is too full. Either way we need
+ -- to flush the buffered data first.
+ flushed_buf <- flushByteWriteBufferGiven h_ old_buf
+ if count < size
+ -- The data is small enough to be buffered.
+ then bufferChunk h_ flushed_buf ptr count
+ else do
+ let offset = bufOffset flushed_buf
+ !bytes <- if can_block
+ then do writeChunk h_ (castPtr ptr) offset count
+ else writeChunkNonBlocking h_ (castPtr ptr) offset count
+ -- Update buffer with actual bytes written.
+ writeIORef haByteBuffer $! bufferAddOffset bytes flushed_buf
+ return bytes
+ debugIO "hPutBuf: done"
+ return b
+
+-- Flush the given buffer via the handle, return the flushed buffer
+flushByteWriteBufferGiven :: Handle__ -> Buffer Word8 -> IO (Buffer Word8)
+flushByteWriteBufferGiven h_@Handle__{..} bbuf = do
+ if (not (isEmptyBuffer bbuf))
+ then do
+ bbuf' <- Buffered.flushWriteBuffer haDevice bbuf
+ debugIO ("flushByteWriteBufferGiven: bbuf=" ++ summaryBuffer bbuf')
+ writeIORef haByteBuffer bbuf'
+ return bbuf'
+ else
+ return bbuf
+
+-- Fill buffer and return bytes buffered/written.
+-- Flushes buffer if it's full after adding the data.
+bufferChunk :: Handle__ -> Buffer Word8 -> Ptr Word8 -> Int -> IO Int
+bufferChunk h_@Handle__{..} old_buf@Buffer{ bufRaw=raw, bufR=w, bufSize=size } ptr !count = do
+ debugIO ("hPutBuf: copying to buffer, w=" ++ show w)
+ copyToRawBuffer raw w ptr count
+ let copied_buf = old_buf{ bufR = w + count }
+ -- If the write filled the buffer completely, we need to flush,
+ -- to maintain the "INVARIANTS on Buffers" from
+ -- GHC.IO.Buffer.checkBuffer: "a write buffer is never full".
+ if isFullBuffer copied_buf
+ then do
+ -- TODO: we should do a non-blocking flush here
+ debugIO "hPutBuf: flushing full buffer after writing"
+ _ <- flushByteWriteBufferGiven h_ copied_buf
+ return ()
+ else do
+ writeIORef haByteBuffer copied_buf
+ return count
+
+writeChunk :: Handle__ -> Ptr Word8 -> Word64 -> Int -> IO Int
+writeChunk h_@Handle__{..} ptr offset bytes
+ = do RawIO.write haDevice ptr offset bytes
+ return bytes
+
+writeChunkNonBlocking :: Handle__ -> Ptr Word8 -> Word64 -> Int -> IO Int
+writeChunkNonBlocking h_@Handle__{..} ptr offset bytes
+ = RawIO.writeNonBlocking haDevice ptr offset bytes
-- ---------------------------------------------------------------------------
-- hGetBuf
@@ -898,12 +915,16 @@ hGetBuf h !ptr count
| count < 0 = illegalBufferSize h "hGetBuf" count
| otherwise =
wantReadableHandle_ "hGetBuf" h $ \ h_@Handle__{..} -> do
- flushCharReadBuffer h_
- buf@Buffer{ bufRaw=raw, bufR=w, bufL=r, bufSize=sz }
+ debugIO $ ":: hGetBuf - " ++ show h ++ " - " ++ show count
+ flushCharReadBuffer h_
+ buf@Buffer{ bufRaw=raw, bufR=w, bufL=r, bufSize=sz }
<- readIORef haByteBuffer
- if isEmptyBuffer buf
- then bufReadEmpty h_ buf (castPtr ptr) 0 count
- else bufReadNonEmpty h_ buf (castPtr ptr) 0 count
+ debugIO ("hGetBuf: " ++ summaryBuffer buf)
+ res <- if isEmptyBuffer buf
+ then bufReadEmpty h_ buf (castPtr ptr) 0 count
+ else bufReadNonEmpty h_ buf (castPtr ptr) 0 count
+ debugIO "** hGetBuf done."
+ return res
-- small reads go through the buffer, large reads are satisfied by
-- taking data first from the buffer and then direct from the file
@@ -911,9 +932,14 @@ hGetBuf h !ptr count
bufReadNonEmpty :: Handle__ -> Buffer Word8 -> Ptr Word8 -> Int -> Int -> IO Int
bufReadNonEmpty h_@Handle__{..}
+ -- w for width, r for ... read ptr?
buf@Buffer{ bufRaw=raw, bufR=w, bufL=r, bufSize=sz }
ptr !so_far !count
= do
+ debugIO ":: bufReadNonEmpty"
+ -- We use < instead of <= because for count == avail
+ -- we need to reset bufL and bufR to zero.
+ -- See also: INVARIANTS on Buffers
let avail = w - r
if (count < avail)
then do
@@ -929,30 +955,47 @@ bufReadNonEmpty h_@Handle__{..}
so_far' = so_far + avail
ptr' = ptr `plusPtr` avail
- if remaining == 0
+ debugIO ("bufReadNonEmpty: " ++ summaryBuffer buf' ++ " s:" ++ show so_far' ++ " r:" ++ show remaining)
+ b <- if remaining == 0
then return so_far'
else bufReadEmpty h_ buf' ptr' so_far' remaining
+ debugIO ":: bufReadNonEmpty - done"
+ return b
-
+-- We want to read more data, but the buffer is empty. (buffL == buffR == 0)
+-- See also Note [INVARIANTS on Buffers] in Buffer.hs
bufReadEmpty :: Handle__ -> Buffer Word8 -> Ptr Word8 -> Int -> Int -> IO Int
bufReadEmpty h_@Handle__{..}
- buf@Buffer{ bufRaw=raw, bufR=w, bufL=r, bufSize=sz }
+ buf@Buffer{ bufRaw=raw, bufR=w, bufL=_r, bufSize=sz, bufOffset=bff }
ptr so_far count
- | count > sz, Just fd <- cast haDevice = loop fd 0 count
+ | count > sz
+ = do
+ bytes_read <- loop haDevice 0 bff count
+ -- bytes_read includes so_far (content that was in the buffer)
+ -- but that is already accounted for in the old offset, so don't
+ -- count it twice.
+ let buf1 = bufferAddOffset (fromIntegral $ bytes_read - so_far) buf
+ writeIORef haByteBuffer buf1
+ debugIO ("bufReadEmpty1.1: " ++ summaryBuffer buf1 ++ " read:" ++ show bytes_read)
+ return bytes_read
| otherwise = do
- (r,buf') <- Buffered.fillReadBuffer haDevice buf
- if r == 0
- then return so_far
- else do writeIORef haByteBuffer buf'
- bufReadNonEmpty h_ buf' ptr so_far count
+ (r,buf') <- Buffered.fillReadBuffer haDevice buf
+ writeIORef haByteBuffer buf'
+ if r == 0 -- end of file reached
+ then return so_far
+ else bufReadNonEmpty h_ buf' ptr so_far count
where
- loop :: FD -> Int -> Int -> IO Int
- loop fd off bytes | bytes <= 0 = return (so_far + off)
- loop fd off bytes = do
- r <- RawIO.read (fd::FD) (ptr `plusPtr` off) bytes
+ -- Read @bytes@ byte into ptr. Repeating the read until either zero
+ -- bytes where read, or we are done reading.
+ loop :: RawIO.RawIO dev => dev -> Int -> Word64 -> Int -> IO Int
+ loop dev delta off bytes | bytes <= 0 = return (so_far + delta)
+ loop dev delta off bytes = do
+ r <- RawIO.read dev (ptr `plusPtr` delta) off bytes
+ debugIO $ show ptr ++ " - loop read@" ++ show delta ++ ": " ++ show r
+ debugIO $ "next:" ++ show (delta + r) ++ " - left:" ++ show (bytes - r)
if r == 0
- then return (so_far + off)
- else loop fd (off + r) (bytes - r)
+ then return (so_far + delta)
+ else loop dev (delta + r) (off + fromIntegral r) (bytes - r)
-- ---------------------------------------------------------------------------
-- hGetBufSome
@@ -984,7 +1027,7 @@ hGetBufSome h !ptr count
buf@Buffer{ bufSize=sz } <- readIORef haByteBuffer
if isEmptyBuffer buf
then case count > sz of -- large read? optimize it with a little special case:
- True | Just fd <- haFD h_ -> do RawIO.read fd (castPtr ptr) count
+ True -> RawIO.read haDevice (castPtr ptr) 0 count
_ -> do (r,buf') <- Buffered.fillReadBuffer haDevice buf
if r == 0
then return 0
@@ -997,9 +1040,6 @@ hGetBufSome h !ptr count
let count' = min count (bufferElems buf)
in bufReadNBNonEmpty h_ buf (castPtr ptr) 0 count'
-haFD :: Handle__ -> Maybe FD
-haFD h_@Handle__{..} = cast haDevice
-
-- | 'hGetBufNonBlocking' @hdl buf count@ reads data from the handle @hdl@
-- into the buffer @buf@ until either EOF is reached, or
-- @count@ 8-bit bytes have been read, or there is no more data available
@@ -1034,25 +1074,25 @@ hGetBufNonBlocking h !ptr count
bufReadNBEmpty :: Handle__ -> Buffer Word8 -> Ptr Word8 -> Int -> Int -> IO Int
bufReadNBEmpty h_@Handle__{..}
- buf@Buffer{ bufRaw=raw, bufR=w, bufL=r, bufSize=sz }
+ buf@Buffer{ bufRaw=raw, bufR=w, bufL=_r, bufSize=sz
+ , bufOffset=offset }
ptr so_far count
- | count > sz,
- Just fd <- cast haDevice = do
- m <- RawIO.readNonBlocking (fd::FD) ptr count
+ | count > sz = do
+ m <- RawIO.readNonBlocking haDevice ptr offset count
case m of
Nothing -> return so_far
Just n -> return (so_far + n)
| otherwise = do
- buf <- readIORef haByteBuffer
+ -- buf <- readIORef haByteBuffer
(r,buf') <- Buffered.fillReadBuffer0 haDevice buf
case r of
Nothing -> return so_far
Just 0 -> return so_far
- Just r -> do
+ Just r' -> do
writeIORef haByteBuffer buf'
- bufReadNBNonEmpty h_ buf' ptr so_far (min count r)
- -- NOTE: new count is min count r
+ bufReadNBNonEmpty h_ buf' ptr so_far (min count r')
+ -- NOTE: new count is min count r'
-- so we will just copy the contents of the
-- buffer in the recursive call, and not
-- loop again.
@@ -1064,6 +1104,9 @@ bufReadNBNonEmpty h_@Handle__{..}
ptr so_far count
= do
let avail = w - r
+ -- We use < instead of <= because for count == avail
+ -- we need to reset bufL and bufR to zero.
+ -- See also [INVARIANTS on Buffers] in Buffer.hs
if (count < avail)
then do
copyFromRawBuffer ptr raw r count
diff --git a/libraries/base/GHC/IO/Handle/Types.hs b/libraries/base/GHC/IO/Handle/Types.hs
index 6923d252b9..2ab91e9f09 100644
--- a/libraries/base/GHC/IO/Handle/Types.hs
+++ b/libraries/base/GHC/IO/Handle/Types.hs
@@ -26,6 +26,7 @@ module GHC.IO.Handle.Types (
BufferList(..),
HandleType(..),
isReadableHandleType, isWritableHandleType, isReadWriteHandleType,
+ isAppendHandleType,
BufferMode(..),
BufferCodec(..),
NewlineMode(..), Newline(..), nativeNewline,
@@ -119,13 +120,14 @@ instance Eq Handle where
_ == _ = False
data Handle__
- = forall dev enc_state dec_state . (IODevice dev, BufferedIO dev, Typeable dev) =>
+ = forall dev enc_state dec_state . (RawIO dev, IODevice dev, BufferedIO dev, Typeable dev) =>
Handle__ {
haDevice :: !dev,
haType :: HandleType, -- type (read/write/append etc.)
haByteBuffer :: !(IORef (Buffer Word8)), -- See [note Buffering Implementation]
haBufferMode :: BufferMode,
haLastDecode :: !(IORef (dec_state, Buffer Word8)),
+ -- ^ The byte buffer just before we did our last batch of decoding.
haCharBuffer :: !(IORef (Buffer CharBufElem)), -- See [note Buffering Implementation]
haBuffers :: !(IORef (BufferList CharBufElem)), -- spare buffers
haEncoder :: Maybe (TextEncoder enc_state),
@@ -170,6 +172,11 @@ isReadWriteHandleType :: HandleType -> Bool
isReadWriteHandleType ReadWriteHandle{} = True
isReadWriteHandleType _ = False
+isAppendHandleType :: HandleType -> Bool
+isAppendHandleType AppendHandle = True
+isAppendHandleType _ = False
+
+
-- INVARIANTS on Handles:
--
-- * A handle *always* has a buffer, even if it is only 1 character long
diff --git a/libraries/base/GHC/IO/Handle/Windows.hs b/libraries/base/GHC/IO/Handle/Windows.hs
new file mode 100644
index 0000000000..19efbea3b5
--- /dev/null
+++ b/libraries/base/GHC/IO/Handle/Windows.hs
@@ -0,0 +1,235 @@
+ {-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE CPP, NoImplicitPrelude #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.IO.Handle.Windows
+-- Copyright : (c) The University of Glasgow, 2017
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : internal
+-- Portability : non-portable
+--
+-- Handle operations implemented by Windows native handles
+--
+-----------------------------------------------------------------------------
+
+module GHC.IO.Handle.Windows (
+ stdin, stdout, stderr,
+ openFile, openBinaryFile, openFileBlocking,
+ handleToHANDLE, mkHandleFromHANDLE
+ ) where
+
+import Data.Maybe
+import Data.Typeable
+
+import GHC.Base
+import GHC.MVar
+import GHC.IO
+import GHC.IO.BufferedIO hiding (flushWriteBuffer)
+import GHC.IO.Encoding
+import GHC.IO.Device as IODevice
+import GHC.IO.Exception
+import GHC.IO.IOMode
+import GHC.IO.Handle.Types
+import GHC.IO.Handle.Internals
+import qualified GHC.IO.Windows.Handle as Win
+
+-- ---------------------------------------------------------------------------
+-- Standard Handles
+
+-- Three handles are allocated during program initialisation. The first
+-- two manage input or output from the Haskell program's standard input
+-- or output channel respectively. The third manages output to the
+-- standard error channel. These handles are initially open.
+
+-- | If the std handles are redirected to file handles then WriteConsole etc
+-- won't work anymore. When the handle is created test it and if it's a file
+-- handle then just convert it to the proper IODevice so WriteFile is used
+-- instead. This is done here so it's buffered and only happens once.
+mkConsoleHandle :: Win.IoHandle Win.ConsoleHandle
+ -> FilePath
+ -> HandleType
+ -> Bool -- buffered?
+ -> Maybe TextEncoding
+ -> NewlineMode
+ -> Maybe HandleFinalizer
+ -> Maybe (MVar Handle__)
+ -> IO Handle
+mkConsoleHandle dev filepath ha_type buffered mb_codec nl finalizer other_side
+ = do isTerm <- IODevice.isTerminal dev
+ case isTerm of
+ True -> mkHandle dev filepath ha_type buffered mb_codec nl finalizer
+ other_side
+ False -> mkHandle (Win.convertHandle dev) filepath ha_type buffered
+ mb_codec nl finalizer other_side
+
+-- | A handle managing input from the Haskell program's standard input channel.
+stdin :: Handle
+{-# NOINLINE stdin #-}
+stdin = unsafePerformIO $ do
+ enc <- getLocaleEncoding
+ mkConsoleHandle Win.stdin "<stdin>" ReadHandle True (Just enc)
+ nativeNewlineMode{-translate newlines-}
+ (Just stdHandleFinalizer) Nothing
+
+-- | A handle managing output to the Haskell program's standard output channel.
+stdout :: Handle
+{-# NOINLINE stdout #-}
+stdout = unsafePerformIO $ do
+ enc <- getLocaleEncoding
+ mkConsoleHandle Win.stdout "<stdout>" WriteHandle True (Just enc)
+ nativeNewlineMode{-translate newlines-}
+ (Just stdHandleFinalizer) Nothing
+
+-- | A handle managing output to the Haskell program's standard error channel.
+stderr :: Handle
+{-# NOINLINE stderr #-}
+stderr = unsafePerformIO $ do
+ enc <- getLocaleEncoding
+ mkConsoleHandle Win.stderr "<stderr>" WriteHandle
+ False{-stderr is unbuffered-} (Just enc)
+ nativeNewlineMode{-translate newlines-}
+ (Just stdHandleFinalizer) Nothing
+
+stdHandleFinalizer :: FilePath -> MVar Handle__ -> IO ()
+stdHandleFinalizer fp m = do
+ h_ <- takeMVar m
+ flushWriteBuffer h_
+ case haType h_ of
+ ClosedHandle -> return ()
+ _other -> closeTextCodecs h_
+ putMVar m (ioe_finalizedHandle fp)
+
+-- ---------------------------------------------------------------------------
+-- Opening and Closing Files
+
+addFilePathToIOError :: String -> FilePath -> IOException -> IOException
+addFilePathToIOError fun fp ioe
+ = ioe{ ioe_location = fun, ioe_filename = Just fp }
+
+-- | Computation 'openFile' @file mode@ allocates and returns a new, open
+-- handle to manage the file @file@. It manages input if @mode@
+-- is 'ReadMode', output if @mode@ is 'WriteMode' or 'AppendMode',
+-- and both input and output if mode is 'ReadWriteMode'.
+--
+-- If the file does not exist and it is opened for output, it should be
+-- created as a new file. If @mode@ is 'WriteMode' and the file
+-- already exists, then it should be truncated to zero length.
+-- Some operating systems delete empty files, so there is no guarantee
+-- that the file will exist following an 'openFile' with @mode@
+-- 'WriteMode' unless it is subsequently written to successfully.
+-- The handle is positioned at the end of the file if @mode@ is
+-- 'AppendMode', and otherwise at the beginning (in which case its
+-- internal position is 0).
+-- The initial buffer mode is implementation-dependent.
+--
+-- This operation may fail with:
+--
+-- * 'isAlreadyInUseError' if the file is already open and cannot be reopened;
+--
+-- * 'isDoesNotExistError' if the file does not exist; or
+--
+-- * 'isPermissionError' if the user does not have permission to open the file.
+--
+-- Note: if you will be working with files containing binary data, you'll want to
+-- be using 'openBinaryFile'.
+openFile :: FilePath -> IOMode -> IO Handle
+openFile fp im =
+ catchException
+ (openFile' fp im dEFAULT_OPEN_IN_BINARY_MODE True)
+ (\e -> ioError (addFilePathToIOError "openFile" fp e))
+
+-- | Like 'openFile', but opens the file in ordinary blocking mode.
+-- This can be useful for opening a FIFO for writing: if we open in
+-- non-blocking mode then the open will fail if there are no readers,
+-- whereas a blocking open will block until a reader appear.
+--
+-- @since 4.4.0.0
+openFileBlocking :: FilePath -> IOMode -> IO Handle
+openFileBlocking fp im =
+ catchException
+ (openFile' fp im dEFAULT_OPEN_IN_BINARY_MODE False)
+ (\e -> ioError (addFilePathToIOError "openFileBlocking" fp e))
+
+-- | Like 'openFile', but open the file in binary mode.
+-- On Windows, reading a file in text mode (which is the default)
+-- will translate CRLF to LF, and writing will translate LF to CRLF.
+-- This is usually what you want with text files. With binary files
+-- this is undesirable; also, as usual under Microsoft operating systems,
+-- text mode treats control-Z as EOF. Binary mode turns off all special
+-- treatment of end-of-line and end-of-file characters.
+-- (See also 'hSetBinaryMode'.)
+
+openBinaryFile :: FilePath -> IOMode -> IO Handle
+openBinaryFile fp m =
+ catchException
+ (openFile' fp m True True)
+ (\e -> ioError (addFilePathToIOError "openBinaryFile" fp e))
+
+openFile' :: String -> IOMode -> Bool -> Bool -> IO Handle
+openFile' filepath iomode binary non_blocking = do
+ -- first open the file to get a Win32 handle
+ (hwnd, hwnd_type) <- Win.openFile filepath iomode non_blocking
+
+ mb_codec <- if binary then return Nothing else fmap Just getLocaleEncoding
+
+ -- then use it to make a Handle
+ mkHandleFromHANDLE hwnd hwnd_type filepath iomode mb_codec
+ `onException` IODevice.close hwnd
+ -- NB. don't forget to close the Handle if mkHandleFromHANDLE fails,
+ -- otherwise this Handle leaks.
+
+-- ---------------------------------------------------------------------------
+-- Converting Windows Handles from/to Handles
+
+mkHandleFromHANDLE
+ :: (RawIO dev, IODevice.IODevice dev, BufferedIO dev, Typeable dev) => dev
+ -> IODeviceType
+ -> FilePath -- a string describing this Windows handle (e.g. the filename)
+ -> IOMode
+ -> Maybe TextEncoding
+ -> IO Handle
+
+mkHandleFromHANDLE dev hw_type filepath iomode mb_codec
+ = do
+ let nl | isJust mb_codec = nativeNewlineMode
+ | otherwise = noNewlineTranslation
+
+ case hw_type of
+ Directory ->
+ ioException (IOError Nothing InappropriateType "openFile"
+ "is a directory" Nothing Nothing)
+
+ Stream
+ -- only *Streams* can be DuplexHandles. Other read/write
+ -- Handles must share a buffer.
+ | ReadWriteMode <- iomode ->
+ mkDuplexHandle dev filepath mb_codec nl
+
+
+ _other -> mkFileHandle dev filepath iomode mb_codec nl
+
+-- | Turn an existing Handle into a Win32 HANDLE. This function throws an
+-- IOError if the Handle does not reference a HANDLE
+handleToHANDLE :: Handle -> IO Win.HANDLE
+handleToHANDLE h = case h of
+ FileHandle _ mv -> do
+ Handle__{haDevice = dev} <- readMVar mv
+ case (cast dev :: Maybe (Win.Io Win.NativeHandle)) of
+ Just hwnd -> return $ Win.toHANDLE hwnd
+ Nothing -> throwErr "not a file HANDLE"
+ DuplexHandle{} -> throwErr "not a file handle"
+ where
+ throwErr msg = ioException $ IOError (Just h)
+ InappropriateType "handleToHANDLE" msg Nothing Nothing
+
+-- ---------------------------------------------------------------------------
+-- Are files opened by default in text or binary mode, if the user doesn't
+-- specify? The thing is, to the Win32 APIs which are lowerlevel there exist no
+-- such thing as binary/text mode. That's strictly a thing of the C library on
+-- top of it. So I'm not sure what to do with this. -Tamar
+
+dEFAULT_OPEN_IN_BINARY_MODE :: Bool
+dEFAULT_OPEN_IN_BINARY_MODE = False
diff --git a/libraries/base/GHC/IO/StdHandles.hs b/libraries/base/GHC/IO/StdHandles.hs
new file mode 100644
index 0000000000..7768c1535c
--- /dev/null
+++ b/libraries/base/GHC/IO/StdHandles.hs
@@ -0,0 +1,73 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE CPP #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.IO.StdHandles
+-- Copyright : (c) The University of Glasgow, 2017
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : internal
+-- Portability : non-portable
+--
+-- This model abtracts away the platform specific handles that can be toggled
+-- through the RTS.
+--
+-----------------------------------------------------------------------------
+
+module GHC.IO.StdHandles
+ ( -- std handles
+ stdin, stdout, stderr,
+ openFile, openBinaryFile, openFileBlocking
+ ) where
+
+import GHC.IO
+import GHC.IO.IOMode
+import GHC.IO.Handle.Types
+
+import qualified GHC.IO.Handle.FD as POSIX
+#if defined(mingw32_HOST_OS)
+import GHC.IO.SubSystem
+import qualified GHC.IO.Handle.Windows as Win
+
+stdin :: Handle
+stdin = POSIX.stdin <!> Win.stdin
+
+stdout :: Handle
+stdout = POSIX.stdout <!> Win.stdout
+
+stderr :: Handle
+stderr = POSIX.stderr <!> Win.stderr
+
+openFile :: FilePath -> IOMode -> IO Handle
+openFile = POSIX.openFile <!> Win.openFile
+
+openBinaryFile :: FilePath -> IOMode -> IO Handle
+openBinaryFile = POSIX.openBinaryFile <!> Win.openBinaryFile
+
+openFileBlocking :: FilePath -> IOMode -> IO Handle
+openFileBlocking = POSIX.openFileBlocking <!> Win.openFileBlocking
+
+#else
+
+stdin :: Handle
+stdin = POSIX.stdin
+
+stdout :: Handle
+stdout = POSIX.stdout
+
+stderr :: Handle
+stderr = POSIX.stderr
+
+openFile :: FilePath -> IOMode -> IO Handle
+openFile = POSIX.openFile
+
+openBinaryFile :: FilePath -> IOMode -> IO Handle
+openBinaryFile = POSIX.openBinaryFile
+
+openFileBlocking :: FilePath -> IOMode -> IO Handle
+openFileBlocking = POSIX.openFileBlocking
+
+#endif
diff --git a/libraries/base/GHC/IO/StdHandles.hs-boot b/libraries/base/GHC/IO/StdHandles.hs-boot
new file mode 100644
index 0000000000..4aae3ef7a3
--- /dev/null
+++ b/libraries/base/GHC/IO/StdHandles.hs-boot
@@ -0,0 +1,23 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE CPP #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.IO.StdHandles [boot]
+-- Copyright : (c) The University of Glasgow, 2017
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : internal
+-- Portability : non-portable
+--
+-----------------------------------------------------------------------------
+
+module GHC.IO.StdHandles where
+
+import GHC.IO.Handle.Types
+
+-- used in GHC.Conc, which is below GHC.IO.Handle.FD
+stdout :: Handle
+
diff --git a/libraries/base/GHC/IO/SubSystem.hs b/libraries/base/GHC/IO/SubSystem.hs
new file mode 100644
index 0000000000..e26fd9f55a
--- /dev/null
+++ b/libraries/base/GHC/IO/SubSystem.hs
@@ -0,0 +1,79 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE CPP #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.IO.SubSystem
+-- Copyright : (c) The University of Glasgow, 2017
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : internal
+-- Portability : non-portable
+--
+-- The SubSystem control interface. These methods can be used to disambiguate
+-- between the two operations.
+--
+-----------------------------------------------------------------------------
+
+module GHC.IO.SubSystem (
+ withIoSubSystem,
+ withIoSubSystem',
+ whenIoSubSystem,
+ ioSubSystem,
+ IoSubSystem(..),
+ conditional,
+ (<!>),
+ isWindowsNativeIO
+ ) where
+
+import GHC.Base
+import GHC.RTS.Flags
+
+#if defined(mingw32_HOST_OS)
+import GHC.IO.Unsafe
+#endif
+
+infixl 7 <!>
+
+-- | 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
+-- the first action.
+conditional :: a -> a -> a
+#if defined(mingw32_HOST_OS)
+conditional posix windows =
+ case ioSubSystem of
+ IoPOSIX -> posix
+ IoNative -> windows
+#else
+conditional posix _ = posix
+#endif
+
+-- | Infix version of `conditional`.
+-- posix <!> windows == conditional posix windows
+(<!>) :: a -> a -> a
+(<!>) = conditional
+
+isWindowsNativeIO :: Bool
+isWindowsNativeIO = False <!> True
+
+ioSubSystem :: IoSubSystem
+#if defined(mingw32_HOST_OS)
+{-# NOINLINE ioSubSystem #-}
+ioSubSystem = unsafeDupablePerformIO getIoManagerFlag
+#else
+ioSubSystem = IoPOSIX
+#endif
+
+withIoSubSystem :: (IoSubSystem -> IO a) -> IO a
+withIoSubSystem f = f ioSubSystem
+
+withIoSubSystem' :: (IoSubSystem -> a) -> a
+withIoSubSystem' f = f ioSubSystem
+
+whenIoSubSystem :: IoSubSystem -> IO () -> IO ()
+whenIoSubSystem m f = do let sub = ioSubSystem
+ when (sub == m) f
+
diff --git a/libraries/base/GHC/IO/Windows/Encoding.hs b/libraries/base/GHC/IO/Windows/Encoding.hs
new file mode 100644
index 0000000000..c0ee649662
--- /dev/null
+++ b/libraries/base/GHC/IO/Windows/Encoding.hs
@@ -0,0 +1,218 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{- |
+ Module : System.Win32.Encoding
+ Copyright : 2012 shelarcy
+ License : BSD-style
+
+ Maintainer : shelarcy@gmail.com
+ Stability : Provisional
+ Portability : Non-portable (Win32 API)
+
+ Enocode/Decode mutibyte charactor using Win32 API.
+-}
+
+module GHC.IO.Windows.Encoding
+ ( encodeMultiByte
+ , encodeMultiByteIO
+ , encodeMultiByteRawIO
+ , decodeMultiByte
+ , decodeMultiByteIO
+ , wideCharToMultiByte
+ , multiByteToWideChar
+ , withGhcInternalToUTF16
+ , withUTF16ToGhcInternal
+ ) where
+
+import Data.Word (Word8, Word16)
+import Foreign.C.Types (CInt(..))
+import Foreign.C.String (peekCAStringLen, peekCWStringLen,
+ withCWStringLen, withCAStringLen, )
+import Foreign.Ptr (nullPtr, Ptr ())
+import Foreign.Marshal.Array (allocaArray)
+import Foreign.Marshal.Unsafe (unsafeLocalState)
+import GHC.Windows
+import GHC.IO.Encoding.CodePage (CodePage, getCurrentCodePage)
+import GHC.IO
+import GHC.Base
+import GHC.Real
+
+#include "windows_cconv.h"
+
+-- | The "System.IO" output functions (e.g. `putStr`) don't
+-- automatically convert to multibyte string on Windows, so this
+-- function is provided to make the conversion from a Unicode string
+-- in the given code page to a proper multibyte string. To get the
+-- code page for the console, use `getCurrentCodePage`.
+--
+encodeMultiByte :: CodePage -> String -> String
+encodeMultiByte cp = unsafeLocalState . encodeMultiByteIO cp
+
+{-# INLINE encodeMultiByteIO' #-}
+-- | String must not be zero length.
+encodeMultiByteIO' :: CodePage -> String -> ((LPCSTR, CInt) -> IO a) -> IO a
+encodeMultiByteIO' cp wstr transformer =
+ withCWStringLen wstr $ \(cwstr,len) -> do
+ mbchars' <- failIfZero "WideCharToMultiByte" $ wideCharToMultiByte
+ cp
+ 0
+ cwstr
+ (fromIntegral len)
+ nullPtr 0
+ nullPtr nullPtr
+ -- mbchar' is the length of buffer required
+ allocaArray (fromIntegral mbchars') $ \mbstr -> do
+ mbchars <- failIfZero "WideCharToMultiByte" $ wideCharToMultiByte
+ cp
+ 0
+ cwstr
+ (fromIntegral len)
+ mbstr mbchars'
+ nullPtr nullPtr
+ transformer (mbstr,fromIntegral mbchars)
+
+-- converts [Char] to UTF-16
+encodeMultiByteIO :: CodePage -> String -> IO String
+encodeMultiByteIO _ "" = return ""
+encodeMultiByteIO cp s = encodeMultiByteIO' cp s toString
+ where toString (st,l) = peekCAStringLen (st,fromIntegral l)
+
+-- converts [Char] to UTF-16
+encodeMultiByteRawIO :: CodePage -> String -> IO (LPCSTR, CInt)
+encodeMultiByteRawIO _ "" = return (nullPtr, 0)
+encodeMultiByteRawIO cp s = encodeMultiByteIO' cp s toSizedCString
+ where toSizedCString (st,l) = return (st, fromIntegral l)
+
+foreign import WINDOWS_CCONV "WideCharToMultiByte"
+ wideCharToMultiByte
+ :: CodePage
+ -> DWORD -- dwFlags,
+ -> LPCWSTR -- lpWideCharStr
+ -> CInt -- cchWideChar
+ -> LPSTR -- lpMultiByteStr
+ -> CInt -- cbMultiByte
+ -> LPCSTR -- lpMultiByteStr
+ -> LPBOOL -- lpbFlags
+ -> IO CInt
+
+-- | The `System.IO` input functions (e.g. `getLine`) don't
+-- automatically convert to Unicode, so this function is provided to
+-- make the conversion from a multibyte string in the given code page
+-- to a proper Unicode string. To get the code page for the console,
+-- use `getConsoleCP`.
+stringToUnicode :: CodePage -> String -> IO String
+stringToUnicode _cp "" = return ""
+ -- MultiByteToWideChar doesn't handle empty strings (#1929)
+stringToUnicode cp mbstr =
+ withCAStringLen mbstr $ \(cstr,len) -> do
+ wchars <- failIfZero "MultiByteToWideChar" $ multiByteToWideChar
+ cp
+ 0
+ cstr
+ (fromIntegral len)
+ nullPtr 0
+ -- wchars is the length of buffer required
+ allocaArray (fromIntegral wchars) $ \cwstr -> do
+ wchars' <- failIfZero "MultiByteToWideChar" $ multiByteToWideChar
+ cp
+ 0
+ cstr
+ (fromIntegral len)
+ cwstr wchars
+ peekCWStringLen (cwstr,fromIntegral wchars') -- converts UTF-16 to [Char]
+
+foreign import WINDOWS_CCONV unsafe "MultiByteToWideChar"
+ multiByteToWideChar
+ :: CodePage
+ -> DWORD -- dwFlags,
+ -> LPCSTR -- lpMultiByteStr
+ -> CInt -- cbMultiByte
+ -> LPWSTR -- lpWideCharStr
+ -> CInt -- cchWideChar
+ -> IO CInt
+
+decodeMultiByte :: CodePage -> String -> String
+decodeMultiByte cp = unsafeLocalState . decodeMultiByteIO cp
+
+-- | Because of `stringToUnicode` is unclear name, we use `decodeMultiByteIO`
+-- for alias of `stringToUnicode`.
+decodeMultiByteIO :: CodePage -> String -> IO String
+decodeMultiByteIO = stringToUnicode
+{-# INLINE decodeMultiByteIO #-}
+
+foreign import WINDOWS_CCONV unsafe "MultiByteToWideChar"
+ multiByteToWideChar'
+ :: CodePage
+ -> DWORD -- dwFlags,
+ -> Ptr Word8 -- lpMultiByteStr
+ -> CInt -- cbMultiByte
+ -> Ptr Word16 -- lpWideCharStr
+ -> CInt -- cchWideChar
+ -> IO CInt
+
+-- TODO: GHC is internally UTF-32 which means we have re-encode for
+-- Windows which is annoying. Switch to UTF-16 on IoNative
+-- being default.
+withGhcInternalToUTF16 :: Ptr Word8 -> Int -> ((Ptr Word16, CInt) -> IO a)
+ -> IO a
+withGhcInternalToUTF16 ptr len fn
+ = do cp <- getCurrentCodePage
+ wchars <- failIfZero "withGhcInternalToUTF16" $
+ multiByteToWideChar' cp 0 ptr (fromIntegral len) nullPtr 0
+ -- wchars is the length of buffer required
+ allocaArray (fromIntegral wchars) $ \cwstr -> do
+ wchars' <- failIfZero "withGhcInternalToUTF16" $
+ multiByteToWideChar' cp 0 ptr (fromIntegral len) cwstr wchars
+ fn (cwstr, wchars')
+
+foreign import WINDOWS_CCONV "WideCharToMultiByte"
+ wideCharToMultiByte'
+ :: CodePage
+ -> DWORD -- dwFlags,
+ -> Ptr Word16 -- lpWideCharStr
+ -> CInt -- cchWideChar
+ -> Ptr Word8 -- lpMultiByteStr
+ -> CInt -- cbMultiByte
+ -> LPCSTR -- lpMultiByteStr
+ -> LPBOOL -- lpbFlags
+ -> IO CInt
+
+-- TODO: GHC is internally UTF-32 which means we have re-encode for
+-- Windows which is annoying. Switch to UTF-16 on IoNative
+-- being default.
+
+-- | Decode a UTF16 buffer into the given buffer in the current code page.
+-- The source UTF16 buffer is filled by the function given as argument.
+withUTF16ToGhcInternal :: Ptr Word8 -- Buffer to store the encoded string in.
+ -> Int -- Length of the buffer
+ -- Function to fill source buffer.
+ -> ( CInt -- Size of available buffer in bytes
+ -> Ptr Word16 -- Temporary source buffer.
+ -> IO CInt -- Actual length of buffer content.
+ )
+ -> IO Int -- Returns number of bytes stored in buffer.
+withUTF16ToGhcInternal ptr len fn
+ = do cp <- getCurrentCodePage
+ -- Annoyingly the IO system is very UTF-32 oriented and asks for bytes
+ -- as buffer reads. Problem is we don't know how many bytes we'll end up
+ -- having as UTF-32 MultiByte encoded UTF-16. So be conservative. We assume
+ -- that a single byte may expand to atmost 1 Word16. So assume that each
+ -- byte does and divide the requested number of bytes by two since each
+ -- Word16 encoded wchar may expand to only two Word8 sequences.
+ let reqBytes = fromIntegral (len `div` 2)
+ allocaArray reqBytes $ \w_ptr -> do
+ w_len <- fn (fromIntegral reqBytes) w_ptr
+ if w_len == 0
+ then return 0 else do
+ -- Get required length of encoding
+ mbchars' <- failIfZero "withUTF16ToGhcInternal" $
+ wideCharToMultiByte' cp 0 w_ptr
+ (fromIntegral w_len) nullPtr
+ 0 nullPtr nullPtr
+ assert (mbchars' <= (fromIntegral len)) $ do
+ -- mbchar' is the length of buffer required
+ mbchars <- failIfZero "withUTF16ToGhcInternal" $
+ wideCharToMultiByte' cp 0 w_ptr
+ (fromIntegral w_len) ptr
+ mbchars' nullPtr nullPtr
+ return $ fromIntegral mbchars
diff --git a/libraries/base/GHC/IO/Windows/Handle.hsc b/libraries/base/GHC/IO/Windows/Handle.hsc
new file mode 100644
index 0000000000..7b39691181
--- /dev/null
+++ b/libraries/base/GHC/IO/Windows/Handle.hsc
@@ -0,0 +1,966 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE FlexibleContexts #-}
+-- Whether there are identities depends on the platform
+{-# OPTIONS_HADDOCK hide #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.IO.Windows.Handle
+-- Copyright : (c) The University of Glasgow, 2017
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : internal
+-- Portability : non-portable
+--
+-- Raw read/write operations on Windows Handles
+--
+-----------------------------------------------------------------------------
+
+module GHC.IO.Windows.Handle
+ ( -- * Basic Types
+ NativeHandle(),
+ ConsoleHandle(),
+ IoHandle(),
+ HANDLE,
+ Io(),
+
+ -- * Utility functions
+ convertHandle,
+ toHANDLE,
+ fromHANDLE,
+ handleToMode,
+ optimizeFileAccess,
+
+ -- * Standard Handles
+ stdin,
+ stdout,
+ stderr,
+
+ -- * File utilities
+ openFile,
+ openFileAsTemp,
+ release
+ ) where
+
+#include <windows.h>
+#include <ntstatus.h>
+#include <winnt.h>
+##include "windows_cconv.h"
+
+-- Can't avoid these semantics leaks, they are base constructs
+import Data.Bits ((.|.), (.&.), shiftL)
+import Data.Functor ((<$>))
+import Data.Typeable
+
+import GHC.Base
+import GHC.Enum
+import GHC.Num
+import GHC.Real
+import GHC.List
+import GHC.Word (Word8, Word16, Word64)
+
+import GHC.IO hiding (mask)
+import GHC.IO.Buffer
+import GHC.IO.BufferedIO
+import qualified GHC.IO.Device
+import GHC.IO.Device (SeekMode(..), IODeviceType(..), IODevice(), devType, setSize)
+import GHC.IO.Exception
+import GHC.IO.IOMode
+import GHC.IO.Windows.Encoding (withGhcInternalToUTF16, withUTF16ToGhcInternal)
+import GHC.IO.Windows.Paths (getDevicePath)
+import GHC.IO.Handle.Internals (debugIO)
+import GHC.IORef
+import GHC.Event.Windows (LPOVERLAPPED, withOverlapped, IOResult(..))
+import Foreign.Ptr
+import Foreign.C
+import Foreign.Marshal.Array (pokeArray)
+import Foreign.Marshal.Alloc (alloca, allocaBytes)
+import Foreign.Marshal.Utils (with, fromBool)
+import Foreign.Storable (Storable (..))
+import qualified GHC.Event.Windows as Mgr
+
+import GHC.Windows (LPVOID, LPDWORD, DWORD, HANDLE, BOOL, LPCTSTR, ULONG, WORD,
+ UCHAR, failIf, iNVALID_HANDLE_VALUE, failWith,
+ failIfFalse_, getLastError)
+import Text.Show
+
+-- -----------------------------------------------------------------------------
+-- The Windows IO device handles
+
+data NativeHandle
+data ConsoleHandle
+
+-- | Bit of a Hack, but we don't want every handle to have a cooked entry
+-- but all copies of the handles for which we do want one need to share
+-- the same value.
+-- We can't store it separately because we don't know when the handle will
+-- be destroyed or invalidated.
+data IoHandle a where
+ NativeHandle :: { getNativeHandle :: HANDLE } -> IoHandle NativeHandle
+ ConsoleHandle :: { getConsoleHandle :: HANDLE
+ , cookedHandle :: IORef Bool
+ } -> IoHandle ConsoleHandle
+
+type Io a = IoHandle a
+
+-- | Convert a ConsoleHandle into a general FileHandle
+-- This will change which DeviceIO is used.
+convertHandle :: Io ConsoleHandle -> Io NativeHandle
+convertHandle = fromHANDLE . toHANDLE
+
+-- | @since 4.11.0.0
+instance Show (Io NativeHandle) where
+ show = show . toHANDLE
+
+-- | @since 4.11.0.0
+instance Show (Io ConsoleHandle) where
+ show = show . getConsoleHandle
+
+-- | @since 4.11.0.0
+instance GHC.IO.Device.RawIO (Io NativeHandle) where
+ read = hwndRead
+ readNonBlocking = hwndReadNonBlocking
+ write = hwndWrite
+ writeNonBlocking = hwndWriteNonBlocking
+
+-- | @since 4.11.0.0
+instance GHC.IO.Device.RawIO (Io ConsoleHandle) where
+ read = consoleRead
+ readNonBlocking = consoleReadNonBlocking
+ write = consoleWrite
+ writeNonBlocking = consoleWriteNonBlocking
+
+-- | Generalize a way to get and create handles.
+class (GHC.IO.Device.RawIO a, IODevice a, BufferedIO a, Typeable a)
+ => RawHandle a where
+ toHANDLE :: a -> HANDLE
+ fromHANDLE :: HANDLE -> a
+ isLockable :: a -> Bool
+ setCooked :: a -> Bool -> IO a
+ isCooked :: a -> IO Bool
+
+instance RawHandle (Io NativeHandle) where
+ toHANDLE = getNativeHandle
+ fromHANDLE = NativeHandle
+ isLockable _ = True
+ setCooked = const . return
+ isCooked _ = return False
+
+instance RawHandle (Io ConsoleHandle) where
+ toHANDLE = getConsoleHandle
+ fromHANDLE h = unsafePerformIO $ ConsoleHandle h <$> newIORef False
+ isLockable _ = False
+ setCooked h val =
+ do writeIORef (cookedHandle h) val
+ return h
+ isCooked h = readIORef (cookedHandle h)
+
+-- -----------------------------------------------------------------------------
+-- The Windows IO device implementation
+
+-- | @since 4.11.0.0
+instance GHC.IO.Device.IODevice (Io NativeHandle) where
+ ready = handle_ready
+ close = handle_close
+ isTerminal = handle_is_console
+ isSeekable = handle_is_seekable
+ seek = handle_seek
+ tell = handle_tell
+ getSize = handle_get_size
+ setSize = handle_set_size
+ setEcho = handle_set_echo
+ getEcho = handle_get_echo
+ setRaw = handle_set_buffering
+ devType = handle_dev_type
+ dup = handle_duplicate
+
+-- | @since 4.11.0.0
+instance GHC.IO.Device.IODevice (Io ConsoleHandle) where
+ ready = handle_ready
+ close = handle_close . convertHandle
+ isTerminal = handle_is_console
+ isSeekable = handle_is_seekable
+ seek = handle_console_seek
+ tell = handle_console_tell
+ getSize = handle_get_console_size
+ setSize = handle_set_console_size
+ setEcho = handle_set_echo
+ getEcho = handle_get_echo
+ setRaw = console_set_buffering
+ devType = handle_dev_type
+ dup = handle_duplicate
+
+-- Default sequential read buffer size.
+-- for Windows 8k seems to be the optimal
+-- buffer size.
+dEFAULT_BUFFER_SIZE :: Int
+dEFAULT_BUFFER_SIZE = 8192
+
+-- | @since 4.11.0.0
+-- See libraries/base/GHC/IO/BufferedIO.hs
+instance BufferedIO (Io NativeHandle) where
+ newBuffer _dev state = newByteBuffer dEFAULT_BUFFER_SIZE state
+ fillReadBuffer = readBuf'
+ fillReadBuffer0 = readBufNonBlocking
+ flushWriteBuffer = writeBuf'
+ flushWriteBuffer0 = writeBufNonBlocking
+
+-- | @since 4.11.0.0
+-- See libraries/base/GHC/IO/BufferedIO.hs
+instance BufferedIO (Io ConsoleHandle) where
+ newBuffer _dev state = newByteBuffer dEFAULT_BUFFER_SIZE state
+ fillReadBuffer = readBuf'
+ fillReadBuffer0 = readBufNonBlocking
+ flushWriteBuffer = writeBuf'
+ flushWriteBuffer0 = writeBufNonBlocking
+
+
+readBuf' :: RawHandle a => a -> Buffer Word8 -> IO (Int, Buffer Word8)
+readBuf' hnd buf = do
+ debugIO ("readBuf handle=" ++ show (toHANDLE hnd) ++ " " ++
+ summaryBuffer buf ++ "\n")
+ (r,buf') <- readBuf hnd buf
+ debugIO ("after: " ++ summaryBuffer buf' ++ "\n")
+ return (r,buf')
+
+writeBuf' :: RawHandle a => a -> Buffer Word8 -> IO (Buffer Word8)
+writeBuf' hnd buf = do
+ debugIO ("writeBuf handle=" ++ show (toHANDLE hnd) ++ " " ++
+ summaryBuffer buf ++ "\n")
+ writeBuf hnd buf
+
+-- -----------------------------------------------------------------------------
+-- Standard I/O handles
+
+type StdHandleId = DWORD
+
+#{enum StdHandleId,
+ , sTD_INPUT_HANDLE = STD_INPUT_HANDLE
+ , sTD_OUTPUT_HANDLE = STD_OUTPUT_HANDLE
+ , sTD_ERROR_HANDLE = STD_ERROR_HANDLE
+}
+
+getStdHandle :: StdHandleId -> IO HANDLE
+getStdHandle hid =
+ failIf (== iNVALID_HANDLE_VALUE) "GetStdHandle" $ c_GetStdHandle hid
+
+stdin, stdout, stderr :: Io ConsoleHandle
+stdin = unsafePerformIO $ mkConsoleHandle =<< getStdHandle sTD_INPUT_HANDLE
+stdout = unsafePerformIO $ mkConsoleHandle =<< getStdHandle sTD_OUTPUT_HANDLE
+stderr = unsafePerformIO $ mkConsoleHandle =<< getStdHandle sTD_ERROR_HANDLE
+
+mkConsoleHandle :: HANDLE -> IO (Io ConsoleHandle)
+mkConsoleHandle hwnd
+ = do ref <- newIORef False
+ return $ ConsoleHandle hwnd ref
+
+-- -----------------------------------------------------------------------------
+-- Some console internal types to detect EOF.
+
+-- ASCII Ctrl+D (EOT) character. Typically used by Unix consoles.
+-- use for cross platform compatibility and to adhere to the ASCII standard.
+acCtrlD :: Int
+acCtrlD = 0x04
+-- ASCII Ctrl+Z (SUB) character. Typically used by Windows consoles to denote
+-- EOT. Use for compatibility with user expectations.
+acCtrlZ :: Int
+acCtrlZ = 0x1A
+
+-- Mask to use to trigger ReadConsole input processing end.
+acEotMask :: ULONG
+acEotMask = (1 `shiftL` acCtrlD) .|. (1 `shiftL` acCtrlZ)
+
+-- Structure to hold the control character masks
+type PCONSOLE_READCONSOLE_CONTROL = Ptr CONSOLE_READCONSOLE_CONTROL
+data CONSOLE_READCONSOLE_CONTROL = CONSOLE_READCONSOLE_CONTROL
+ { crcNLength :: ULONG
+ , crcNInitialChars :: ULONG
+ , crcDwCtrlWakeupMask :: ULONG
+ , crcDwControlKeyState :: ULONG
+ } deriving Show
+
+instance Storable CONSOLE_READCONSOLE_CONTROL where
+ sizeOf = const #size CONSOLE_READCONSOLE_CONTROL
+ alignment = const #alignment CONSOLE_READCONSOLE_CONTROL
+ poke buf crc = do
+ (#poke CONSOLE_READCONSOLE_CONTROL, nLength) buf
+ (crcNLength crc)
+ (#poke CONSOLE_READCONSOLE_CONTROL, nInitialChars) buf
+ (crcNInitialChars crc)
+ (#poke CONSOLE_READCONSOLE_CONTROL, dwCtrlWakeupMask) buf
+ (crcDwCtrlWakeupMask crc)
+ (#poke CONSOLE_READCONSOLE_CONTROL, dwControlKeyState) buf
+ (crcDwControlKeyState crc)
+
+ peek buf = do
+ vNLength <-
+ (#peek CONSOLE_READCONSOLE_CONTROL, nLength) buf
+ vNInitialChars <-
+ (#peek CONSOLE_READCONSOLE_CONTROL, nInitialChars) buf
+ vDwCtrlWakeupMask <-
+ (#peek CONSOLE_READCONSOLE_CONTROL, dwCtrlWakeupMask) buf
+ vDwControlKeyState <-
+ (#peek CONSOLE_READCONSOLE_CONTROL, dwControlKeyState) buf
+ return $ CONSOLE_READCONSOLE_CONTROL {
+ crcNLength = vNLength,
+ crcNInitialChars = vNInitialChars,
+ crcDwCtrlWakeupMask = vDwCtrlWakeupMask,
+ crcDwControlKeyState = vDwControlKeyState
+ }
+
+-- Create CONSOLE_READCONSOLE_CONTROL for breaking on control characters
+-- specified by acEotMask
+eotControl :: CONSOLE_READCONSOLE_CONTROL
+eotControl =
+ CONSOLE_READCONSOLE_CONTROL
+ { crcNLength = fromIntegral $
+ sizeOf (undefined :: CONSOLE_READCONSOLE_CONTROL)
+ , crcNInitialChars = 0
+ , crcDwCtrlWakeupMask = acEotMask
+ , crcDwControlKeyState = 0
+ }
+
+type PINPUT_RECORD = Ptr ()
+-- -----------------------------------------------------------------------------
+-- Foreign imports
+
+
+foreign import WINDOWS_CCONV safe "windows.h CreateFileW"
+ c_CreateFile :: LPCTSTR -> DWORD -> DWORD -> LPSECURITY_ATTRIBUTES
+ -> DWORD -> DWORD -> HANDLE
+ -> IO HANDLE
+
+foreign import WINDOWS_CCONV safe "windows.h SetFileCompletionNotificationModes"
+ c_SetFileCompletionNotificationModes :: HANDLE -> UCHAR -> IO BOOL
+
+foreign import WINDOWS_CCONV safe "windows.h ReadFile"
+ c_ReadFile :: HANDLE -> LPVOID -> DWORD -> LPDWORD -> LPOVERLAPPED
+ -> IO BOOL
+
+foreign import WINDOWS_CCONV safe "windows.h WriteFile"
+ c_WriteFile :: HANDLE -> LPVOID -> DWORD -> LPDWORD -> LPOVERLAPPED
+ -> IO BOOL
+
+foreign import WINDOWS_CCONV safe "windows.h GetStdHandle"
+ c_GetStdHandle :: StdHandleId -> IO HANDLE
+
+foreign import ccall safe "__handle_ready"
+ c_handle_ready :: HANDLE -> BOOL -> CInt -> IO CInt
+
+foreign import ccall safe "__is_console"
+ c_is_console :: HANDLE -> IO BOOL
+
+foreign import ccall safe "__set_console_buffering"
+ c_set_console_buffering :: HANDLE -> BOOL -> IO BOOL
+
+foreign import ccall safe "__set_console_echo"
+ c_set_console_echo :: HANDLE -> BOOL -> IO BOOL
+
+foreign import ccall safe "__get_console_echo"
+ c_get_console_echo :: HANDLE -> IO BOOL
+
+foreign import ccall safe "__close_handle"
+ c_close_handle :: HANDLE -> IO Bool
+
+foreign import ccall safe "__handle_type"
+ c_handle_type :: HANDLE -> IO Int
+
+foreign import ccall safe "__set_file_pointer"
+ c_set_file_pointer :: HANDLE -> CLong -> DWORD -> Ptr CLong -> IO BOOL
+
+foreign import ccall safe "__get_file_pointer"
+ c_get_file_pointer :: HANDLE -> IO CLong
+
+foreign import ccall safe "__get_file_size"
+ c_get_file_size :: HANDLE -> IO CLong
+
+foreign import ccall safe "__set_file_size"
+ c_set_file_size :: HANDLE -> CLong -> IO BOOL
+
+foreign import ccall safe "__duplicate_handle"
+ c_duplicate_handle :: HANDLE -> Ptr HANDLE -> IO BOOL
+
+foreign import ccall safe "__set_console_pointer"
+ c_set_console_pointer :: HANDLE -> CLong -> DWORD -> Ptr CLong -> IO BOOL
+
+foreign import ccall safe "__get_console_pointer"
+ c_get_console_pointer :: HANDLE -> IO CLong
+
+foreign import ccall safe "__get_console_buffer_size"
+ c_get_console_buffer_size :: HANDLE -> IO CLong
+
+foreign import ccall safe "__set_console_buffer_size"
+ c_set_console_buffer_size :: HANDLE -> CLong -> IO BOOL
+
+foreign import WINDOWS_CCONV safe "windows.h ReadConsoleW"
+ c_read_console :: HANDLE -> Ptr Word16 -> DWORD -> Ptr DWORD
+ -> PCONSOLE_READCONSOLE_CONTROL -> IO BOOL
+
+foreign import WINDOWS_CCONV safe "windows.h WriteConsoleW"
+ c_write_console :: HANDLE -> Ptr Word16 -> DWORD -> Ptr DWORD -> Ptr ()
+ -> IO BOOL
+
+foreign import WINDOWS_CCONV safe "windows.h ReadConsoleInputW"
+ c_read_console_input :: HANDLE -> PINPUT_RECORD -> DWORD -> LPDWORD -> IO BOOL
+
+type LPSECURITY_ATTRIBUTES = LPVOID
+
+-- -----------------------------------------------------------------------------
+-- Reading and Writing
+
+-- For this to actually block, the file handle must have
+-- been created with FILE_FLAG_OVERLAPPED not set. As an implementation note I
+-- am choosing never to let this block. But this can be easily accomplished by
+-- a getOverlappedResult call with True
+hwndRead :: Io NativeHandle -> Ptr Word8 -> Word64 -> Int -> IO Int
+hwndRead hwnd ptr offset bytes
+ = fmap fromIntegral $ Mgr.withException "hwndRead" $
+ withOverlapped "hwndRead" (toHANDLE hwnd) offset (startCB ptr) completionCB
+ where
+ startCB outBuf lpOverlapped = do
+ debugIO ":: hwndRead"
+ -- See Note [ReadFile/WriteFile].
+ ret <- c_ReadFile (toHANDLE hwnd) (castPtr outBuf)
+ (fromIntegral bytes) nullPtr lpOverlapped
+ return $ Mgr.CbNone ret
+
+ completionCB err dwBytes
+ | err == #{const ERROR_SUCCESS} = Mgr.ioSuccess $ fromIntegral dwBytes
+ | err == #{const ERROR_HANDLE_EOF} = Mgr.ioSuccess 0
+ | err == #{const STATUS_END_OF_FILE} = Mgr.ioSuccess 0
+ | err == #{const ERROR_BROKEN_PIPE} = Mgr.ioSuccess 0
+ | err == #{const STATUS_PIPE_BROKEN} = Mgr.ioSuccess 0
+ | err == #{const ERROR_NO_MORE_ITEMS} = Mgr.ioSuccess 0
+ | err == #{const ERROR_MORE_DATA} = Mgr.ioSuccess $ fromIntegral dwBytes
+ | otherwise = Mgr.ioFailed err
+
+-- In WinIO we'll never block in the FFI call, so this call is equivalent to
+-- hwndRead, Though we may revisit this when implementing sockets and pipes.
+-- It still won't block, but may set up extra book keeping so threadWait and
+-- threadWrite may work.
+hwndReadNonBlocking :: Io NativeHandle -> Ptr Word8 -> Word64 -> Int
+ -> IO (Maybe Int)
+hwndReadNonBlocking hwnd ptr offset bytes
+ = do val <- withOverlapped "hwndReadNonBlocking" (toHANDLE hwnd) offset
+ (startCB ptr) completionCB
+ return $ ioValue val
+ where
+ startCB inputBuf lpOverlapped = do
+ debugIO ":: hwndReadNonBlocking"
+ -- See Note [ReadFile/WriteFile].
+ ret <- c_ReadFile (toHANDLE hwnd) (castPtr inputBuf)
+ (fromIntegral bytes) nullPtr lpOverlapped
+ return $ Mgr.CbNone ret
+
+ completionCB err dwBytes
+ | err == #{const ERROR_SUCCESS} = Mgr.ioSuccess $ Just $! fromIntegral dwBytes
+ | err == #{const ERROR_HANDLE_EOF} = Mgr.ioSuccess Nothing
+ | err == #{const STATUS_END_OF_FILE} = Mgr.ioSuccess Nothing
+ | err == #{const ERROR_BROKEN_PIPE} = Mgr.ioSuccess Nothing
+ | err == #{const STATUS_PIPE_BROKEN} = Mgr.ioSuccess Nothing
+ | err == #{const ERROR_NO_MORE_ITEMS} = Mgr.ioSuccess Nothing
+ | err == #{const ERROR_MORE_DATA} = Mgr.ioSuccess $ Just $! fromIntegral dwBytes
+ | otherwise = Mgr.ioFailedAny err
+
+hwndWrite :: Io NativeHandle -> Ptr Word8 -> Word64 -> Int -> IO ()
+hwndWrite hwnd ptr offset bytes
+ = do _ <- Mgr.withException "hwndWrite" $
+ withOverlapped "hwndWrite" (toHANDLE hwnd) offset (startCB ptr)
+ completionCB
+ return ()
+ where
+ startCB outBuf lpOverlapped = do
+ debugIO ":: hwndWrite"
+ -- See Note [ReadFile/WriteFile].
+ ret <- c_WriteFile (toHANDLE hwnd) (castPtr outBuf)
+ (fromIntegral bytes) nullPtr lpOverlapped
+ return $ Mgr.CbNone ret
+
+ completionCB err dwBytes
+ | err == #{const ERROR_SUCCESS} = Mgr.ioSuccess $ fromIntegral dwBytes
+ | err == #{const ERROR_HANDLE_EOF} = Mgr.ioSuccess $ fromIntegral dwBytes
+ | otherwise = Mgr.ioFailed err
+
+hwndWriteNonBlocking :: Io NativeHandle -> Ptr Word8 -> Word64 -> Int -> IO Int
+hwndWriteNonBlocking hwnd ptr offset bytes
+ = do val <- withOverlapped "hwndReadNonBlocking" (toHANDLE hwnd) offset
+ (startCB ptr) completionCB
+ return $ fromIntegral $ ioValue val
+ where
+ startCB :: Ptr a -> LPOVERLAPPED -> IO (Mgr.CbResult a1)
+ startCB outBuf lpOverlapped = do
+ debugIO ":: hwndWriteNonBlocking"
+ -- See Note [ReadFile/WriteFile].
+ ret <- c_WriteFile (toHANDLE hwnd) (castPtr outBuf)
+ (fromIntegral bytes) nullPtr lpOverlapped
+ return $ Mgr.CbNone ret
+
+ completionCB err dwBytes
+ | err == #{const ERROR_SUCCESS} = Mgr.ioSuccess $ fromIntegral dwBytes
+ | err == #{const ERROR_HANDLE_EOF} = Mgr.ioSuccess $ fromIntegral dwBytes
+ | otherwise = Mgr.ioFailed err
+
+-- Note [ReadFile/WriteFile]
+-- The results of these functions are somewhat different when working in an
+-- asynchronous manner. The returning bool has two meaning.
+--
+-- True: The operation is done and was completed synchronously. This is
+-- possible because of the optimization flags we enable. In this case
+-- there won't be a completion event for this call and so we shouldn't
+-- queue one up. If we do this request will never terminate. It's also
+-- safe to free the OVERLAPPED structure immediately.
+--
+-- False: Only indicates that the operation was not completed synchronously, a
+-- call to GetLastError () is needed to find out the actual status. If
+-- the result is ERROR_IO_PENDING then the operation has been queued on
+-- the completion port and we should proceed asynchronously. Any other
+-- state is usually an indication that the call failed.
+--
+-- NB. reading an EOF will result in ERROR_HANDLE_EOF or STATUS_END_OF_FILE
+-- during the checking of the completion results. We need to check for these
+-- so we don't incorrectly fail.
+
+
+consoleWrite :: Io ConsoleHandle -> Ptr Word8 -> Word64 -> Int -> IO ()
+consoleWrite hwnd ptr _offset bytes
+ = alloca $ \res ->
+ do failIfFalse_ "GHC.IO.Handle.consoleWrite" $ do
+ debugIO ":: consoleWrite"
+ withGhcInternalToUTF16 ptr bytes $ \(w_ptr, w_len) -> do
+ success <- c_write_console (toHANDLE hwnd) w_ptr
+ (fromIntegral w_len) res nullPtr
+ if not success
+ then return False
+ else do val <- fromIntegral <$> peek res
+ return $ val == w_len
+
+consoleWriteNonBlocking :: Io ConsoleHandle -> Ptr Word8 -> Word64 -> Int -> IO Int
+consoleWriteNonBlocking hwnd ptr _offset bytes
+ = alloca $ \res ->
+ do failIfFalse_ "GHC.IO.Handle.consoleWriteNonBlocking" $ do
+ debugIO ":: consoleWriteNonBlocking"
+ withGhcInternalToUTF16 ptr bytes $ \(w_ptr, w_len) -> do
+ c_write_console (toHANDLE hwnd) w_ptr (fromIntegral w_len)
+ res nullPtr
+ val <- fromIntegral <$> peek res
+ return val
+
+consoleRead :: Io ConsoleHandle -> Ptr Word8 -> Word64 -> Int -> IO Int
+consoleRead hwnd ptr _offset bytes
+ = withUTF16ToGhcInternal ptr bytes $ \reqBytes w_ptr ->
+ alloca $ \res -> do
+ cooked <- isCooked hwnd
+ -- Cooked input must be handled differently when the STD handles are
+ -- attached to a real console handle. For File based handles we can't do
+ -- proper cooked inputs, but since the actions are async you would get
+ -- results as soon as available.
+ --
+ -- For console handles We have to use a lower level API then ReadConsole,
+ -- namely we must use ReadConsoleInput which requires us to process
+ -- all console message manually.
+ --
+ -- Do note that MSYS2 shells such as bash don't attach to a real handle,
+ -- and instead have by default a pipe/file based std handles. Which
+ -- means the cooked behaviour is best when used in a native Windows
+ -- terminal such as cmd, powershell or ConEmu.
+ case cooked of
+ False -> do
+ debugIO "consoleRead :: un-cooked I/O read."
+ -- eotControl allows us to handle control characters like EOL
+ -- without needing a newline, which would sort of defeat the point
+ -- of an EOL.
+ res_code <- with eotControl $ \p_eotControl ->
+ c_read_console (toHANDLE hwnd) w_ptr (fromIntegral reqBytes) res
+ p_eotControl
+
+ -- Restore a quirk of the POSIX read call, which only returns a fail
+ -- when the handle is invalid, e.g. closed or not a handle. It how-
+ -- ever returns 0 when the handle is valid but unreadable, such as
+ -- passing a handle with no GENERIC_READ permission, like /dev/null
+ err <- getLastError
+ when (not res_code) $
+ case () of
+ _ | err == #{const ERROR_INVALID_FUNCTION} -> return ()
+ | otherwise -> failWith "GHC.IO.Handle.consoleRead" err
+ b_read <- fromIntegral <$> peek res
+ if b_read /= 1
+ then return b_read
+ else do w_first <- peekElemOff w_ptr 0
+ case () of
+ -- Handle Ctrl+Z which is the actual EOL sequence on
+ -- windows, but also handle Ctrl+D which is what the
+ -- ASCII standard defines as EOL.
+ _ | w_first == fromIntegral acCtrlD -> return 0
+ | w_first == fromIntegral acCtrlZ -> return 0
+ | otherwise -> return b_read
+ True -> do
+ debugIO "consoleRead :: cooked I/O read."
+ -- Input is cooked, don't wait till a line return and consume all
+ -- characters as they are. Technically this function can handle any
+ -- console event. Including mouse, window and virtual key events
+ -- but for now I'm only interested in key presses.
+ let entries = fromIntegral $ reqBytes `div` (#size INPUT_RECORD)
+ allocaBytes entries $ \p_inputs ->
+ readEvent p_inputs entries res w_ptr
+
+ where readEvent p_inputs entries res w_ptr = do
+ failIfFalse_ "GHC.IO.Handle.consoleRead" $
+ c_read_console_input (toHANDLE hwnd) p_inputs
+ (fromIntegral entries) res
+
+ b_read <- fromIntegral <$> peek res
+ read <- cobble b_read w_ptr p_inputs
+ if read > 0
+ then return $ fromIntegral read
+ else readEvent p_inputs entries res w_ptr
+
+ -- Dereference and read console input records. We only read the bare
+ -- minimum required to know which key/sequences were pressed. To do
+ -- this and prevent having to fully port the PINPUT_RECORD structure
+ -- in Haskell we use some GCC builtins to find the correct offsets.
+ cobble :: Int -> Ptr Word16 -> PINPUT_RECORD -> IO Int
+ cobble 0 _ _ = do debugIO "cobble: done."
+ return 0
+ cobble n w_ptr p_inputs =
+ do eventType <- peekByteOff p_inputs 0 :: IO WORD
+ debugIO $ "cobble: Length=" ++ show n
+ debugIO $ "cobble: Type=" ++ show eventType
+ let ni_offset = #size INPUT_RECORD
+ let event = #{const __builtin_offsetof (INPUT_RECORD, Event)}
+ let char_offset = event + #{const __builtin_offsetof (KEY_EVENT_RECORD, uChar)}
+ let btnDown_offset = event + #{const __builtin_offsetof (KEY_EVENT_RECORD, bKeyDown)}
+ let repeat_offset = event + #{const __builtin_offsetof (KEY_EVENT_RECORD, wRepeatCount)}
+ let n' = n - 1
+ let p_inputs' = p_inputs `plusPtr` ni_offset
+ btnDown <- peekByteOff p_inputs btnDown_offset
+ repeated <- fromIntegral <$> (peekByteOff p_inputs repeat_offset :: IO WORD)
+ debugIO $ "cobble: BtnDown=" ++ show btnDown
+ -- Handle the key only on button down and not on button up.
+ if eventType == #{const KEY_EVENT} && btnDown
+ then do debugIO $ "cobble: read-char."
+ char <- peekByteOff p_inputs char_offset
+ let w_ptr' = w_ptr `plusPtr` 1
+ debugIO $ "cobble: offset - " ++ show char_offset
+ debugIO $ "cobble: show > " ++ show char
+ debugIO $ "cobble: repeat: " ++ show repeated
+ pokeArray w_ptr $ replicate repeated char
+ (+1) <$> cobble n' w_ptr' p_inputs'
+ else do debugIO $ "cobble: skip event."
+ cobble n' w_ptr p_inputs'
+
+
+consoleReadNonBlocking :: Io ConsoleHandle -> Ptr Word8 -> Word64 -> Int
+ -> IO (Maybe Int)
+consoleReadNonBlocking hwnd ptr offset bytes
+ = Just <$> consoleRead hwnd ptr offset bytes
+
+-- -----------------------------------------------------------------------------
+-- Operations on file handles
+
+handle_ready :: RawHandle a => a -> Bool -> Int -> IO Bool
+handle_ready hwnd write msecs = do
+ r <- throwErrnoIfMinus1Retry "GHC.IO.Windows.Handle.handle_ready" $
+ c_handle_ready (toHANDLE hwnd) write (fromIntegral msecs)
+ return (toEnum (fromIntegral r))
+
+handle_is_console :: RawHandle a => a -> IO Bool
+handle_is_console = c_is_console . toHANDLE
+
+handle_close :: RawHandle a => a -> IO ()
+handle_close h = do release h
+ failIfFalse_ "handle_close" $ c_close_handle (toHANDLE h)
+
+handle_dev_type :: RawHandle a => a -> IO IODeviceType
+handle_dev_type hwnd = do _type <- c_handle_type $ toHANDLE hwnd
+ return $ case _type of
+ _ | _type == 3 -> Stream
+ | _type == 5 -> RawDevice
+ | otherwise -> RegularFile
+
+handle_is_seekable :: RawHandle a => a -> IO Bool
+handle_is_seekable hwnd = do
+ t <- handle_dev_type hwnd
+ return (t == RegularFile || t == RawDevice)
+
+handle_seek :: RawHandle a => a -> SeekMode -> Integer -> IO Integer
+handle_seek hwnd mode off =
+ with 0 $ \off_rel -> do
+ failIfFalse_ "GHC.IO.Handle.handle_seek" $
+ c_set_file_pointer (toHANDLE hwnd) (fromIntegral off) seektype off_rel
+ fromIntegral <$> peek off_rel
+ where
+ seektype :: DWORD
+ seektype = case mode of
+ AbsoluteSeek -> #{const FILE_BEGIN}
+ RelativeSeek -> #{const FILE_CURRENT}
+ SeekFromEnd -> #{const FILE_END}
+
+handle_tell :: RawHandle a => a -> IO Integer
+handle_tell hwnd =
+ fromIntegral `fmap`
+ (throwErrnoIfMinus1Retry "GHC.IO.Handle.handle_tell" $
+ c_get_file_pointer (toHANDLE hwnd))
+
+handle_set_size :: RawHandle a => a -> Integer -> IO ()
+handle_set_size hwnd size =
+ failIfFalse_ "GHC.IO.Handle.handle_set_size" $
+ c_set_file_size (toHANDLE hwnd) (fromIntegral size)
+
+handle_get_size :: RawHandle a => a -> IO Integer
+handle_get_size hwnd =
+ fromIntegral `fmap`
+ (throwErrnoIfMinus1Retry "GHC.IO.Handle.handle_set_size" $
+ c_get_file_size (toHANDLE hwnd))
+
+handle_set_echo :: RawHandle a => a -> Bool -> IO ()
+handle_set_echo hwnd value =
+ failIfFalse_ "GHC.IO.Handle.handle_set_echo" $
+ c_set_console_echo (toHANDLE hwnd) value
+
+handle_get_echo :: RawHandle a => a -> IO Bool
+handle_get_echo = c_get_console_echo . toHANDLE
+
+handle_duplicate :: RawHandle a => a -> IO a
+handle_duplicate hwnd = alloca $ \ptr -> do
+ failIfFalse_ "GHC.IO.Handle.handle_duplicate" $
+ c_duplicate_handle (toHANDLE hwnd) ptr
+ fromHANDLE <$> peek ptr
+
+console_set_buffering :: Io ConsoleHandle -> Bool -> IO ()
+console_set_buffering hwnd value = setCooked hwnd value >> return ()
+
+handle_set_buffering :: RawHandle a => a -> Bool -> IO ()
+handle_set_buffering hwnd value =
+ failIfFalse_ "GHC.IO.Handle.handle_set_buffering" $
+ c_set_console_buffering (toHANDLE hwnd) value
+
+handle_console_seek :: RawHandle a => a -> SeekMode -> Integer -> IO Integer
+handle_console_seek hwnd mode off =
+ with 0 $ \loc_ptr -> do
+ failIfFalse_ "GHC.IO.Handle.handle_console_seek" $
+ c_set_console_pointer (toHANDLE hwnd) (fromIntegral off) seektype loc_ptr
+ fromIntegral <$> peek loc_ptr
+ where
+ seektype :: DWORD
+ seektype = case mode of
+ AbsoluteSeek -> #{const FILE_BEGIN}
+ RelativeSeek -> #{const FILE_CURRENT}
+ SeekFromEnd -> #{const FILE_END}
+
+handle_console_tell :: RawHandle a => a -> IO Integer
+handle_console_tell hwnd =
+ fromIntegral `fmap`
+ (throwErrnoIfMinus1Retry "GHC.IO.Handle.handle_console_tell" $
+ c_get_console_pointer (toHANDLE hwnd))
+
+handle_set_console_size :: RawHandle a => a -> Integer -> IO ()
+handle_set_console_size hwnd size =
+ failIfFalse_ "GHC.IO.Handle.handle_set_console_size" $
+ c_set_console_buffer_size (toHANDLE hwnd) (fromIntegral size)
+
+handle_get_console_size :: RawHandle a => a -> IO Integer
+handle_get_console_size hwnd =
+ fromIntegral `fmap`
+ (throwErrnoIfMinus1Retry "GHC.IO.Handle.handle_get_console_size" $
+ c_get_console_buffer_size (toHANDLE hwnd))
+
+-- -----------------------------------------------------------------------------
+-- opening files
+
+-- | Describes if and which temp file flags to use.
+data TempFileOptions = NoTemp | TempNonExcl | TempExcl deriving Eq
+
+-- | Open a file and make an 'NativeHandle' for it. Truncates the file to zero
+-- size when the `IOMode` is `WriteMode`.
+openFile
+ :: FilePath -- ^ file to open
+ -> IOMode -- ^ mode in which to open the file
+ -> Bool -- ^ open the file in non-blocking mode?
+ -> IO (Io NativeHandle, IODeviceType)
+openFile filepath iomode non_blocking = openFile' filepath iomode non_blocking NoTemp
+
+-- | Open a file as a temporary file and make an 'NativeHandle' for it.
+-- Truncates the file to zero size when the `IOMode` is `WriteMode`.
+openFileAsTemp
+ :: FilePath -- ^ file to open
+ -> Bool -- ^ open the file in non-blocking mode?
+ -> Bool -- ^ Exclusive mode
+ -> IO (Io NativeHandle, IODeviceType)
+openFileAsTemp filepath non_blocking excl
+ = openFile' filepath ReadWriteMode non_blocking (if excl then TempExcl else TempNonExcl)
+
+-- | Open a file and make an 'NativeHandle' for it. Truncates the file to zero
+-- size when the `IOMode` is `WriteMode`.
+openFile'
+ :: FilePath -- ^ file to open
+ -> IOMode -- ^ mode in which to open the file
+ -> Bool -- ^ open the file in non-blocking mode?
+ -> TempFileOptions
+ -> IO (Io NativeHandle, IODeviceType)
+openFile' filepath iomode non_blocking tmp_opts =
+ do devicepath <- getDevicePath filepath
+ h <- createFile devicepath
+ -- Attach the handle to the I/O manager's CompletionPort. This allows the
+ -- I/O manager to service requests for this Handle.
+ Mgr.associateHandle' h
+ let hwnd = fromHANDLE h
+ _type <- devType hwnd
+
+ -- Use the rts to enforce any file locking we may need.
+ let write_lock = iomode /= ReadMode
+
+ case _type of
+ -- Regular files need to be locked.
+ -- See also Note [RTS File locking]
+ RegularFile -> do
+ optimizeFileAccess h -- Set a few optimization flags on file handles.
+ (unique_dev, unique_ino) <- getUniqueFileInfo hwnd
+ r <- lockFile (fromIntegral $ ptrToWordPtr h) unique_dev unique_ino
+ (fromBool write_lock)
+ when (r == -1) $
+ ioException (IOError Nothing ResourceBusy "openFile"
+ "file is locked" Nothing Nothing)
+
+ -- I don't see a reason for blocking directories. So unlike the FD
+ -- implementation I'll allow it.
+ _ -> return ()
+
+ -- We want to truncate() if this is an open in WriteMode, but only
+ -- if the target is a RegularFile. but TRUNCATE_EXISTING would fail if
+ -- the file didn't exit. So just set the size afterwards.
+ when (iomode == WriteMode && _type == RegularFile) $
+ setSize hwnd 0
+
+ return (hwnd, _type)
+ where
+ flagIf p f2
+ | p = f2
+ | otherwise = 0
+ -- We have to use in-process locking (e.g. use the locking mechanism
+ -- in the rts) so we're consistent with the linux behavior and the
+ -- rts knows about the lock. See #4363 for more.
+ file_share_mode = #{const FILE_SHARE_READ}
+ .|. #{const FILE_SHARE_DELETE}
+ -- Don't support shared writing for temp files.
+ .|. (flagIf (tmp_opts == NoTemp)
+ #{const FILE_SHARE_WRITE})
+
+ file_access_mode =
+ case iomode of
+ ReadMode -> #{const GENERIC_READ}
+ WriteMode -> #{const GENERIC_WRITE}
+ ReadWriteMode -> #{const GENERIC_READ}
+ .|. #{const GENERIC_WRITE}
+ AppendMode -> #{const GENERIC_WRITE}
+ .|. #{const FILE_APPEND_DATA}
+
+ file_open_mode =
+ case iomode of
+ ReadMode -> #{const OPEN_EXISTING} -- O_RDONLY
+ WriteMode -> #{const OPEN_ALWAYS} -- O_CREAT | O_WRONLY | O_TRUNC
+ ReadWriteMode ->
+ case tmp_opts of
+ NoTemp -> #{const OPEN_ALWAYS} -- O_CREAT | O_RDWR
+ TempNonExcl -> #{const CREATE_ALWAYS} -- O_CREAT | O_RDWR
+ TempExcl -> #{const CREATE_NEW} -- O_CREAT | O_RDWR | O_EXCL
+ AppendMode -> #{const OPEN_ALWAYS} -- O_APPEND
+
+ file_create_flags =
+ if non_blocking
+ -- On Windows, the choice of whether an operation completes
+ -- asynchronously or not depends on how the Handle was created
+ -- and not on the operation called. As in, the behaviour of
+ -- ReadFile and WriteFile depends on the flags used to open the
+ -- handle. For WinIO we always use FILE_FLAG_OVERLAPPED, which
+ -- means we always issue asynchronous file operation using an
+ -- OVERLAPPED structure. All blocking, if required must be done
+ -- on the Haskell side by using existing mechanisms such as MVar
+ -- or IOPorts.
+ then #{const FILE_FLAG_OVERLAPPED}
+ -- I beleive most haskell programs do sequential scans, so
+ -- optimize for the common case. Though ideally, this would
+ -- be parameterized by openFile. This will absolutely trash
+ -- the cache on reverse scans.
+ --
+ -- TODO: make a parameter to openFile and specify only for
+ -- operations we know are sequential. This parameter should
+ -- be usable by madvise too.
+ .|. #{const FILE_FLAG_SEQUENTIAL_SCAN}
+ .|. (flagIf (tmp_opts /= NoTemp)
+ -- Hold data in cache for as long as possible
+ #{const FILE_ATTRIBUTE_TEMPORARY} )
+ else #{const FILE_ATTRIBUTE_NORMAL}
+ .|. (flagIf (tmp_opts /= NoTemp)
+ -- Hold data in cache for as long as possible
+ #{const FILE_ATTRIBUTE_TEMPORARY} )
+
+ createFile devicepath =
+ withCWString devicepath $ \fp ->
+ failIf (== iNVALID_HANDLE_VALUE) "CreateFile" $
+ c_CreateFile fp file_access_mode
+ file_share_mode
+ nullPtr
+ file_open_mode
+ file_create_flags
+ nullPtr
+
+-- Tell the OS that we support skipping the request Queue if the
+-- IRQ can be handled immediately, e.g. if the data is in the cache.
+optimizeFileAccess :: HANDLE -> IO ()
+optimizeFileAccess handle =
+ failIfFalse_ "SetFileCompletionNotificationModes" $
+ c_SetFileCompletionNotificationModes handle
+ ( #{const FILE_SKIP_COMPLETION_PORT_ON_SUCCESS}
+ .|. #{const FILE_SKIP_SET_EVENT_ON_HANDLE})
+
+-- Reconstruct an I/O mode from an open HANDLE
+handleToMode :: HANDLE -> IO IOMode
+handleToMode hwnd = do
+ mask <- c_get_handle_access_mask hwnd
+ let hasFlag flag = (flag .&. mask) == flag
+ case () of
+ () | hasFlag (#{const FILE_APPEND_DATA}) -> return AppendMode
+ | hasFlag (#{const GENERIC_WRITE} .|. #{const GENERIC_READ}) -> return ReadWriteMode
+ | hasFlag (#{const GENERIC_READ}) -> return ReadMode
+ | hasFlag (#{const GENERIC_WRITE}) -> return WriteMode
+ | otherwise -> error "unknown access mask in handleToMode."
+
+foreign import ccall unsafe "__get_handle_access_mask"
+ c_get_handle_access_mask :: HANDLE -> IO DWORD
+
+release :: RawHandle a => a -> IO ()
+release h = if isLockable h
+ then do let handle = fromIntegral $ ptrToWordPtr $ toHANDLE h
+ _ <- unlockFile handle
+ return ()
+ else return ()
+
+-- -----------------------------------------------------------------------------
+-- Locking/unlocking
+
+foreign import ccall unsafe "lockFile"
+ lockFile :: CUIntPtr -> Word64 -> Word64 -> CInt -> IO CInt
+
+foreign import ccall unsafe "unlockFile"
+ unlockFile :: CUIntPtr -> IO CInt
+
+-- | Returns -1 on error. Otherwise writes two values representing
+-- the file into the given ptrs.
+foreign import ccall unsafe "get_unique_file_info_hwnd"
+ c_getUniqueFileInfo :: HANDLE -> Ptr Word64 -> Ptr Word64 -> IO ()
+
+-- | getUniqueFileInfo assumes the C call to getUniqueFileInfo
+-- succeeds.
+getUniqueFileInfo :: RawHandle a => a -> IO (Word64, Word64)
+getUniqueFileInfo handle = do
+ with 0 $ \devptr -> do
+ with 0 $ \inoptr -> do
+ c_getUniqueFileInfo (toHANDLE handle) devptr inoptr
+ liftM2 (,) (peek devptr) (peek inoptr)
diff --git a/libraries/base/GHC/IO/Windows/Paths.hs b/libraries/base/GHC/IO/Windows/Paths.hs
new file mode 100644
index 0000000000..851dc37508
--- /dev/null
+++ b/libraries/base/GHC/IO/Windows/Paths.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE Trustworthy #-}
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE CPP #-}
+-- Whether there are identities depends on the platform
+{-# OPTIONS_HADDOCK hide #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.IO.Windows.Paths
+-- Copyright : (c) The University of Glasgow, 2017
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : libraries@haskell.org
+-- Stability : internal
+-- Portability : non-portable
+--
+-- Windows FilePath handling utility for GHC code.
+--
+-----------------------------------------------------------------------------
+
+module GHC.IO.Windows.Paths
+ (getDevicePath
+ ) where
+
+#include "windows_cconv.h"
+
+import GHC.Base
+import GHC.IO
+
+import Foreign.C.String
+import Foreign.Marshal.Alloc (free)
+
+foreign import WINDOWS_CCONV safe "__hs_create_device_name"
+ c_GetDevicePath :: CWString -> IO CWString
+
+-- | This function converts Windows paths between namespaces. More specifically
+-- It converts an explorer style path into a NT or Win32 namespace.
+-- This has several caveats but they are caviats that are native to Windows and
+-- not POSIX. See
+-- https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247.aspx.
+-- Anything else such as raw device paths we leave untouched. The main benefit
+-- of doing any of this is that we can break the MAX_PATH restriction and also
+-- access raw handles that we couldn't before.
+getDevicePath :: FilePath -> IO FilePath
+getDevicePath path
+ = do str <- withCWString path c_GetDevicePath
+ newPath <- peekCWString str
+ free str
+ return newPath
diff --git a/libraries/base/GHC/IOPort.hs b/libraries/base/GHC/IOPort.hs
new file mode 100644
index 0000000000..46a553ca51
--- /dev/null
+++ b/libraries/base/GHC/IOPort.hs
@@ -0,0 +1,122 @@
+{-# LANGUAGE Unsafe #-}
+{-# LANGUAGE NoImplicitPrelude, MagicHash, UnboxedTuples #-}
+{-# OPTIONS_GHC -funbox-strict-fields #-}
+{-# OPTIONS_HADDOCK hide #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module : GHC.IOPort
+-- Copyright : (c) Tamar Christina 2019
+-- License : see libraries/base/LICENSE
+--
+-- Maintainer : cvs-ghc@haskell.org
+-- Stability : internal
+-- Portability : non-portable (GHC Extensions)
+--
+-- The IOPort type. This is a facility used by the Windows IO subsystem.
+-- We have strict rules with an I/O Port:
+-- * writing more than once is an error
+-- * reading more than once is an error
+--
+-- It gives us the ability to have one thread to block, wait for a result from
+-- another thread and then being woken up. *Nothing* more.
+--
+-- This type is very much GHC internal. It might be changed or removed without
+-- notice in future releases.
+--
+-----------------------------------------------------------------------------
+
+module GHC.IOPort (
+ -- * IOPorts
+ IOPort(..)
+ , newIOPort
+ , newEmptyIOPort
+ , readIOPort
+ , writeIOPort
+ , doubleReadException
+ ) where
+
+import GHC.Base
+import GHC.Exception
+import Text.Show
+
+data IOPortException = IOPortException deriving Show
+
+instance Exception IOPortException where
+ displayException IOPortException = "IOPortException"
+
+
+doubleReadException :: SomeException
+doubleReadException = toException IOPortException
+
+data IOPort a = IOPort (IOPort# RealWorld a)
+{- ^
+An 'IOPort' is a synchronising variable, used
+for communication between concurrent threads, where one of the threads is
+controlled by an external state. e.g. by an I/O action that is serviced by the
+runtime. It can be thought of as a box, which may be empty or full.
+
+It is mostly similar to the behavior of 'Control.Concurrent.MVar.MVar'
+except 'writeIOPort' doesn't block if the variable is full and the GC
+won't forcibly release the lock if it thinks
+there's a deadlock.
+
+The properties of IOPorts are:
+* Writing to an empty IOPort will not block.
+* Writing to an full IOPort will not block. It might throw an exception.
+* Reading from an IOPort for the second time might throw an exception.
+* Reading from a full IOPort will not block, return the value and empty the port.
+* Reading from an empty IOPort will block until a write.
+* Reusing an IOPort (that is, reading or writing twice) is not supported
+ and might throw an exception. Even if reads and writes are
+ interleaved.
+
+This type is very much GHC internal. It might be changed or removed without
+notice in future releases.
+
+-}
+
+-- | @since 4.1.0.0
+instance Eq (IOPort a) where
+ (IOPort ioport1#) == (IOPort ioport2#) =
+ isTrue# (sameIOPort# ioport1# ioport2#)
+
+
+
+-- |Create an 'IOPort' which is initially empty.
+newEmptyIOPort :: IO (IOPort a)
+newEmptyIOPort = IO $ \ s# ->
+ case newIOPort# s# of
+ (# s2#, svar# #) -> (# s2#, IOPort svar# #)
+
+-- |Create an 'IOPort' which contains the supplied value.
+newIOPort :: a -> IO (IOPort a)
+newIOPort value =
+ newEmptyIOPort >>= \ ioport ->
+ writeIOPort ioport value >>
+ return ioport
+
+-- |Atomically read the the contents of the 'IOPort'. If the 'IOPort' is
+-- currently empty, 'readIOPort' will wait until it is full. After a
+-- 'readIOPort', the 'IOPort' is left empty.
+--
+-- There is one important property of 'readIOPort':
+--
+-- * Only a single threads can be blocked on an 'IOPort'.
+--
+readIOPort :: IOPort a -> IO a
+readIOPort (IOPort ioport#) = IO $ \ s# -> readIOPort# ioport# s#
+
+-- |Put a value into an 'IOPort'. If the 'IOPort' is currently full,
+-- 'writeIOPort' will throw an exception.
+--
+-- There is one important property of 'writeIOPort':
+--
+-- * Only a single thread can be blocked on an 'IOPort'.
+--
+writeIOPort :: IOPort a -> a -> IO Bool
+writeIOPort (IOPort ioport#) x = IO $ \ s# ->
+ case writeIOPort# ioport# x s# of
+ (# s, 0# #) -> (# s, False #)
+ (# s, _ #) -> (# s, True #)
+
diff --git a/libraries/base/GHC/RTS/Flags.hsc b/libraries/base/GHC/RTS/Flags.hsc
index fc863fb3fc..03cd368723 100644
--- a/libraries/base/GHC/RTS/Flags.hsc
+++ b/libraries/base/GHC/RTS/Flags.hsc
@@ -25,10 +25,12 @@ module GHC.RTS.Flags
, TraceFlags (..)
, TickyFlags (..)
, ParFlags (..)
+ , IoSubSystem (..)
, getRTSFlags
, getGCFlags
, getConcFlags
, getMiscFlags
+ , getIoManagerFlag
, getDebugFlags
, getCCFlags
, getProfFlags
@@ -40,8 +42,7 @@ module GHC.RTS.Flags
#include "Rts.h"
#include "rts/Flags.h"
-import Control.Applicative
-import Control.Monad
+import Data.Functor ((<$>))
import Foreign
import Foreign.C
@@ -87,6 +88,32 @@ 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 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 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 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 4.8.0.0
@@ -148,6 +175,8 @@ data MiscFlags = MiscFlags
, linkerAlwaysPic :: Bool
, linkerMemBase :: Word
-- ^ address to ask the OS for memory for the linker, 0 ==> off
+ , ioManager :: IoSubSystem
+ , numIoWorkerThreads :: Word32
} deriving ( Show -- ^ @since 4.8.0.0
, Generic -- ^ @since 4.15.0.0
)
@@ -449,6 +478,7 @@ getConcFlags = do
ConcFlags <$> #{peek CONCURRENT_FLAGS, ctxtSwitchTime} ptr
<*> #{peek CONCURRENT_FLAGS, ctxtSwitchTicks} ptr
+{-# INLINEABLE getMiscFlags #-}
getMiscFlags :: IO MiscFlags
getMiscFlags = do
let ptr = (#ptr RTS_FLAGS, MiscFlags) rtsFlagsPtr
@@ -470,6 +500,40 @@ getMiscFlags = do
<*> (toBool <$>
(#{peek MISC_FLAGS, linkerAlwaysPic} ptr :: IO CBool))
<*> #{peek MISC_FLAGS, linkerMemBase} ptr
+ <*> (toEnum . fromIntegral
+ <$> (#{peek MISC_FLAGS, ioManager} ptr :: IO Word32))
+ <*> (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
diff --git a/libraries/base/GHC/TopHandler.hs b/libraries/base/GHC/TopHandler.hs
index bb358a337f..4997d827f5 100644
--- a/libraries/base/GHC/TopHandler.hs
+++ b/libraries/base/GHC/TopHandler.hs
@@ -40,8 +40,8 @@ import GHC.Base
import GHC.Conc hiding (throwTo)
import GHC.Real
import GHC.IO
-import GHC.IO.Handle.FD
import GHC.IO.Handle
+import GHC.IO.StdHandles
import GHC.IO.Exception
import GHC.Weak
diff --git a/libraries/base/GHC/Windows.hs b/libraries/base/GHC/Windows.hs
index 45032d56ac..d8f8bef804 100644
--- a/libraries/base/GHC/Windows.hs
+++ b/libraries/base/GHC/Windows.hs
@@ -26,11 +26,22 @@ module GHC.Windows (
LPBOOL,
BYTE,
DWORD,
+ DDWORD,
UINT,
+ ULONG,
ErrCode,
HANDLE,
LPWSTR,
LPTSTR,
+ LPCTSTR,
+ LPVOID,
+ LPDWORD,
+ LPSTR,
+ LPCSTR,
+ LPCWSTR,
+ WORD,
+ UCHAR,
+ NTSTATUS,
-- * Constants
iNFINITE,
@@ -56,39 +67,67 @@ module GHC.Windows (
-- $errno
c_maperrno,
c_maperrno_func,
+
+ -- * Misc
+ ddwordToDwords,
+ dwordsToDdword,
+ nullHANDLE,
) where
+import Data.Bits (shiftL, shiftR, (.|.), (.&.))
import Data.Char
import Data.OldList
import Data.Maybe
import Data.Word
+import Data.Int
import Foreign.C.Error
import Foreign.C.String
import Foreign.C.Types
import Foreign.Ptr
import GHC.Base
+import GHC.Enum (maxBound)
import GHC.IO
import GHC.Num
+import GHC.Real (fromIntegral)
import System.IO.Error
import qualified Numeric
-#if defined(i386_HOST_ARCH)
-# define WINDOWS_CCONV stdcall
-#elif defined(x86_64_HOST_ARCH)
-# define WINDOWS_CCONV ccall
+#if MIN_VERSION_base(4,7,0)
+import Data.Bits (finiteBitSize)
#else
-# error Unknown mingw32 arch
+import Data.Bits (Bits, bitSize)
+
+finiteBitSize :: (Bits a) => a -> Int
+finiteBitSize = bitSize
#endif
-type BOOL = Bool
-type LPBOOL = Ptr BOOL
-type BYTE = Word8
-type DWORD = Word32
-type UINT = Word32
-type ErrCode = DWORD
-type HANDLE = Ptr ()
-type LPWSTR = Ptr CWchar
+#include "windows_cconv.h"
+
+type BOOL = Bool
+type LPBOOL = Ptr BOOL
+type BYTE = Word8
+type DWORD = Word32
+type UINT = Word32
+type ULONG = Word32
+type ErrCode = DWORD
+type HANDLE = Ptr ()
+type LPWSTR = Ptr CWchar
+type LPCTSTR = LPTSTR
+type LPVOID = Ptr ()
+type LPDWORD = Ptr DWORD
+type LPSTR = Ptr CChar
+type LPCSTR = LPSTR
+type LPCWSTR = LPWSTR
+type WORD = Word16
+type UCHAR = Word8
+type NTSTATUS = Int32
+
+nullHANDLE :: HANDLE
+nullHANDLE = nullPtr
+
+-- Not really a basic type, but used in many places
+type DDWORD = Word64
-- | Be careful with this. LPTSTR can mean either WCHAR* or CHAR*, depending
-- on whether the UNICODE macro is defined in the corresponding C code.
@@ -194,3 +233,15 @@ foreign import WINDOWS_CCONV unsafe "windows.h LocalFree"
-- | Get the last system error produced in the current thread.
foreign import WINDOWS_CCONV unsafe "windows.h GetLastError"
getLastError :: IO ErrCode
+
+----------------------------------------------------------------
+-- Misc helpers
+----------------------------------------------------------------
+
+ddwordToDwords :: DDWORD -> (DWORD,DWORD)
+ddwordToDwords n =
+ (fromIntegral (n `shiftR` finiteBitSize (undefined :: DWORD))
+ ,fromIntegral (n .&. fromIntegral (maxBound :: DWORD)))
+
+dwordsToDdword:: (DWORD,DWORD) -> DDWORD
+dwordsToDdword (hi,low) = (fromIntegral low) .|. (fromIntegral hi `shiftL` finiteBitSize hi)
diff --git a/libraries/base/System/IO.hs b/libraries/base/System/IO.hs
index a4d4ec4e67..03e0e06319 100644
--- a/libraries/base/System/IO.hs
+++ b/libraries/base/System/IO.hs
@@ -232,6 +232,12 @@ import Foreign.C.String
import Foreign.Ptr
import Foreign.Marshal.Alloc
import Foreign.Storable
+import GHC.IO.SubSystem
+import GHC.IO.Windows.Handle (openFileAsTemp)
+import GHC.IO.Handle.Windows (mkHandleFromHANDLE)
+import GHC.IO.Device as IODevice
+import GHC.Real (fromIntegral)
+import Foreign.Marshal.Utils (new)
#endif
import Foreign.C.Types
import System.Posix.Internals
@@ -245,13 +251,14 @@ import GHC.IORef
import GHC.Num
import GHC.IO hiding ( bracket, onException )
import GHC.IO.IOMode
-import GHC.IO.Handle.FD
import qualified GHC.IO.FD as FD
import GHC.IO.Handle
+import qualified GHC.IO.Handle.FD as POSIX
import GHC.IO.Handle.Text ( hGetBufSome, hPutStrLn )
import GHC.IO.Exception ( userError )
import GHC.IO.Encoding
import Text.Read
+import GHC.IO.StdHandles
import GHC.Show
import GHC.MVar
@@ -529,13 +536,29 @@ openTempFile' loc tmp_dir template binary mode
-- beginning with '.' as the second component.
_ -> errorWithoutStackTrace "bug in System.IO.openTempFile"
#if defined(mingw32_HOST_OS)
- findTempName = do
+ findTempName = findTempNamePosix <!> findTempNameWinIO
+
+ findTempNameWinIO = do
+ let label = if null prefix then "ghc" else prefix
+ withCWString tmp_dir $ \c_tmp_dir ->
+ withCWString label $ \c_template ->
+ withCWString suffix $ \c_suffix -> do
+ c_ptr <- new nullPtr
+ res <- c_createUUIDTempFileErrNo c_tmp_dir c_template c_suffix
+ c_ptr
+ if not res
+ then do errno <- getErrno
+ ioError (errnoToIOError loc errno Nothing (Just tmp_dir))
+ else do c_p <- peek c_ptr
+ filename <- peekCWString c_p
+ free c_p
+ handleResultsWinIO filename ((fromIntegral mode .&. o_EXCL) == o_EXCL)
+
+ findTempNamePosix = do
let label = if null prefix then "ghc" else prefix
withCWString tmp_dir $ \c_tmp_dir ->
withCWString label $ \c_template ->
withCWString suffix $ \c_suffix ->
- -- NOTE: revisit this when new I/O manager in place and use a UUID
- -- based one when we are no longer MAX_PATH bound.
allocaBytes (sizeOf (undefined :: CWchar) * 260) $ \c_str -> do
res <- c_getTempFileNameErrorNo c_tmp_dir c_template c_suffix 0
c_str
@@ -543,9 +566,9 @@ openTempFile' loc tmp_dir template binary mode
then do errno <- getErrno
ioError (errnoToIOError loc errno Nothing (Just tmp_dir))
else do filename <- peekCWString c_str
- handleResults filename
+ handleResultsPosix filename
- handleResults filename = do
+ handleResultsPosix filename = do
let oflags1 = rw_flags .|. o_EXCL
binary_flags
| binary = o_BINARY
@@ -561,14 +584,26 @@ openTempFile' loc tmp_dir template binary mode
True{-is_nonblock-}
enc <- getLocaleEncoding
- h <- mkHandleFromFD fD fd_type filename ReadWriteMode
+ h <- POSIX.mkHandleFromFD fD fd_type filename ReadWriteMode
False{-set non-block-} (Just enc)
return (filename, h)
+ handleResultsWinIO filename excl = do
+ (hwnd, hwnd_type) <- openFileAsTemp filename True excl
+ mb_codec <- if binary then return Nothing else fmap Just getLocaleEncoding
+
+ -- then use it to make a Handle
+ h <- mkHandleFromHANDLE hwnd hwnd_type filename ReadWriteMode mb_codec
+ `onException` IODevice.close hwnd
+ return (filename, h)
+
foreign import ccall "getTempFileNameErrorNo" c_getTempFileNameErrorNo
:: CWString -> CWString -> CWString -> CUInt -> Ptr CWchar -> IO Bool
+foreign import ccall "__createUUIDTempFileErrNo" c_createUUIDTempFileErrNo
+ :: CWString -> CWString -> CWString -> Ptr CWString -> IO Bool
+
pathSeparator :: String -> Bool
pathSeparator template = any (\x-> x == '/' || x == '\\') template
@@ -588,7 +623,7 @@ output_flags = std_flags
True{-is_nonblock-}
enc <- getLocaleEncoding
- h <- mkHandleFromFD fD fd_type filepath ReadWriteMode False{-set non-block-} (Just enc)
+ h <- POSIX.mkHandleFromFD fD fd_type filepath ReadWriteMode False{-set non-block-} (Just enc)
return (filepath, h)
diff --git a/libraries/base/System/Timeout.hs b/libraries/base/System/Timeout.hs
index df2c0f055a..1c41dc2ca2 100644
--- a/libraries/base/System/Timeout.hs
+++ b/libraries/base/System/Timeout.hs
@@ -15,7 +15,7 @@
-- Attach a timeout event to arbitrary 'IO' computations.
--
-------------------------------------------------------------------------------
-
+-- TODO: Inspect is still suitable.
module System.Timeout ( Timeout, timeout ) where
#if !defined(mingw32_HOST_OS)
diff --git a/libraries/base/base.cabal b/libraries/base/base.cabal
index 33eccf214f..4a7fe6e133 100644
--- a/libraries/base/base.cabal
+++ b/libraries/base/base.cabal
@@ -37,6 +37,7 @@ extra-source-files:
include/ieee-flpt.h
include/md5.h
include/fs.h
+ include/winio_structs.h
install-sh
source-repository head
@@ -200,6 +201,7 @@ Library
GHC.Enum
GHC.Environment
GHC.Err
+ GHC.Event.TimeOut
GHC.Exception
GHC.Exception.Type
GHC.ExecutionStack
@@ -238,6 +240,8 @@ Library
GHC.IO.Handle.Types
GHC.IO.IOMode
GHC.IO.Unsafe
+ GHC.IO.StdHandles
+ GHC.IO.SubSystem
GHC.IOArray
GHC.IORef
GHC.Int
@@ -304,6 +308,8 @@ Library
Type.Reflection
Type.Reflection.Unsafe
Unsafe.Coerce
+ -- TODO: remove
+ GHC.IOPort
reexported-modules:
GHC.Num.Integer
@@ -324,6 +330,8 @@ Library
GHC.IO.Handle.Lock.NoOp
GHC.IO.Handle.Lock.Windows
GHC.StaticPtr.Internal
+ GHC.Event.Internal.Types
+ -- GHC.IOPort -- TODO: hide again after debug
System.Environment.ExecutablePath
System.CPUTime.Utils
@@ -332,8 +340,6 @@ Library
cbits/PrelIOUtils.c
cbits/SetEnv.c
cbits/WCsubst.c
- cbits/Win32Utils.c
- cbits/consUtils.c
cbits/iconv.c
cbits/inputReady.c
cbits/md5.c
@@ -363,14 +369,50 @@ Library
-- mingwex and mingw32. the __math_err symbol is defined in
-- mingw32 which is required by mingwex.
-- shlwapi: provides PathFileExistsW
- extra-libraries: wsock32, user32, shell32, msvcrt, mingw32, mingwex, shlwapi
+ -- ws2_32: provides access to socket types and functions
+ -- ole32: provides UUID functionality.
+ -- rpcrt4: provides RPC UUID creation.
+ -- ntdll: provides access to functions to inspect window handles
+ extra-libraries: wsock32, user32, shell32, msvcrt, mingw32,
+ mingwex, ws2_32, shlwapi, ole32, rpcrt4, ntdll
+ -- Minimum supported Windows version.
+ -- These numbers can be found at:
+ -- https://msdn.microsoft.com/en-us/library/windows/desktop/aa383745(v=vs.85).aspx
+ -- If we're compiling on windows, enforce that we only support Windows 7+
+ -- Adding this here means it doesn't have to be done in individual .c files
+ -- and also centralizes the versioning.
+ cpp-options: -D_WIN32_WINNT=0x06010000
+ cc-options: -D_WIN32_WINNT=0x06010000
exposed-modules:
GHC.IO.Encoding.CodePage.API
GHC.IO.Encoding.CodePage.Table
GHC.Conc.Windows
+ GHC.Conc.WinIO
+ GHC.Conc.POSIX
+ GHC.Conc.POSIX.Const
GHC.Windows
+ GHC.Event.Windows
+ GHC.Event.Windows.Clock
+ GHC.Event.Windows.ConsoleEvent
+ GHC.Event.Windows.FFI
+ GHC.Event.Windows.ManagedThreadPool
+ GHC.Event.Windows.Thread
+ GHC.IO.Handle.Windows
+ GHC.IO.Windows.Handle
+ GHC.IO.Windows.Encoding
+ GHC.IO.Windows.Paths
other-modules:
+ GHC.Event.Arr
+ GHC.Event.Array
+ GHC.Event.IntTable
+ GHC.Event.PSQ
+ GHC.Event.Unique
System.CPUTime.Windows
+ c-sources:
+ cbits/Win32Utils.c
+ cbits/consUtils.c
+ cbits/IOutils.c
+
else
exposed-modules:
GHC.Event
diff --git a/libraries/base/cbits/IOutils.c b/libraries/base/cbits/IOutils.c
new file mode 100644
index 0000000000..8d3ae35588
--- /dev/null
+++ b/libraries/base/cbits/IOutils.c
@@ -0,0 +1,484 @@
+/*
+ * (c) The GHC Team 2017-2018.
+ *
+ * I/O Utility functions for Windows.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <winsock2.h>
+#include <windows.h>
+#include <io.h>
+#include <math.h>
+
+/* Import some functions defined in base. */
+extern void maperrno(void);
+
+/* Enum of Handle type. */
+typedef
+enum HandleType
+ {
+ TYPE_CHAR, // 0
+ TYPE_DISK, // 1
+ TYPE_PIPE, // 2
+ TYPE_SOCKET, // 3
+ TYPE_REMOTE, // 4
+ TYPE_RAW, // 5
+ TYPE_UNKNOWN // 6
+ } HANDLE_TYPE;
+
+/*
+ * handleReady(hwnd) checks to see whether input is available on the file
+ * handle 'hwnd'. Input meaning 'can I safely read at least a
+ * *character* from this file object without blocking?'
+ */
+int
+__handle_ready(HANDLE hFile, bool write, int msecs)
+{
+ DWORD handleType = GetFileType (hFile);
+
+ DWORD rc;
+ DWORD avail;
+
+ switch (handleType)
+ {
+ case FILE_TYPE_CHAR:
+ {
+ INPUT_RECORD buf[1];
+ DWORD count;
+
+ /* A Console Handle will appear to be ready
+ (WaitForSingleObject() returned WAIT_OBJECT_0) when
+ it has events in its input buffer, but these events might
+ not be keyboard events, so when we read from the Handle the
+ read() will block. So here we try to discard non-keyboard
+ events from a console handle's input buffer and then try
+ the WaitForSingleObject() again.
+ Phyx: I'm worried that we're discarding events someone else may need. */
+ while (true) // keep trying until we find a real key event
+ {
+ rc = WaitForSingleObject( hFile, msecs );
+ switch (rc)
+ {
+ case WAIT_TIMEOUT:
+ return false;
+ case WAIT_OBJECT_0:
+ break;
+ default:
+ /* WAIT_FAILED */
+ maperrno();
+ return -1;
+ }
+
+ while (true) // discard non-key events
+ {
+ /* I wonder if we can do better by grabbing a list of
+ input records at a time by using PeekConsoleInput. */
+ rc = PeekConsoleInput(hFile, buf, 1, &count);
+ if (rc == 0) {
+ rc = GetLastError();
+ if (rc == ERROR_INVALID_HANDLE || rc == ERROR_INVALID_FUNCTION)
+ return true;
+ else {
+ maperrno();
+ return -1;
+ }
+ }
+
+ if (count == 0)
+ break; /* no more events => wait again. */
+
+ /* discard console events that are not "key down", because
+ these will also be discarded by ReadFile(). */
+ if (buf[0].EventType == KEY_EVENT &&
+ buf[0].Event.KeyEvent.bKeyDown &&
+ buf[0].Event.KeyEvent.uChar.AsciiChar != '\0')
+ return true; /* it's a proper keypress. */
+ else
+ {
+ /* it's a non-key event, a key up event, or a
+ non-character key (e.g. shift). discard it. */
+ rc = ReadConsoleInput(hFile, buf, 1, &count);
+ if (rc == 0) {
+ rc = GetLastError();
+ if (rc == ERROR_INVALID_HANDLE || rc == ERROR_INVALID_FUNCTION)
+ return true;
+ else {
+ maperrno();
+ return -1;
+ }
+ }
+ }
+ }
+ }
+ }
+ case FILE_TYPE_DISK:
+ /* assume that disk files are always ready. */
+ return true;
+
+ case FILE_TYPE_PIPE:
+ {
+ // Try to see if this is a socket
+ //-------------------------
+ // Create new event
+ WSAEVENT newEvent = WSACreateEvent();
+
+ //-------------------------
+ // Associate event types FD_WRITE or FD_READ
+ // with the listening socket and NewEvent
+ rc = WSAEventSelect((SOCKET)hFile, newEvent, write ? FD_WRITE : FD_READ);
+
+ if (rc == WSAENOTSOCK)
+ {
+ CloseHandle (newEvent);
+
+ // WaitForMultipleObjects() doesn't work for pipes (it
+ // always returns WAIT_OBJECT_0 even when no data is
+ // available). If the HANDLE is a pipe, therefore, we try
+ // PeekNamedPipe:
+ //
+ rc = PeekNamedPipe( hFile, NULL, 0, NULL, &avail, NULL );
+ if (rc != 0)
+ return avail != 0;
+ else {
+ rc = GetLastError();
+ if (rc == ERROR_BROKEN_PIPE)
+ return true; // this is probably what we want
+
+ if (rc != ERROR_INVALID_HANDLE && rc != ERROR_INVALID_FUNCTION) {
+ maperrno();
+ return -1;
+ }
+ }
+ /* PeekNamedPipe didn't work - fall through to the general case */
+ }
+ else if (rc != 0)
+ {
+ CloseHandle (newEvent);
+ // It seems to be a socket but can't determine the state.
+ // Maybe not initialized. Either way, we know enough.
+ return false;
+ }
+
+ // Wait for the socket event to trigger.
+ rc = WaitForSingleObject( newEvent, msecs );
+ CloseHandle (newEvent);
+
+ /* 1 => Input ready, 0 => not ready, -1 => error */
+ switch (rc)
+ {
+ case WAIT_TIMEOUT:
+ return false;
+ case WAIT_OBJECT_0:
+ return true;
+ default:
+ {
+ /* WAIT_FAILED */
+ maperrno();
+ return -1;
+ }
+ }
+ }
+ default:
+ rc = WaitForSingleObject( hFile, msecs );
+
+ /* 1 => Input ready, 0 => not ready, -1 => error */
+ switch (rc)
+ {
+ case WAIT_TIMEOUT:
+ return false;
+ case WAIT_OBJECT_0:
+ return true;
+ default:
+ {
+ /* WAIT_FAILED */
+ maperrno();
+ return -1;
+ }
+ }
+ }
+}
+
+bool
+__is_console(HANDLE hFile)
+{
+ /* Broken handle can't be terminal */
+ if (hFile == INVALID_HANDLE_VALUE)
+ return false;
+
+ DWORD handleType = GetFileType (hFile);
+
+ /* TTY must be a character device */
+ if (handleType == FILE_TYPE_CHAR)
+ return true;
+
+ DWORD st;
+ /* GetConsoleMode appears to fail when it's not a TTY. In
+ particular, it's what most of our terminal functions
+ assume works, so if it doesn't work for all intents
+ and purposes we're not dealing with a terminal. */
+ if (!GetConsoleMode(hFile, &st)) {
+ /* Clear the error buffer before returning. */
+ SetLastError (ERROR_SUCCESS);
+ return false;
+ }
+
+ return true;
+}
+
+#if !defined(ENABLE_VIRTUAL_TERMINAL_INPUT)
+#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
+#endif
+
+bool
+__set_console_buffering(HANDLE hFile, bool cooked)
+{
+ if (hFile == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ DWORD st;
+ if (!GetConsoleMode(hFile, &st)) {
+ return false;
+ }
+
+ /* According to GetConsoleMode() docs, it is not possible to
+ leave ECHO_INPUT enabled without also having LINE_INPUT,
+ so we have to turn both off here.
+ We toggle ENABLE_VIRTUAL_TERMINAL_INPUT to enable us to receive
+ virtual keyboard keys in ReadConsole. */
+ DWORD flgs = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
+ DWORD enabled = (st & ~flgs) | ENABLE_VIRTUAL_TERMINAL_INPUT;
+ DWORD disabled = (st | ENABLE_LINE_INPUT) & ~ENABLE_VIRTUAL_TERMINAL_INPUT;
+
+
+ return SetConsoleMode(hFile, cooked ? enabled : disabled);
+}
+
+bool
+__set_console_echo(HANDLE hFile, bool on)
+{
+ DWORD st;
+ DWORD flgs = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
+
+ if (hFile == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ return GetConsoleMode(hFile, &st) &&
+ SetConsoleMode(hFile, ( on ? (st | flgs) : (st & ~flgs)));
+}
+
+bool
+__get_console_echo(HANDLE hFile)
+{
+ DWORD st;
+
+ if (hFile == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ return GetConsoleMode(hFile, &st) &&
+ (st & ENABLE_ECHO_INPUT) == ENABLE_ECHO_INPUT;
+}
+
+bool
+__flush_input_console(HANDLE hFile)
+{
+ if ( hFile == INVALID_HANDLE_VALUE )
+ return false;
+
+ /* If the 'handle' isn't connected to a console; treat the flush
+ * operation as a NOP.
+ */
+ DWORD unused;
+ if ( !GetConsoleMode(hFile, &unused) &&
+ GetLastError() == ERROR_INVALID_HANDLE ) {
+ return false;
+ }
+
+ if ( FlushConsoleInputBuffer(hFile) )
+ return true;
+
+ maperrno();
+ return false;
+}
+
+HANDLE_TYPE
+__handle_type (HANDLE hFile)
+{
+ DWORD handleType = GetFileType (hFile);
+ switch (handleType)
+ {
+ case FILE_TYPE_PIPE:
+ {
+ WSAEVENT newEvent = WSACreateEvent();
+ DWORD rc = WSAEventSelect((SOCKET)hFile, newEvent, FD_CLOSE);
+ CloseHandle (newEvent);
+ if (rc == WSAENOTSOCK)
+ return TYPE_SOCKET;
+ else
+ return TYPE_PIPE;
+ }
+ case FILE_TYPE_CHAR:
+ return TYPE_CHAR;
+ case FILE_TYPE_DISK:
+ return TYPE_DISK;
+ case FILE_TYPE_REMOTE:
+ return TYPE_REMOTE;
+ case FILE_TYPE_UNKNOWN:
+ default:
+ return TYPE_UNKNOWN;
+ }
+}
+
+bool
+__close_handle (HANDLE hFile)
+{
+ switch (__handle_type (hFile))
+ {
+ case TYPE_SOCKET:
+ return closesocket ((SOCKET)hFile) == 0;
+ default:
+ return CloseHandle (hFile);
+ }
+}
+
+bool __set_file_pointer (HANDLE hFile, int64_t pos, DWORD moveMethod,
+ int64_t* outPos)
+{
+ LARGE_INTEGER ret;
+ LARGE_INTEGER li;
+ li.QuadPart = pos;
+ bool success = SetFilePointerEx (hFile, li, &ret, moveMethod)
+ != INVALID_SET_FILE_POINTER;
+ *outPos = ret.QuadPart;
+ return success;
+}
+
+int64_t __get_file_pointer (HANDLE hFile)
+{
+ LARGE_INTEGER ret;
+ LARGE_INTEGER pos;
+ pos.QuadPart = 0;
+ if (SetFilePointerEx(hFile, pos, &ret, FILE_CURRENT)
+ == INVALID_SET_FILE_POINTER)
+ return -1;
+
+ return ret.QuadPart;
+}
+
+int64_t __get_file_size (HANDLE hFile)
+{
+ /* Broken handle can't do stat. */
+ if (hFile == INVALID_HANDLE_VALUE)
+ return false;
+
+ switch (GetFileType (hFile))
+ {
+ case FILE_TYPE_CHAR:
+ case FILE_TYPE_DISK:
+ break;
+ default:
+ return -1;
+ }
+
+ LARGE_INTEGER ret;
+ if (!GetFileSizeEx(hFile, &ret))
+ return -1;
+
+ return ret.QuadPart;
+}
+
+bool __set_file_size (HANDLE hFile, int64_t size)
+{
+ LARGE_INTEGER li;
+ li.QuadPart = size;
+ if(!SetFilePointerEx (hFile, li, NULL, FILE_BEGIN))
+ return false;
+
+ return SetEndOfFile (hFile);
+}
+
+bool __duplicate_handle (HANDLE hFile, HANDLE* hFileDup)
+{
+ switch (__handle_type (hFile))
+ {
+ case TYPE_SOCKET:
+ // should use WSADuplicateSocket
+ return false;
+ default:
+ return DuplicateHandle(GetCurrentProcess(),
+ hFile,
+ GetCurrentProcess(),
+ hFileDup,
+ 0,
+ FALSE,
+ DUPLICATE_SAME_ACCESS);
+ }
+}
+
+bool __set_console_pointer (HANDLE hFile, int64_t pos, DWORD moveMethod,
+ int64_t* outPos)
+{
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if(!GetConsoleScreenBufferInfo (hFile, &info))
+ return false;
+
+ COORD point;
+ switch (moveMethod)
+ {
+ case FILE_END:
+ {
+ int64_t end = info.dwSize.X * info.dwSize.Y;
+ pos = end + pos;
+ point = (COORD) { pos % info.dwSize.X, pos / info.dwSize.X };
+ break;
+ }
+ case FILE_CURRENT:
+ {
+ int64_t current = (info.dwCursorPosition.Y * info.dwSize.X)
+ + info.dwCursorPosition.X;
+ pos = current + pos;
+ point = (COORD) { pos % info.dwSize.X, pos / info.dwSize.X };
+ break;
+ }
+ case FILE_BEGIN:
+ default:
+ point = (COORD) { pos % info.dwSize.X, pos / info.dwSize.X };
+ break;
+ }
+
+ *outPos = pos;
+ return SetConsoleCursorPosition (hFile, point);
+}
+
+int64_t __get_console_pointer (HANDLE hFile)
+{
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ if(!GetConsoleScreenBufferInfo (hFile, &info))
+ return -1;
+
+ return (info.dwCursorPosition.Y * info.dwSize.X) + info.dwCursorPosition.X;
+}
+
+int64_t __get_console_buffer_size (HANDLE hFile)
+{
+ CONSOLE_SCREEN_BUFFER_INFO ret;
+ if (!GetConsoleScreenBufferInfo(hFile, &ret))
+ return -1;
+
+ return ret.dwSize.X * ret.dwSize.Y;
+}
+
+bool __set_console_buffer_size (HANDLE hFile, int64_t size)
+{
+ CONSOLE_SCREEN_BUFFER_INFO ret;
+ if (!GetConsoleScreenBufferInfo(hFile, &ret))
+ return false;
+
+ COORD sz = {ret.dwSize.X, (int)ceil(size / ret.dwSize.X)};
+ return SetConsoleScreenBufferSize (hFile, sz);
+}
+
+
diff --git a/libraries/base/cbits/Win32Utils.c b/libraries/base/cbits/Win32Utils.c
index 886c277b5c..f3dec0d98d 100644
--- a/libraries/base/cbits/Win32Utils.c
+++ b/libraries/base/cbits/Win32Utils.c
@@ -5,14 +5,21 @@
------------------------------------------------------------------------- */
#if defined(_WIN32)
+/* Use Mingw's C99 print functions. */
+#define __USE_MINGW_ANSI_STDIO 1
+/* Using Secure APIs */
+#define MINGW_HAS_SECURE_API 1
#include "HsBase.h"
#include <stdbool.h>
#include <stdint.h>
-/* Using Secure APIs */
-#define MINGW_HAS_SECURE_API 1
#include <wchar.h>
#include <windows.h>
+#include <io.h>
+#include <objbase.h>
+#include <ntstatus.h>
+#include <winternl.h>
+#include "fs.h"
/* This is the error table that defines the mapping between OS error
codes and errno values */
@@ -131,9 +138,8 @@ LPWSTR base_getErrorMessage(DWORD err)
return what;
}
-int get_unique_file_info(int fd, HsWord64 *dev, HsWord64 *ino)
+int get_unique_file_info_hwnd(HANDLE h, HsWord64 *dev, HsWord64 *ino)
{
- HANDLE h = (HANDLE)_get_osfhandle(fd);
BY_HANDLE_FILE_INFORMATION info;
if (GetFileInformationByHandle(h, &info))
@@ -148,12 +154,100 @@ int get_unique_file_info(int fd, HsWord64 *dev, HsWord64 *ino)
return -1;
}
+int get_unique_file_info(int fd, HsWord64 *dev, HsWord64 *ino)
+{
+ HANDLE h = (HANDLE)_get_osfhandle(fd);
+ return get_unique_file_info_hwnd (h, dev, ino);
+}
+
BOOL file_exists(LPCTSTR path)
{
DWORD r = GetFileAttributes(path);
return r != INVALID_FILE_ATTRIBUTES;
}
+/* If true then caller needs to free tempFileName. */
+bool __createUUIDTempFileErrNo (wchar_t* pathName, wchar_t* prefix,
+ wchar_t* suffix, wchar_t** tempFileName)
+{
+ *tempFileName = NULL;
+ int retry = 5;
+ bool success = false;
+ while (retry-- > 0 && !success)
+ {
+ GUID guid;
+ ZeroMemory (&guid, sizeof (guid));
+ if (CoCreateGuid (&guid) != S_OK)
+ goto fail;
+
+ RPC_WSTR guidStr;
+ if (UuidToStringW ((UUID*)&guid, &guidStr) != S_OK)
+ goto fail;
+
+ /* We can't create a device path here since this path escapes the compiler
+ so instead return a normal path and have openFile deal with it. */
+ wchar_t* devName = malloc (sizeof (wchar_t) * wcslen (pathName));
+ wcscpy (devName, pathName);
+ int len = wcslen (devName) + wcslen (suffix) + wcslen (prefix)
+ + wcslen (guidStr) + 3;
+ *tempFileName = malloc (len * sizeof (wchar_t));
+ if (*tempFileName == NULL)
+ goto fail;
+
+ /* Only add a slash if path didn't already end in one, otherwise we create
+ an invalid path. */
+ bool slashed = devName[wcslen(devName)-1] == '\\';
+ wchar_t* sep = slashed ? L"" : L"\\";
+ if (-1 == swprintf_s (*tempFileName, len, L"%ls%ls%ls-%ls%ls",
+ devName, sep, prefix, guidStr, suffix))
+ goto fail;
+
+ free (devName);
+ RpcStringFreeW (&guidStr);
+ /* This should never happen because GUIDs are unique. But in case hell
+ froze over let's check anyway. */
+ DWORD dwAttrib = GetFileAttributesW (*tempFileName);
+ success = (dwAttrib == INVALID_FILE_ATTRIBUTES
+ || (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
+ if (!success)
+ free (*tempFileName);
+ }
+
+ return success;
+
+fail:
+ if (*tempFileName != NULL) {
+ free (*tempFileName);
+ }
+ maperrno();
+ return false;
+}
+
+
+/* Seems to be part of the Windows SDK so provide an inline definition for
+ use and rename it so it doesn't conflict for people who do have the SDK. */
+
+typedef struct _MY_PUBLIC_OBJECT_BASIC_INFORMATION {
+ ULONG Attributes;
+ ACCESS_MASK GrantedAccess;
+ ULONG HandleCount;
+ ULONG PointerCount;
+ ULONG Reserved[10];
+ } MY_PUBLIC_OBJECT_BASIC_INFORMATION, *PMY_PUBLIC_OBJECT_BASIC_INFORMATION;
+
+ACCESS_MASK __get_handle_access_mask (HANDLE handle)
+{
+ MY_PUBLIC_OBJECT_BASIC_INFORMATION obi;
+ if (STATUS_SUCCESS != NtQueryObject(handle, ObjectBasicInformation, &obi,
+ sizeof(obi), NULL))
+ {
+ return obi.GrantedAccess;
+ }
+
+ maperrno();
+ return 0;
+}
+
bool getTempFileNameErrorNo (wchar_t* pathName, wchar_t* prefix,
wchar_t* suffix, uint32_t uUnique,
wchar_t* tempFileName)
diff --git a/libraries/base/cbits/consUtils.c b/libraries/base/cbits/consUtils.c
index 0c9202d0c9..ac5d3ea75a 100644
--- a/libraries/base/cbits/consUtils.c
+++ b/libraries/base/cbits/consUtils.c
@@ -1,4 +1,4 @@
-/*
+/*
* (c) The University of Glasgow 2002
*
* Win32 Console API support
@@ -46,7 +46,7 @@ set_console_buffering__(int fd, int cooked)
leave ECHO_INPUT enabled without also having LINE_INPUT,
so we have to turn both off here. */
DWORD flgs = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
-
+
if ( (h = (HANDLE)_get_osfhandle(fd)) != INVALID_HANDLE_VALUE ) {
if ( GetConsoleMode(h,&st) &&
SetConsoleMode(h, cooked ? (st | ENABLE_LINE_INPUT) : st & ~flgs) ) {
@@ -62,9 +62,9 @@ set_console_echo__(int fd, int on)
HANDLE h;
DWORD st;
DWORD flgs = ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT;
-
+
if ( (h = (HANDLE)_get_osfhandle(fd)) != INVALID_HANDLE_VALUE ) {
- if ( GetConsoleMode(h,&st) &&
+ if ( GetConsoleMode(h,&st) &&
SetConsoleMode(h,( on ? (st | flgs) : (st & ~ENABLE_ECHO_INPUT))) ) {
return 0;
}
@@ -77,7 +77,7 @@ get_console_echo__(int fd)
{
HANDLE h;
DWORD st;
-
+
if ( (h = (HANDLE)_get_osfhandle(fd)) != INVALID_HANDLE_VALUE ) {
if ( GetConsoleMode(h,&st) ) {
return (st & ENABLE_ECHO_INPUT ? 1 : 0);
@@ -86,26 +86,4 @@ get_console_echo__(int fd)
return -1;
}
-int
-flush_input_console__(int fd)
-{
- HANDLE h = (HANDLE)_get_osfhandle(fd);
-
- if ( h != INVALID_HANDLE_VALUE ) {
- /* If the 'fd' isn't connected to a console; treat the flush
- * operation as a NOP.
- */
- DWORD unused;
- if ( !GetConsoleMode(h,&unused) &&
- GetLastError() == ERROR_INVALID_HANDLE ) {
- return 0;
- }
- if ( FlushConsoleInputBuffer(h) ) {
- return 0;
- }
- }
- /* ToDo: translate GetLastError() into something errno-friendly */
- return -1;
-}
-
#endif /* defined(_WIN32) || ... */
diff --git a/libraries/base/include/alignment.h b/libraries/base/include/alignment.h
new file mode 100644
index 0000000000..cb2f7da35f
--- /dev/null
+++ b/libraries/base/include/alignment.h
@@ -0,0 +1,3 @@
+#if __GLASGOW_HASKELL__ < 711
+#define hsc_alignment(t ) hsc_printf ( "%lu", (unsigned long)offsetof(struct {char x__; t(y__); }, y__));
+#endif
diff --git a/libraries/base/include/consUtils.h b/libraries/base/include/consUtils.h
index 3536593f3c..db5fc8eaef 100644
--- a/libraries/base/include/consUtils.h
+++ b/libraries/base/include/consUtils.h
@@ -1,4 +1,4 @@
-/*
+/*
* (c) The University of Glasgow, 2000-2002
*
* Win32 Console API helpers.
@@ -9,4 +9,3 @@ extern int is_console__(int fd);
extern int set_console_buffering__(int fd, int cooked);
extern int set_console_echo__(int fd, int on);
extern int get_console_echo__(int fd);
-extern int flush_input_console__ (int fd);
diff --git a/libraries/base/include/windows_cconv.h b/libraries/base/include/windows_cconv.h
new file mode 100644
index 0000000000..4fa84071c8
--- /dev/null
+++ b/libraries/base/include/windows_cconv.h
@@ -0,0 +1,12 @@
+#if !defined(__WINDOWS_CCONV_H)
+#define __WINDOWS_CCONV_H
+
+#if defined(i386_HOST_ARCH)
+# define WINDOWS_CCONV stdcall
+#elif defined(x86_64_HOST_ARCH)
+# define WINDOWS_CCONV ccall
+#else
+# error Unknown mingw32 arch
+#endif
+
+#endif
diff --git a/libraries/base/include/winio_structs.h b/libraries/base/include/winio_structs.h
new file mode 100644
index 0000000000..da9dab05b7
--- /dev/null
+++ b/libraries/base/include/winio_structs.h
@@ -0,0 +1,40 @@
+/*
+ * (c) Tamar Christina, 2019.
+ *
+ * Structures supporting the IOCP based I/O Manager or Windows.
+ */
+
+#include <Windows.h>
+#include <stdint.h>
+
+#if defined(_WIN64)
+# define ALIGNMENT __attribute__ ((aligned (8)))
+#elif defined(_WIN32)
+# define ALIGNMENT __attribute__ ((aligned (8)))
+#else
+# error "unknown environment, can't determine alignment"
+#endif
+
+/* Completion data structure. Must be kept in sync with that in
+ GHC.Event.Windows or horrible things happen. */
+typedef struct _CompletionData {
+ /* The Handle to the object for which the I/O operation is in progress. */
+ HWND cdHandle;
+ /* Handle to the callback routine to call to notify that an operation has
+ finished. This value is opaque as it shouldn't be accessible
+ outside the Haskell world. */
+ uintptr_t cdCallback;
+} CompletionData, *LPCompletionData;
+
+/* The Windows API Requires an OVERLAPPED struct for asynchronous access,
+ however if we pad the structure we can give extra book keeping information
+ without needing to look these up later. Do not modify this struct unless
+ you know what you're doing. */
+typedef struct _HASKELL_OVERLAPPED {
+ /* Windows OVERLAPPED structure. NOTE: MUST BE FIRST element. */
+ OVERLAPPED hoOverlapped;
+ /* Pointer to additional payload in Haskell land. This will contain a
+ foreign pointer. We only use atomic operations to access this field in
+ order to correctly handle multiple threads using it. */
+ LPCompletionData hoData ALIGNMENT;
+} HASKELL_OVERLAPPED;
diff --git a/libraries/base/tests/Concurrent/ThreadDelay001.hs b/libraries/base/tests/Concurrent/ThreadDelay001.hs
index 3b0f806e22..fb4385be31 100644
--- a/libraries/base/tests/Concurrent/ThreadDelay001.hs
+++ b/libraries/base/tests/Concurrent/ThreadDelay001.hs
@@ -1,22 +1,39 @@
-
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE BangPatterns #-}
-- Test that threadDelay actually sleeps for (at least) as long as we
-- ask it
+-- On windows the resolution of getCurrentTime is far too low to avoid
+-- false positives for this test. So we use the internal method from
+-- GHC.Event.Windows.Clock instead.
+
module Main (main) where
import Control.Concurrent
import Control.Monad
import Data.Time
-
+#if defined(mingw32_HOST_OS)
+import GHC.Event.Windows.Clock
+#endif
main :: IO ()
main = mapM_ delay (0 : take 7 (iterate (*5) 100))
delay :: Int -> IO ()
delay n = do
+#if defined(mingw32_HOST_OS)
+ !sec_start <- getClock >>= getTime
+ threadDelay n
+ !sec_end <- getClock >>= getTime
+
+ let diff = sec_end - sec_start
+ when (diff * 1000000 < fromIntegral n) $ do
+ putStrLn "threadDelay returned early"
+ print(sec_start, sec_end, n, diff*1000000)
+
+#else
tS <- getCurrentTime
threadDelay n
tE <- getCurrentTime
-
let req = fromIntegral n * 10 ^ (6 :: Int)
obs = floor (diffUTCTime tE tS * 10 ^ (12 :: Int))
diff = obs - req
@@ -24,4 +41,4 @@ delay n = do
diff' = fromIntegral diff / 10 ^ (12 :: Int)
when (obs < req) $ print (tS, tE, req, obs, diff, diff')
-
+#endif
diff --git a/libraries/base/tests/IO/T4144.hs b/libraries/base/tests/IO/T4144.hs
index 329601ca38..1fc16c0f07 100644
--- a/libraries/base/tests/IO/T4144.hs
+++ b/libraries/base/tests/IO/T4144.hs
@@ -46,15 +46,21 @@ remaining (BSIODevice bs mPos)
sizeBS :: BSIODevice -> Int
sizeBS (BSIODevice bs _) = B.length bs
-seekBS :: BSIODevice -> SeekMode -> Int -> IO ()
-seekBS dev AbsoluteSeek pos
+seekBS :: BSIODevice -> SeekMode -> Int -> IO Integer
+seekBS dev@(BSIODevice _ mPos) mode pos
+ = do seekBS' dev mode pos
+ maybe 0 fromIntegral <$> tryReadMVar mPos
+
+
+seekBS' :: BSIODevice -> SeekMode -> Int -> IO ()
+seekBS' dev AbsoluteSeek pos
| pos < 0 = error "Cannot seek to a negative position!"
| pos > sizeBS dev = error "Cannot seek past end of handle!"
| otherwise = case dev of
BSIODevice _ mPos
-> modifyMVar_ mPos $ \_ -> return pos
-seekBS dev SeekFromEnd pos = seekBS dev AbsoluteSeek (sizeBS dev - pos)
-seekBS dev RelativeSeek pos
+seekBS' dev SeekFromEnd pos = seekBS' dev AbsoluteSeek (sizeBS dev - pos)
+seekBS' dev RelativeSeek pos
= case dev of
BSIODevice _bs mPos
-> modifyMVar_ mPos $ \curPos ->
@@ -69,12 +75,12 @@ tellBS (BSIODevice _ mPos) = readMVar mPos
dupBS :: BSIODevice -> IO BSIODevice
dupBS (BSIODevice bs mPos) = BSIODevice bs <$> (readMVar mPos >>= newMVar)
-readBS :: BSIODevice -> Ptr Word8 -> Int -> IO Int
-readBS dev@(BSIODevice bs mPos) buff amount
+readBS :: BSIODevice -> Ptr Word8 -> Word64 -> Int -> IO Int
+readBS dev@(BSIODevice bs mPos) buff offset amount
= do
rem <- remaining dev
if amount > rem
- then readBS dev buff rem
+ then readBS dev buff offset rem
else B.unsafeUseAsCString bs $ \ptr ->
do
memcpy buff (castPtr ptr) (fromIntegral amount)
@@ -91,7 +97,7 @@ instance BufferedIO BSIODevice where
instance RawIO BSIODevice where
read = readBS
- readNonBlocking dev buff n = Just `liftM` readBS dev buff n
+ readNonBlocking dev buff offset n = Just `liftM` readBS dev buff offset n
instance IODevice BSIODevice where
ready _ True _ = return False -- read only
@@ -112,3 +118,4 @@ instance IODevice BSIODevice where
main = bsHandle "test" "<fake file>" >>= Data.ByteString.Char8.hGetContents >>= print
+
diff --git a/libraries/base/tests/IO/all.T b/libraries/base/tests/IO/all.T
index 0ba27f1b42..f03f6a01f1 100644
--- a/libraries/base/tests/IO/all.T
+++ b/libraries/base/tests/IO/all.T
@@ -16,8 +16,8 @@ test('hFileSize001', normal, compile_and_run, [''])
test('hFileSize002', [omit_ways(['ghci'])], compile_and_run, [''])
test('hFlush001', [], compile_and_run, [''])
-test('hGetBuffering001',
- [omit_ways(['ghci']), set_stdin('hGetBuffering001.hs')],
+test('hGetBuffering001',
+ [omit_ways(['ghci']), set_stdin('hGetBuffering001.hs')],
compile_and_run, [''])
test('hGetContentsS001', normal, compile_and_run, [''])
@@ -47,7 +47,7 @@ test('hSeek004', [], compile_and_run, ['-cpp'])
test('hSetBuffering002', set_stdin('hSetBuffering002.hs'), compile_and_run, [''])
test('hSetBuffering003',
- [omit_ways(['ghci']), set_stdin('hSetBuffering003.hs')],
+ [omit_ways(['ghci']), set_stdin('hSetBuffering003.hs')],
compile_and_run, [''])
test('hSetBuffering004', set_stdin('hSetBuffering004.hs'), compile_and_run, [''])
@@ -68,6 +68,7 @@ test('openFile005', [], compile_and_run, [''])
test('openFile006', [], compile_and_run, [''])
test('openFile007', [], compile_and_run, [''])
test('openFile008', cmd_prefix('ulimit -n 2048; '), compile_and_run, [''])
+test('openFile009', [], compile_and_run, [''])
test('putStr001', normal, compile_and_run, [''])
test('readFile001', [], compile_and_run, [''])
@@ -86,7 +87,8 @@ test('hGetBuf001',
compile_and_run, ['-package unix'])
# As discussed in #16819, this test is racy in a threaded environment.
-test('hDuplicateTo001', [omit_ways(concurrent_ways)], compile_and_run, [''])
+test('hDuplicateTo001', [omit_ways(concurrent_ways),
+ when(opsys('mingw32'), skip)], compile_and_run, [''])
test('countReaders001', [], compile_and_run, [''])
@@ -130,7 +132,10 @@ test('T4144', normal, compile_and_run, [''])
test('encodingerror001', normal, compile_and_run, [''])
-test('T4808', [fragile_for(16909, concurrent_ways), exit_code(1)], compile_and_run, [''])
+# Requires use of the FD interface which is not supported under WINIO
+test('T4808', [when(opsys('mingw32'), skip)
+ ,fragile_for(16909, concurrent_ways), exit_code(1)]
+ , compile_and_run, [''])
test('T4895', normal, compile_and_run, [''])
test('T7853', normal, compile_and_run, [''])
# Tests ability to perform >32-bit IO operations
diff --git a/libraries/base/tests/IO/hClose002.stdout-mingw32 b/libraries/base/tests/IO/hClose002.stdout-mingw32
new file mode 100644
index 0000000000..e05b87a7eb
--- /dev/null
+++ b/libraries/base/tests/IO/hClose002.stdout-mingw32
@@ -0,0 +1,4 @@
+Left hClose002.tmp: hClose: invalid argument (The handle is invalid.)
+Right ()
+Right ()
+Right ()
diff --git a/libraries/base/tests/IO/openFile002.stderr-mingw32 b/libraries/base/tests/IO/openFile002.stderr-mingw32
new file mode 100644
index 0000000000..a75cc496f4
--- /dev/null
+++ b/libraries/base/tests/IO/openFile002.stderr-mingw32
@@ -0,0 +1 @@
+openFile002.exe: nonexistent: openFile: does not exist (The system cannot find the file specified.)
diff --git a/libraries/base/tests/IO/openFile002.stderr-mingw32-2 b/libraries/base/tests/IO/openFile002.stderr-mingw32-2
new file mode 100644
index 0000000000..b011f34146
--- /dev/null
+++ b/libraries/base/tests/IO/openFile002.stderr-mingw32-2
@@ -0,0 +1 @@
+openFile002: nonexistent: openFile: does not exist (No such file or directory)
diff --git a/libraries/base/tests/IO/openFile003.stdout-mingw32 b/libraries/base/tests/IO/openFile003.stdout-mingw32
index 77ad0a860a..b808fccc3f 100644
--- a/libraries/base/tests/IO/openFile003.stdout-mingw32
+++ b/libraries/base/tests/IO/openFile003.stdout-mingw32
@@ -1,4 +1,4 @@
-Left openFile003Dir: openFile: permission denied (Permission denied)
-Left openFile003Dir: openFile: permission denied (Permission denied)
-Left openFile003Dir: openFile: permission denied (Permission denied)
-Left openFile003Dir: openFile: permission denied (Permission denied)
+Left openFile003Dir: openFile: permission denied (Access is denied.)
+Left openFile003Dir: openFile: permission denied (Access is denied.)
+Left openFile003Dir: openFile: permission denied (Access is denied.)
+Left openFile003Dir: openFile: permission denied (Access is denied.)
diff --git a/libraries/base/tests/IO/openFile009.hs b/libraries/base/tests/IO/openFile009.hs
new file mode 100644
index 0000000000..b3aa8c9f9b
--- /dev/null
+++ b/libraries/base/tests/IO/openFile009.hs
@@ -0,0 +1,19 @@
+import System.IO
+import System.Cmd
+import System.FilePath
+import Text.Printf
+import System.Directory
+import Control.Monad
+
+testfile = "openFile009_testfile"
+
+-- Make sure opening with append doesn't truncate files.
+main = do
+ h <- openFile testfile WriteMode
+ hPutStr h "Hello"
+ hClose h
+ h <- openFile testfile AppendMode
+ hPutStr h " World!"
+ hClose h
+ s <- readFile testfile
+ putStrLn s
diff --git a/libraries/base/tests/IO/openFile009.stdout b/libraries/base/tests/IO/openFile009.stdout
new file mode 100644
index 0000000000..980a0d5f19
--- /dev/null
+++ b/libraries/base/tests/IO/openFile009.stdout
@@ -0,0 +1 @@
+Hello World!
diff --git a/libraries/base/tests/Numeric/all.T b/libraries/base/tests/Numeric/all.T
index 74d4c06514..0d6d0728d1 100644
--- a/libraries/base/tests/Numeric/all.T
+++ b/libraries/base/tests/Numeric/all.T
@@ -11,7 +11,7 @@ test('num008', normal, compile_and_run, [''])
test('num009', [ when(fast(), skip)
# , when(wordsize(32), expect_broken(15062))
, when(platform('powerpc64le-unknown-linux'), expect_broken(13634))],
- compile_and_run, [opts])
+ compile_and_run, [''])
test('num010',
when(platform('i386-apple-darwin'), expect_broken_for(7043, ['ghci'])),
compile_and_run,
diff --git a/libraries/base/tests/tempfiles.stdout-mingw32 b/libraries/base/tests/tempfiles.stdout-mingw32
deleted file mode 100644
index 5d7b23db0e..0000000000
--- a/libraries/base/tests/tempfiles.stdout-mingw32
+++ /dev/null
@@ -1,12 +0,0 @@
-.no_prefix.hs
-True
-False
-no_suffix
-True
-False
-one_suffix.hs
-True
-False
-two_suffixes.hs.blah
-True
-False
diff --git a/libraries/ghc-heap/GHC/Exts/Heap/Closures.hs b/libraries/ghc-heap/GHC/Exts/Heap/Closures.hs
index bb9d440b37..8a959fc2a0 100644
--- a/libraries/ghc-heap/GHC/Exts/Heap/Closures.hs
+++ b/libraries/ghc-heap/GHC/Exts/Heap/Closures.hs
@@ -229,7 +229,15 @@ data GenClosure b
}
-- | An @MVar#@, with a queue of thread state objects blocking on them
- | MVarClosure
+ | MVarClosure
+ { info :: !StgInfoTable
+ , queueHead :: !b -- ^ Pointer to head of queue
+ , queueTail :: !b -- ^ Pointer to tail of queue
+ , value :: !b -- ^ Pointer to closure
+ }
+
+ -- | An @IOPort#@, with a queue of thread state objects blocking on them
+ | IOPortClosure
{ info :: !StgInfoTable
, queueHead :: !b -- ^ Pointer to head of queue
, queueTail :: !b -- ^ Pointer to tail of queue
@@ -340,6 +348,7 @@ allClosures (MutArrClosure {..}) = mccPayload
allClosures (SmallMutArrClosure {..}) = mccPayload
allClosures (MutVarClosure {..}) = [var]
allClosures (MVarClosure {..}) = [queueHead,queueTail,value]
+allClosures (IOPortClosure {..}) = [queueHead,queueTail,value]
allClosures (FunClosure {..}) = ptrArgs
allClosures (BlockingQueueClosure {..}) = [link, blackHole, owner, queue]
allClosures (WeakClosure {..}) = [cfinalizers, key, value, finalizer, link]
diff --git a/libraries/ghc-prim/ghc-prim.cabal b/libraries/ghc-prim/ghc-prim.cabal
index e68b09c944..bca9225023 100644
--- a/libraries/ghc-prim/ghc-prim.cabal
+++ b/libraries/ghc-prim/ghc-prim.cabal
@@ -1,4 +1,4 @@
-cabal-version: 2.1
+cabal-version: 2.2
name: ghc-prim
version: 0.7.0
-- NOTE: Don't forget to update ./changelog.md
diff --git a/libraries/haskeline b/libraries/haskeline
-Subproject d3885e4bc1dfe6b06829871361bf9330414fc9e
+Subproject 5f16b76168f13c6413413386efc44fb1152048d
diff --git a/libraries/process b/libraries/process
-Subproject 8f4ecebb6578a179a6c04074cb06600683e2e50
+Subproject cb1d1a6ead68f0e1b209277e79ec608980e9ac8
diff --git a/rts/FileLock.c b/rts/FileLock.c
index 351d2a58f7..4509de1a42 100644
--- a/rts/FileLock.c
+++ b/rts/FileLock.c
@@ -25,10 +25,10 @@ typedef struct {
// Two hash tables. The first maps objects (device/inode pairs) to
// Lock objects containing the number of active readers or writers. The
-// second maps file descriptors to lock objects, so that we can unlock
-// by FD without needing to fstat() again.
+// second maps file descriptors or file handles to lock objects, so that we can
+// unlock by FD or HANDLE without needing to fstat() again.
static HashTable *obj_hash;
-static HashTable *fd_hash;
+static HashTable *key_hash;
#if defined(THREADED_RTS)
static Mutex file_lock_mutex;
@@ -53,7 +53,7 @@ void
initFileLocking(void)
{
obj_hash = allocHashTable();
- fd_hash = allocHashTable(); /* ordinary word-based table */
+ key_hash = allocHashTable(); /* ordinary word-based table */
#if defined(THREADED_RTS)
initMutex(&file_lock_mutex);
#endif
@@ -69,14 +69,14 @@ void
freeFileLocking(void)
{
freeHashTable(obj_hash, freeLock);
- freeHashTable(fd_hash, NULL);
+ freeHashTable(key_hash, NULL);
#if defined(THREADED_RTS)
closeMutex(&file_lock_mutex);
#endif
}
int
-lockFile(int fd, StgWord64 dev, StgWord64 ino, int for_writing)
+lockFile(StgWord64 id, StgWord64 dev, StgWord64 ino, int for_writing)
{
Lock key, *lock;
@@ -94,7 +94,7 @@ lockFile(int fd, StgWord64 dev, StgWord64 ino, int for_writing)
lock->inode = ino;
lock->readers = for_writing ? -1 : 1;
insertHashTable_(obj_hash, (StgWord)lock, (void *)lock, hashLock);
- insertHashTable(fd_hash, fd, lock);
+ insertHashTable(key_hash, id, lock);
RELEASE_LOCK(&file_lock_mutex);
return 0;
}
@@ -105,7 +105,7 @@ lockFile(int fd, StgWord64 dev, StgWord64 ino, int for_writing)
RELEASE_LOCK(&file_lock_mutex);
return -1;
}
- insertHashTable(fd_hash, fd, lock);
+ insertHashTable(key_hash, id, lock);
lock->readers++;
RELEASE_LOCK(&file_lock_mutex);
return 0;
@@ -113,15 +113,15 @@ lockFile(int fd, StgWord64 dev, StgWord64 ino, int for_writing)
}
int
-unlockFile(int fd)
+unlockFile(StgWord64 id)
{
Lock *lock;
ACQUIRE_LOCK(&file_lock_mutex);
- lock = lookupHashTable(fd_hash, fd);
+ lock = lookupHashTable(key_hash, id);
if (lock == NULL) {
- // errorBelch("unlockFile: fd %d not found", fd);
+ // errorBelch("unlockFile: key %d not found", key);
// This is normal: we didn't know when calling unlockFile
// whether this FD referred to a locked file or not.
RELEASE_LOCK(&file_lock_mutex);
@@ -138,7 +138,7 @@ unlockFile(int fd)
removeHashTable_(obj_hash, (StgWord)lock, NULL, hashLock, cmpLocks);
stgFree(lock);
}
- removeHashTable(fd_hash, fd, NULL);
+ removeHashTable(key_hash, id, NULL);
RELEASE_LOCK(&file_lock_mutex);
return 0;
diff --git a/rts/HeapStackCheck.cmm b/rts/HeapStackCheck.cmm
index 1c1de089dc..b8df323c8b 100644
--- a/rts/HeapStackCheck.cmm
+++ b/rts/HeapStackCheck.cmm
@@ -17,8 +17,8 @@
#if defined(__PIC__)
import pthread_mutex_unlock;
#endif
-import EnterCriticalSection;
-import LeaveCriticalSection;
+import AcquireSRWLockExclusive;
+import ReleaseSRWLockExclusives;
/* Stack/Heap Check Failure
* ------------------------
diff --git a/rts/Linker.c b/rts/Linker.c
index 443de6a356..4b551f0073 100644
--- a/rts/Linker.c
+++ b/rts/Linker.c
@@ -1681,12 +1681,10 @@ int ocTryLoad (ObjectCode* oc) {
are to be loaded by this call.
This call is intended to have no side-effects when a non-duplicate
- symbol is re-inserted. A symbol is only a duplicate if the object file
- it is defined in has had it's relocations resolved. A resolved object
- file means the symbols inside it are required.
+ symbol is re-inserted.
- The symbol address is not used to distinguish symbols. Duplicate symbols
- are distinguished by name, oc and attributes (weak symbols etc).
+ We set the Address to NULL since that is not used to distinguish
+ symbols. Duplicate symbols are distinguished by name and oc.
*/
int x;
Symbol_t symbol;
diff --git a/rts/Prelude.h b/rts/Prelude.h
index c6971677af..d2511b2fc3 100644
--- a/rts/Prelude.h
+++ b/rts/Prelude.h
@@ -43,6 +43,7 @@ PRELUDE_CLOSURE(base_GHCziIOziException_blockedIndefinitelyOnSTM_closure);
PRELUDE_CLOSURE(base_GHCziIOziException_cannotCompactFunction_closure);
PRELUDE_CLOSURE(base_GHCziIOziException_cannotCompactPinned_closure);
PRELUDE_CLOSURE(base_GHCziIOziException_cannotCompactMutable_closure);
+PRELUDE_CLOSURE(base_GHCziIOPort_doubleReadException_closure);
PRELUDE_CLOSURE(base_ControlziExceptionziBase_nonTermination_closure);
PRELUDE_CLOSURE(base_ControlziExceptionziBase_nestedAtomically_closure);
PRELUDE_CLOSURE(base_GHCziEventziThread_blockedOnBadFD_closure);
@@ -52,8 +53,12 @@ PRELUDE_CLOSURE(base_GHCziExceptionziType_overflowException_closure);
PRELUDE_CLOSURE(base_GHCziConcziSync_runSparks_closure);
PRELUDE_CLOSURE(base_GHCziConcziIO_ensureIOManagerIsRunning_closure);
+PRELUDE_CLOSURE(base_GHCziConcziIO_interruptIOManager_closure);
PRELUDE_CLOSURE(base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure);
PRELUDE_CLOSURE(base_GHCziConcziSignal_runHandlersPtr_closure);
+#if defined(mingw32_HOST_OS)
+PRELUDE_CLOSURE(base_GHCziEventziWindows_processRemoteCompletion_closure);
+#endif
PRELUDE_CLOSURE(base_GHCziTopHandler_flushStdHandles_closure);
PRELUDE_CLOSURE(base_GHCziTopHandler_runMainIO_closure);
@@ -85,8 +90,12 @@ PRELUDE_INFO(base_GHCziStable_StablePtr_con_info);
#define runSparks_closure DLL_IMPORT_DATA_REF(base_GHCziConcziSync_runSparks_closure)
#define ensureIOManagerIsRunning_closure DLL_IMPORT_DATA_REF(base_GHCziConcziIO_ensureIOManagerIsRunning_closure)
+#define interruptIOManager_closure DLL_IMPORT_DATA_REF(base_GHCziConcziIO_interruptIOManager_closure)
#define ioManagerCapabilitiesChanged_closure DLL_IMPORT_DATA_REF(base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure)
#define runHandlersPtr_closure DLL_IMPORT_DATA_REF(base_GHCziConcziSignal_runHandlersPtr_closure)
+#if defined(mingw32_HOST_OS)
+#define processRemoteCompletion_closure DLL_IMPORT_DATA_REF(base_GHCziEventziWindows_processRemoteCompletion_closure)
+#endif
#define flushStdHandles_closure DLL_IMPORT_DATA_REF(base_GHCziTopHandler_flushStdHandles_closure)
#define runMainIO_closure DLL_IMPORT_DATA_REF(base_GHCziTopHandler_runMainIO_closure)
@@ -101,6 +110,8 @@ PRELUDE_INFO(base_GHCziStable_StablePtr_con_info);
#define cannotCompactMutable_closure DLL_IMPORT_DATA_REF(base_GHCziIOziException_cannotCompactMutable_closure)
#define nonTermination_closure DLL_IMPORT_DATA_REF(base_ControlziExceptionziBase_nonTermination_closure)
#define nestedAtomically_closure DLL_IMPORT_DATA_REF(base_ControlziExceptionziBase_nestedAtomically_closure)
+#define doubleReadException DLL_IMPORT_DATA_REF(base_GHCziIOPort_doubleReadException_closure)
+
#define blockedOnBadFD_closure DLL_IMPORT_DATA_REF(base_GHCziEventziThread_blockedOnBadFD_closure)
#define Czh_con_info DLL_IMPORT_DATA_REF(ghczmprim_GHCziTypes_Czh_con_info)
diff --git a/rts/PrimOps.cmm b/rts/PrimOps.cmm
index 1fd746edf6..1aa001c953 100644
--- a/rts/PrimOps.cmm
+++ b/rts/PrimOps.cmm
@@ -31,8 +31,10 @@ import pthread_mutex_unlock;
#endif
import CLOSURE base_ControlziExceptionziBase_nestedAtomically_closure;
import CLOSURE base_GHCziIOziException_heapOverflow_closure;
-import EnterCriticalSection;
-import LeaveCriticalSection;
+import CLOSURE base_GHCziIOziException_blockedIndefinitelyOnMVar_closure;
+import CLOSURE base_GHCziIOPort_doubleReadException_closure;
+import AcquireSRWLockExclusive;
+import ReleaseSRWLockExclusive;
import CLOSURE ghczmprim_GHCziTypes_False_closure;
#if defined(PROFILING)
import CLOSURE CCS_MAIN;
@@ -1593,6 +1595,7 @@ stg_takeMVarzh ( P_ mvar /* :: MVar a */ )
SET_HDR(q, stg_MVAR_TSO_QUEUE_info, CCS_SYSTEM);
// Write barrier before we make the new MVAR_TSO_QUEUE
// visible to other cores.
+ // See Note [Heap memory barriers]
prim_write_barrier;
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
@@ -1761,6 +1764,7 @@ stg_putMVarzh ( P_ mvar, /* :: MVar a */
StgMVarTSOQueue_tso(q) = CurrentTSO;
SET_HDR(q, stg_MVAR_TSO_QUEUE_info, CCS_SYSTEM);
+ //See Note [Heap memory barriers]
prim_write_barrier;
if (StgMVar_head(mvar) == stg_END_TSO_QUEUE_closure) {
@@ -1943,6 +1947,7 @@ stg_readMVarzh ( P_ mvar, /* :: MVar a */ )
*/
if (StgMVar_value(mvar) == stg_END_TSO_QUEUE_closure) {
+ // Add MVar to mutable list
if (info == stg_MVAR_CLEAN_info) {
ccall dirty_MVAR(BaseReg "ptr", mvar "ptr", StgMVar_value(mvar));
}
@@ -1960,6 +1965,7 @@ stg_readMVarzh ( P_ mvar, /* :: MVar a */ )
StgMVarTSOQueue_tso(q) = CurrentTSO;
SET_HDR(q, stg_MVAR_TSO_QUEUE_info, CCS_SYSTEM);
+ //See Note [Heap memory barriers]
prim_write_barrier;
StgTSO__link(CurrentTSO) = q;
@@ -1998,6 +2004,240 @@ stg_tryReadMVarzh ( P_ mvar, /* :: MVar a */ )
}
/* -----------------------------------------------------------------------------
+ * IOPort primitives
+ *
+ * readIOPort & writeIOPort work as follows. Firstly, an important invariant:
+ *
+ * Only one read and one write is allowed for an IOPort.
+ * Reading or writing to the same port twice will throw an exception.
+ *
+ * readIOPort:
+ * IOPort empty : then add ourselves to the blocking queue
+ * IOPort full : remove the value from the IOPort, and
+ * blocking queue empty : return
+ * blocking queue non-empty : perform the only blocked
+ * writeIOPort from the queue, and
+ * wake up the thread
+ * (IOPort is now empty)
+ *
+ * writeIOPort is just the dual of the above algorithm.
+ *
+ * How do we "perform a writeIOPort"? Well, By storing the value and prt on the
+ * stack, same way we do with MVars. Semantically the operations mutate the
+ * stack the same way so we will re-use the logic and datastructures for MVars
+ * for IOPort. See stg_block_putmvar and stg_block_takemvar in HeapStackCheck.c
+ * for the stack layout, and the PerformPut and PerformTake macros below. We
+ * also re-use the closure types MVAR_CLEAN/_DIRTY for IOPort.
+ *
+ * The remaining caveats of MVar thus also apply for an IOPort. The main
+ * crucial difference between an MVar and IOPort is that the scheduler will not
+ * be allowed to interrupt a blocked IOPort just because it thinks there's a
+ * deadlock. This is especially crucial for the non-threaded runtime.
+ *
+ * To avoid double reads/writes we set only the head to a MVarTSOQueue when
+ * a reader queues up on a port.
+ * We set the tail to the port itself upon reading. We can do this
+ * since there can only be one reader/writer for the port. In contrast to MVars
+ * which do need to keep a list of blocked threads.
+ *
+ * This means IOPorts have these valid states and transitions:
+ *
+ ┌─────────┐
+ │ Empty │ head == tail == value == END_TSO_QUEUE
+ ├─────────┤
+ │ │
+ write │ │ read
+ v v
+ value != END_TSO_QUEUE ┌─────────┐ ┌─────────┐ value == END_TSO_QUEUE
+ head == END_TSO_QUEUE │ full │ │ reading │ head == queue with single reader
+ tail == END_TSO_QUEUE └─────────┘ └─────────┘ tail == END_TSO_QUEUE
+ │ │
+ read │ │ write
+ │ │
+ v v
+ ┌──────────┐ value != END_TSO_QUEUE
+ │ Used │ head == END_TSO_QUEUE
+ └──────────┘ tail == ioport
+
+ *
+ * -------------------------------------------------------------------------- */
+
+
+stg_readIOPortzh ( P_ ioport /* :: IOPort a */ )
+{
+ W_ val, info, tso, q;
+
+ LOCK_CLOSURE(ioport, info);
+
+ /* If the Port is empty, put ourselves on the blocked readers
+ * list and wait until we're woken up.
+ */
+ if (StgMVar_value(ioport) == stg_END_TSO_QUEUE_closure) {
+
+ // There is or was already another reader, throw exception.
+ if (StgMVar_head(ioport) != stg_END_TSO_QUEUE_closure ||
+ StgMVar_tail(ioport) != stg_END_TSO_QUEUE_closure) {
+ unlockClosure(ioport, info);
+ jump stg_raiseIOzh(base_GHCziIOPort_doubleReadException_closure);
+ }
+
+ if (info == stg_MVAR_CLEAN_info) {
+ ccall dirty_MVAR(BaseReg "ptr", ioport "ptr", StgMVar_value(ioport));
+ }
+
+ ALLOC_PRIM_WITH_CUSTOM_FAILURE
+ (SIZEOF_StgMVarTSOQueue,
+ unlockClosure(ioport, stg_MVAR_DIRTY_info);
+ GC_PRIM_P(stg_readIOPortzh, ioport));
+
+ q = Hp - SIZEOF_StgMVarTSOQueue + WDS(1);
+
+ // link = stg_END_TSO_QUEUE_closure since we check that
+ // there is no other reader above.
+ StgMVarTSOQueue_link(q) = stg_END_TSO_QUEUE_closure;
+ StgMVarTSOQueue_tso(q) = CurrentTSO;
+
+ SET_HDR(q, stg_MVAR_TSO_QUEUE_info, CCS_SYSTEM);
+ //See Note [Heap memory barriers]
+ prim_write_barrier;
+
+ StgMVar_head(ioport) = q;
+ StgTSO__link(CurrentTSO) = q;
+ StgTSO_block_info(CurrentTSO) = ioport;
+ StgTSO_why_blocked(CurrentTSO) = BlockedOnIOCompletion::I16;
+
+ //Unlocks the closure as well
+ jump stg_block_readmvar(ioport);
+
+ }
+
+ //This way we can check of there has been a read already.
+ //Upon reading we set tail to indicate the port is now closed.
+ if (StgMVar_tail(ioport) == stg_END_TSO_QUEUE_closure) {
+ StgMVar_tail(ioport) = ioport;
+ StgMVar_head(ioport) = stg_END_TSO_QUEUE_closure;
+ } else {
+ //Or another thread has read already: Throw an exception.
+ unlockClosure(ioport, info);
+ jump stg_raiseIOzh(base_GHCziIOPort_doubleReadException_closure);
+ }
+
+ val = StgMVar_value(ioport);
+
+ unlockClosure(ioport, info);
+ return (val);
+}
+
+stg_writeIOPortzh ( P_ ioport, /* :: IOPort a */
+ P_ val, /* :: a */ )
+{
+ W_ info, tso, q;
+
+ LOCK_CLOSURE(ioport, info);
+
+ /* If there is already a value in the port, then raise an exception
+ as it's the second write.
+ Correct usages of IOPort should never have a second
+ write. */
+ if (StgMVar_value(ioport) != stg_END_TSO_QUEUE_closure) {
+ unlockClosure(ioport, info);
+ jump stg_raiseIOzh(base_GHCziIOPort_doubleReadException_closure);
+ return (0);
+ }
+
+ // We are going to mutate the closure, make sure its current pointers
+ // are marked.
+ if (info == stg_MVAR_CLEAN_info) {
+ ccall update_MVAR(BaseReg "ptr", ioport "ptr", StgMVar_value(ioport) "ptr");
+ }
+
+ q = StgMVar_head(ioport);
+loop:
+ if (q == stg_END_TSO_QUEUE_closure) {
+ /* No takes, the IOPort is now full. */
+ if (info == stg_MVAR_CLEAN_info) {
+ ccall dirty_MVAR(BaseReg "ptr", ioport "ptr");
+ }
+ StgMVar_value(ioport) = val;
+
+ unlockClosure(ioport, stg_MVAR_DIRTY_info);
+ return (1);
+ }
+ //Possibly IND added by removeFromMVarBlockedQueue
+ if (StgHeader_info(q) == stg_IND_info ||
+ StgHeader_info(q) == stg_MSG_NULL_info) {
+ q = StgInd_indirectee(q);
+ goto loop;
+ }
+
+ // There is a readIOPort waiting: wake it up
+ tso = StgMVarTSOQueue_tso(q);
+
+ // Assert no read has happened yet.
+ ASSERT(StgMVar_tail(ioport) == stg_END_TSO_QUEUE_closure);
+ // And there is only one reader queued up.
+ ASSERT(StgMVarTSOQueue_link(q) == stg_END_TSO_QUEUE_closure);
+
+ // We perform the read here, so set tail/head accordingly.
+ StgMVar_head(ioport) = stg_END_TSO_QUEUE_closure;
+ StgMVar_tail(ioport) = ioport;
+
+ // In contrast to MVars we do not need to move on to the
+ // next element in the waiting list here, as there can only ever
+ // be one thread blocked on a port.
+
+ ASSERT(StgTSO_block_info(tso) == ioport);
+ // save why_blocked here, because waking up the thread destroys
+ // this information
+ W_ why_blocked;
+ why_blocked = TO_W_(StgTSO_why_blocked(tso));
+
+ // actually perform the takeMVar
+ W_ stack;
+ stack = StgTSO_stackobj(tso);
+ PerformTake(stack, val);
+
+ // indicate that the operation has now completed.
+ StgTSO__link(tso) = stg_END_TSO_QUEUE_closure;
+
+ if (TO_W_(StgStack_dirty(stack)) == 0) {
+ ccall dirty_STACK(MyCapability() "ptr", stack "ptr");
+ }
+
+ ccall tryWakeupThread(MyCapability() "ptr", tso);
+
+ // For MVars we loop here, waking up all readers.
+ // IOPorts however can only have on reader. So we are done
+ // at this point.
+
+ //Either there was no reader queued, or he must have been
+ //blocked on BlockedOnIOCompletion
+ ASSERT(why_blocked == BlockedOnIOCompletion);
+
+ unlockClosure(ioport, info);
+ return (1);
+}
+/* -----------------------------------------------------------------------------
+ IOPort primitives
+ -------------------------------------------------------------------------- */
+
+stg_newIOPortzh ( gcptr init )
+{
+ W_ ioport;
+
+ ALLOC_PRIM_ (SIZEOF_StgMVar, stg_newIOPortzh);
+
+ ioport = Hp - SIZEOF_StgMVar + WDS(1);
+ SET_HDR(ioport, stg_MVAR_DIRTY_info,CCCS);
+ // MVARs start dirty: generation 0 has no mutable list
+ StgMVar_head(ioport) = stg_END_TSO_QUEUE_closure;
+ StgMVar_tail(ioport) = stg_END_TSO_QUEUE_closure;
+ StgMVar_value(ioport) = stg_END_TSO_QUEUE_closure;
+
+ return (ioport);
+}
+
+/* -----------------------------------------------------------------------------
Stable pointer primitives
------------------------------------------------------------------------- */
diff --git a/rts/RaiseAsync.c b/rts/RaiseAsync.c
index e8a6a81747..719c05435d 100644
--- a/rts/RaiseAsync.c
+++ b/rts/RaiseAsync.c
@@ -174,11 +174,11 @@ throwToSelf (Capability *cap, StgTSO *tso, StgClosure *exception)
- or it is masking exceptions (TSO_BLOCKEX)
- Currently, if the target is BlockedOnMVar, BlockedOnSTM, or
- BlockedOnBlackHole then we acquire ownership of the TSO by locking
- its parent container (e.g. the MVar) and then raise the exception.
- We might change these cases to be more message-passing-like in the
- future.
+ Currently, if the target is BlockedOnMVar, BlockedOnSTM,
+ BlockedOnIOCompletion or BlockedOnBlackHole then we acquire ownership of the
+ TSO by locking its parent container (e.g. the MVar) and then raise the
+ exception. We might change these cases to be more message-passing-like in
+ the future.
Returns:
@@ -343,6 +343,7 @@ check_target:
case BlockedOnMVar:
case BlockedOnMVarRead:
+ case BlockedOnIOCompletion:
{
/*
To establish ownership of this TSO, we need to acquire a
@@ -367,7 +368,9 @@ check_target:
// we have the MVar, let's check whether the thread
// is still blocked on the same MVar.
- if ((target->why_blocked != BlockedOnMVar && target->why_blocked != BlockedOnMVarRead)
+ if ((target->why_blocked != BlockedOnMVar
+ && target->why_blocked != BlockedOnMVarRead
+ && target->why_blocked != BlockedOnIOCompletion)
|| (StgMVar *)target->block_info.closure != mvar) {
unlockClosure((StgClosure *)mvar, info);
goto retry;
@@ -679,6 +682,7 @@ removeFromQueues(Capability *cap, StgTSO *tso)
case BlockedOnMVar:
case BlockedOnMVarRead:
+ case BlockedOnIOCompletion:
removeFromMVarBlockedQueue(tso);
goto done;
diff --git a/rts/RtsFlags.c b/rts/RtsFlags.c
index 6180f42e39..3963e6d0d5 100644
--- a/rts/RtsFlags.c
+++ b/rts/RtsFlags.c
@@ -35,6 +35,7 @@
#endif
#include <fs_rts.h>
+#include <stdbool.h>
// Flag Structure
RTS_FLAGS RtsFlags;
@@ -254,6 +255,16 @@ 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
+#if defined(THREADED_RTS) && defined(mingw32_HOST_OS)
+ RtsFlags.MiscFlags.numIoWorkerThreads = getNumberOfProcessors();
+#else
+ RtsFlags.MiscFlags.numIoWorkerThreads = 1;
+#endif
#if defined(THREADED_RTS)
RtsFlags.ParFlags.nCapabilities = 1;
@@ -474,7 +485,14 @@ 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)",
#if defined(THREADED_RTS)
+#if defined(mingw32_HOST_OS)
+" --io-manager-threads=<num>",
+" The number of worker threads to use in the native I/O manager to",
+" handle completion events. (defualt: num cores)",
+#endif
" -e<n> Maximum number of outstanding local sparks (default: 4096)",
#endif
#if defined(x86_64_HOST_ARCH)
@@ -933,6 +951,16 @@ 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])) {
+ OPTION_UNSAFE;
+ RtsFlags.MiscFlags.ioManager = IO_MNGR_POSIX;
+ }
else if (strequal("info",
&rts_argv[arg][2])) {
OPTION_SAFE;
@@ -945,6 +973,31 @@ error = true;
RtsFlags.GcFlags.useNonmoving = true;
}
#if defined(THREADED_RTS)
+#if defined(mingw32_HOST_OS)
+ else if (!strncmp("io-manager-threads",
+ &rts_argv[arg][2], 18)) {
+ OPTION_SAFE;
+ uint32_t num;
+ if (rts_argv[arg][20] == '=') {
+ num = (StgWord)strtol(rts_argv[arg]+21,
+ (char **) NULL, 10);
+ } else {
+ errorBelch("%s: Expected number of threads to use.",
+ rts_argv[arg]);
+ error = true;
+ break;
+ }
+
+ if (num < 1) {
+ errorBelch("%s: Expected number of threads to be at least 1.",
+ rts_argv[arg]);
+ error = true;
+ break;
+ }
+
+ RtsFlags.MiscFlags.numIoWorkerThreads = num;
+ }
+#endif
else if (!strncmp("numa", &rts_argv[arg][2], 4)) {
if (!osBuiltWithNumaSupport()) {
errorBelch("%s: This GHC build was compiled without NUMA support.",
@@ -2460,3 +2513,16 @@ built in the -debug, -eventlog, -prof ways. And even if they do, the
damage should be limited to DOS, information disclosure and writing
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
+}
diff --git a/rts/RtsFlags.h b/rts/RtsFlags.h
index c36c64a63b..bfcc43af42 100644
--- a/rts/RtsFlags.h
+++ b/rts/RtsFlags.h
@@ -10,6 +10,7 @@
#pragma once
#include "BeginPrivate.h"
+#include <stdbool.h>
/* Routines that operate-on/to-do-with RTS flags: */
@@ -21,6 +22,7 @@ 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);
extern RtsConfig rtsConfig;
diff --git a/rts/RtsStartup.c b/rts/RtsStartup.c
index f73d0bd742..a52c02190e 100644
--- a/rts/RtsStartup.c
+++ b/rts/RtsStartup.c
@@ -46,11 +46,13 @@
#endif
#if defined(mingw32_HOST_OS) && !defined(THREADED_RTS)
-#include "win32/AsyncIO.h"
+#include "win32/AsyncMIO.h"
+#include "win32/AsyncWinIO.h"
#endif
#if defined(mingw32_HOST_OS)
#include <fenv.h>
+#include <windows.h>
#else
#include "posix/TTY.h"
#endif
@@ -120,6 +122,21 @@ void fpreset(void) {
_fpreset();
}
#endif
+
+/* Set the console's CodePage to UTF-8 if using the new I/O manager and the CP
+ is still the default one. */
+static void
+initConsoleCP (void)
+{
+ /* Check if the codepage is still the system default ANSI codepage. */
+ if (GetConsoleCP () == GetOEMCP ()) {
+ if (! SetConsoleCP (CP_UTF8))
+ errorBelch ("Unable to set console CodePage, Unicode output may be "
+ "garbled.\n");
+ else
+ IF_DEBUG (scheduler, debugBelch ("Codepage set to UTF-8.\n"));
+ }
+}
#endif
/* -----------------------------------------------------------------------------
@@ -220,6 +237,12 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config)
#endif /* DEBUG */
}
+ /* Initialize console Codepage. */
+#if defined(mingw32_HOST_OS)
+ if (is_io_mng_native_p())
+ initConsoleCP();
+#endif
+
/* Initialise the stats department, phase 1 */
initStats1();
@@ -277,10 +300,13 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config)
getStablePtr((StgPtr)nestedAtomically_closure);
getStablePtr((StgPtr)runSparks_closure);
getStablePtr((StgPtr)ensureIOManagerIsRunning_closure);
+ getStablePtr((StgPtr)interruptIOManager_closure);
getStablePtr((StgPtr)ioManagerCapabilitiesChanged_closure);
#if !defined(mingw32_HOST_OS)
getStablePtr((StgPtr)blockedOnBadFD_closure);
getStablePtr((StgPtr)runHandlersPtr_closure);
+#else
+ getStablePtr((StgPtr)processRemoteCompletion_closure);
#endif
// Initialize the top-level handler system
@@ -316,7 +342,10 @@ hs_init_ghc(int *argc, char **argv[], RtsConfig rts_config)
#endif
#if defined(mingw32_HOST_OS) && !defined(THREADED_RTS)
- startupAsyncIO();
+ if (is_io_mng_native_p())
+ startupAsyncWinIO();
+ else
+ startupAsyncIO();
#endif
x86_init_fpu();
@@ -498,7 +527,10 @@ hs_exit_(bool wait_foreign)
#endif
#if defined(mingw32_HOST_OS) && !defined(THREADED_RTS)
- shutdownAsyncIO(wait_foreign);
+ if (is_io_mng_native_p())
+ shutdownAsyncWinIO(wait_foreign);
+ else
+ shutdownAsyncIO(wait_foreign);
#endif
/* free hash table storage */
diff --git a/rts/RtsSymbols.c b/rts/RtsSymbols.c
index ff32932ea9..6432cbdcdd 100644
--- a/rts/RtsSymbols.c
+++ b/rts/RtsSymbols.c
@@ -26,6 +26,7 @@
#include <io.h>
#include <windows.h>
#include <shfolder.h> /* SHGetFolderPathW */
+#include "win32/AsyncWinIO.h"
#endif
#if defined(openbsd_HOST_OS)
@@ -142,11 +143,15 @@
/* see Note [Symbols for MinGW's printf] */ \
SymI_HasProto(_lock_file) \
SymI_HasProto(_unlock_file) \
+ SymI_HasProto(__mingw_vsnwprintf) \
+ /* ^^ Need to figure out why this is needed. */ \
/* See Note [_iob_func symbol] */ \
RTS_WIN64_ONLY(SymI_HasProto_redirect( \
__imp___acrt_iob_func, __rts_iob_func, true)) \
RTS_WIN32_ONLY(SymI_HasProto_redirect( \
- __imp____acrt_iob_func, __rts_iob_func, true))
+ __imp____acrt_iob_func, __rts_iob_func, true)) \
+ SymI_HasProto(__mingw_vsnwprintf)
+ /* ^^ Need to figure out why this is needed. */
#define RTS_MINGW_COMPAT_SYMBOLS \
SymI_HasProto_deprecated(access) \
@@ -337,11 +342,15 @@
SymI_HasProto(blockUserSignals) \
SymI_HasProto(unblockUserSignals)
#else
-#define RTS_USER_SIGNALS_SYMBOLS \
- SymI_HasProto(ioManagerWakeup) \
- SymI_HasProto(sendIOManagerEvent) \
- SymI_HasProto(readIOManagerEvent) \
- SymI_HasProto(getIOManagerEvent) \
+#define RTS_USER_SIGNALS_SYMBOLS \
+ SymI_HasProto(registerIOCPHandle) \
+ SymI_HasProto(getOverlappedEntries) \
+ SymI_HasProto(completeSynchronousRequest) \
+ SymI_HasProto(registerAlertableWait) \
+ SymI_HasProto(ioManagerWakeup) \
+ SymI_HasProto(sendIOManagerEvent) \
+ SymI_HasProto(readIOManagerEvent) \
+ SymI_HasProto(getIOManagerEvent) \
SymI_HasProto(console_handler)
#endif
@@ -706,6 +715,9 @@
SymI_HasProto(stg_newMVarzh) \
SymI_HasProto(stg_newMutVarzh) \
SymI_HasProto(stg_newTVarzh) \
+ SymI_HasProto(stg_readIOPortzh) \
+ SymI_HasProto(stg_writeIOPortzh) \
+ SymI_HasProto(stg_newIOPortzh) \
SymI_HasProto(stg_noDuplicatezh) \
SymI_HasProto(stg_atomicModifyMutVar2zh) \
SymI_HasProto(stg_atomicModifyMutVarzuzh) \
diff --git a/rts/Schedule.c b/rts/Schedule.c
index ce1a1fc060..6b10326859 100644
--- a/rts/Schedule.c
+++ b/rts/Schedule.c
@@ -34,6 +34,7 @@
#include "AwaitEvent.h"
#if defined(mingw32_HOST_OS)
#include "win32/IOManager.h"
+#include "win32/AsyncWinIO.h"
#endif
#include "Trace.h"
#include "RaiseAsync.h"
@@ -198,6 +199,7 @@ schedule (Capability *initialCapability, Task *task)
bool ready_to_gc;
cap = initialCapability;
+ t = NULL;
// Pre-condition: this task owns initialCapability.
// The sched_mutex is *NOT* held
@@ -301,8 +303,13 @@ schedule (Capability *initialCapability, Task *task)
// Additionally, it is not fatal for the
// threaded RTS to reach here with no threads to run.
//
+ // Since IOPorts have no deadlock avoidance guarantees you may also reach
+ // this point when blocked on an IO Port. If this is the case the only
+ // thing that could unblock it is an I/O event.
+ //
// win32: might be here due to awaitEvent() being abandoned
- // as a result of a console event having been delivered.
+ // as a result of a console event having been delivered or as a result of
+ // waiting on an async I/O to complete with WinIO.
#if defined(THREADED_RTS)
scheduleYield(&cap,task);
@@ -310,9 +317,16 @@ schedule (Capability *initialCapability, Task *task)
if (emptyRunQueue(cap)) continue; // look for work again
#endif
-#if !defined(THREADED_RTS) && !defined(mingw32_HOST_OS)
+#if !defined(THREADED_RTS)
if ( emptyRunQueue(cap) ) {
+#if defined(mingw32_HOST_OS)
+ /* Notify the I/O manager that we have nothing to do. If there are
+ any outstanding I/O requests we'll block here. If there are not
+ then this is a user error and we will abort soon. */
+ awaitEvent (emptyRunQueue(cap));
+#else
ASSERT(sched_state >= SCHED_INTERRUPTING);
+#endif
}
#endif
@@ -622,6 +636,9 @@ schedulePreLoop(void)
static void
scheduleFindWork (Capability **pcap)
{
+#if defined(mingw32_HOST_OS) && !defined(THREADED_RTS)
+ queueIOThread();
+#endif
scheduleStartSignalHandlers(*pcap);
scheduleProcessInbox(pcap);
@@ -928,6 +945,7 @@ scheduleDetectDeadlock (Capability **pcap, Task *task)
*/
if (recent_activity != ACTIVITY_INACTIVE) return;
#endif
+ if (task->incall->tso && task->incall->tso->why_blocked == BlockedOnIOCompletion) return;
debugTrace(DEBUG_sched, "deadlocked, forcing major GC...");
@@ -980,6 +998,10 @@ scheduleDetectDeadlock (Capability **pcap, Task *task)
throwToSingleThreaded(cap, task->incall->tso,
(StgClosure *)nonTermination_closure);
return;
+ case BlockedOnIOCompletion:
+ /* We're blocked waiting for an external I/O call, let's just
+ chill for a bit. */
+ return;
default:
barf("deadlock: main thread blocked in a strange way");
}
@@ -2555,6 +2577,14 @@ scheduleThread(Capability *cap, StgTSO *tso)
}
void
+scheduleThreadNow(Capability *cap, StgTSO *tso)
+{
+ // The thread goes at the *beginning* of the run-queue,
+ // in order to execute it as soon as possible.
+ pushOnRunQueue(cap,tso);
+}
+
+void
scheduleThreadOn(Capability *cap, StgWord cpu USED_IF_THREADS, StgTSO *tso)
{
tso->flags |= TSO_LOCKED; // we requested explicit affinity; don't
@@ -2676,9 +2706,10 @@ initScheduler(void)
sched_state = SCHED_RUNNING;
recent_activity = ACTIVITY_YES;
-#if defined(THREADED_RTS)
+
/* Initialise the mutex and condition variables used by
* the scheduler. */
+#if defined(THREADED_RTS)
initMutex(&sched_mutex);
initMutex(&sync_finished_mutex);
initCondition(&sync_finished_cond);
@@ -3164,6 +3195,11 @@ resurrectThreads (StgTSO *threads)
throwToSingleThreaded(cap, tso,
(StgClosure *)blockedIndefinitelyOnSTM_closure);
break;
+ case BlockedOnIOCompletion:
+ /* I/O Ports may not be reachable by the GC as they may be getting
+ * notified by the RTS. As such this call should be treated as if
+ * it is masking the exception. */
+ continue;
case NotBlocked:
/* This might happen if the thread was blocked on a black hole
* belonging to a thread that we've just woken up (raiseAsync
diff --git a/rts/Schedule.h b/rts/Schedule.h
index 2d8d813464..a550a6763a 100644
--- a/rts/Schedule.h
+++ b/rts/Schedule.h
@@ -27,6 +27,10 @@ void markScheduler (evac_fn evac, void *user);
// Place a new thread on the run queue of the current Capability
void scheduleThread (Capability *cap, StgTSO *tso);
+// Place a new thread on the run queue of the current Capability
+// at the front of the queue.
+void scheduleThreadNow (Capability *cap, StgTSO *tso);
+
// Place a new thread on the run queue of a specified Capability
// (cap is the currently owned Capability, cpu is the number of
// the desired Capability).
@@ -176,7 +180,7 @@ pushOnRunQueue (Capability *cap, StgTSO *tso)
INLINE_HEADER StgTSO *
popRunQueue (Capability *cap)
{
- ASSERT(cap->n_run_queue != 0);
+ ASSERT(cap->n_run_queue > 0);
StgTSO *t = cap->run_queue_hd;
ASSERT(t != END_TSO_QUEUE);
cap->run_queue_hd = t->_link;
diff --git a/rts/StgMiscClosures.cmm b/rts/StgMiscClosures.cmm
index 4293dfb787..7a8f20dded 100644
--- a/rts/StgMiscClosures.cmm
+++ b/rts/StgMiscClosures.cmm
@@ -15,8 +15,8 @@
import pthread_mutex_lock;
import ghczmprim_GHCziTypes_Czh_info;
import ghczmprim_GHCziTypes_Izh_info;
-import EnterCriticalSection;
-import LeaveCriticalSection;
+import AcquireSRWLockExclusive;
+import ReleaseSRWLockExclusive;
/* ----------------------------------------------------------------------------
Stack underflow
diff --git a/rts/Threads.c b/rts/Threads.c
index 22d58bb48b..54c703963e 100644
--- a/rts/Threads.c
+++ b/rts/Threads.c
@@ -288,6 +288,7 @@ tryWakeupThread (Capability *cap, StgTSO *tso)
switch (tso->why_blocked)
{
+ case BlockedOnIOCompletion:
case BlockedOnMVar:
case BlockedOnMVarRead:
{
@@ -868,12 +869,16 @@ printThreadBlockage(StgTSO *tso)
debugBelch("is blocked until %ld", (long)(tso->block_info.target));
break;
#endif
+ break;
case BlockedOnMVar:
debugBelch("is blocked on an MVar @ %p", tso->block_info.closure);
break;
case BlockedOnMVarRead:
debugBelch("is blocked on atomic MVar read @ %p", tso->block_info.closure);
break;
+ case BlockedOnIOCompletion:
+ debugBelch("is blocked on I/O Completion port @ %p", tso->block_info.closure);
+ break;
case BlockedOnBlackHole:
debugBelch("is blocked on a black hole %p",
((StgBlockingQueue*)tso->block_info.bh->bh));
diff --git a/rts/Trace.c b/rts/Trace.c
index b35be3c1e7..6d77cc1254 100644
--- a/rts/Trace.c
+++ b/rts/Trace.c
@@ -22,6 +22,7 @@
#include "Threads.h"
#include "Printer.h"
#include "RtsFlags.h"
+#include "ThreadLabels.h"
#if defined(HAVE_UNISTD_H)
#include <unistd.h>
@@ -164,6 +165,7 @@ static char *thread_stop_reasons[] = {
[6 + BlockedOnSTM] = "blocked on STM",
[6 + BlockedOnDoProc] = "blocked on asyncDoProc",
[6 + BlockedOnCCall] = "blocked on a foreign call",
+ [6 + BlockedOnIOCompletion] = "blocked on I/O Completion port",
[6 + BlockedOnCCall_Interruptible] = "blocked on a foreign call (interruptible)",
[6 + BlockedOnMsgThrowTo] = "blocked on throwTo",
[6 + ThreadMigrating] = "migrating"
@@ -179,45 +181,50 @@ static void traceSchedEvent_stderr (Capability *cap, EventTypeNum tag,
ACQUIRE_LOCK(&trace_utx);
tracePreface();
+ char *threadLabel = (char *)lookupThreadLabel(tso->id);
+ if(!threadLabel)
+ {
+ threadLabel = "";
+ }
switch (tag) {
case EVENT_CREATE_THREAD: // (cap, thread)
- debugBelch("cap %d: created thread %" FMT_Word "\n",
- cap->no, (W_)tso->id);
+ debugBelch("cap %d: created thread %" FMT_Word "[\"%s\"]" "\n",
+ cap->no, (W_)tso->id, threadLabel);
break;
case EVENT_RUN_THREAD: // (cap, thread)
- debugBelch("cap %d: running thread %" FMT_Word " (%s)\n",
- cap->no, (W_)tso->id, what_next_strs[tso->what_next]);
+ debugBelch("cap %d: running thread %" FMT_Word "[\"%s\"]"" (%s)\n",
+ cap->no, (W_)tso->id, threadLabel, what_next_strs[tso->what_next]);
break;
case EVENT_THREAD_RUNNABLE: // (cap, thread)
- debugBelch("cap %d: thread %" FMT_Word " appended to run queue\n",
- cap->no, (W_)tso->id);
+ debugBelch("cap %d: thread %" FMT_Word "[\"%s\"]"" appended to run queue\n",
+ cap->no, (W_)tso->id, threadLabel);
break;
case EVENT_MIGRATE_THREAD: // (cap, thread, new_cap)
- debugBelch("cap %d: thread %" FMT_Word " migrating to cap %d\n",
- cap->no, (W_)tso->id, (int)info1);
+ debugBelch("cap %d: thread %" FMT_Word "[\"%s\"]" " migrating to cap %d\n",
+ cap->no, (W_)tso->id, threadLabel, (int)info1);
break;
case EVENT_THREAD_WAKEUP: // (cap, thread, info1_cap)
- debugBelch("cap %d: waking up thread %" FMT_Word " on cap %d\n",
- cap->no, (W_)tso->id, (int)info1);
+ debugBelch("cap %d: waking up thread %" FMT_Word "[\"%s\"]" " on cap %d\n",
+ cap->no, (W_)tso->id, threadLabel, (int)info1);
break;
case EVENT_STOP_THREAD: // (cap, thread, status)
if (info1 == 6 + BlockedOnBlackHole) {
- debugBelch("cap %d: thread %" FMT_Word " stopped (blocked on black hole owned by thread %lu)\n",
- cap->no, (W_)tso->id, (long)info2);
+ debugBelch("cap %d: thread %" FMT_Word "[\"%s\"]" " stopped (blocked on black hole owned by thread %lu)\n",
+ cap->no, (W_)tso->id, threadLabel, (long)info2);
} else if (info1 == StackOverflow) {
- debugBelch("cap %d: thead %" FMT_Word
+ debugBelch("cap %d: thead %" FMT_Word "[\"%s\"]"
" stopped (stack overflow, size %lu)\n",
- cap->no, (W_)tso->id, (long)info2);
+ cap->no, (W_)tso->id, threadLabel, (long)info2);
} else {
- debugBelch("cap %d: thread %" FMT_Word " stopped (%s)\n",
- cap->no, (W_)tso->id, thread_stop_reasons[info1]);
+ debugBelch("cap %d: thread %" FMT_Word "[\"%s\"]" " stopped (%s)\n",
+ cap->no, (W_)tso->id, threadLabel, thread_stop_reasons[info1]);
}
break;
default:
- debugBelch("cap %d: thread %" FMT_Word ": event %d\n\n",
- cap->no, (W_)tso->id, tag);
+ debugBelch("cap %d: thread %" FMT_Word "[\"%s\"]" ": event %d\n\n",
+ cap->no, (W_)tso->id, threadLabel, tag);
break;
}
diff --git a/rts/TraverseHeap.c b/rts/TraverseHeap.c
index 8bf58c11ee..636737aa0f 100644
--- a/rts/TraverseHeap.c
+++ b/rts/TraverseHeap.c
@@ -1250,6 +1250,7 @@ inner_loop:
traversePushClosure(ts, (StgClosure *) tso->trec, c, child_data);
if ( tso->why_blocked == BlockedOnMVar
|| tso->why_blocked == BlockedOnMVarRead
+ || tso->why_blocked == BlockedOnIOCompletion
|| tso->why_blocked == BlockedOnBlackHole
|| tso->why_blocked == BlockedOnMsgThrowTo
) {
diff --git a/rts/eventlog/EventLog.c b/rts/eventlog/EventLog.c
index e3597cd73c..11e8a5e0b6 100644
--- a/rts/eventlog/EventLog.c
+++ b/rts/eventlog/EventLog.c
@@ -1484,6 +1484,7 @@ void printAndClearEventBuf (EventsBuf *ebuf)
"printAndClearEventLog: could not flush event log\n"
);
resetEventsBuf(ebuf);
+ flushEventLog();
return;
}
diff --git a/rts/eventlog/EventLogWriter.c b/rts/eventlog/EventLogWriter.c
index 5387f932eb..047c211db4 100644
--- a/rts/eventlog/EventLogWriter.c
+++ b/rts/eventlog/EventLogWriter.c
@@ -122,6 +122,8 @@ writeEventLogFile(void *eventlog, size_t eventlog_size)
begin += written;
}
release_event_log_lock();
+
+ flushEventLogFile ();
return true;
}
diff --git a/rts/ghc.mk b/rts/ghc.mk
index 7e7747b485..32c49d9099 100644
--- a/rts/ghc.mk
+++ b/rts/ghc.mk
@@ -22,7 +22,7 @@ rts_VERSION = 1.0
# Minimum supported Windows version.
# These numbers can be found at:
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa383745(v=vs.85).aspx
-# If we're compiling on windows, enforce that we only support Vista SP1+
+# If we're compiling on windows, enforce that we only support Windows 7+
# Adding this here means it doesn't have to be done in individual .c files
# and also centralizes the versioning.
rts_WINVER = 0x06010000
@@ -205,7 +205,7 @@ rts_dist_$1_CC_OPTS += -DRtsWay=\"rts_$1\"
# Adding this here means it doesn't have to be done in individual .c files
# and also centralizes the versioning.
ifeq "$$(TargetOS_CPP)" "mingw32"
-rts_dist_$1_CC_OPTS += -DWINVER=$(rts_WINVER)
+rts_dist_$1_CC_OPTS += -D_WIN32_WINNT=$(rts_WINVER)
endif
ifneq "$$(UseSystemLibFFI)" "YES"
diff --git a/rts/linker/PEi386.c b/rts/linker/PEi386.c
index f494eee567..6585c36bf0 100644
--- a/rts/linker/PEi386.c
+++ b/rts/linker/PEi386.c
@@ -2060,7 +2060,6 @@ SymbolAddr *lookupSymbol_PEi386(SymbolName *lbl)
sym = lookupSymbolInDLLs(lbl);
return sym; // might be NULL if not found
} else {
-#if defined(mingw32_HOST_OS)
// If Windows, perform initialization of uninitialized
// Symbols from the C runtime which was loaded above.
// We do this on lookup to prevent the hit when
@@ -2093,7 +2092,6 @@ SymbolAddr *lookupSymbol_PEi386(SymbolName *lbl)
clearImportSymbol (pinfo->owner, lbl);
return pinfo->value;
}
-#endif
return loadSymbol(lbl, pinfo);
}
}
diff --git a/rts/package.conf.in b/rts/package.conf.in
index 45866a1ecd..8b7390865b 100644
--- a/rts/package.conf.in
+++ b/rts/package.conf.in
@@ -97,6 +97,7 @@ ld-options:
, "-Wl,-u,_base_GHCziIOziException_cannotCompactFunction_closure"
, "-Wl,-u,_base_GHCziIOziException_cannotCompactPinned_closure"
, "-Wl,-u,_base_GHCziIOziException_cannotCompactMutable_closure"
+ , "-Wl,-u,_base_GHCziIOPort_doubleReadException_closure"
, "-Wl,-u,_base_ControlziExceptionziBase_nonTermination_closure"
, "-Wl,-u,_base_ControlziExceptionziBase_nestedAtomically_closure"
, "-Wl,-u,_base_GHCziEventziThread_blockedOnBadFD_closure"
@@ -105,8 +106,12 @@ ld-options:
, "-Wl,-u,_base_GHCziExceptionziType_overflowException_closure"
, "-Wl,-u,_base_GHCziConcziSync_runSparks_closure"
, "-Wl,-u,_base_GHCziConcziIO_ensureIOManagerIsRunning_closure"
+ , "-Wl,-u,_base_GHCziConcziIO_interruptIOManager_closure"
, "-Wl,-u,_base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure"
, "-Wl,-u,_base_GHCziConcziSignal_runHandlersPtr_closure"
+#if defined(mingw32_HOST_OS)
+ , "-Wl,-u,_base_GHCziEventziWindows_processRemoteCompletion_closure"
+#endif
, "-Wl,-u,_base_GHCziTopHandler_flushStdHandles_closure"
, "-Wl,-u,_base_GHCziTopHandler_runMainIO_closure"
, "-Wl,-u,_ghczmprim_GHCziTypes_Czh_con_info"
@@ -206,6 +211,7 @@ ld-options:
, "-Wl,-u,base_GHCziIOziException_cannotCompactFunction_closure"
, "-Wl,-u,base_GHCziIOziException_cannotCompactPinned_closure"
, "-Wl,-u,base_GHCziIOziException_cannotCompactMutable_closure"
+ , "-Wl,-u,base_GHCziIOPort_doubleReadException_closure"
, "-Wl,-u,base_ControlziExceptionziBase_nonTermination_closure"
, "-Wl,-u,base_ControlziExceptionziBase_nestedAtomically_closure"
, "-Wl,-u,base_GHCziEventziThread_blockedOnBadFD_closure"
@@ -214,8 +220,12 @@ ld-options:
, "-Wl,-u,base_GHCziExceptionziType_overflowException_closure"
, "-Wl,-u,base_GHCziConcziSync_runSparks_closure"
, "-Wl,-u,base_GHCziConcziIO_ensureIOManagerIsRunning_closure"
+ , "-Wl,-u,base_GHCziConcziIO_interruptIOManager_closure"
, "-Wl,-u,base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure"
, "-Wl,-u,base_GHCziConcziSignal_runHandlersPtr_closure"
+#if defined(mingw32_HOST_OS)
+ , "-Wl,-u,base_GHCziEventziWindows_processRemoteCompletion_closure"
+#endif
, "-Wl,-u,base_GHCziTopHandler_flushStdHandles_closure"
, "-Wl,-u,base_GHCziTopHandler_runMainIO_closure"
, "-Wl,-u,ghczmprim_GHCziTypes_Czh_con_info"
diff --git a/rts/rts.cabal.in b/rts/rts.cabal.in
index 7895ae26f5..1a1eb30611 100644
--- a/rts/rts.cabal.in
+++ b/rts/rts.cabal.in
@@ -97,6 +97,15 @@ library
dbghelp
-- for process information
psapi
+ -- TODO: Hadrian will use this cabal file, so drop WINVER from Hadrian's configs.
+ -- Minimum supported Windows version.
+ -- These numbers can be found at:
+ -- https://msdn.microsoft.com/en-us/library/windows/desktop/aa383745(v=vs.85).aspx
+ -- If we're compiling on windows, enforce that we only support Windows 7+
+ -- Adding this here means it doesn't have to be done in individual .c files
+ -- and also centralizes the versioning.
+ cpp-options: -D_WIN32_WINNT=0x06010000
+ cc-options: -D_WIN32_WINNT=0x06010000
if flag(need-pthread)
-- for pthread_getthreadid_np, pthread_create, ...
extra-libraries: pthread
@@ -218,11 +227,13 @@ library
"-Wl,-u,_base_GHCziIOziException_cannotCompactFunction_closure"
"-Wl,-u,_base_GHCziIOziException_cannotCompactPinned_closure"
"-Wl,-u,_base_GHCziIOziException_cannotCompactMutable_closure"
+ "-Wl,-u,_base_GHCziIOPort_doubleReadException_closure"
"-Wl,-u,_base_ControlziExceptionziBase_nonTermination_closure"
"-Wl,-u,_base_ControlziExceptionziBase_nestedAtomically_closure"
"-Wl,-u,_base_GHCziEventziThread_blockedOnBadFD_closure"
"-Wl,-u,_base_GHCziConcziSync_runSparks_closure"
"-Wl,-u,_base_GHCziConcziIO_ensureIOManagerIsRunning_closure"
+ "-Wl,-u,_base_GHCziConcziIO_interruptIOManager_closure"
"-Wl,-u,_base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure"
"-Wl,-u,_base_GHCziConcziSignal_runHandlersPtr_closure"
"-Wl,-u,_base_GHCziTopHandler_flushStdHandles_closure"
@@ -297,11 +308,13 @@ library
"-Wl,-u,base_GHCziIOziException_cannotCompactFunction_closure"
"-Wl,-u,base_GHCziIOziException_cannotCompactPinned_closure"
"-Wl,-u,base_GHCziIOziException_cannotCompactMutable_closure"
+ "-Wl,-u,base_GHCziIOPort_doubleReadException_closure"
"-Wl,-u,base_ControlziExceptionziBase_nonTermination_closure"
"-Wl,-u,base_ControlziExceptionziBase_nestedAtomically_closure"
"-Wl,-u,base_GHCziEventziThread_blockedOnBadFD_closure"
"-Wl,-u,base_GHCziConcziSync_runSparks_closure"
"-Wl,-u,base_GHCziConcziIO_ensureIOManagerIsRunning_closure"
+ "-Wl,-u,base_GHCziConcziIO_interruptIOManager_closure"
"-Wl,-u,base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure"
"-Wl,-u,base_GHCziConcziSignal_runHandlersPtr_closure"
"-Wl,-u,base_GHCziTopHandler_flushStdHandles_closure"
@@ -358,6 +371,17 @@ library
-- This symbol is useful in gdb, but not referred to anywhere,
-- so we need to force it to be included in the binary.
ld-options: "-Wl,-u,findPtr"
+ -- This symbol is useful in gdb, but not referred to anywhere,
+ -- so we need to force it to be included in the binary.
+ "-Wl,-u,findPtr"
+
+ if os(windows)
+ if flag(leading-underscore)
+ ld-options:
+ "-Wl,-u,_base_GHCziEventziWindows_processRemoteCompletion_closure"
+ else
+ ld-options:
+ "-Wl,-u,base_GHCziEventziWindows_processRemoteCompletion_closure"
if os(osx)
ld-options: "-Wl,-search_paths_first"
@@ -489,7 +513,8 @@ library
-- I wish we had wildcards..., this would be:
-- *.c hooks/**/*.c sm/**/*.c eventlog/**/*.c linker/**/*.c
if os(windows)
- c-sources: win32/AsyncIO.c
+ c-sources: win32/AsyncMIO.c
+ win32/AsyncWinIO.c
win32/AwaitEvent.c
win32/ConsoleHandler.c
win32/GetEnv.c
diff --git a/rts/sm/Compact.c b/rts/sm/Compact.c
index 5031c535a1..b1250b77e0 100644
--- a/rts/sm/Compact.c
+++ b/rts/sm/Compact.c
@@ -461,6 +461,7 @@ thread_TSO (StgTSO *tso)
|| tso->why_blocked == BlockedOnMVarRead
|| tso->why_blocked == BlockedOnBlackHole
|| tso->why_blocked == BlockedOnMsgThrowTo
+ || tso->why_blocked == BlockedOnIOCompletion
|| tso->why_blocked == NotBlocked
) {
thread_(&tso->block_info.closure);
diff --git a/rts/sm/Sanity.c b/rts/sm/Sanity.c
index ea64483418..c09e28c0aa 100644
--- a/rts/sm/Sanity.c
+++ b/rts/sm/Sanity.c
@@ -32,11 +32,13 @@
#include "sm/NonMoving.h"
#include "sm/NonMovingMark.h"
#include "Profiling.h" // prof_arena
+#include "HeapAlloc.h"
/* -----------------------------------------------------------------------------
Forward decls.
-------------------------------------------------------------------------- */
+int isHeapAlloced ( StgPtr p);
static void checkSmallBitmap ( StgPtr payload, StgWord bitmap, uint32_t );
static void checkLargeBitmap ( StgPtr payload, StgLargeBitmap*, uint32_t );
static void checkClosureShallow ( const StgClosure * );
@@ -46,6 +48,13 @@ static W_ countNonMovingSegments ( struct NonmovingSegment *segs );
static W_ countNonMovingHeap ( struct NonmovingHeap *heap );
/* -----------------------------------------------------------------------------
+ Debugging utility.
+ -------------------------------------------------------------------------- */
+
+// the HEAP_ALLOCED macro in function form. Useful for use in GDB or similar.
+int isHeapAlloced ( StgPtr p) { return HEAP_ALLOCED(p); }
+
+/* -----------------------------------------------------------------------------
Check stack sanity
-------------------------------------------------------------------------- */
@@ -618,6 +627,7 @@ checkTSO(StgTSO *tso)
|| tso->why_blocked == BlockedOnMVarRead
|| tso->why_blocked == BlockedOnBlackHole
|| tso->why_blocked == BlockedOnMsgThrowTo
+ || tso->why_blocked == BlockedOnIOCompletion
|| tso->why_blocked == NotBlocked
) {
ASSERT(LOOKS_LIKE_CLOSURE_PTR(tso->block_info.closure));
diff --git a/rts/sm/Scav.c b/rts/sm/Scav.c
index 501d958aae..dd9a96adf8 100644
--- a/rts/sm/Scav.c
+++ b/rts/sm/Scav.c
@@ -129,6 +129,7 @@ scavengeTSO (StgTSO *tso)
|| tso->why_blocked == BlockedOnMVarRead
|| tso->why_blocked == BlockedOnBlackHole
|| tso->why_blocked == BlockedOnMsgThrowTo
+ || tso->why_blocked == BlockedOnIOCompletion
|| tso->why_blocked == NotBlocked
) {
evacuate(&tso->block_info.closure);
diff --git a/rts/win32/AsyncIO.c b/rts/win32/AsyncMIO.c
index 49da79d2dd..5d55f79d74 100644
--- a/rts/win32/AsyncIO.c
+++ b/rts/win32/AsyncMIO.c
@@ -3,6 +3,9 @@
* Integrating Win32 asynchronous I/O with the GHC RTS.
*
* (c) sof, 2002-2003.
+ *
+ * NOTE: This is the MIO manager, only used for --io-manager=posix.
+ * For the WINIO manager see base in the GHC.Event modules.
*/
#if !defined(THREADED_RTS)
@@ -13,7 +16,7 @@
#include <stdio.h>
#include "Schedule.h"
#include "Capability.h"
-#include "win32/AsyncIO.h"
+#include "win32/AsyncMIO.h"
#include "win32/IOManager.h"
/*
@@ -46,7 +49,7 @@ typedef struct CompletedReq {
#define MAX_REQUESTS 200
-static CRITICAL_SECTION queue_lock;
+static Mutex queue_lock;
static HANDLE completed_req_event = INVALID_HANDLE_VALUE;
static HANDLE abandon_req_wait = INVALID_HANDLE_VALUE;
static HANDLE wait_handles[2];
@@ -67,17 +70,16 @@ onIOComplete(unsigned int reqID,
dwRes = WaitForSingleObject(completed_table_sema, INFINITE);
switch (dwRes) {
case WAIT_OBJECT_0:
- case WAIT_ABANDONED:
break;
default:
/* Not likely */
fprintf(stderr,
- "onIOComplete: failed to grab table semaphore (res=%d, err=%d), "
- "dropping request 0x%x\n", reqID, dwRes, GetLastError());
+ "onIOComplete: failed to grab table semaphore (res=%d, err=%ld), "
+ "dropping request 0x%lx\n", reqID, dwRes, GetLastError());
fflush(stderr);
return;
}
- EnterCriticalSection(&queue_lock);
+ OS_ACQUIRE_LOCK(&queue_lock);
if (completed_hw == MAX_REQUESTS) {
/* Shouldn't happen */
fprintf(stderr, "onIOComplete: ERROR -- Request table overflow (%d); "
@@ -102,7 +104,7 @@ onIOComplete(unsigned int reqID,
SetEvent(completed_req_event);
}
}
- LeaveCriticalSection(&queue_lock);
+ OS_RELEASE_LOCK(&queue_lock);
}
unsigned int
@@ -112,9 +114,9 @@ addIORequest(int fd,
HsInt len,
char* buf)
{
- EnterCriticalSection(&queue_lock);
+ OS_ACQUIRE_LOCK(&queue_lock);
issued_reqs++;
- LeaveCriticalSection(&queue_lock);
+ OS_RELEASE_LOCK(&queue_lock);
#if 0
fprintf(stderr, "addIOReq: %d %d %d\n", fd, forWriting, len);
fflush(stderr);
@@ -125,9 +127,9 @@ addIORequest(int fd,
unsigned int
addDelayRequest(HsInt usecs)
{
- EnterCriticalSection(&queue_lock);
+ OS_ACQUIRE_LOCK(&queue_lock);
issued_reqs++;
- LeaveCriticalSection(&queue_lock);
+ OS_RELEASE_LOCK(&queue_lock);
#if 0
fprintf(stderr, "addDelayReq: %d\n", usecs); fflush(stderr);
#endif
@@ -137,9 +139,9 @@ addDelayRequest(HsInt usecs)
unsigned int
addDoProcRequest(void* proc, void* param)
{
- EnterCriticalSection(&queue_lock);
+ OS_ACQUIRE_LOCK(&queue_lock);
issued_reqs++;
- LeaveCriticalSection(&queue_lock);
+ OS_RELEASE_LOCK(&queue_lock);
#if 0
fprintf(stderr, "addProcReq: %p %p\n", proc, param); fflush(stderr);
#endif
@@ -153,7 +155,7 @@ startupAsyncIO()
if (!StartIOManager()) {
return 0;
}
- InitializeCriticalSection(&queue_lock);
+ OS_INIT_LOCK(&queue_lock);
/* Create a pair of events:
*
* - completed_req_event -- signals the deposit of request result;
@@ -197,7 +199,7 @@ shutdownAsyncIO(bool wait_threads)
CloseHandle(completed_table_sema);
completed_table_sema = NULL;
}
- DeleteCriticalSection(&queue_lock);
+ OS_CLOSE_LOCK(&queue_lock);
}
/*
@@ -228,7 +230,7 @@ start:
issued_reqs, completed_hw, wait);
fflush(stderr);
#endif
- EnterCriticalSection(&queue_lock);
+ OS_ACQUIRE_LOCK(&queue_lock);
// Nothing immediately available & we won't wait
if ((!wait && completed_hw == 0)
#if 0
@@ -237,12 +239,12 @@ start:
(issued_reqs == 0 && completed_hw == 0)
#endif
) {
- LeaveCriticalSection(&queue_lock);
+ OS_RELEASE_LOCK(&queue_lock);
return 0;
}
if (completed_hw == 0) {
// empty table, drop lock and wait
- LeaveCriticalSection(&queue_lock);
+ OS_RELEASE_LOCK(&queue_lock);
if ( wait && sched_state == SCHED_RUNNING ) {
DWORD dwRes = WaitForMultipleObjects(2, wait_handles,
FALSE, INFINITE);
@@ -344,7 +346,7 @@ start:
}
completed_hw = 0;
ResetEvent(completed_req_event);
- LeaveCriticalSection(&queue_lock);
+ OS_RELEASE_LOCK(&queue_lock);
return 1;
}
#endif /* !THREADED_RTS */
@@ -373,6 +375,7 @@ abandonRequestWait( void )
* properly serviced (see resetAbandon() below). --SDM 18/12/2003
*/
SetEvent(abandon_req_wait);
+ interruptIOManagerEvent ();
}
void
diff --git a/rts/win32/AsyncIO.h b/rts/win32/AsyncMIO.h
index 75d0e0460d..63d8f34827 100644
--- a/rts/win32/AsyncIO.h
+++ b/rts/win32/AsyncMIO.h
@@ -3,10 +3,15 @@
* Integrating Win32 asynchronous I/O with the GHC RTS.
*
* (c) sof, 2002-2003.
+ *
+ * NOTE: This is the MIO manager, only used for --io-manager=posix.
+ * For the WINIO manager see AsyncWinIO.h.
*/
#pragma once
+#include "Rts.h"
+
extern unsigned int
addIORequest(int fd,
bool forWriting,
diff --git a/rts/win32/AsyncWinIO.c b/rts/win32/AsyncWinIO.c
new file mode 100644
index 0000000000..2af806b1c8
--- /dev/null
+++ b/rts/win32/AsyncWinIO.c
@@ -0,0 +1,545 @@
+/* AsyncIO.h
+ *
+ * Integrating Win32 asynchronous IOCP with the GHC RTS.
+ *
+ * (c) Tamar Christina, 2018 - 2019
+ *
+ * NOTE: This is the WinIO manager, only used for --io-manager=native.
+ * For the MIO manager see AsyncIO.h.
+ */
+
+#include "Rts.h"
+#include <rts/IOManager.h>
+#include "AsyncWinIO.h"
+#include "Prelude.h"
+#include "Capability.h"
+#include "Schedule.h"
+#include "Rts.h"
+#include "ThreadLabels.h"
+
+#include <stdbool.h>
+#include <windows.h>
+#include <stdint.h>
+#include <stdio.h>
+
+/* Note [Non-Threaded WINIO design]
+ Compared to Async MIO, Async WINIO does all of the heavy processing at the
+ Haskell side of things. The same code as the threaded WINIO is re-used for
+ the Non-threaded version. Of course since we are in a non-threaded rts we
+ can't block on foreign calls without hanging the application.
+
+ This file thus serves as a back-end service that continuously reads pending
+ events from the given I/O completion port and notifies the Haskell I/O manager
+ of work that has been completed. This does incur a slight cost in that the
+ rts has to actually schedule the Haskell thread to do the work, however this
+ shouldn't be a problem for performance.
+
+ It is however a problem for the workload buffer we use as we are not allowed
+ to service new requests until the old ones have actually been read and
+ processes by the Haskell I/O side.
+
+ To account for this the I/O manager works in two stages.
+
+ 1) Like the threaded version, any long wait we do, we prefer to do it in an
+ alterable state so that we can respond immediately to new requests. Note
+ that once we know which completion port handle we are bound to we no longer
+ need the Haskell side to tell us of new work. We can simply handle any new
+ work pre-emptively.
+
+ 2) We block in a non-alertable state whenever
+ a) The Completion port handle is yet unknown.
+ b) The RTS requested the I/O manager be shutdown via an event --TODO: Remove?
+ c) We are waiting on the Haskell I/O manager to service a previous
+ request as to allow us to re-use the buffer.
+
+ We would ideally like to spend as little time as possible in 2).
+
+ The workflow for this I/O manager is as follows:
+
+ +------------------------+
+ | Worker thread creation |
+ +-----------+------------+
+ |
+ |
+ +-------------v---------------+
+ +------> Block in unalertable wait +-----+
+ | +-------------+---------------+ |
+ | | |
+ | | |
+ | +-----------v------------+ |
+ | |Init by Haskell I/O call| | If init already
+ wait for I/O | +-----------+------------+ |
+ processing in | | |
+ Haskell side | | |
+ | +--------v---------+ |
+ Also process | | alertable wait <-----------+
+ events like | +--------+---------+
+ shutdown | |
+ | |
+ | +-------v--------+
+ +------------+process response|
+ +----------------+
+
+ The non-alertable wait itself is split into two phases during regular
+ execution:
+ 1.) canQueueIOThread == true
+ 2.) canQueueIOThread == false, outstanding_service_requests == true
+
+ `notifyScheduler` puts us into the first phase. During which we wait
+ for the scheduler to call `queueIOThread`.
+ During the second phase we wait for the queued haskell thread to run.
+
+ The alertable wait is done by calling into GetQueuedCompletionStatusEx.
+ After we return from the call we notify the haskell side of new events
+ via `notifyScheduler`.
+
+ notifyScheduler set's flags to indicate to the scheduler that new IO work
+ needs to be processed. At this point the next call to `schedule` will
+ check the flag and schedule execution of a haskell thread executing
+ processRemoteCompletion.
+
+ `processRemoteCompletion` will process IO results invoking call backs and
+ processing timer events. Once done it resets `outstanding_service_requests`
+ and wakes up the IOManager thread. Which at this point becomes unblocked
+ and reenters the altertable wait state. This is done by calling into
+ registerAlterableWait.
+
+ As a design decision to keep this side as light as possible no bookkeeping
+ is done here to track requests. That is, this file has no way of knowing
+ of the remaining outstanding I/O requests, how many it actually completed
+ in the last call as that list may contain spurious events.
+
+ It works around this by having the Haskell side tell it how much work it
+ still has left to do.
+
+ Unlike the Threaded version we use a single worker thread to handle
+ completions and so it won't scale as well. But if high scalability is needed
+ then use the threaded runtime. This would have to become threadsafe
+ in order to use multiple threads, but this is non-trivial as the non-threaded
+ rts has no locks around any of the key parts.
+
+ See also Note [WINIO Manager design].
+
+
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Note [Notifying the RTS/Haskell of completed events]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ The C side runner can't directly create a haskell thread.
+ With the current API of the haskell runtime this would be terrible
+ unsound. In particular the GC assumes no heap objects are generated,
+ and no heap memory is requested while it is running.
+
+ To work around this the scheduler invokes queueIOThread which checks
+ if a (haskell) thread should be created to process IO requests.
+ Since we only use this code path in the non-threaded runtime this
+ ensures there is only one OS thread at a time making use of the haskell
+ heap.
+
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ Note [Non-Threaded IO Manager startup sequence]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Under the new IO Manager we run a bit of initialization under
+ hs_init(). The first call into actual IO manager code is a
+ invocation of startupAsyncWinIO();
+
+ There we initialize IO manager locale variables and.
+ * call ioManagerStart()
+ * Creat a thread to execute "runner"
+
+ We never truely shut down the IO Manager. While this means we
+ might block forever on the IOPort if the IO Manager is no longer
+ needed we consider this cheap compared to the complexity of
+ properly handling pausing and resuming of the manager.
+
+ */
+
+/* The IOCP Handle all I/O requests are associated with for this RTS. */
+static HANDLE completionPortHandle = INVALID_HANDLE_VALUE;
+/* Boolean controlling if the I/O manager is/should still be running. */
+static bool running = false;
+
+/* Boolean to indicate whether we have outstanding I/O requests that still need
+ to be processed by the I/O manager on the Haskell side.
+ Set by:
+ notifyScheduler (true)
+ registerAlertableWait (false)
+ Read by:
+ runner
+ */
+volatile bool outstanding_service_requests = false;
+/* Indicates wether we have hit one case where we serviced as much requests as
+ we could because the buffer got full. In such cases for the next requests
+ we expand the buffers so we have room to process requests in bigger
+ batches.
+ Set by:
+ runner
+ Read by:
+ registerAlertableWait
+*/
+static bool queue_full = false;
+
+/* Timeout to use for the next alertable wait. INFINITE means never timeout.
+ Also see note [WINIO Timer management]. */
+static DWORD timeout = INFINITE;
+
+static HANDLE workerThread = NULL;
+static DWORD workerThreadId = 0;
+
+/* Synchronization mutex for modifying the above state variables in a thread
+ safe way. */
+static SRWLOCK wio_runner_lock;
+
+/* Conditional variable to wake the I/O manager up from a non-alertable waiting
+ state. */
+static CONDITION_VARIABLE wakeEvent;
+/* Conditional variable to force the system (haskell) thread to wait for a request to
+ complete. */
+static CONDITION_VARIABLE threadIOWait;
+
+/* Number of callbacks to reserve slots for in ENTRIES. This is also the
+ total number of concurrent I/O requests we can handle in one go. */
+static uint32_t num_callbacks = 32;
+/* Buffer for I/O request information. */
+static OVERLAPPED_ENTRY *entries;
+/* Number of I/O calls verified to have completed in the last round by the
+ Haskell I/O Manager. */
+static uint32_t num_last_completed;
+
+/* Notify the Haskell side of this many new finished requests */
+static uint32_t num_notify;
+
+/* Indicates to the scheduler that new work is available for processing.
+ Set by:
+ runner
+ queueIOThread
+ Read by
+ queueIOThread
+*/
+static volatile bool canQueueIOThread;
+
+static void notifyScheduler(uint32_t num);
+
+static DWORD WINAPI runner (LPVOID lpParam);
+
+/* Create and initialize the non-threaded I/O manager.
+
+ Called just once from hs_init. */
+bool startupAsyncWinIO(void)
+{
+ ASSERT(!running);
+ running = true;
+ outstanding_service_requests = false;
+ completionPortHandle = INVALID_HANDLE_VALUE;
+
+ InitializeSRWLock (&wio_runner_lock);
+ InitializeConditionVariable (&wakeEvent);
+ InitializeConditionVariable (&threadIOWait);
+
+ entries = calloc (sizeof (OVERLAPPED_ENTRY), num_callbacks);
+
+ /* Start the I/O manager before creating the worker thread to prevent a busy
+ wait or spin-lock, this will call registerIOCPHandle allowing us to
+ skip the initial un-alertable wait. */
+ ioManagerStart ();
+
+ workerThread = CreateThread (NULL, 0, runner, NULL, 0, &workerThreadId);
+ if (!workerThread)
+ {
+ barf ("could not create I/O manager thread.");
+ return false;
+ }
+
+ return true;
+}
+
+/* Terminate the I/O manager, if WAIT_THREADS then the call will block until
+ all helper threads are finished. */
+void shutdownAsyncWinIO(bool wait_threads)
+{
+ if (workerThread != NULL)
+ {
+ if (wait_threads)
+ {
+ AcquireSRWLockExclusive (&wio_runner_lock);
+
+ running = false;
+ ioManagerWakeup ();
+ PostQueuedCompletionStatus (completionPortHandle, 0, 0, NULL);
+ WakeConditionVariable (&wakeEvent);
+ WakeConditionVariable (&threadIOWait);
+
+ ReleaseSRWLockExclusive (&wio_runner_lock);
+
+ /* Now wait for the thread to actually finish. */
+ WaitForSingleObject (workerThread, INFINITE);
+ }
+ completionPortHandle = INVALID_HANDLE_VALUE;
+ workerThread = NULL;
+ workerThreadId = 0;
+ free (entries);
+ entries = NULL;
+ }
+
+ /* Call back into the Haskell side to terminate things there too. */
+ ioManagerDie ();
+}
+
+/* Register the I/O completetion port handle PORT that the I/O manager will be
+ monitoring. All handles are expected to be associated with this handle. */
+void registerIOCPHandle (HANDLE port)
+{
+ AcquireSRWLockExclusive (&wio_runner_lock);
+
+ completionPortHandle = port;
+
+ ReleaseSRWLockExclusive (&wio_runner_lock);
+}
+
+/* Callback hook so the Haskell part of the I/O manager can notify this manager
+ that a request someone is waiting on was completed synchronously. This means
+ we need to wake up the scheduler as there is work to be done. */
+
+void completeSynchronousRequest (void)
+{
+ AcquireSRWLockExclusive (&wio_runner_lock);
+
+ WakeConditionVariable (&threadIOWait);
+
+ ReleaseSRWLockExclusive (&wio_runner_lock);
+}
+
+
+/* Register outstanding I/O requests that the I/O manager should handle.
+
+ This function will unblock the runner if it has been blocked in an
+ non-alertable wait. It might end an alertable wait as well but this
+ depends on the exact parameters provided.
+
+ The haskell side will call this to inform the runner either about new
+ I/O requests or to update the number of outstanding requests after
+ processing a bundle.
+
+ * has_timeout tells us if the mssec parameter is valid.
+ * MSSEC is the maximum amount of time in milliseconds that an alertable wait
+ should be done for before the haskell side requested to be notified of progress.
+ * NUM_REQ is the total overall number of outstanding I/O requests.
+ * pending_service indicates that there might be still a outstanding service
+ request queued and therefore we shouldn't unblock the runner quite yet.
+
+ `pending_service` is needed in case we cancel an IO operation. We don't want this
+ to result in two processRemoteCompletion threads being queued. As this is both harder
+ to reason about and bad for performance. So we only reset outstanding_service_requests
+ if no service is pending.
+
+ */
+
+void registerAlertableWait (bool has_timeout, DWORD mssec, uint64_t num_req, bool pending_service)
+{
+ ASSERT(completionPortHandle != INVALID_HANDLE_VALUE);
+ AcquireSRWLockExclusive (&wio_runner_lock);
+
+ bool interrupt = false;
+
+ if (num_req == 0 && !has_timeout) {
+ timeout = INFINITE;
+ }
+ else if(has_timeout) {
+ timeout = mssec;
+ }
+ outstanding_service_requests = pending_service;
+
+ //Resize queue if required
+ if (queue_full)
+ {
+ num_callbacks *= 2;
+ OVERLAPPED_ENTRY *new
+ = realloc (entries,
+ sizeof (OVERLAPPED_ENTRY) * num_callbacks);
+ if (new)
+ entries = new;
+ queue_full = false;
+ }
+
+ /* If the new timeout is earlier than the old one we have to reschedule the
+ wait. Do this by interrupting the current operation and setting the new
+ timeout, since it must be the shortest one in the queue. */
+ if (timeout > mssec)
+ {
+ timeout = mssec;
+ interrupt = true;
+ }
+
+ ReleaseSRWLockExclusive (&wio_runner_lock);
+
+ // Since we call registerAlertableWait only after
+ // processing I/O requests it's always desireable to wake
+ // up the runner here.
+ WakeConditionVariable (&wakeEvent);
+
+ if (interrupt) {
+ PostQueuedCompletionStatus (completionPortHandle, 0, 0, NULL);
+ }
+}
+
+/* Exported callback function that will be called by the RTS to collect the
+ finished overlapped entried belonging to the completed I/O requests. The
+ number of read entries will be returned in NUM.
+
+ NOTE: This function isn't thread safe, but is intended to be called only
+ when requested by the I/O manager via notifyScheduler. In
+ that context it is thread safe as we're guaranteeing that the I/O
+ manager is blocked waiting for the read to happen followed by a
+ registerAlertableWait call. */
+OVERLAPPED_ENTRY* getOverlappedEntries (uint32_t *num)
+{
+ *num = num_last_completed;
+ return entries;
+}
+
+
+/* Called by the scheduler when we have ran out of work to do and we have at
+ least one thread blocked on an I/O Port. When WAIT then if this function
+ returns you will have at least one action to service, though this may be a
+ wake-up action. */
+
+void awaitAsyncRequests (bool wait)
+{
+ if(queueIOThread()) {
+ return;
+ }
+ AcquireSRWLockExclusive (&wio_runner_lock);
+ /* We don't deal with spurious requests here, that's left up to AwaitEvent.c
+ because in principle we need to check if the capability work queue is now
+ not empty but we can't do that here. Also these locks don't guarantee
+ fairness, as such a request may have completed without us seeing a
+ timeslice in between. */
+ if (wait && outstanding_service_requests)
+ SleepConditionVariableSRW (&threadIOWait, &wio_runner_lock, INFINITE, 0);
+
+ ReleaseSRWLockExclusive (&wio_runner_lock);
+}
+
+
+
+/* Sets `canQueueIOThread` to indicate to the scheduler that it should
+ queue a new haskell thread to process IO events. */
+static void notifyScheduler(uint32_t num) {
+ AcquireSRWLockExclusive (&wio_runner_lock);
+ ASSERT(!canQueueIOThread);
+ canQueueIOThread = true;
+ num_notify = num;
+ WakeConditionVariable(&threadIOWait);
+ ReleaseSRWLockExclusive (&wio_runner_lock);
+}
+
+/* Queues a new haskell thread to process IO events
+ if there is work to do.
+
+ Returns true if a thread/work was queued.
+
+ Precond:
+ Not already waiting on service requests.
+ Postcond:
+ outstanding_service_requests = true
+ processRemoteCompletion queued.
+ IO runner thread blocked until processRemoteCompletion has run.
+ */
+bool queueIOThread()
+{
+ bool result = false;
+#if !defined(THREADED_RTS)
+ AcquireSRWLockExclusive (&wio_runner_lock);
+ if(canQueueIOThread)
+ {
+ ASSERT(!outstanding_service_requests);
+ num_last_completed = num_notify;
+ outstanding_service_requests = true;
+ canQueueIOThread = false;
+ Capability *cap = &MainCapability;
+ StgTSO * tso = createStrictIOThread (cap, RtsFlags.GcFlags.initialStkSize,
+ processRemoteCompletion_closure);
+ labelThread(cap, tso, "ProcessIOThread");
+
+ ASSERT(tso);
+ scheduleThreadNow (cap, tso);
+ result = true;
+ }
+ ReleaseSRWLockExclusive (&wio_runner_lock);
+#endif
+ return result;
+}
+
+/* Main thread runner for the non-threaded I/O Manager. */
+
+static DWORD WINAPI runner (LPVOID lpParam STG_UNUSED)
+{
+ /* The last event that was sent to the I/O manager. */
+ HsWord32 lastEvent = 0;
+ while (running)
+ {
+ AcquireSRWLockExclusive (&wio_runner_lock);
+
+ lastEvent = readIOManagerEvent ();
+ /* Non-alertable wait. While here we can't server any I/O requests so we
+ would ideally like to spent as little time here as possible. As such
+ there are only 3 reasons to enter this state:
+
+ 1) I/O manager hasn't been fully initialized yet.
+ 2) I/O manager was told to shutdown, instead of doing that we just
+ block indefinitely so we don't have to recreate the thread to start
+ back up.
+ 3) We are waiting for the RTS to service the last round of requests. */
+ while (completionPortHandle == INVALID_HANDLE_VALUE
+ || lastEvent == IO_MANAGER_DIE
+ || outstanding_service_requests
+ || canQueueIOThread)
+ {
+ // fprintf(stderr, "NonAlert sleep:(%x, %i, %i)\n",
+ // lastEvent, outstanding_service_requests, canQueueIOThread);
+ // fflush(stderr);
+ SleepConditionVariableSRW (&wakeEvent, &wio_runner_lock, INFINITE, 0);
+ HsWord32 nextEvent = readIOManagerEvent ();
+ lastEvent = nextEvent ? nextEvent : lastEvent;
+ }
+
+ ReleaseSRWLockExclusive (&wio_runner_lock);
+
+ ULONG num_removed = 0;
+ ZeroMemory (entries, sizeof (entries[0]) * num_callbacks);
+ if (GetQueuedCompletionStatusEx (completionPortHandle, entries,
+ num_callbacks, &num_removed, timeout,
+ false))
+ {
+ if (num_removed > 0)
+ {
+ queue_full = num_removed == num_callbacks;
+ }
+ }
+ else if (WAIT_TIMEOUT == GetLastError ())
+ {
+ num_removed = 0;
+ }
+ // We always queue a haskell thread upon returning from GetQueuedCompletionStatusEx.
+ // We only return from GetQueuedCompletionStatusEx if:
+ // * IO was processed, in which case we need to process the events.
+ // * A timer event was registered/timed out. We need the process expired timers
+ // and update the timeout.
+ // * We woke up spuriously, which is quite rare.
+ // This simplifies the logic in exchange for a *very* small chance of redundant
+ // haskell threads. A redundant thread would be queued if:
+ // * We wake up spuriously
+ // * All returned results have been canceled already.
+ // It's not realistic nor worthwhile to check for these edge cases so we don't.
+ notifyScheduler (num_removed);
+
+ AcquireSRWLockExclusive (&wio_runner_lock);
+
+ if (!running)
+ ExitThread (0);
+
+ ReleaseSRWLockExclusive (&wio_runner_lock);
+ }
+ return 0;
+}
diff --git a/rts/win32/AsyncWinIO.h b/rts/win32/AsyncWinIO.h
new file mode 100644
index 0000000000..3ddf5de77a
--- /dev/null
+++ b/rts/win32/AsyncWinIO.h
@@ -0,0 +1,25 @@
+/* AsyncIO.h
+ *
+ * Integrating Win32 asynchronous IOCP with the GHC RTS.
+ *
+ * (c) Tamar Christina, 2018
+ *
+ * NOTE: This is the WinIO manager, only used for --io-manager=native.
+ * For the MIO manager see AsyncIO.h.
+ */
+
+#pragma once
+
+#include "Rts.h"
+#include <stdbool.h>
+#include <windows.h>
+
+extern bool startupAsyncWinIO(void);
+extern void shutdownAsyncWinIO(bool wait_threads);
+extern void awaitAsyncRequests(bool wait);
+extern void registerIOCPHandle (HANDLE port);
+extern void registerAlertableWait (bool has_timeout, DWORD mssec, uint64_t num_req, bool service_pending);
+
+extern OVERLAPPED_ENTRY* getOverlappedEntries (uint32_t *num);
+extern void completeSynchronousRequest (void);
+extern bool queueIOThread(void);
diff --git a/rts/win32/AwaitEvent.c b/rts/win32/AwaitEvent.c
index b639121c87..6a621d6ef5 100644
--- a/rts/win32/AwaitEvent.c
+++ b/rts/win32/AwaitEvent.c
@@ -14,15 +14,18 @@
*
*/
#include "Rts.h"
+#include "RtsFlags.h"
#include "Schedule.h"
#include "AwaitEvent.h"
#include <windows.h>
-#include "win32/AsyncIO.h"
+#include "win32/AsyncMIO.h"
+#include "win32/AsyncWinIO.h"
#include "win32/ConsoleHandler.h"
+#include <stdbool.h>
// Used to avoid calling abandonRequestWait() if we don't need to.
// Protected by sched_mutex.
-static uint32_t workerWaitingForRequests = 0;
+static bool workerWaitingForRequests = false;
void
awaitEvent(bool wait)
@@ -30,9 +33,12 @@ awaitEvent(bool wait)
do {
/* Try to de-queue completed IO requests
*/
- workerWaitingForRequests = 1;
- awaitRequests(wait);
- workerWaitingForRequests = 0;
+ workerWaitingForRequests = true;
+ if (is_io_mng_native_p())
+ awaitAsyncRequests(wait);
+ else
+ awaitRequests(wait);
+ workerWaitingForRequests = false;
// If a signal was raised, we need to service it
// XXX the scheduler loop really should be calling
diff --git a/rts/win32/ConsoleHandler.c b/rts/win32/ConsoleHandler.c
index 3ddf4103da..05d15868eb 100644
--- a/rts/win32/ConsoleHandler.c
+++ b/rts/win32/ConsoleHandler.c
@@ -1,13 +1,15 @@
/*
* Console control handler support.
*
+ * NOTE: This is the MIO manager, only used for --io-manager=posix.
+ * For the WINIO manager see base in the GHC.Event modules.
*/
#include "Rts.h"
#include <windows.h>
#include "ConsoleHandler.h"
#include "Schedule.h"
#include "RtsUtils.h"
-#include "AsyncIO.h"
+#include "AsyncMIO.h"
#include "RtsSignals.h"
extern int stg_InstallConsoleEvent(int action, StgStablePtr *handler);
@@ -86,7 +88,6 @@ static BOOL WINAPI shutdown_handler(DWORD dwCtrlType)
return false;
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
-
// If we're already trying to interrupt the RTS, terminate with
// extreme prejudice. So the first ^C tries to exit the program
// cleanly, and the second one just kills it.
@@ -223,12 +224,12 @@ static BOOL WINAPI generic_handler(DWORD dwCtrlType)
#if defined(THREADED_RTS)
sendIOManagerEvent((StgWord8) ((dwCtrlType<<1) | 1));
+ interruptIOManagerEvent ();
#else
if ( stg_pending_events < N_PENDING_EVENTS ) {
stg_pending_buf[stg_pending_events] = dwCtrlType;
stg_pending_events++;
}
-
// we need to wake up awaitEvent()
abandonRequestWait();
#endif
diff --git a/rts/win32/ConsoleHandler.h b/rts/win32/ConsoleHandler.h
index 06af9dd0d0..bb7278abba 100644
--- a/rts/win32/ConsoleHandler.h
+++ b/rts/win32/ConsoleHandler.h
@@ -1,6 +1,8 @@
/*
* Console control handler support.
*
+ * NOTE: This is the MIO manager, only used for --io-manager=posix.
+ * For the WINIO manager see base in the GHC.Event modules.
*/
#pragma once
@@ -16,24 +18,24 @@
*/
#if !defined(THREADED_RTS)
-/*
+/*
* under THREADED_RTS, console events are passed to the IO manager
* thread, which starts up the handler. See ThrIOManager.c.
*/
/*
- * Function: signals_pending()
- *
+ * Function: signals_pending()
+ *
* Used by the RTS to check whether new signals have been 'recently' reported.
- * If so, the RTS arranges for the delivered signals to be handled by
- * de-queueing them from their table, running the associated Haskell
+ * If so, the RTS arranges for the delivered signals to be handled by
+ * de-queueing them from their table, running the associated Haskell
* signal handler.
*/
extern StgInt stg_pending_events;
#define signals_pending() ( stg_pending_events > 0)
-/*
+/*
* Function: anyUserHandlers()
*
* Used by the Scheduler to decide whether its worth its while to stick
diff --git a/rts/win32/IOManager.c b/rts/win32/IOManager.c
index f155180ef3..47bcf4bcf4 100644
--- a/rts/win32/IOManager.c
+++ b/rts/win32/IOManager.c
@@ -3,6 +3,9 @@
* Non-blocking / asynchronous I/O for Win32.
*
* (c) sof, 2002-2003.
+ *
+ * NOTE: This is the MIO manager, only used for --io-manager=posix.
+ * For the WINIO manager see base in the GHC.Event modules.
*/
#if !defined(THREADED_RTS)
@@ -22,7 +25,7 @@
* Internal state maintained by the IO manager.
*/
typedef struct IOManagerState {
- CritSection manLock;
+ Mutex manLock;
WorkQueue* workQueue;
int queueSize;
int numWorkers;
@@ -30,7 +33,7 @@ typedef struct IOManagerState {
HANDLE hExitEvent;
unsigned int requestID;
/* fields for keeping track of active WorkItems */
- CritSection active_work_lock;
+ Mutex active_work_lock;
WorkItem* active_work_items;
UINT sleepResolution;
} IOManagerState;
@@ -65,7 +68,7 @@ IOWorkerProc(PVOID param)
// The error code is communicated back on completion of request; reset.
errCode = 0;
- EnterCriticalSection(&iom->manLock);
+ OS_ACQUIRE_LOCK(&iom->manLock);
/* Signal that the worker is idle.
*
* 'workersIdle' is used when determining whether or not to
@@ -73,7 +76,7 @@ IOWorkerProc(PVOID param)
* (see addIORequest().)
*/
iom->workersIdle++;
- LeaveCriticalSection(&iom->manLock);
+ OS_RELEASE_LOCK(&iom->manLock);
/*
* A possible future refinement is to make long-term idle threads
@@ -85,19 +88,19 @@ IOWorkerProc(PVOID param)
if (rc == WAIT_OBJECT_0) {
// we received the exit event
- EnterCriticalSection(&iom->manLock);
+ OS_ACQUIRE_LOCK(&iom->manLock);
ioMan->numWorkers--;
- LeaveCriticalSection(&iom->manLock);
+ OS_RELEASE_LOCK(&iom->manLock);
return 0;
}
- EnterCriticalSection(&iom->manLock);
+ OS_ACQUIRE_LOCK(&iom->manLock);
/* Signal that the thread is 'non-idle' and about to consume
* a work item.
*/
iom->workersIdle--;
iom->queueSize--;
- LeaveCriticalSection(&iom->manLock);
+ OS_RELEASE_LOCK(&iom->manLock);
if ( rc == (WAIT_OBJECT_0 + 1) ) {
/* work item available, fetch it. */
@@ -266,17 +269,17 @@ IOWorkerProc(PVOID param)
} else {
fprintf(stderr, "unable to fetch work; fatal.\n");
fflush(stderr);
- EnterCriticalSection(&iom->manLock);
+ OS_ACQUIRE_LOCK(&iom->manLock);
ioMan->numWorkers--;
- LeaveCriticalSection(&iom->manLock);
+ OS_RELEASE_LOCK(&iom->manLock);
return 1;
}
} else {
fprintf(stderr, "waiting failed (%lu); fatal.\n", rc);
fflush(stderr);
- EnterCriticalSection(&iom->manLock);
+ OS_ACQUIRE_LOCK(&iom->manLock);
ioMan->numWorkers--;
- LeaveCriticalSection(&iom->manLock);
+ OS_RELEASE_LOCK(&iom->manLock);
return 1;
}
}
@@ -334,13 +337,13 @@ StartIOManager(void)
}
ioMan->hExitEvent = hExit;
- InitializeCriticalSection(&ioMan->manLock);
+ OS_INIT_LOCK(&ioMan->manLock);
ioMan->workQueue = wq;
ioMan->numWorkers = 0;
ioMan->workersIdle = 0;
ioMan->queueSize = 0;
ioMan->requestID = 1;
- InitializeCriticalSection(&ioMan->active_work_lock);
+ OS_INIT_LOCK(&ioMan->active_work_lock);
ioMan->active_work_items = NULL;
ioMan->sleepResolution = sleepResolution;
@@ -360,7 +363,7 @@ int
depositWorkItem( unsigned int reqID,
WorkItem* wItem )
{
- EnterCriticalSection(&ioMan->manLock);
+ OS_ACQUIRE_LOCK(&ioMan->manLock);
#if 0
fprintf(stderr, "depositWorkItem: %d/%d\n",
@@ -397,9 +400,9 @@ depositWorkItem( unsigned int reqID,
if ( (ioMan->workersIdle < ioMan->queueSize) ) {
/* see if giving up our quantum ferrets out some idle threads.
*/
- LeaveCriticalSection(&ioMan->manLock);
+ OS_RELEASE_LOCK(&ioMan->manLock);
Sleep(0);
- EnterCriticalSection(&ioMan->manLock);
+ OS_ACQUIRE_LOCK(&ioMan->manLock);
if ( (ioMan->workersIdle < ioMan->queueSize) ) {
/* No, go ahead and create another. */
ioMan->numWorkers++;
@@ -408,7 +411,7 @@ depositWorkItem( unsigned int reqID,
}
}
}
- LeaveCriticalSection(&ioMan->manLock);
+ OS_RELEASE_LOCK(&ioMan->manLock);
if (SubmitWork(ioMan->workQueue,wItem)) {
/* Note: the work item has potentially been consumed by a worker thread
@@ -522,17 +525,17 @@ void ShutdownIOManager ( bool wait_threads )
if (wait_threads) {
/* Wait for all worker threads to die. */
for (;;) {
- EnterCriticalSection(&ioMan->manLock);
+ OS_ACQUIRE_LOCK(&ioMan->manLock);
num = ioMan->numWorkers;
- LeaveCriticalSection(&ioMan->manLock);
+ OS_RELEASE_LOCK(&ioMan->manLock);
if (num == 0)
break;
Sleep(10);
}
FreeWorkQueue(ioMan->workQueue);
CloseHandle(ioMan->hExitEvent);
- DeleteCriticalSection(&ioMan->active_work_lock);
- DeleteCriticalSection(&ioMan->manLock);
+ OS_CLOSE_LOCK(&ioMan->active_work_lock);
+ OS_CLOSE_LOCK(&ioMan->manLock);
mmresult = timeEndPeriod(ioMan->sleepResolution);
if (mmresult != MMSYSERR_NOERROR) {
@@ -550,10 +553,10 @@ void
RegisterWorkItem(IOManagerState* ioMan,
WorkItem* wi)
{
- EnterCriticalSection(&ioMan->active_work_lock);
+ OS_ACQUIRE_LOCK(&ioMan->active_work_lock);
wi->link = ioMan->active_work_items;
ioMan->active_work_items = wi;
- LeaveCriticalSection(&ioMan->active_work_lock);
+ OS_RELEASE_LOCK(&ioMan->active_work_lock);
}
static
@@ -563,7 +566,7 @@ DeregisterWorkItem(IOManagerState* ioMan,
{
WorkItem *ptr, *prev;
- EnterCriticalSection(&ioMan->active_work_lock);
+ OS_ACQUIRE_LOCK(&ioMan->active_work_lock);
for(prev=NULL,ptr=ioMan->active_work_items;ptr;prev=ptr,ptr=ptr->link) {
if (wi->requestID == ptr->requestID) {
if (prev==NULL) {
@@ -571,13 +574,13 @@ DeregisterWorkItem(IOManagerState* ioMan,
} else {
prev->link = ptr->link;
}
- LeaveCriticalSection(&ioMan->active_work_lock);
+ OS_RELEASE_LOCK(&ioMan->active_work_lock);
return;
}
}
fprintf(stderr, "DeregisterWorkItem: unable to locate work item %d\n",
wi->requestID);
- LeaveCriticalSection(&ioMan->active_work_lock);
+ OS_RELEASE_LOCK(&ioMan->active_work_lock);
}
@@ -596,11 +599,11 @@ void
abandonWorkRequest ( int reqID )
{
WorkItem *ptr;
- EnterCriticalSection(&ioMan->active_work_lock);
+ OS_ACQUIRE_LOCK(&ioMan->active_work_lock);
for(ptr=ioMan->active_work_items;ptr;ptr=ptr->link) {
if (ptr->requestID == (unsigned int)reqID ) {
ptr->abandonOp = 1;
- LeaveCriticalSection(&ioMan->active_work_lock);
+ OS_RELEASE_LOCK(&ioMan->active_work_lock);
return;
}
}
@@ -608,7 +611,7 @@ abandonWorkRequest ( int reqID )
* finished sometime since awaitRequests() last drained the completed
* request table; i.e., not an error.
*/
- LeaveCriticalSection(&ioMan->active_work_lock);
+ OS_RELEASE_LOCK(&ioMan->active_work_lock);
}
#endif
diff --git a/rts/win32/IOManager.h b/rts/win32/IOManager.h
index a5bd61ab1b..cb876db9cc 100644
--- a/rts/win32/IOManager.h
+++ b/rts/win32/IOManager.h
@@ -3,6 +3,9 @@
* Non-blocking / asynchronous I/O for Win32.
*
* (c) sof, 2002-2003
+ *
+ * NOTE: This is the MIO manager, only used for --io-manager=posix.
+ * For the WINIO manager see base in the GHC.Event modules.
*/
#pragma once
@@ -102,3 +105,5 @@ extern int AddProcRequest ( void* proc,
CompletionProc onCompletion);
extern void abandonWorkRequest ( int reqID );
+
+extern void interruptIOManagerEvent ( void );
diff --git a/rts/win32/OSMem.c b/rts/win32/OSMem.c
index dd0f60ff0a..fd26d06c4e 100644
--- a/rts/win32/OSMem.c
+++ b/rts/win32/OSMem.c
@@ -37,28 +37,11 @@ static alloc_rec* allocs = NULL;
/* free_blocks are kept in ascending order, and adjacent blocks are merged */
static block_rec* free_blocks = NULL;
-/* Mingw-w64 does not currently have this in their header. So we have to import it.*/
-typedef LPVOID(WINAPI *VirtualAllocExNumaProc)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD, DWORD);
-
-/* Cache NUMA API call. */
-VirtualAllocExNumaProc _VirtualAllocExNuma;
-
void
osMemInit(void)
{
allocs = NULL;
free_blocks = NULL;
-
- /* Resolve and cache VirtualAllocExNuma. */
- if (osNumaAvailable() && RtsFlags.GcFlags.numa)
- {
- _VirtualAllocExNuma = (VirtualAllocExNumaProc)(void*)GetProcAddress(GetModuleHandleW(L"kernel32"), "VirtualAllocExNuma");
- if (!_VirtualAllocExNuma)
- {
- sysErrorBelch(
- "osBindMBlocksToNode: VirtualAllocExNuma does not exist. How did you get this far?");
- }
- }
}
static
@@ -569,7 +552,7 @@ void osBindMBlocksToNode(
On windows also -xb is broken, it does nothing so that can't
be used to tweak it (see #12577). So for now, just let the OS decide.
*/
- temp = _VirtualAllocExNuma(
+ temp = VirtualAllocExNuma(
GetCurrentProcess(),
NULL, // addr? See base memory
size,
diff --git a/rts/win32/OSThreads.c b/rts/win32/OSThreads.c
index fe35f35e82..4701c344a0 100644
--- a/rts/win32/OSThreads.c
+++ b/rts/win32/OSThreads.c
@@ -27,69 +27,6 @@ static uint32_t* cpuGroupCumulativeCache = NULL;
/* Processor group dist cache. */
static uint8_t* cpuGroupDistCache = NULL;
-/* Win32 threads and synchronisation objects */
-
-/* A Condition is represented by a Win32 Event object;
- * a Mutex by a Mutex kernel object.
- *
- * ToDo: go through the defn and usage of these to
- * make sure the semantics match up with that of
- * the (assumed) pthreads behaviour. This is really
- * just a first pass at getting something compilable.
- */
-
-void
-initCondition( Condition* pCond )
-{
- HANDLE h = CreateEvent(NULL,
- FALSE, /* auto reset */
- FALSE, /* initially not signalled */
- NULL); /* unnamed => process-local. */
-
- if ( h == NULL ) {
- sysErrorBelch("initCondition: unable to create");
- stg_exit(EXIT_FAILURE);
- }
- *pCond = h;
- return;
-}
-
-void
-closeCondition( Condition* pCond )
-{
- if ( CloseHandle(*pCond) == 0 ) {
- sysErrorBelch("closeCondition: failed to close");
- }
- return;
-}
-
-bool
-broadcastCondition ( Condition* pCond )
-{
- PulseEvent(*pCond);
- return true;
-}
-
-bool
-signalCondition ( Condition* pCond )
-{
- if (SetEvent(*pCond) == 0) {
- sysErrorBelch("SetEvent");
- stg_exit(EXIT_FAILURE);
- }
- return true;
-}
-
-bool
-waitCondition ( Condition* pCond, Mutex* pMut )
-{
- RELEASE_LOCK(pMut);
- WaitForSingleObject(*pCond, INFINITE);
- /* Hmm..use WaitForMultipleObjects() ? */
- ACQUIRE_LOCK(pMut);
- return true;
-}
-
void
yieldThread()
{
@@ -150,35 +87,6 @@ osThreadIsAlive(OSThreadId id)
return (exit_code == STILL_ACTIVE);
}
-#if defined(USE_CRITICAL_SECTIONS)
-void
-initMutex (Mutex* pMut)
-{
- InitializeCriticalSectionAndSpinCount(pMut,4000);
-}
-void
-closeMutex (Mutex* pMut)
-{
- DeleteCriticalSection(pMut);
-}
-#else
-void
-initMutex (Mutex* pMut)
-{
- HANDLE h = CreateMutex ( NULL, /* default sec. attributes */
- TRUE, /* not owned => initially signalled */
- NULL
- );
- *pMut = h;
- return;
-}
-void
-closeMutex (Mutex* pMut)
-{
- CloseHandle(*pMut);
-}
-#endif
-
void
newThreadLocalKey (ThreadLocalKey *key)
{
@@ -252,6 +160,13 @@ forkOS_createThread ( HsStablePtr entry )
(unsigned*)&pId) == 0);
}
+#if defined(x86_64_HOST_ARCH)
+
+#if !defined(ALL_PROCESSOR_GROUPS)
+#define ALL_PROCESSOR_GROUPS 0xffff
+#endif
+#endif
+
void freeThreadingResources (void)
{
if (cpuGroupCache)
@@ -426,12 +341,15 @@ getNumberOfProcessors (void)
if (nproc)
{
- IF_DEBUG(scheduler, debugBelch("[*] Total number of active processors detected: %u\n", nproc));
+ IF_DEBUG(scheduler, debugBelch("[*] Total number of active "
+ "processors detected: %u\n", nproc));
return nproc;
}
- IF_DEBUG(scheduler, debugBelch("Could not determine Max number of logical processors.\n"
- "Falling back to old code which limits to 64 logical processors.\n"));
+ IF_DEBUG(scheduler, debugBelch("Could not determine Max number of "
+ "logical processors.\n"
+ "Falling back to old code which limits "
+ "to 64 logical processors.\n"));
}
#endif
@@ -484,7 +402,6 @@ setThreadAffinity (uint32_t n, uint32_t m) // cap N of M
for (i = 0; i < n_groups; i++)
{
#if defined(x86_64_HOST_ARCH)
- // If we support the new API, use it.
if (mask[i] > 0)
{
GROUP_AFFINITY hGroup;
@@ -515,24 +432,15 @@ setThreadAffinity (uint32_t n, uint32_t m) // cap N of M
free(mask);
}
-typedef BOOL (WINAPI *PCSIO)(HANDLE);
-
void
interruptOSThread (OSThreadId id)
{
HANDLE hdl;
- PCSIO pCSIO;
if (!(hdl = OpenThread(THREAD_TERMINATE,FALSE,id))) {
sysErrorBelch("interruptOSThread: OpenThread");
stg_exit(EXIT_FAILURE);
}
- pCSIO = (PCSIO)(void*)GetProcAddress(GetModuleHandle(TEXT("Kernel32.dll")),
- "CancelSynchronousIo");
- if ( NULL != pCSIO ) {
- pCSIO(hdl);
- } else {
- // Nothing to do, unfortunately
- }
+ CancelSynchronousIo(hdl);
CloseHandle(hdl);
}
@@ -600,3 +508,55 @@ KernelThreadId kernelThreadId (void)
DWORD tid = GetCurrentThreadId();
return tid;
}
+
+/* Win32 threads and synchronisation objects */
+
+/* A Condition is represented by a Win32 Conditional variable which is a
+ * user-mode object and so incurs no context switching overhead.
+ * a Mutex by a Mutex kernel object.
+ */
+
+void
+initCondition( Condition* pCond )
+{
+ InitializeConditionVariable(pCond);
+ return;
+}
+
+void
+closeCondition( Condition* pCond STG_UNUSED)
+{
+ return;
+}
+
+bool
+broadcastCondition ( Condition* pCond )
+{
+ WakeAllConditionVariable(pCond);
+ return true;
+}
+
+bool
+signalCondition ( Condition* pCond )
+{
+ WakeConditionVariable(pCond);
+ return true;
+}
+
+bool
+waitCondition ( Condition* pCond, Mutex* pMut )
+{
+ SleepConditionVariableSRW(pCond, pMut, INFINITE, 0);
+ return true;
+}
+
+void
+initMutex (Mutex* pMut)
+{
+ InitializeSRWLock(pMut);
+}
+void
+closeMutex (Mutex* pMut)
+{
+ (void)pMut;
+}
diff --git a/rts/win32/ThrIOManager.c b/rts/win32/ThrIOManager.c
index 44414b92c3..1b7ba851e2 100644
--- a/rts/win32/ThrIOManager.c
+++ b/rts/win32/ThrIOManager.c
@@ -5,41 +5,35 @@
* The IO manager thread in THREADED_RTS.
* See also libraries/base/GHC/Conc.hs.
*
+ * NOTE: This is used by both MIO and WINIO
* ---------------------------------------------------------------------------*/
#include "Rts.h"
#include "IOManager.h"
+#include "rts\OSThreads.h"
#include "Prelude.h"
#include <windows.h>
// Here's the Event that we use to wake up the IO manager thread
static HANDLE io_manager_event = INVALID_HANDLE_VALUE;
-// must agree with values in GHC.Conc:
-#define IO_MANAGER_WAKEUP 0xffffffff
-#define IO_MANAGER_DIE 0xfffffffe
-// spurious wakeups are returned as zero.
-// console events are ((event<<1) | 1)
-
-#if defined(THREADED_RTS)
-
#define EVENT_BUFSIZ 256
+// We lock using OS_ACQUIRE_LOCK the ensure the non-threaded WINIO
+// C thread does not race with the scheduler code which can also
+// access the event queue via FFI.
Mutex event_buf_mutex;
StgWord32 event_buf[EVENT_BUFSIZ];
uint32_t next_event;
-#endif
-
+/*Creates the IO Managers event object.
+ Idempotent after first call.
+*/
HANDLE
getIOManagerEvent (void)
{
- // This function has to exist even in the non-THREADED_RTS,
- // because code in GHC.Conc refers to it. It won't ever be called
- // unless we're in the threaded RTS, however.
-#if defined(THREADED_RTS)
HANDLE hRes;
- ACQUIRE_LOCK(&event_buf_mutex);
+ OS_ACQUIRE_LOCK(&event_buf_mutex);
if (io_manager_event == INVALID_HANDLE_VALUE) {
hRes = CreateEvent ( NULL, // no security attrs
@@ -55,29 +49,27 @@ getIOManagerEvent (void)
hRes = io_manager_event;
}
- RELEASE_LOCK(&event_buf_mutex);
+ OS_RELEASE_LOCK(&event_buf_mutex);
return hRes;
-#else
- return NULL;
-#endif
}
HsWord32
readIOManagerEvent (void)
{
- // This function must exist even in non-THREADED_RTS,
- // see getIOManagerEvent() above.
-#if defined(THREADED_RTS)
HsWord32 res;
- ACQUIRE_LOCK(&event_buf_mutex);
+ OS_ACQUIRE_LOCK(&event_buf_mutex);
if (io_manager_event != INVALID_HANDLE_VALUE) {
if (next_event == 0) {
res = 0; // no event to return
} else {
- res = (HsWord32)(event_buf[--next_event]);
+ do {
+ // Dequeue as many wakeup events as possible.
+ res = (HsWord32)(event_buf[--next_event]);
+ } while (res == IO_MANAGER_WAKEUP && next_event);
+
if (next_event == 0) {
if (!ResetEvent(io_manager_event)) {
sysErrorBelch("readIOManagerEvent");
@@ -89,36 +81,47 @@ readIOManagerEvent (void)
res = 0;
}
- RELEASE_LOCK(&event_buf_mutex);
+ OS_RELEASE_LOCK(&event_buf_mutex);
- // debugBelch("readIOManagerEvent: %d\n", res);
+ //debugBelch("readIOManagerEvent: %d\n", res);
return res;
-#else
- return 0;
-#endif
}
void
sendIOManagerEvent (HsWord32 event)
{
-#if defined(THREADED_RTS)
- ACQUIRE_LOCK(&event_buf_mutex);
+ OS_ACQUIRE_LOCK(&event_buf_mutex);
- // debugBelch("sendIOManagerEvent: %d\n", event);
+ //debugBelch("sendIOManagerEvent: %d to %p\n", event, io_manager_event);
if (io_manager_event != INVALID_HANDLE_VALUE) {
if (next_event == EVENT_BUFSIZ) {
errorBelch("event buffer overflowed; event dropped");
} else {
+ event_buf[next_event++] = (StgWord32)event;
if (!SetEvent(io_manager_event)) {
- sysErrorBelch("sendIOManagerEvent");
+ sysErrorBelch("sendIOManagerEvent: SetEvent");
stg_exit(EXIT_FAILURE);
}
- event_buf[next_event++] = (StgWord32)event;
}
}
- RELEASE_LOCK(&event_buf_mutex);
-#endif
+ OS_RELEASE_LOCK(&event_buf_mutex);
+}
+
+void
+interruptIOManagerEvent (void)
+{
+ if (is_io_mng_native_p ()) {
+ OS_ACQUIRE_LOCK(&event_buf_mutex);
+
+ /* How expensive is this??. */
+ Capability *cap;
+ cap = rts_lock();
+ rts_evalIO(&cap, interruptIOManager_closure, NULL);
+ rts_unlock(cap);
+
+ OS_RELEASE_LOCK(&event_buf_mutex);
+ }
}
void
@@ -127,7 +130,6 @@ ioManagerWakeup (void)
sendIOManagerEvent(IO_MANAGER_WAKEUP);
}
-#if defined(THREADED_RTS)
void
ioManagerDie (void)
{
@@ -135,9 +137,9 @@ ioManagerDie (void)
// IO_MANAGER_DIE must be idempotent, as it is called
// repeatedly by shutdownCapability(). Try conc059(threaded1) to
// illustrate the problem.
- ACQUIRE_LOCK(&event_buf_mutex);
+ OS_ACQUIRE_LOCK(&event_buf_mutex);
io_manager_event = INVALID_HANDLE_VALUE;
- RELEASE_LOCK(&event_buf_mutex);
+ OS_RELEASE_LOCK(&event_buf_mutex);
// ToDo: wait for the IO manager to pick up the event, and
// then release the Event and Mutex objects we've allocated.
}
@@ -145,7 +147,9 @@ ioManagerDie (void)
void
ioManagerStart (void)
{
+#if defined(THREADED_RTS)
initMutex(&event_buf_mutex);
+#endif
next_event = 0;
// Make sure the IO manager thread is running
@@ -156,4 +160,3 @@ ioManagerStart (void)
rts_unlock(cap);
}
}
-#endif
diff --git a/rts/win32/WorkQueue.c b/rts/win32/WorkQueue.c
index e560bd24cd..dba20c668b 100644
--- a/rts/win32/WorkQueue.c
+++ b/rts/win32/WorkQueue.c
@@ -3,11 +3,13 @@
*
* (c) sof, 2002-2003.
*/
+#include "Rts.h"
#include "WorkQueue.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <windows.h>
static void queue_error_rc( char* loc, DWORD err);
static void queue_error( char* loc, char* reason);
@@ -48,7 +50,7 @@ NewWorkQueue()
memset(wq, 0, sizeof *wq);
- InitializeCriticalSection(&wq->queueLock);
+ OS_INIT_LOCK(&wq->queueLock);
wq->workAvailable = newSemaphore(0, WORKQUEUE_SIZE);
wq->roomAvailable = newSemaphore(WORKQUEUE_SIZE, WORKQUEUE_SIZE);
@@ -83,7 +85,7 @@ FreeWorkQueue ( WorkQueue* pq )
if ( pq->roomAvailable ) {
CloseHandle(pq->roomAvailable);
}
- DeleteCriticalSection(&pq->queueLock);
+ OS_CLOSE_LOCK(&pq->queueLock);
free(pq);
return;
}
@@ -147,13 +149,13 @@ FetchWork ( WorkQueue* pq, void** ppw )
return false;
}
- EnterCriticalSection(&pq->queueLock);
+ OS_ACQUIRE_LOCK(&pq->queueLock);
*ppw = pq->items[pq->head];
/* For sanity's sake, zero out the pointer. */
pq->items[pq->head] = NULL;
pq->head = (pq->head + 1) % WORKQUEUE_SIZE;
rc = ReleaseSemaphore(pq->roomAvailable,1, NULL);
- LeaveCriticalSection(&pq->queueLock);
+ OS_RELEASE_LOCK(&pq->queueLock);
if ( 0 == rc ) {
queue_error_rc("FetchWork.ReleaseSemaphore()", GetLastError());
return false;
@@ -191,11 +193,11 @@ SubmitWork ( WorkQueue* pq, void* pw )
return false;
}
- EnterCriticalSection(&pq->queueLock);
+ OS_ACQUIRE_LOCK(&pq->queueLock);
pq->items[pq->tail] = pw;
pq->tail = (pq->tail + 1) % WORKQUEUE_SIZE;
rc = ReleaseSemaphore(pq->workAvailable,1, NULL);
- LeaveCriticalSection(&pq->queueLock);
+ OS_RELEASE_LOCK(&pq->queueLock);
if ( 0 == rc ) {
queue_error_rc("SubmitWork.ReleaseSemaphore()", GetLastError());
return false;
diff --git a/rts/win32/WorkQueue.h b/rts/win32/WorkQueue.h
index 4dbfcd40d3..569a7b4445 100644
--- a/rts/win32/WorkQueue.h
+++ b/rts/win32/WorkQueue.h
@@ -14,12 +14,12 @@
#define WORKQUEUE_SIZE 16
typedef HANDLE Semaphore;
-typedef CRITICAL_SECTION CritSection;
+typedef SRWLOCK Mutex;
typedef struct WorkQueue {
/* the master lock, need to be grabbed prior to
using any of the other elements of the struct. */
- CritSection queueLock;
+ Mutex queueLock;
/* consumers/workers block waiting for 'workAvailable' */
Semaphore workAvailable;
Semaphore roomAvailable;
diff --git a/rts/win32/libHSbase.def b/rts/win32/libHSbase.def
index de4db2244b..cb9c32729e 100644
--- a/rts/win32/libHSbase.def
+++ b/rts/win32/libHSbase.def
@@ -27,8 +27,10 @@ EXPORTS
base_GHCziPtr_FunPtr_con_info
base_GHCziConcziIO_ensureIOManagerIsRunning_closure
+ base_GHCziConcziIO_interruptIOManager_closure
base_GHCziConcziIO_ioManagerCapabilitiesChanged_closure
base_GHCziConcziSync_runSparks_closure
+ base_GHCziEventziWindows_processRemoteCompletion_closure
base_GHCziTopHandler_flushStdHandles_closure
@@ -41,7 +43,7 @@ EXPORTS
base_GHCziIOziException_cannotCompactFunction_closure
base_GHCziIOziException_cannotCompactPinned_closure
base_GHCziIOziException_cannotCompactMutable_closure
-
+ base_GHCziIOPort_doubleReadException_closure
base_ControlziExceptionziBase_nonTermination_closure
base_ControlziExceptionziBase_nestedAtomically_closure
base_GHCziEventziThread_blockedOnBadFD_closure
diff --git a/testsuite/config/ghc b/testsuite/config/ghc
index b561fc806e..4f053eb50f 100644
--- a/testsuite/config/ghc
+++ b/testsuite/config/ghc
@@ -68,6 +68,14 @@ if (ghc_with_llvm and not config.unregisterised):
config.compile_ways.append('optllvm')
config.run_ways.append('optllvm')
+# WinIO I/O manager for Windows
+if windows:
+ winio_ways = ['winio', 'winio_threaded']
+ if config.speed == 0:
+ config.run_ways += winio_ways
+ else:
+ config.other_ways += winio_ways
+
config.way_flags = {
'normal' : [],
'normal_h' : [],
@@ -108,6 +116,8 @@ config.way_flags = {
'nonmoving_thr': ['-threaded'],
'nonmoving_thr_ghc': ['+RTS', '-xn', '-N2', '-RTS', '-threaded'],
'compacting_gc': [],
+ 'winio': [],
+ 'winio_threaded': ['-threaded'],
}
config.way_rts_flags = {
@@ -150,6 +160,8 @@ config.way_rts_flags = {
'nonmoving_thr' : ['-xn', '-N2'],
'nonmoving_thr_ghc': ['-xn', '-N2'],
'compacting_gc': ['-c'],
+ 'winio': ['--io-manager=native'],
+ 'winio_threaded': ['--io-manager=native'],
}
# Useful classes of ways that can be used with only_ways(), omit_ways() and
diff --git a/testsuite/mk/test.mk b/testsuite/mk/test.mk
index cb4b3747f5..b2cdf78f5f 100644
--- a/testsuite/mk/test.mk
+++ b/testsuite/mk/test.mk
@@ -60,7 +60,7 @@ TEST_HC_OPTS += -Werror=compat
# removing this line.
TEST_HC_OPTS += -dno-debug-output
-TEST_HC_OPTS_INTERACTIVE = $(TEST_HC_OPTS) --interactive -v0 -ignore-dot-ghci -fno-ghci-history
+TEST_HC_OPTS_INTERACTIVE = $(TEST_HC_OPTS) --interactive -v0 -ignore-dot-ghci -fno-ghci-history +RTS --io-manager=native -RTS
RUNTEST_OPTS =
diff --git a/testsuite/tests/backpack/cabal/T16219/backpack-issue.cabal b/testsuite/tests/backpack/cabal/T16219/backpack-issue.cabal
index 69f47e8e54..3a15b2e705 100644
--- a/testsuite/tests/backpack/cabal/T16219/backpack-issue.cabal
+++ b/testsuite/tests/backpack/cabal/T16219/backpack-issue.cabal
@@ -1,8 +1,9 @@
+cabal-version: 2.2
name: backpack-issue
version: 0.1.0.0
-- synopsis:
-- description:
-license: BSD3
+license: BSD-3-Clause
license-file: LICENSE
author: Isaac Elliott
maintainer: isaace71295@gmail.com
@@ -10,7 +11,6 @@ maintainer: isaace71295@gmail.com
-- category:
build-type: Simple
extra-source-files: CHANGELOG.md
-cabal-version: >=2
library library-a
signatures: A.Sig
diff --git a/testsuite/tests/backpack/cabal/bkpcabal01/bkpcabal01.cabal b/testsuite/tests/backpack/cabal/bkpcabal01/bkpcabal01.cabal
index c46675f1ce..8955d65329 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal01/bkpcabal01.cabal
+++ b/testsuite/tests/backpack/cabal/bkpcabal01/bkpcabal01.cabal
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: bkpcabal01
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.25
library impl
exposed-modules: H, I
diff --git a/testsuite/tests/backpack/cabal/bkpcabal02/bkpcabal02.cabal b/testsuite/tests/backpack/cabal/bkpcabal02/bkpcabal02.cabal
index 92ba58633a..a94a6521a7 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal02/bkpcabal02.cabal
+++ b/testsuite/tests/backpack/cabal/bkpcabal02/bkpcabal02.cabal
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: bkpcabal01
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.25
library p
signatures: H
diff --git a/testsuite/tests/backpack/cabal/bkpcabal03/asig1/asig1.cabal b/testsuite/tests/backpack/cabal/bkpcabal03/asig1/asig1.cabal
index f45c925414..3977ac1927 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal03/asig1/asig1.cabal
+++ b/testsuite/tests/backpack/cabal/bkpcabal03/asig1/asig1.cabal
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: asig1
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.25
library
build-depends: base
diff --git a/testsuite/tests/backpack/cabal/bkpcabal03/asig2/asig2.cabal b/testsuite/tests/backpack/cabal/bkpcabal03/asig2/asig2.cabal
index 1d802d8fe3..e97ccbdc7e 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal03/asig2/asig2.cabal
+++ b/testsuite/tests/backpack/cabal/bkpcabal03/asig2/asig2.cabal
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: asig2
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.25
library
build-depends: base
diff --git a/testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in1 b/testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in1
index 689a7c87be..8fb32a789d 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in1
+++ b/testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in1
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: bkpcabal03
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.25
library
build-depends: asig1, base
diff --git a/testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in2 b/testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in2
index a25c7fed46..8db22c1da7 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in2
+++ b/testsuite/tests/backpack/cabal/bkpcabal03/bkpcabal03.cabal.in2
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: bkpcabal03
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.25
library
build-depends: asig1, asig2, base
diff --git a/testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in1 b/testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in1
index 1ce11c5bcc..456c9f92c7 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in1
+++ b/testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in1
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: bkpcabal04
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.25
library p
signatures: A
diff --git a/testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in2 b/testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in2
index e6fa4c6660..f15f5ac520 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in2
+++ b/testsuite/tests/backpack/cabal/bkpcabal04/bkpcabal04.cabal.in2
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: bkpcabal04
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.25
library p
signatures: A
diff --git a/testsuite/tests/backpack/cabal/bkpcabal05/bkpcabal05.cabal b/testsuite/tests/backpack/cabal/bkpcabal05/bkpcabal05.cabal
index 47e78a4b74..723f105ce3 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal05/bkpcabal05.cabal
+++ b/testsuite/tests/backpack/cabal/bkpcabal05/bkpcabal05.cabal
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: bkpcabal05
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.25
library
signatures: A
diff --git a/testsuite/tests/backpack/cabal/bkpcabal06/bkpcabal06.cabal b/testsuite/tests/backpack/cabal/bkpcabal06/bkpcabal06.cabal
index ff322a4e02..e62687dcc0 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal06/bkpcabal06.cabal
+++ b/testsuite/tests/backpack/cabal/bkpcabal06/bkpcabal06.cabal
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: bkpcabal06
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: 2.0
library sig
signatures: P
diff --git a/testsuite/tests/backpack/cabal/bkpcabal07/bkpcabal07.cabal b/testsuite/tests/backpack/cabal/bkpcabal07/bkpcabal07.cabal
index 4f66fc44a9..db4d04e9da 100644
--- a/testsuite/tests/backpack/cabal/bkpcabal07/bkpcabal07.cabal
+++ b/testsuite/tests/backpack/cabal/bkpcabal07/bkpcabal07.cabal
@@ -1,10 +1,10 @@
+cabal-version: 2.2
name: bkpcabal06
version: 0.1.0.0
-license: BSD3
+license: BSD-3-Clause
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=2.0
library indef
signatures: P
diff --git a/testsuite/tests/cabal/cabal01/test.cabal b/testsuite/tests/cabal/cabal01/test.cabal
index b5c3b74ebe..c37f41c648 100644
--- a/testsuite/tests/cabal/cabal01/test.cabal
+++ b/testsuite/tests/cabal/cabal01/test.cabal
@@ -1,3 +1,4 @@
+Cabal-Version: 2.2
Name: test
Version: 1.0
Exposed-Modules: A
diff --git a/testsuite/tests/cabal/cabal04/Makefile b/testsuite/tests/cabal/cabal04/Makefile
index e9366fa349..e8b3aab3ac 100644
--- a/testsuite/tests/cabal/cabal04/Makefile
+++ b/testsuite/tests/cabal/cabal04/Makefile
@@ -14,7 +14,7 @@ cabal04:
$(MAKE) -s --no-print-directory clean
'$(TEST_HC)' $(TEST_HC_OPTS) -v0 --make Setup
$(SETUP) clean
- $(SETUP) configure --with-ghc='$(TEST_HC)' --ghc-options='$(filter-out -rtsopts,$(TEST_HC_OPTS))' $(VANILLA) $(PROF) $(DYN)
+ $(SETUP) configure --with-ghc='$(TEST_HC)' --ghc-options='$(filter-out -with-rtsopts="--io-manager=native",$(filter-out -rtsopts,$(TEST_HC_OPTS)))' $(VANILLA) $(PROF) $(DYN)
$(SETUP) build 2> err
! grep -v "Creating library file" err
ifneq "$(CLEANUP)" ""
diff --git a/testsuite/tests/cabal/cabal06/p-1.0/p.cabal b/testsuite/tests/cabal/cabal06/p-1.0/p.cabal
index ab7b3ebffe..00336e7bae 100644
--- a/testsuite/tests/cabal/cabal06/p-1.0/p.cabal
+++ b/testsuite/tests/cabal/cabal06/p-1.0/p.cabal
@@ -4,7 +4,7 @@ license-file: LICENSE
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.20
+cabal-version: 2.0
library
exposed-modules: P
diff --git a/testsuite/tests/cabal/cabal06/p-1.1/p.cabal b/testsuite/tests/cabal/cabal06/p-1.1/p.cabal
index 8a7b7b271d..31ba170e04 100644
--- a/testsuite/tests/cabal/cabal06/p-1.1/p.cabal
+++ b/testsuite/tests/cabal/cabal06/p-1.1/p.cabal
@@ -4,7 +4,7 @@ license-file: LICENSE
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.20
+cabal-version: 2.0
library
exposed-modules: P
diff --git a/testsuite/tests/cabal/cabal06/q/q.cabal b/testsuite/tests/cabal/cabal06/q/q.cabal
index 7b3a074f88..770c0bfc3f 100644
--- a/testsuite/tests/cabal/cabal06/q/q.cabal
+++ b/testsuite/tests/cabal/cabal06/q/q.cabal
@@ -4,7 +4,7 @@ license-file: LICENSE
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.20
+cabal-version: 2.0
library
exposed-modules: Q
diff --git a/testsuite/tests/cabal/cabal06/r/r.cabal b/testsuite/tests/cabal/cabal06/r/r.cabal
index 60e16c1c78..2df73e0ed3 100644
--- a/testsuite/tests/cabal/cabal06/r/r.cabal
+++ b/testsuite/tests/cabal/cabal06/r/r.cabal
@@ -4,7 +4,7 @@ license-file: LICENSE
author: Edward Z. Yang
maintainer: ezyang@cs.stanford.edu
build-type: Simple
-cabal-version: >=1.20
+cabal-version: 2.0
executable cabal06
build-depends: base, p, q
diff --git a/testsuite/tests/driver/T4437.hs b/testsuite/tests/driver/T4437.hs
index a0bcebf889..c83d29b03d 100644
--- a/testsuite/tests/driver/T4437.hs
+++ b/testsuite/tests/driver/T4437.hs
@@ -40,9 +40,6 @@ expectedGhcOnlyExtensions =
[ "RelaxedLayout"
, "AlternativeLayoutRule"
, "AlternativeLayoutRuleTransitional"
- , "LinearTypes"
- , "QualifiedDo"
- , "LexicalNegation"
]
expectedCabalOnlyExtensions :: [String]
diff --git a/testsuite/tests/ghci/linking/dyn/Makefile b/testsuite/tests/ghci/linking/dyn/Makefile
index ee345e5560..ee53d0463e 100644
--- a/testsuite/tests/ghci/linking/dyn/Makefile
+++ b/testsuite/tests/ghci/linking/dyn/Makefile
@@ -24,7 +24,7 @@ else
CFLAGS = -fPIC
endif
-MY_TEST_HC_OPTS = $(filter-out -rtsopts,$(TEST_HC_OPTS)) $(CFLAGS)
+MY_TEST_HC_OPTS = $(TEST_HC_OPTS) $(CFLAGS)
# --------------------------------------------------------------
# Note: libAS.def is not used directly in these tests but is
diff --git a/testsuite/tests/ghci/linking/dyn/T10955dyn.stderr b/testsuite/tests/ghci/linking/dyn/T10955dyn.stderr
new file mode 100644
index 0000000000..e69dbaad75
--- /dev/null
+++ b/testsuite/tests/ghci/linking/dyn/T10955dyn.stderr
@@ -0,0 +1,4 @@
+Warning: -rtsopts and -with-rtsopts have no effect with -shared.
+ Call hs_init_ghc() from your main() function to set these options.
+Warning: -rtsopts and -with-rtsopts have no effect with -shared.
+ Call hs_init_ghc() from your main() function to set these options.
diff --git a/testsuite/tests/ghci/linking/dyn/load_short_name.stderr b/testsuite/tests/ghci/linking/dyn/load_short_name.stderr
new file mode 100644
index 0000000000..cd2812b10c
--- /dev/null
+++ b/testsuite/tests/ghci/linking/dyn/load_short_name.stderr
@@ -0,0 +1,2 @@
+Warning: -rtsopts and -with-rtsopts have no effect with -shared.
+ Call hs_init_ghc() from your main() function to set these options.
diff --git a/utils/fs/fs.c b/utils/fs/fs.c
index 51f45eb2d0..ebed2ca0fc 100644
--- a/utils/fs/fs.c
+++ b/utils/fs/fs.c
@@ -412,7 +412,7 @@ int FS(_stat64) (const char *path, struct __stat64 *buffer)
static __time64_t ftToPosix(FILETIME ft)
{
- // takes the last modified date
+ /* takes the last modified date. */
LARGE_INTEGER date, adjust;
date.HighPart = ft.dwHighDateTime;
date.LowPart = ft.dwLowDateTime;
diff --git a/utils/fs/fs.h b/utils/fs/fs.h
index cb04d54127..9e4b8e2052 100644
--- a/utils/fs/fs.h
+++ b/utils/fs/fs.h
@@ -45,6 +45,5 @@ int FS(_wunlink) (const wchar_t *filename);
int FS(remove) (const char *path);
int FS(_wremove) (const wchar_t *path);
#else
-
FILE *FS(fopen) (const char* filename, const char* mode);
#endif
diff --git a/utils/genprimopcode/Main.hs b/utils/genprimopcode/Main.hs
index 31d363c0fa..3fe744fec3 100644
--- a/utils/genprimopcode/Main.hs
+++ b/utils/genprimopcode/Main.hs
@@ -919,6 +919,8 @@ ppType (TyApp (TyCon "StableName#") [x]) = "mkStableNamePrimTy " ++ ppType x
ppType (TyApp (TyCon "MVar#") [x,y]) = "mkMVarPrimTy " ++ ppType x
++ " " ++ ppType y
+ppType (TyApp (TyCon "IOPort#") [x,y]) = "mkIOPortPrimTy " ++ ppType x
+ ++ " " ++ ppType y
ppType (TyApp (TyCon "TVar#") [x,y]) = "mkTVarPrimTy " ++ ppType x
++ " " ++ ppType y
ppType (TyApp (TyCon "Void#") []) = "voidPrimTy"
diff --git a/utils/ghc-cabal/ghc.mk b/utils/ghc-cabal/ghc.mk
index 381bc53a02..a964e55070 100644
--- a/utils/ghc-cabal/ghc.mk
+++ b/utils/ghc-cabal/ghc.mk
@@ -22,10 +22,11 @@ CABAL_CONSTRAINT := --constraint="Cabal == $(CABAL_DOTTED_VERSION)"
# generate MIN_VERSION_<pkgname>() CPP macros. The generation of those
# macros is triggered by `-hide-all-packages`, so we have to explicitly
# enumerate all packages we need in scope.
+CABAL_BUILD_DEPS := ghc-prim base binary array transformers time containers bytestring deepseq process pretty directory filepath template-haskell
ifeq "$(Windows_Host)" "YES"
-CABAL_BUILD_DEPS := ghc-prim base array transformers time containers bytestring deepseq process pretty directory filepath Win32 template-haskell
+CABAL_BUILD_DEPS += Win32
else
-CABAL_BUILD_DEPS := ghc-prim base array transformers time containers bytestring deepseq process pretty directory filepath unix template-haskell
+CABAL_BUILD_DEPS += unix
endif
ghc-cabal_DIST_BINARY_NAME = ghc-cabal$(exeext0)
@@ -65,7 +66,6 @@ $(ghc-cabal_DIST_BINARY): $(CABAL_LEXER_DEP) utils/ghc-cabal/Main.hs $(TOUCH_DEP
-no-user-package-db \
-Wall -fno-warn-unused-imports -fno-warn-warnings-deprecations \
-DCABAL_VERSION=$(CABAL_VERSION) \
- -DCABAL_PARSEC \
-DBOOTSTRAPPING \
-odir bootstrapping \
-hidir bootstrapping \
diff --git a/utils/hp2ps/Main.h b/utils/hp2ps/Main.h
index da856f4884..4c6101f2bb 100644
--- a/utils/hp2ps/Main.h
+++ b/utils/hp2ps/Main.h
@@ -24,7 +24,7 @@ void _stgAssert PROTO((char *, unsigned int));
/* partain: some ubiquitous types: floatish & intish.
Dubious to use float/int, but that is what it used to be...
- (WDP 95/03)
+ (WDP 95/03)
*/
typedef double floatish;
typedef double doublish; /* higher precision, if anything; little used */
diff --git a/utils/hsc2hs b/utils/hsc2hs
-Subproject e792dd8e5589d42a4d416f78df8efb70995f95e
+Subproject 7accbea001bcac638c4320d3755af2947811490