[Git][ghc/ghc][wip/fendor/27836] Fix #27836, normalise unit database path in ExternalUnitDatabases
by Hannes Siebenhandl (@fendor) 28 Sep '26
by Hannes Siebenhandl (@fendor) 28 Sep '26
28 Sep '26
Hannes Siebenhandl pushed to branch wip/fendor/27836 at Glasgow Haskell Compiler / GHC
Commits:
d15219a9 by Hannes Siebenhandl at 2026-09-28T11:11:18+02:00
Fix #27836, normalise unit database path in ExternalUnitDatabases
Add a regression test, proofing that all variations:
- local-unit-db/my-test.conf
- ./local-unit-db/my-test.conf
- ./local-unit-db/my-test.conf/
are parsed only once.
- - - - -
11 changed files:
- compiler/GHC/Data/OsPath.hs
- compiler/GHC/Unit/External/Database.hs
- + testsuite/tests/driver/T27836/A.hs
- + testsuite/tests/driver/T27836/B.hs
- + testsuite/tests/driver/T27836/C.hs
- + testsuite/tests/driver/T27836/Makefile
- + testsuite/tests/driver/T27836/T27836.stdout
- + testsuite/tests/driver/T27836/all.T
- + testsuite/tests/driver/T27836/unitA
- + testsuite/tests/driver/T27836/unitB
- + testsuite/tests/driver/T27836/unitC
Changes:
=====================================
compiler/GHC/Data/OsPath.hs
=====================================
@@ -13,6 +13,7 @@ module GHC.Data.OsPath
, (<.>)
, splitSearchPath
, isRelative
+ , normalise
, dropTrailingPathSeparator
, takeDirectory
, isSuffixOf
=====================================
compiler/GHC/Unit/External/Database.hs
=====================================
@@ -53,9 +53,15 @@ import System.FilePath as FilePath
-- ExternalUnitDatabases
-- ----------------------------------------------------------------------------
+-- | A normalised 'OsPath' that we use as the key to cache 'UnitDatabase's.
+--
+newtype NormalisedUnitDbLoc = NormalisedUnitDbLoc OsPath
+ deriving (Eq, Ord)
+
-- | Caches unit databases in-memory.
data ExternalUnitDatabases unit = ExternalUnitDatabases
- { eud_cachedDatabases :: !(Map OsPath (UnitDatabase unit))
+ { eud_cachedDatabases :: !(Map NormalisedUnitDbLoc (UnitDatabase unit))
+ -- ^ The 'Map' is keyed by a normalised 'OsPath', ensuring
}
emptyExternalUnitDatabases :: ExternalUnitDatabases unit
@@ -67,18 +73,20 @@ emptyExternalUnitDatabases =
insertExternalUnitDatabases :: UnitDatabase unit -> ExternalUnitDatabases unit -> ExternalUnitDatabases unit
insertExternalUnitDatabases unit_db eud =
ExternalUnitDatabases
- { eud_cachedDatabases = Map.insert (unitDatabasePath unit_db) unit_db (eud_cachedDatabases eud)
+ { eud_cachedDatabases =
+ Map.insert (normalisedUnitDatabaseKey unit_db) unit_db (eud_cachedDatabases eud)
}
deleteExternalUnitDatabases :: OsPath -> ExternalUnitDatabases unit -> ExternalUnitDatabases unit
deleteExternalUnitDatabases unit_db_path eud =
ExternalUnitDatabases
- { eud_cachedDatabases = Map.delete unit_db_path (eud_cachedDatabases eud)
+ { eud_cachedDatabases =
+ Map.delete (normalisedUnitDatabaseKeyOsPath unit_db_path) (eud_cachedDatabases eud)
}
lookupExternalUnitDatabases :: OsPath -> ExternalUnitDatabases unit -> Maybe (UnitDatabase unit)
lookupExternalUnitDatabases key eud =
- Map.lookup key (eud_cachedDatabases eud)
+ Map.lookup (normalisedUnitDatabaseKeyOsPath key) (eud_cachedDatabases eud)
-- ----------------------------------------------------------------------------
-- UnitDatabase
@@ -87,12 +95,38 @@ lookupExternalUnitDatabases key eud =
-- | Unit database entry.
data UnitDatabase unit = UnitDatabase
{ unitDatabasePath :: OsPath
+ -- ^ On-disk location of the unit database.
+ --
, unitDatabaseUnits :: [GenUnitInfo unit]
+ -- ^ Units read from the database located at 'unitDatabasePath'.
+ --
+ -- These units have the @$pkgroot@ variable already resolved.
}
instance (Outputable u) => Outputable (UnitDatabase u) where
ppr (UnitDatabase fp _u) = text "DB:" <+> ppr fp
+normalisedUnitDatabaseKey :: UnitDatabase unit -> NormalisedUnitDbLoc
+normalisedUnitDatabaseKey unit_db = normalisedUnitDatabaseKeyOsPath $ unitDatabasePath unit_db
+
+-- | Normalise the unit database location and drop trailing path separators.
+--
+-- This ensures that two databases that point to the same location can be cached.
+--
+-- We need to remove trailing slash from the 'OsPath' before calculating @$pkgroot@ (Fix #16360)
+normalisedUnitDatabaseKeyOsPath :: OsPath -> NormalisedUnitDbLoc
+normalisedUnitDatabaseKeyOsPath db_path =
+ NormalisedUnitDbLoc $ OsPath.normalise $ OsPath.dropTrailingPathSeparator db_path
+
+-- | Get the root location of a unit database.
+-- Used to replace @$pkgroot@ variables in the 'GenericUnitInfo'.
+unitDatabaseRoot :: OsPath -> OsPath
+unitDatabaseRoot p =
+ let
+ NormalisedUnitDbLoc np = normalisedUnitDatabaseKeyOsPath p
+ in
+ OsPath.takeDirectory np
+
-- ----------------------------------------------------------------------------
--
-- Merging databases
@@ -258,14 +292,12 @@ readUnitDatabase logger cfg conf_file = do
"can't find a package database at " ++ show conf_file
let
- -- Fix #16360: remove trailing slash from conf_file before calculating pkgroot
- conf_file' = OsPath.dropTrailingPathSeparator conf_file
top_dir = OsPath.unsafeEncodeUtf (unitDbConfigGHCDir cfg)
- pkgroot = OsPath.takeDirectory conf_file'
+ pkgroot = unitDatabaseRoot conf_file
pkg_configs1 = map (mungeUnitInfo top_dir pkgroot . mapUnitInfo (\(UnitKey x) -> UnitId x) . mkUnitKeyInfo)
proto_pkg_configs
--
- return $ UnitDatabase conf_file' pkg_configs1
+ return $ UnitDatabase conf_file pkg_configs1
where
readDirStyleUnitInfo :: OsPath -> IO [DbUnitInfo]
readDirStyleUnitInfo conf_dir = do
=====================================
testsuite/tests/driver/T27836/A.hs
=====================================
@@ -0,0 +1,4 @@
+module A where
+
+a :: Int
+a = 1
=====================================
testsuite/tests/driver/T27836/B.hs
=====================================
@@ -0,0 +1,4 @@
+module B where
+
+b :: Int
+b = 1
=====================================
testsuite/tests/driver/T27836/C.hs
=====================================
@@ -0,0 +1,4 @@
+module C where
+
+b :: Int
+b = 1
=====================================
testsuite/tests/driver/T27836/Makefile
=====================================
@@ -0,0 +1,21 @@
+TOP=../../..
+include $(TOP)/mk/boilerplate.mk
+include $(TOP)/mk/test.mk
+
+T27836:
+ mkdir local-unit-db/
+ cd local-unit-db/ && '$(GHC_PKG)' init my-test.conf
+ # All these unit database paths should resolve to the same location
+ # thus after the first time parsing it, we shouldn't parse it again.
+ '$(TEST_HC)' $(TEST_HC_OPTS) --make \
+ -unit @unitA -unit @unitB -unit @unitC \
+ -v2 -fforce-recomp 2>&1 \
+ | grep 'Using binary package database:'
+
+ echo ""
+ echo ""
+ echo "All -package-db variations:"
+ echo "- local-unit-db/my-test.conf"
+ echo "- ./local-unit-db/my-test.conf"
+ echo "- ./local-unit-db/my-test.conf/"
+ echo "Must be parsed only once."
=====================================
testsuite/tests/driver/T27836/T27836.stdout
=====================================
@@ -0,0 +1,8 @@
+Using binary package database: local-unit-db/my-test.conf/package.cache
+
+
+All -package-db variations:
+- local-unit-db/my-test.conf
+- ./local-unit-db/my-test.conf
+- ./local-unit-db/my-test.conf/
+Must be parsed only once.
=====================================
testsuite/tests/driver/T27836/all.T
=====================================
@@ -0,0 +1 @@
+test('T27836', [extra_files(['A.hs', 'B.hs', 'C.hs', 'unitA', 'unitB', 'unitC'])], makefile_test, ['T27836'])
=====================================
testsuite/tests/driver/T27836/unitA
=====================================
@@ -0,0 +1 @@
+-hide-all-packages -no-user-package-db -package-db local-unit-db/my-test.conf -package base -this-unit-id a-0.0.0 A
=====================================
testsuite/tests/driver/T27836/unitB
=====================================
@@ -0,0 +1 @@
+-hide-all-packages -no-user-package-db -package-db ./local-unit-db/my-test.conf -package base -this-unit-id b-0.0.0 B
=====================================
testsuite/tests/driver/T27836/unitC
=====================================
@@ -0,0 +1 @@
+-hide-all-packages -no-user-package-db -package-db ./local-unit-db/my-test.conf/ -package base -this-unit-id c-0.0.0 C
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d15219a9f3cf6cfc143231c888a6acd…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d15219a9f3cf6cfc143231c888a6acd…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/io-manager-deadlock-detection] 2 commits: Add tests for deadlock detection, issue #26408
by Duncan Coutts (@dcoutts) 28 Sep '26
by Duncan Coutts (@dcoutts) 28 Sep '26
28 Sep '26
Duncan Coutts pushed to branch wip/io-manager-deadlock-detection at Glasgow Haskell Compiler / GHC
Commits:
ba7a6066 by Duncan Coutts at 2026-09-28T09:52:17+01:00
Add tests for deadlock detection, issue #26408
We have three tests:
T26408a: make sure that we can detect a /partial deadlock/ that is not
also a /system deadlock/
T26408b: make sure that we can detect a /system deadlock/ promptly
without relying on idle GC
T26408c: make sure that we do not falsely detect a deadlock when there is
nothing to do except wait on signals
- - - - -
80e2ceee by Duncan Coutts at 2026-09-28T09:52:17+01:00
Update the user guide with the revised idle GC behaviour
It's now not just for the threaded RTS, but general.
Add a new section in the RTS chapter: Idle GC and deadlock detection.
Provide general documentation on both topics. In particular, document
the behaviour of deadlock detection and how it depends on idle GC.
Move the existing entries for -I and -Iw to this section.
Add a changelog entry.
- - - - -
11 changed files:
- + changelog.d/idle-gc-and-deadlock-detection
- docs/users_guide/profiling.rst
- docs/users_guide/runtime_control.rst
- testsuite/tests/rts/Makefile
- + testsuite/tests/rts/T26408a.hs
- + testsuite/tests/rts/T26408a.stderr
- + testsuite/tests/rts/T26408b.hs
- + testsuite/tests/rts/T26408b.stderr
- + testsuite/tests/rts/T26408c.hs
- + testsuite/tests/rts/T26408c.stdout
- testsuite/tests/rts/all.T
Changes:
=====================================
changelog.d/idle-gc-and-deadlock-detection
=====================================
@@ -0,0 +1,22 @@
+section: rts
+issues: #27040 #26408
+mrs: !14820
+synopsis: Idle GC and deadlock detection in the non-threaded RTS now behave the same as in the threaded RTS
+description: {
+Idle GC now works in the non-threaded RTS and is enabled by default.
+
+Deadlock detection in the non-threaded RTS now follows the same design and
+behaviour as in the threaded RTS. In particular, it can detect partial
+deadlocks where some but not all threads are deadlocked. The detection is no
+longer prompt however, as the detection runs as part of idle GC, which (by
+default) is performed 0.3 seconds after all capabilities become idle.
+
+The user guide now documents that deadlock detection relies on idle GC,
+and thus turning off idle GC disables deadlock detection. This is true
+of the threaded RTS in previous GHC releases but it was not previously
+documented behaviour.
+
+Internally, the idle GC and deadlock detection now follow the same design and
+use 99% the same code between the non-threaded and threaded RTS. Having a
+single design improves the comprehension and maintainability of the RTS code.
+}
=====================================
docs/users_guide/profiling.rst
=====================================
@@ -691,11 +691,13 @@ enclosed between ``+RTS ... -RTS`` as usual):
order to increase the resolution of the time profiler.
Using a value of zero disables the RTS clock completely, and has the
- effect of disabling timers that depend on it: the context switch
- timer and the heap profiling timer. Context switches will still
- happen, but deterministically and at a rate much faster than normal.
- Disabling the interval timer is useful for debugging, because it
- eliminates a source of non-determinism at runtime.
+ effect of disabling the timers and features that depend on it: the context
+ switch timer, the heap profiling timer, the eventlog flush timer, and the
+ idle GC timer. Context switches will still happen, but deterministically
+ and at a rate much faster than normal. Disabling the interval timer is
+ useful for debugging, because it eliminates a source of non-determinism at
+ runtime. Note however that disabling the idle GC timer not only disables
+ idle GC but also deadlock detection (see rts-flag:`-I`).
.. rts-flag:: -xc
=====================================
docs/users_guide/runtime_control.rst
=====================================
@@ -731,61 +731,6 @@ performance.
the default small ``-A`` value is suboptimal, as it can be in
programs that create large amounts of long-lived data.
-.. rts-flag:: -I ⟨seconds⟩
-
- :default: 0.3 seconds in the threaded runtime, 0 in the non-threaded runtime
-
- .. index::
- single: idle GC
-
- Set the amount of idle time which must pass before a idle GC is
- performed. Setting ``-I0`` disables the idle GC.
-
- In the threaded and SMP versions of the RTS (see :ghc-flag:`-threaded`,
- :ref:`options-linker`), a major GC is automatically performed if the
- runtime has been idle (no Haskell computation has been running) for a
- period of time.
-
- For an interactive application, it is probably a good idea to use
- the idle GC, because this will allow finalizers to run and
- deadlocked threads to be detected in the idle time when no Haskell
- computation is happening. Also, it will mean that a GC is less
- likely to happen when the application is busy, and so responsiveness
- may be improved. However, if the amount of live data in the heap is
- particularly large, then the idle GC can cause a significant delay,
- and too small an interval could adversely affect interactive
- responsiveness.
-
- The idle period timer only resets after some activity
- by a Haskell thread. If your program is doing literally nothing then
- after the first idle collection is triggered then no more future collections
- will be scheduled until more work is performed.
-
- This is an experimental feature, please let us know if it causes
- problems and/or could benefit from further tuning.
-
-.. rts-flag:: -Iw ⟨seconds⟩
-
- :default: 0 seconds
-
- .. index::
- single: idle GC
-
- Set the minimum wait time between runs of the idle GC.
-
- By default, if idle GC is enabled in the threaded runtime, a major
- GC will be performed every time the process goes idle for a
- sufficiently long duration (see :rts-flag:`-I ⟨seconds⟩`). For
- large server processes accepting regular but infrequent requests
- (e.g., once per second), an expensive, major GC may run after
- every request. As an alternative to shutting off idle GC entirely
- (with ``-I0``), a minimum wait time between idle GCs can be
- specified with this flag. For example, ``-Iw60`` will ensure that
- an idle GC runs at most once per minute.
-
- This is an experimental feature, please let us know if it causes
- problems and/or could benefit from further tuning.
-
.. rts-flag:: -ki ⟨size⟩
:default: 1k
@@ -1011,6 +956,116 @@ performance.
``GHC.Stats.getRTSStats`` from Haskell.
+Idle GC and deadlock detection
+------------------------------
+
+The RTS performs idle GC and deadlock detection. These features are more
+closely related and dependent than one might expect. In particular, deadlock
+detection relies on idle GC.
+
+The primary purpose of idle GC is to reclaim memory and return it to the system
+when the application has been idle for a short time. It also gives an
+opportunity for finalisers to run more promptly. See :rts-flag:`-I ⟨seconds⟩`.
+
+Deadlock detection is a debugging feature to try to discover when some or all
+Haskell threads are deadlocked. For the purpose of this explanation we define:
+
+ * a "partial deadlock" to be a set of threads that are deadlocked; and
+ * a "system deadlock" is when all threads are deadlocked.
+
+The deadlock detection is implemented by the GC, and it is capable of detecting
+partial deadlocks. If a deadlock is discovered, the RTS will throw an
+asynchronous exception to (some or all of) the blocked threads to enable
+progress and enable better reporting of the problem. In particular it throws
+the async exceptions ``BlockedIndefinitelyOnMVar`` or
+``BlockedIndefinitelyOnSTM``, as appropriate.
+
+The deadlock detection can be run during a major GC, but it is more expensive
+than a normal major GC so it is not run during normal major GCs. The deadlock
+detection mode of GC is run by the idle GC. This is on the basis that when the
+application is idle is when we are most likely to find a deadlock, and also
+that the extra time cost is unlikely to be a problem because the application is
+already idle.
+
+There are a few consequence of this:
+
+ * Deadlock detection relies on idle GC and will not run if idle GC is disabled
+ with ``-I0``.
+ * Deadlock detection will not run if the application remains busy, and thus
+ idle GC is not invoked.
+ * Deadlock detection is not prompt: it will only occur after the idle GC
+ delay, depending on both :rts-flag:`-I ⟨seconds⟩` and
+ :rts-flag:`-Iw ⟨seconds⟩`.
+
+In the non-threaded RTS there is additional behaviour: system deadlocks will be
+detected promptly, and this works even if idle GC is otherwise disabled.
+
+One cannot rely on deadlock detection as part of the normal program behaviour.
+It should only be used for debugging. Firstly, deadlock detection can be
+disabled or indefinately deferred due to activity. Secondly, the set of threads
+that deadlock detection throws exceptions to is not guaranteed to be minimal:
+a thread waiting on a deadlocked thread may also have an exception thrown to it
+even if it could make progress after the deadlocked thread is terminated.
+
+Deadlock detection can be invoked manually using ``performDeadlockDetection``
+from ``System.Mem.Experimental``.
+
+
+.. rts-flag:: -I ⟨seconds⟩
+
+ :default: 0.3 seconds
+
+ .. index::
+ single: idle GC
+
+ A major GC is automatically performed if the runtime has been idle (no
+ Haskell computation has been running) for a period of time. Set the amount
+ of idle time which must pass before a idle GC is performed.
+
+ Setting ``-I0`` disables the idle GC. This also has the unfortunate side
+ effect of disabling thread deadlock detection (the implementation of which
+ uses the idle GC).
+
+ For an interactive application, it is probably a good idea to use
+ the idle GC, because this will allow finalizers to run and
+ deadlocked threads to be detected in the idle time when no Haskell
+ computation is happening. Also, it will mean that a GC is less
+ likely to happen when the application is busy, and so responsiveness
+ may be improved. However, if the amount of live data in the heap is
+ particularly large, then the idle GC can cause a significant delay,
+ and too small an interval could adversely affect interactive
+ responsiveness.
+
+ The idle period timer only resets after some activity
+ by a Haskell thread. If your program is doing literally nothing then
+ after the first idle collection is triggered then no more future collections
+ will be scheduled until more work is performed.
+
+ Please let us know if it causes problems and/or could benefit from further
+ tuning.
+
+.. rts-flag:: -Iw ⟨seconds⟩
+
+ :default: 0 seconds
+
+ .. index::
+ single: idle GC
+
+ Set the minimum wait time between runs of the idle GC.
+
+ By default (and if idle GC is not disabled) a major
+ GC will be performed every time the process goes idle for a
+ sufficiently long duration (see :rts-flag:`-I ⟨seconds⟩`). For
+ large server processes accepting regular but infrequent requests
+ (e.g., once per second), an expensive, major GC may run after
+ every request. As an alternative to shutting off idle GC entirely
+ (with ``-I0``), a minimum wait time between idle GCs can be
+ specified with this flag. For example, ``-Iw60`` will ensure that
+ an idle GC runs at most once per minute.
+
+ This is an experimental feature, please let us know if it causes
+ problems and/or could benefit from further tuning.
+
.. _rts-options-statistics:
=====================================
testsuite/tests/rts/Makefile
=====================================
@@ -157,3 +157,11 @@ HSC2HS_OPTS = --cc="$(TEST_CC)" $(addprefix --cflag=,$(TEST_CC_OPTS)) --ld=$(TES
IOManager.hs: IOManager.hsc
'$(HSC2HS)' $(HSC2HS_OPTS) $<
+
+.PHONY: T26408c
+T26408c:
+ "$(TEST_HC)" $(TEST_HC_OPTS) -rtsopts T26408c.hs; \
+ ./T26408c +RTS -I0 -T & \
+ sleep 1; \
+ kill -s USR1 $$!; \
+ wait $$!
=====================================
testsuite/tests/rts/T26408a.hs
=====================================
@@ -0,0 +1,54 @@
+import Control.Concurrent
+import Control.Concurrent.STM
+import Control.Exception
+import Control.Monad
+
+-- | Test to make sure that we can detect a /partial deadlock/ that is /not/
+-- also a /system deadlock/: that is we can detect a set of deadlocked threads
+-- even when there are other unrelated threads that are waiting on I\/O or
+-- timeouts.
+--
+-- Historically this did not work in the non-threaded RTS which would only do
+-- deadlock detection if there was a system deadlock: no runnable threads /and/
+-- no pending I\/O or timers.
+--
+-- See Note [Deadlock detection]
+--
+main :: IO ()
+main = do
+
+ -- Set up two threads that are deadlocked on each other
+ aDone <- newTVarIO False
+ bDone <- newTVarIO False
+ let blockingThread theirDone ourDone =
+ atomically $ do
+ done <- readTVar theirDone
+ guard done
+ writeTVar ourDone True
+ _ <- forkIO (blockingThread bDone aDone)
+ _ <- forkIO (blockingThread aDone bDone)
+
+ -- Set up another thread that is blocked on a long timeout.
+ --
+ -- We use a timeout rather than I/O as it's more portable, whereas I/O waits
+ -- are different between posix and windows I/O managers.
+ --
+ -- One gotcha is that when the timeout completes then the deadlock will be
+ -- detected again (since the bug is about I/O or timeouts masking deadlock
+ -- detection). So for a reliable test the timeout used here must be longer
+ -- than the test framework's own timeout. So we use maxBound, and we adjust
+ -- the test framework's timeout to be short (see run_timeout_multiplier).
+ _ <- forkIO (threadDelay maxBound)
+
+ -- Wait on the two deadlocked threads to terminate. This is now a partial
+ -- deadlock but not a system deadlock (the main thread and the first two
+ -- threads are deadlocked, but the one waiting on the timeout is ok).
+ --
+ -- We now expect that the threads that are deadlocked are detected as such
+ -- and an exception is thrown to some or all of them.
+ --
+ -- Note that if this fails, the test itself will effectively deadlock and
+ -- will rely on the test framework's timeout.
+ atomically $ do
+ status <- mapM readTVar [aDone, bDone]
+ guard (or status)
=====================================
testsuite/tests/rts/T26408a.stderr
=====================================
@@ -0,0 +1,3 @@
+T26408a: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnSTM:
+
+thread blocked indefinitely in an STM transaction
=====================================
testsuite/tests/rts/T26408b.hs
=====================================
@@ -0,0 +1,41 @@
+import Control.Concurrent
+import Control.Concurrent.STM
+import Control.Exception
+import Control.Monad
+
+-- | Test to make sure that we can detect a /system deadlock/ promptly which
+-- means without relying on idle GC. To check this we have to turn off idle GC,
+-- (+RTS -I0) which is what normally triggers deadlock detection.
+--
+-- This is only expected to work in the non-threaded RTS. In principle this
+-- test could also work with the threaded RTS when using only a single
+-- capability, if the I\/O managers can support it. Currently that is not the
+-- case.
+--
+-- See Note [Deadlock detection]
+--
+main :: IO ()
+main = do
+
+ -- Set up two threads that are deadlocked on each other
+ aDone <- newTVarIO False
+ bDone <- newTVarIO False
+ let blockingThread theirDone ourDone =
+ atomically $ do
+ done <- readTVar theirDone
+ guard done
+ writeTVar ourDone True
+ _ <- forkIO (blockingThread bDone aDone)
+ _ <- forkIO (blockingThread aDone bDone)
+
+ -- Wait on the two deadlocked threads to terminate. This is now a system
+ -- deadlock, involving all three threads (the main thread and the other two).
+ --
+ -- We now expect that the threads that are deadlocked are detected as such
+ -- and an exception is thrown to some or all of them.
+ --
+ -- Note that if this fails, the test itself will effectively deadlock and
+ -- will rely on the test framework's timeout.
+ atomically $ do
+ status <- mapM readTVar [aDone, bDone]
+ guard (or status)
=====================================
testsuite/tests/rts/T26408b.stderr
=====================================
@@ -0,0 +1,3 @@
+T26408b: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnSTM:
+
+thread blocked indefinitely in an STM transaction
=====================================
testsuite/tests/rts/T26408c.hs
=====================================
@@ -0,0 +1,61 @@
+import Control.Concurrent
+import Control.Concurrent.STM
+import Control.Exception
+import Control.Monad
+
+import System.Posix.Signals
+import GHC.Stats.Experimental
+
+-- | Test to make sure that we do not falsely detect a deadlock when there is
+-- nothing to do except wait on signals. This is to make sure that our prompt
+-- system deadlock detection is not too eager.
+--
+-- We arrange a partial deadlock that can be broken if a signal arrives and
+-- the signal handler can unblock things.
+--
+-- This should /not/ result in any deadlock being detected, and no other odd
+-- behaviour, like getting caught in a loop and doing too many idle GCs. So we
+-- count and report the number of major GCs.
+--
+main :: IO ()
+main = do
+
+ -- Set up two threads that are deadlocked on each other
+ aDone <- newTVarIO False
+ bDone <- newTVarIO False
+ let blockingThread theirDone ourDone =
+ atomically $ do
+ done <- readTVar theirDone
+ guard done
+ writeTVar ourDone True
+
+ installHandler userDefinedSignal1
+ (Catch $ do
+ atomically $ writeTVar aDone True
+ >> writeTVar bDone True
+ putStrLn "caught SIGUSR1")
+ Nothing
+
+ _ <- forkIO (blockingThread bDone aDone)
+ _ <- forkIO (blockingThread aDone bDone)
+ yield
+
+ -- Wait on the deadlocked threads to terminate. We now expect that the threads
+ -- that are deadlocked are detected as such and an exception is raised.
+ -- Note that if this fails, the test itself will effectively deadlock and
+ -- will rely on the test framework's timeout.
+ putStrLn "waiting on deadlocked threads..."
+ atomically $ do
+ status <- mapM readTVar [aDone, bDone]
+ guard (or status)
+
+ -- We expect exactly 1 major GC: the one triggered for deadlock detection
+ -- where we find no deadlock. We should then sleep without doing more GC.
+ -- An easy mistake would be looping: detecting probable deadlock, do idle GC.
+ -- See notifyIdleGcDeadlock().
+ stats <- getRTSStats
+ putStrLn ("terminated normally, major GCs = " ++ show (major_gcs stats))
+ -- This test _could_ be sensitive to changes in default heap sizes etc. If
+ -- this number ends up as 2 for example, then adjust the RTS opts (in the
+ -- makefile) so that the heap is big enough that normally no major GC would
+ -- be needed.
=====================================
testsuite/tests/rts/T26408c.stdout
=====================================
@@ -0,0 +1,5 @@
+[1 of 2] Compiling Main ( T26408c.hs, T26408c.o )
+[2 of 2] Linking T26408c
+waiting on deadlocked threads...
+caught SIGUSR1
+terminated normally, major GCs = 1
=====================================
testsuite/tests/rts/all.T
=====================================
@@ -682,6 +682,24 @@ test('T22859',
omit_ways(llvm_ways + ['nonmoving_thr', 'nonmoving_thr_sanity'])],
compile_and_run, ['-with-rtsopts -A8K'])
+# Deadlock detection testsL
+# A: check we can detect a partial deadlock that is not a system deadlock
+test('T26408a', [exit_code(1), run_timeout_multiplier(0.1)], compile_and_run, [''])
+# B: check we can detect system deadlock with idle GC turned off (+RTS -I0)
+test('T26408b', [omit_ways(threaded_ways), extra_run_opts('+RTS -I0 -RTS'),
+ exit_code(1), run_timeout_multiplier(0.1)],
+ compile_and_run, ['-rtsopts'])
+# C: check we do not falsely detect deadlock when blocked only on signals
+# Skip for win32, wasm and js since this test requires posix signals
+test('T26408c', [when(opsys('mingw32'), skip),
+ when(arch('wasm32'), skip), js_skip,
+ run_timeout_multiplier(0.1)
+ ], makefile_test, ['T26408c'])
+# TODO: change omit_ways(threaded_ways) above
+# to all_io_managers_except['mio', 'winio']
+# and test +RTS -N1 with threaded ways, but will be broken for now.
+
+
# These tests need access to the internal RTS headers.
# TODO: there is probably some cleaner way to do this, and it should probably
# be guarded for in-tree tests, since it cannot work against an arbitrary
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/e5743b87c87d2a58b1e3c0c2551952…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/e5743b87c87d2a58b1e3c0c2551952…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/T27844] Switch off `do_not_prevent_rewriting` to see why it is useful
by Simon Peyton Jones (@simonpj) 27 Sep '26
by Simon Peyton Jones (@simonpj) 27 Sep '26
27 Sep '26
Simon Peyton Jones pushed to branch wip/T27844 at Glasgow Haskell Compiler / GHC
Commits:
4f82c588 by Simon Peyton Jones at 2026-09-28T07:59:40+01:00
Switch off `do_not_prevent_rewriting` to see why it is useful
- - - - -
1 changed file:
- compiler/GHC/Tc/Solver/Equality.hs
Changes:
=====================================
compiler/GHC/Tc/Solver/Equality.hs
=====================================
@@ -2291,6 +2291,7 @@ canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
; check_result <- checkTypeEq ev eq_rel lhs rhs
; case check_result of
PuFail reason
+{-
| reason `cterHasOnlyProblems` do_not_prevent_rewriting
-> do { new_ev <- rewriteEqEvidenceSwapOnly ev eq_rel swapped lhs rhs
; continueWith $ Right $
@@ -2298,6 +2299,7 @@ canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
, eq_lhs = lhs , eq_rhs = rhs } }
| otherwise
+-}
-> -- We can't unify, and this equality should not be used to rewrite
-- other constraints (e.g. because it has an occurs check).
-- So add it to the inert Irreds.
@@ -2328,6 +2330,7 @@ canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
, eq_lhs = lhs
, eq_rhs = reductionReducedType rhs_redn } } }
+{-
-- | Some problems prevent /unification/ but not /rewriting/:
--
-- Skolem-escape: if we have [W] alpha[2] ~ Maybe b[3]
@@ -2339,6 +2342,8 @@ canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
do_not_prevent_rewriting :: CheckTyEqResult
do_not_prevent_rewriting = cteProblem cteSkolemEscape S.<>
cteProblem cteConcrete
+-}
+
----------------------
finishCanWithIrred :: CtIrredReason -> CtEvidence
-> TcS (StopOrContinue (Either IrredCt a))
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/4f82c588e8cfcf0540006d5c6532732…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/4f82c588e8cfcf0540006d5c6532732…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/T27844] Switch off `do_not_prevent_rewriting` to see why it is useful
by Simon Peyton Jones (@simonpj) 27 Sep '26
by Simon Peyton Jones (@simonpj) 27 Sep '26
27 Sep '26
Simon Peyton Jones pushed to branch wip/T27844 at Glasgow Haskell Compiler / GHC
Commits:
7efb4ae9 by Simon Peyton Jones at 2026-09-27T23:48:39+01:00
Switch off `do_not_prevent_rewriting` to see why it is useful
- - - - -
1 changed file:
- compiler/GHC/Tc/Solver/Equality.hs
Changes:
=====================================
compiler/GHC/Tc/Solver/Equality.hs
=====================================
@@ -2291,6 +2291,7 @@ canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
; check_result <- checkTypeEq ev eq_rel lhs rhs
; case check_result of
PuFail reason
+{-
| reason `cterHasOnlyProblems` do_not_prevent_rewriting
-> do { new_ev <- rewriteEqEvidenceSwapOnly ev eq_rel swapped lhs rhs
; continueWith $ Right $
@@ -2298,6 +2299,7 @@ canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
, eq_lhs = lhs , eq_rhs = rhs } }
| otherwise
+-}
-> -- We can't unify, and this equality should not be used to rewrite
-- other constraints (e.g. because it has an occurs check).
-- So add it to the inert Irreds.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/7efb4ae9f22d9b73266e2c2cfa750b9…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/7efb4ae9f22d9b73266e2c2cfa750b9…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
Simon Peyton Jones pushed to branch wip/T27731 at Glasgow Haskell Compiler / GHC
Commits:
d2286d58 by Simon Peyton Jones at 2026-09-27T23:43:04+01:00
Wibbles [skip-ci]
- - - - -
2 changed files:
- compiler/GHC/Tc/Utils/Monad.hs
- testsuite/tests/typecheck/should_fail/ClassOperator.stderr
Changes:
=====================================
compiler/GHC/Tc/Utils/Monad.hs
=====================================
@@ -69,7 +69,7 @@ module GHC.Tc.Utils.Monad(
wrapLocM, wrapLocFstM, wrapLocFstMA, wrapLocSndM, wrapLocSndMA, wrapLocM_,
wrapLocMA_,wrapLocMA,
getErrsVar, setErrsVar,
- addErr,
+ addErr, captureMessagesTcM,
failWith, failAt,
addErrAt, addErrs,
checkErr, checkErrAt,
@@ -1604,26 +1604,26 @@ tcTryM thing_inside
-- built-in in exception; see IOEnv.failM
-----------------------
-capture_constraints :: TcM r -> TcM (r, WantedConstraints)
--- capture_constraints simply captures and returns the
+captureConstraintsTcM :: TcM r -> TcM (r, WantedConstraints)
+-- captureConstraintsTcM simply captures and returns the
-- constraints generated by thing_inside
-- Precondition: thing_inside must not throw an exception!
-- Reason for precondition: an exception would blow past the place
-- where we read the lie_var, and we'd lose the constraints altogether
-capture_constraints thing_inside
+captureConstraintsTcM thing_inside
= do { lie_var <- newTcRef emptyWC
; res <- updLclEnv (\ env -> env { tcl_lie = lie_var }) $
thing_inside
; lie <- readTcRef lie_var
; return (res, lie) }
-capture_messages :: TcM r -> TcM (r, Messages TcRnMessage)
--- capture_messages simply captures and returns the
+captureMessagesTcM :: TcM r -> TcM (r, Messages TcRnMessage)
+-- captureMessagesTcM simply captures and returns the
-- errors and warnings generated by thing_inside
-- Precondition: thing_inside must not throw an exception!
-- Reason for precondition: an exception would blow past the place
-- where we read the msg_var, and we'd lose the constraints altogether
-capture_messages thing_inside
+captureMessagesTcM thing_inside
= do { msg_var <- newTcRef emptyMessages
; res <- setErrsVar msg_var thing_inside
; msgs <- readTcRef msg_var
@@ -1643,8 +1643,8 @@ capture_messages thing_inside
-- propagate any errors/warnings generated by m
askErrsFound :: TcRn a -> TcRn (a, Bool)
askErrsFound thing_inside
- = do { ((mb_res, lie), msgs) <- capture_messages $
- capture_constraints $
+ = do { ((mb_res, lie), msgs) <- captureMessagesTcM $
+ captureConstraintsTcM $
tcTryM thing_inside
; addMessages msgs
@@ -1665,7 +1665,7 @@ tryCaptureConstraints :: TcM a -> TcM (Maybe a, WantedConstraints)
-- it returns Nothing and the /insoluble/ constraints
-- Error messages are propagated
tryCaptureConstraints thing_inside
- = do { (mb_res, lie) <- capture_constraints $
+ = do { (mb_res, lie) <- captureConstraintsTcM $
tcTryM thing_inside
-- See Note [Constraints and errors]
@@ -1794,7 +1794,7 @@ tryTc :: TcRn a -> TcRn (Maybe a, Messages TcRnMessage)
-- It also returns all the errors and warnings accumulated by m
-- It always succeeds (never raises an exception)
tryTc thing_inside
- = capture_messages (attemptM thing_inside)
+ = captureMessagesTcM (attemptM thing_inside)
-----------------------
discardErrs :: TcRn a -> TcRn a
@@ -1827,8 +1827,8 @@ tryTcDiscardingErrs' :: (WantedConstraints -> Messages TcRnMessage -> r -> Bool)
-- otherwise discard everything from 'thing_inside', including errors,
-- and try 'recover' instead.
tryTcDiscardingErrs' validate recover_invalid recover_error thing_inside
- = do { ((mb_res, lie), msgs) <- capture_messages $
- capture_constraints $
+ = do { ((mb_res, lie), msgs) <- captureMessagesTcM $
+ captureConstraintsTcM $
tcTryM thing_inside
; case mb_res of
Just res | not (errorsFound msgs)
=====================================
testsuite/tests/typecheck/should_fail/ClassOperator.stderr
=====================================
@@ -1,52 +1,52 @@
-
ClassOperator.hs:14:3: error: [GHC-39999]
• Could not deduce ‘a ><> b0’
from the context: a ><> b
bound by the type signature for:
- (**>) :: forall a b. (a ><> b) => a -> a -> ()
+ (<**) :: forall a b. (a ><> b) => a -> a -> ()
at ClassOperator.hs:14:3-44
The type variable ‘b0’ is ambiguous
- • In the ambiguity check for ‘**>’
+ • In the ambiguity check for ‘<**’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method:
- (**>) :: forall a b. (a ><> b) => a -> a -> ()
+ (<**) :: forall a b. (a ><> b) => a -> a -> ()
In the class declaration for ‘><>’
ClassOperator.hs:14:3: error: [GHC-39999]
• Could not deduce ‘a ><> b0’
from the context: a ><> b
bound by the type signature for:
- (**<) :: forall a b. (a ><> b) => a -> a -> ()
+ (>**) :: forall a b. (a ><> b) => a -> a -> ()
at ClassOperator.hs:14:3-44
The type variable ‘b0’ is ambiguous
- • In the ambiguity check for ‘**<’
+ • In the ambiguity check for ‘>**’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method:
- (**<) :: forall a b. (a ><> b) => a -> a -> ()
+ (>**) :: forall a b. (a ><> b) => a -> a -> ()
In the class declaration for ‘><>’
ClassOperator.hs:14:3: error: [GHC-39999]
• Could not deduce ‘a ><> b0’
from the context: a ><> b
bound by the type signature for:
- (>**) :: forall a b. (a ><> b) => a -> a -> ()
+ (**<) :: forall a b. (a ><> b) => a -> a -> ()
at ClassOperator.hs:14:3-44
The type variable ‘b0’ is ambiguous
- • In the ambiguity check for ‘>**’
+ • In the ambiguity check for ‘**<’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method:
- (>**) :: forall a b. (a ><> b) => a -> a -> ()
+ (**<) :: forall a b. (a ><> b) => a -> a -> ()
In the class declaration for ‘><>’
ClassOperator.hs:14:3: error: [GHC-39999]
• Could not deduce ‘a ><> b0’
from the context: a ><> b
bound by the type signature for:
- (<**) :: forall a b. (a ><> b) => a -> a -> ()
+ (**>) :: forall a b. (a ><> b) => a -> a -> ()
at ClassOperator.hs:14:3-44
The type variable ‘b0’ is ambiguous
- • In the ambiguity check for ‘<**’
+ • In the ambiguity check for ‘**>’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the class method:
- (<**) :: forall a b. (a ><> b) => a -> a -> ()
+ (**>) :: forall a b. (a ><> b) => a -> a -> ()
In the class declaration for ‘><>’
+
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d2286d58c40bba550efdf12b2870b66…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/d2286d58c40bba550efdf12b2870b66…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/az/exactprint-annotation-rationalisation] 59 commits: Correct the documentation of `explicitUnits`
by Alan Zimmerman (@alanz) 27 Sep '26
by Alan Zimmerman (@alanz) 27 Sep '26
27 Sep '26
Alan Zimmerman pushed to branch wip/az/exactprint-annotation-rationalisation at Glasgow Haskell Compiler / GHC
Commits:
53eead15 by Wolfgang Jeltsch at 2026-09-24T20:12:03-04:00
Correct the documentation of `explicitUnits`
Resolves #27787.
- - - - -
35bf6f4b by Simon Jakobi at 2026-09-24T20:12:49-04:00
Remove stale specHeader bullet from Note [Don't make fillers for constraint types]
The Note said that Specialise.specHeader also calls mkAbsentFiller.
abfc224a27c removed that call: the specialiser no longer drops dead
arguments at all, see Note [Do not drop dead args from specialisations]
in GHC.Core.Opt.Specialise.
Assisted-by: Claude Fable 5.1
- - - - -
1abef1ef by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Improve the `mhu-closure` makefile
- - - - -
a79a68ea by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Make `ghci-multi` hide all unneeded packages
- - - - -
d6e72450 by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Improve the definition of the home unit closure property
- - - - -
c9a2766d by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Re-implement the home unit closure check
Compared to the previous implementation, the new one has the following
advantages:
* It is correct.
- It distinguishes between units that have the same unit ID but
different ABI hashes.
- When `-hide-all-packages` is not used, it considers as home unit
dependencies also units that are made implicitly available
because they are in the package database.
* It seems to be faster in usual settings.
- In particular, it does not have a preparation phase in which it
merges the dependency information from all home units, so that
its running time does not grow linearly with the size of the
package database in normal Cabal scenarios.
* It is (hopefully) clearer.
* It is better documented.
- - - - -
fa96acf8 by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Remove erroneous GHC flag in `hadrian-multi`
The flag was `-Werror=-Wno-error=incomplete-record-selectors`. Its
purpose was to permit incomplete record selectors, but because of the
illegal combination of `-Werror=` and `-Wno-error` it was ignored,
without any negative consequences.
- - - - -
9f48a5b9 by Alan Zimmerman at 2026-09-25T17:02:50-04:00
EPA: Add specific layout field to AnnList
Up to now AnnList has effectively served to represent lists of things
having layout, regardless of whether they do or not.
In Haskell, layout is optional in many places, as there is also an
alternative syntax using braces and semi-colons, which is free-form.
This commit introduces AnnListLayout, a data structure capturing this
distinction.
It is deliberately set up similarly to EpLayout, so that these two can
be merged in time.
The transition is done mechanically, where AnnListBraces is not used
yet, the original Nothing case maps to AnnListNoLayout, and the Just
case to AnnListLayout.
Later commits will fine tune these against the actual parser layout.
- - - - -
81e219fd by Alan Zimmerman at 2026-09-27T09:57:12+01:00
EPA: Use EpaLocation in EpVirtualBraces
Haddock processing uses EpLayout.EpVirtualBraces to record the current
layout column, for allocating haddock comments correctly.
For exact printing, we need the line as well as the col, so we know
how many lines to skip when the indented code block starts.
This commit updates EpLayout.EpVirtualBraces so that it carries an
EpaLocation instead of just the Int column, to allow it to be used for
both purposes.
It is a simple mechanical change, with no other impact, but it allows
us to use EpLayout instead of AnnListLayout in AnnList, in the next
commit in this series.
- - - - -
89ff21da by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Use vocurly as basis for AnnListLayout
For the existing cases that populate AnnList.
This also shows up AnnList usages that can never capture layout.
- - - - -
6bc967fa by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: AnnList clean up patch for empty where clause
- - - - -
d091def3 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Add TTG fields to FamilyInfo
This commit is a simple add, with unused extension fields.
It lays the groundwork for using them for exact print annotations
- - - - -
b4437661 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
WIP annclassdecl
- - - - -
ee3e8896 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Update AnnClsInstDecl to contain AnnList
It was already separately tracking the '{', '}' and leading ';' values.
This aligns it with layout-introducing parser productions ('vocurly'),
so the layout processing can be handled uniformly
- - - - -
9312f068 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Tidy up markAnnListA'
It takes an action which can modify the AnnList, but this
is never used. So remove it.
- - - - -
97f2bb65 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Use AnnList for GADT declarations
- - - - -
bf81506e by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Use AnnList in AnnClassDecl
- - - - -
f930eea8 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
MOVE EPA: Plan for Fixing AnnList Layout Properly
- - - - -
af4534ac by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make local binds located
Use LHsLocalBinds instead of HsLocalBinds
This reverses an earlier change, and brings processing of local binds
into alignement with the exact printing principle that the LocatedA
contents provide the location and extent of the enclosed item,
together with any comments or context-specific trailing items like
commas or semi colons.
The internal TTG extension points only carry exact print annotations
related to printing the item within its bounds.
This change brings back the problem of EmptyLocalBinds, which by
definition cannot have a location. These get a noSrcSpan location,
which is ignored in the exact printing process.
- - - - -
5bc8abf7 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Use AnnList in HsMultiIf
It introduces layout, capture it
- - - - -
701dbb38 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Use AnnList in DecBrl
- - - - -
b21856e1 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Replace AnnListLayout / AnnListBrackets with EpLayout
- - - - -
3d1b5cee by Alan Zimmerman at 2026-09-27T10:11:11+01:00
WIP: setLayoutBoth inside markAnnListA
Current problem is it is a global one-shot, and MG has one too
Check: is it needed?
if so, honouring EpLayout may be needed
- - - - -
517d9c62 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA some tests for layout. WIP
- - - - -
250e388f by Alan Zimmerman at 2026-09-27T10:11:11+01:00
MOVE: Exactprint layout scope plan. Do not commit
- - - - -
a8bda73c by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: tidy up a bit. Combine somewhere else
- - - - -
b0623353 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Introduce LayoutFrame stacks in ExactPrint state
- - - - -
0d74271f by Alan Zimmerman at 2026-09-27T10:11:11+01:00
Align with ghc-exactprint
- - - - -
4c71a47a by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Fix AnnList leading semis when layout
When there is layout, anything printed triggers it, including leading
semis.
Add some test case examples too
- - - - -
f3198746 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
Exactprint: tidy up layout capturing
- - - - -
b01f993d by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: add makeDelta test for Test20297
The original #20297 related to comment placement for exact printing
after running ExactPrint.makeDelta on ParsedSource. Add an additional
test that explicitly tests this.
- - - - -
a462788f by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make ValBinds LocatedA in HsLocalBindsLR
If we have items with an AnnList Annotation, as part of another, which
has different content, it must be LocatedA. This is a technical requirement
due to the way comment allocation happens during the makeDelta processing.
Comments are queued for printing, and as soon as any non-comment is to
be printed, the pending comments before that position are printed, and
in makeDelta processing these attached as preceding comments to the thing
just printed.
The means they end up captured inside the layout region of the AnnList item.
So for
foo = x
where -- comment
x = 3
the comment ends up as a preceding comment of `x = 3`, inside the
layout, and prints as
foo = x
where
-- comment
x = 3
- - - - -
366a7f4c by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make IPBinds LocatedA
- - - - -
859c4ead by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make ClsInstDecl decls LocatedA
- - - - -
6333e313 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make ClassDecls decls LocatedA
- - - - -
5f5e4bd2 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make ClosedTypeFamily equations Located
For FamilyDecl/ClosedTypeFamily
- - - - -
ddb07ab0 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make dd_cons located
- - - - -
90ca508c by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make HsMultiIf options located
- - - - -
e32e1d5d by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make DecBrl contents LocatedA
- - - - -
d48bd4ed by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Located do statements
- - - - -
5acd759a by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Print RecStmt located
- - - - -
d1d40ffb by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: HsCmdDo Located
- - - - -
a60b28fa by Alan Zimmerman at 2026-09-27T10:11:11+01:00
Extend Test20297 for additional occurrences
- - - - -
cef6a257 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Tweak PatSynBind location for comments
- - - - -
4d9588bc by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: WIP, add test for where ;; ValBinds
- - - - -
fc91c514 by Alan Zimmerman at 2026-09-27T10:11:11+01:00
EPA: Make EpVirtualBraces noSpanAnchor if no contents
Otherwise the vocurly is emitted at the start of the next
item parsed
Check: is this needed, or just a check for empty contents?
- - - - -
9914ff22 by Alan Zimmerman at 2026-09-27T16:09:47+01:00
EPA: Use EpNoLayout in AnnList with empty contents and no explicit braces
TODO: get rid of unused first arg to epVirtualBraces
- - - - -
c13ed1d8 by Alan Zimmerman at 2026-09-27T16:10:19+01:00
TODO: XClassDecl: remove additional EpLayout
- - - - -
fd49b138 by Alan Zimmerman at 2026-09-27T16:10:19+01:00
WIP: turn off exactprint debug printing
- - - - -
2ca50511 by Alan Zimmerman at 2026-09-27T16:10:19+01:00
EPA: Remove CanUpdateAnchorOnly
- - - - -
53f9429e by Alan Zimmerman at 2026-09-27T16:10:19+01:00
EPA: Sync with ghc-exactprint after EPWriter -> CPS
- - - - -
eb753e22 by Alan Zimmerman at 2026-09-27T18:43:49+01:00
EPA: Fix EpVirtualBraces for HsDo
The start of layout must include the leading semi, so we comb3 to
ensure it is included if the initial stmts are empty
- - - - -
23f600c3 by Alan Zimmerman at 2026-09-27T18:45:21+01:00
Plan update DO NOT COMMIT
- - - - -
3ba8e610 by Alan Zimmerman at 2026-09-27T18:45:21+01:00
EPA: First pass implementation of HsList, for ClassDecls
Just as a straight list replacement to start with, no payload.
This shows the scope and invasiveness of the initial change
- - - - -
badd3010 by Alan Zimmerman at 2026-09-27T18:45:21+01:00
EPA: HsList attempt WIP
- - - - -
850a5f08 by Alan Zimmerman at 2026-09-27T18:45:21+01:00
Enable ppr test for Haddock1. It currently fails
- - - - -
f1d8367c by Alan Zimmerman at 2026-09-27T18:45:21+01:00
WIP on removing NoEpAnn. Likely abandon
- - - - -
26d9b114 by Alan Zimmerman at 2026-09-27T18:45:21+01:00
EPA: Add an overview doc for exact printing
- - - - -
0a75a65b by Simon Peyton Jones at 2026-09-27T18:45:21+01:00
Added an intro section
- - - - -
115 changed files:
- .gitlab-ci.yml
- + ANNLIST-LAYOUT-PLAN.md
- + ExactPrint.md
- + LAYOUT-SCOPE-PLAN.md
- compiler/GHC/Core/Opt/WorkWrap/Utils.hs
- compiler/GHC/Driver/Backpack.hs
- compiler/GHC/Driver/Downsweep.hs
- compiler/GHC/Driver/Errors/Ppr.hs
- compiler/GHC/Driver/Errors/Types.hs
- compiler/GHC/Driver/Main/Interactive.hs
- compiler/GHC/Hs.hs
- compiler/GHC/Hs/Binds.hs
- compiler/GHC/Hs/Decls.hs
- compiler/GHC/Hs/DocString.hs
- compiler/GHC/Hs/Dump.hs
- compiler/GHC/Hs/Expr.hs
- compiler/GHC/Hs/ImpExp.hs
- compiler/GHC/Hs/Instances.hs
- compiler/GHC/Hs/Stats.hs
- compiler/GHC/Hs/Utils.hs
- compiler/GHC/HsToCore/Arrows.hs
- compiler/GHC/HsToCore/Docs.hs
- compiler/GHC/HsToCore/Expr.hs
- compiler/GHC/HsToCore/Expr.hs-boot
- compiler/GHC/HsToCore/Match.hs
- compiler/GHC/HsToCore/Pmc/Desugar.hs
- compiler/GHC/HsToCore/Quote.hs
- compiler/GHC/HsToCore/Ticks.hs
- compiler/GHC/Iface/Ext/Ast.hs
- compiler/GHC/Parser.y
- compiler/GHC/Parser/Annotation.hs
- compiler/GHC/Parser/PostProcess.hs
- compiler/GHC/Parser/PostProcess/Haddock.hs
- compiler/GHC/Rename/Bind.hs
- compiler/GHC/Rename/Expr.hs
- compiler/GHC/Rename/Module.hs
- compiler/GHC/Rename/Names.hs
- compiler/GHC/Rename/Splice.hs
- compiler/GHC/Rename/Utils.hs
- compiler/GHC/Runtime/Eval.hs
- compiler/GHC/Tc/Deriv.hs
- compiler/GHC/Tc/Deriv/Functor.hs
- compiler/GHC/Tc/Deriv/Generate.hs
- compiler/GHC/Tc/Gen/Arrow.hs
- compiler/GHC/Tc/Gen/Bind.hs
- compiler/GHC/Tc/Gen/Expr.hs
- compiler/GHC/Tc/Gen/Match.hs
- compiler/GHC/Tc/Module.hs
- compiler/GHC/Tc/TyCl.hs
- compiler/GHC/Tc/TyCl/Instance.hs
- compiler/GHC/Tc/TyCl/PatSyn.hs
- compiler/GHC/Tc/Types/Origin.hs
- compiler/GHC/Tc/Utils/Env.hs
- compiler/GHC/Tc/Zonk/Type.hs
- compiler/GHC/ThToHs.hs
- compiler/GHC/Unit/Env.hs
- compiler/GHC/Unit/State.hs
- compiler/Language/Haskell/Syntax.hs
- compiler/Language/Haskell/Syntax/Basic.hs
- compiler/Language/Haskell/Syntax/Binds.hs
- compiler/Language/Haskell/Syntax/Decls.hs
- compiler/Language/Haskell/Syntax/Expr.hs
- compiler/Language/Haskell/Syntax/Extension.hs
- ghc/GHCi/UI.hs
- hadrian/ghci-multi-cabal.in
- testsuite/tests/driver/multipleHomeUnits/mhu-closure/Makefile
- testsuite/tests/driver/multipleHomeUnits/mhu-closure/mhu-closure.stderr
- testsuite/tests/driver/multipleHomeUnits/mhu-closure/mhu-closure.stdout
- testsuite/tests/ghc-api/exactprint/T22919.stderr
- testsuite/tests/ghc-api/exactprint/Test20239.stderr
- testsuite/tests/ghc-api/exactprint/ZeroWidthSemi.stderr
- testsuite/tests/haddock/should_compile_flag_haddock/T17544.stderr
- testsuite/tests/haddock/should_compile_flag_haddock/T17544_kw.stderr
- testsuite/tests/haddock/should_compile_flag_haddock/T24221.stderr
- testsuite/tests/module/mod185.stderr
- testsuite/tests/parser/should_compile/DumpParsedAst.stderr
- testsuite/tests/parser/should_compile/DumpParsedAstComments.stderr
- testsuite/tests/parser/should_compile/DumpRenamedAst.stderr
- testsuite/tests/parser/should_compile/DumpSemis.hs
- testsuite/tests/parser/should_compile/DumpSemis.stderr
- testsuite/tests/parser/should_compile/DumpTypecheckedAst.stderr
- testsuite/tests/parser/should_compile/KindSigs.stderr
- testsuite/tests/parser/should_compile/T14189.stderr
- testsuite/tests/parser/should_compile/T15279.stderr
- testsuite/tests/parser/should_compile/T15323.stderr
- testsuite/tests/parser/should_compile/T20452.stderr
- testsuite/tests/parser/should_compile/T20718.stderr
- testsuite/tests/parser/should_compile/T20718b.stderr
- testsuite/tests/parser/should_compile/T20846.stderr
- testsuite/tests/parser/should_compile/T23315/T23315.stderr
- testsuite/tests/perf/compiler/hard_hole_fits.stderr
- testsuite/tests/printer/AnnotationNoListTuplePuns.stdout
- + testsuite/tests/printer/Layout.hs
- testsuite/tests/printer/Makefile
- testsuite/tests/printer/Ppr003.hs
- testsuite/tests/printer/T18791.stderr
- + testsuite/tests/printer/Test20297.expected.hs
- testsuite/tests/printer/Test20297.hs
- testsuite/tests/printer/Test20297.stdout
- + testsuite/tests/printer/Test20297MD.stdout
- testsuite/tests/printer/Test24533.stdout
- testsuite/tests/printer/all.T
- utils/check-exact/ExactPrint.hs
- utils/check-exact/Main.hs
- utils/check-exact/Parsers.hs
- utils/check-exact/Transform.hs
- utils/check-exact/Utils.hs
- utils/haddock/haddock-api/src/Haddock/Backends/Hoogle.hs
- utils/haddock/haddock-api/src/Haddock/Backends/LaTeX.hs
- utils/haddock/haddock-api/src/Haddock/Backends/Xhtml/Decl.hs
- utils/haddock/haddock-api/src/Haddock/Convert.hs
- utils/haddock/haddock-api/src/Haddock/GhcUtils.hs
- utils/haddock/haddock-api/src/Haddock/Interface/Create.hs
- utils/haddock/haddock-api/src/Haddock/Interface/Rename.hs
- utils/haddock/haddock-api/src/Haddock/Types.hs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/88562119b53de864156349b1b274f7…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/88562119b53de864156349b1b274f7…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc][wip/io-manager-deadlock-detection] 33 commits: Correct the documentation of `explicitUnits`
by Duncan Coutts (@dcoutts) 27 Sep '26
by Duncan Coutts (@dcoutts) 27 Sep '26
27 Sep '26
Duncan Coutts pushed to branch wip/io-manager-deadlock-detection at Glasgow Haskell Compiler / GHC
Commits:
53eead15 by Wolfgang Jeltsch at 2026-09-24T20:12:03-04:00
Correct the documentation of `explicitUnits`
Resolves #27787.
- - - - -
35bf6f4b by Simon Jakobi at 2026-09-24T20:12:49-04:00
Remove stale specHeader bullet from Note [Don't make fillers for constraint types]
The Note said that Specialise.specHeader also calls mkAbsentFiller.
abfc224a27c removed that call: the specialiser no longer drops dead
arguments at all, see Note [Do not drop dead args from specialisations]
in GHC.Core.Opt.Specialise.
Assisted-by: Claude Fable 5.1
- - - - -
1abef1ef by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Improve the `mhu-closure` makefile
- - - - -
a79a68ea by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Make `ghci-multi` hide all unneeded packages
- - - - -
d6e72450 by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Improve the definition of the home unit closure property
- - - - -
c9a2766d by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Re-implement the home unit closure check
Compared to the previous implementation, the new one has the following
advantages:
* It is correct.
- It distinguishes between units that have the same unit ID but
different ABI hashes.
- When `-hide-all-packages` is not used, it considers as home unit
dependencies also units that are made implicitly available
because they are in the package database.
* It seems to be faster in usual settings.
- In particular, it does not have a preparation phase in which it
merges the dependency information from all home units, so that
its running time does not grow linearly with the size of the
package database in normal Cabal scenarios.
* It is (hopefully) clearer.
* It is better documented.
- - - - -
fa96acf8 by Wolfgang Jeltsch at 2026-09-25T17:02:07-04:00
Remove erroneous GHC flag in `hadrian-multi`
The flag was `-Werror=-Wno-error=incomplete-record-selectors`. Its
purpose was to permit incomplete record selectors, but because of the
illegal combination of `-Werror=` and `-Wno-error` it was ignored,
without any negative consequences.
- - - - -
9f48a5b9 by Alan Zimmerman at 2026-09-25T17:02:50-04:00
EPA: Add specific layout field to AnnList
Up to now AnnList has effectively served to represent lists of things
having layout, regardless of whether they do or not.
In Haskell, layout is optional in many places, as there is also an
alternative syntax using braces and semi-colons, which is free-form.
This commit introduces AnnListLayout, a data structure capturing this
distinction.
It is deliberately set up similarly to EpLayout, so that these two can
be merged in time.
The transition is done mechanically, where AnnListBraces is not used
yet, the original Nothing case maps to AnnListNoLayout, and the Just
case to AnnListLayout.
Later commits will fine tune these against the actual parser layout.
- - - - -
388f9c66 by Duncan Coutts at 2026-09-27T18:43:02+01:00
Move idle GC tracking to its own file, step 1 of 2
Add a new IdleGC.{c,h} module. Move the RecentActivity type,
recent_activity variable and {get,set}RecentActivity wrappers.
- - - - -
fac92964 by Duncan Coutts at 2026-09-27T18:43:02+01:00
Move idle GC functionality to its own file, step 2 of 2
We can now fully encapsulate the recent_activity state, exposing just
three hooks, and one query used in the scheduler and timer tick.
This is a prelude to making changes to the implementation of tracking
when to perform an idle gc.
- - - - -
39167176 by Duncan Coutts at 2026-09-27T18:43:02+01:00
Correct comments in idle GC code
The comments still referred to stopTimer/startTimer. These were
replaced recently by pauseTimer/unpauseTimer, but the comment was not
updated.
- - - - -
6ceae159 by Duncan Coutts at 2026-09-27T18:43:02+01:00
Make signal handling be a responsibility of the I/O manager(s)
Previously it was scattered between I/O managers and the scheduler, and
especially the scheduler's deadlock detection.
Previously the scheduler would poll for pending signals each iteration
of the scheduler loop. The scheduler also had some hairy signal
functionality in the deadlock detection: in the non-threaded RTS (only)
if there were still no threads running after deadlock detection then it
would block waiting for signals.
But signals can and (in my opinion) should be thought of as just a funny
kind of I/O, and thus should be a responsibility of the I/O manager.
So now we have the I/O managers poll for signals when they are polling
for I/O completion (and removing the separate poll in the scheduler).
And when I/O managers block waiting for I/O then they now also start
signal handlers if they get interrupted by a signal. Crucially, if there
is no pending I/O or timers, the awaitCompletedTimeoutsOrIO will still
block waiting for signals.
This patch puts us into an intermediate state: it temporarily breaks
deadlock detection in the non-threaded RTS. The waiting on I/O currently
happens before deadlock detection. This means we'll now wait forever on
signals before doing deadlock detection. We need to move waiting after
deadlock detection. We'll do that in a later patch.
- - - - -
2d010a57 by Duncan Coutts at 2026-09-27T18:43:02+01:00
Clean up the RTS internal signal handling API
Now that the I/O manager is responsible for signals, we can simplify the
API we present for signal handling.
We now just need startPendingSignalHandlers, which is called from the
I/O managers. We can get rid of awaitUserSignals. We also don't need
RtsSignals.h to re-export the platform-specific posix/Signals.h or
win32/ConsoleHandler.h
We can also hide more of the implementation of signals. Less has to be
exposed in posix/Signals.h or win32/ConsoleHandler.h. Indeed,
posix/Signals.h becomes empty and we remove it. Partly this is because
we don't need inline functions (or macros) in the interface.
Also remove signal_handlers from RTS ABI exported symbols list. It does
not appear to have any users in the core libs, and its really an
internal implementation detail. It should not be exposed unless it's
really necessary.
- - - - -
345837c6 by Duncan Coutts at 2026-09-27T18:43:02+01:00
In the scheduler, move I/O blocking after deadlock detection
To make deadlock detection effective in the non-threaded RTS when there
are deadlocked threads and other unrelated threads waiting on I/O, we
need to arrange to do deadlock detection before we block in scheduler
to wait on I/O.
The solution is to:
1. adjust scheduleFindWork, which runs before deadlock detection, to
only poll for I/O and not block; and
2. add a step after deadlock detection to wait on I/O if there are
still no threads to run (and there's any I/O or timeouts outstanding)
The scheduleCheckBlockedThreads is now so simple that it made more sense
to inline it into scheduleFindWork.
- - - - -
2456bc80 by Duncan Coutts at 2026-09-27T18:43:02+01:00
Remove bogus anyPendingTimeoutsOrIO guard from scheduleDetectDeadlock
The deadlock detection was only invoked if both of these conditions
hold:
1. the run queue is empty
2. there is no pending I/O or timeouts
The second condition is unnecessary. The deadlock detection mechanism
can find deadlocks even if there are other threads waiting on I/O or
timers. Having this extra condition means that we fail to detect
blocked threads if there are any threads waiting on I/O or timers.
Part of fixing issue #26408
- - - - -
84a9ada4 by Duncan Coutts at 2026-09-27T18:43:02+01:00
Don't consider pending I/O for early context switch optimisation
Context switches are normally initiated by the timer signal. If however
the user specifies "context switch as often as possible", with +RTS -C0
then the scheduler arranges for an early context switch (when it's just
about to run a Haskell thread).
Context switching very often is expensive, so as an optimisation there
cases where we do not arrange an early context switch:
1. if there's no other threads to run
2. if there is no pending I/O or timers
This patch eliminates case 2, leaving only case 1.
The rationale is as follows. The use of this was inconsistent across
platforms and threaded/non-threaded RTS ways. It only worked on the
non-threaded RTS and on Windows only worked for the win32-legacy I/O
manager. On all other combinations anyPendingTimeoutsOrIO would always
return false. The fact that nobody noticed and complained about this
inconsistency suggests that the feature is not relied upon.
If however it turns out that applications do rely on this, then the
proper thing to do is not to restore this check, but to add a new I/O
manager hint function that returns if there is any pending events that
are likely to happen *soon*: for example timeouts expiring within one
timeslice, or I/O waits on things likely to complete soon like disk I/O,
but not for example socket/pipe I/O.
The motivation to avoid this use of anyPendingTimeoutsOrIO is to
allow us to eliminate anyPendingTimeoutsOrIO entirely. All other uses
of this are just guards on {await,poll}CompletedTimeoutsOrIO and
the guards can safely be folded into those functions. This will better
cope with some I/O managers having no proper implementation of
anyPendingTimeoutsOrIO.
Ultimately this will let us simplify the scheduler which currently has
to have special #ifdef mingw32_HOST_OS cases to cope with the lack of a
working anyPendingTimeoutsOrIO for some Windows I/O managers
- - - - -
0bfa75e3 by Duncan Coutts at 2026-09-27T18:43:02+01:00
Remove anyPendingTimeoutsOrIO guarding {poll,await}CompletedTimeoutsOrIO
Previously the API of the I/O manager used a two step process: check
anyPendingTimeoutsOrIO and then call {poll,await}CompletedTimeoutsOrIO.
This was primarily there as a performance thing, to cheaply check if we
need to do anything.
And then because anyPendingTimeoutsOrIO existed, it was used for other
things too. We have now eliminated the other uses, and are just left
with the performance pattern.
But this was problematic because not all I/O managers correctly
implement anyPendingTimeoutsOrIO (specifically the win32 ones), and now
that we also make I/O managers responsible for signals then we need to
poll/await even if there is no pending I/O or timeouts. If there is no
pending I/O or timeouts then await needs to degenerate to just waiting
forever for any signals.
- - - - -
1af20d4f by Duncan Coutts at 2026-09-27T18:43:02+01:00
Remove anyPendingTimeoutsOrIO, it is no longer used
And this avoids the problems arising from the win32 I/O managers having
had a bogus implementation.
- - - - -
b89c0adc by Duncan Coutts at 2026-09-27T18:43:02+01:00
Remove second scheduler call to awaitCompletedTimeoutsOrIO
Previously awaitCompletedTimeoutsOrIO was called both before and after
deadlock detection in the scheduler. The reason for that was that the
win32 I/O managers had a bogus implementation of anyPendingTimeoutsOrIO
and this was used to guard the call of awaitCompletedTimeoutsOrIO prior
to deadlock detection. This meant the first call site was never actually
called when using the win32 I/O managers. This was the reason for the
second call: the first one was never used. What a mess.
So now we have a simple design in the scheduler:
1. poll for completed I/O, timers or signals
2. if no runnable threads: do deadlock detection
3. if still no runnable threads: block waiting for I/O, timers or
signals.
- - - - -
e9d80b12 by Duncan Coutts at 2026-09-27T18:43:03+01:00
Lift emptyRunQueue guard out of scheduleDetectDeadlock
this improved the clarity of the logic when reading the scheduler code.
And improve the comment about scheduleDoGC that was rather unclear.
- - - - -
f446219c by Duncan Coutts at 2026-09-27T18:43:03+01:00
Make interruptIOManager work for WinIO (non-threaded)
Fixes #27403
The idle GC scheme now fundamentally relies on interruptIOManager
working, so it's no longer acceptable for it to be missing for WinIO.
Not having it would cause lock ups.
- - - - -
901ef7bb by Duncan Coutts at 2026-09-27T18:43:03+01:00
Make non-threaded deadlock detection also rely on idle GC
Only do deadlock detection GC when idle GC kicks in. This also relies on
using wakeUpRts, so now do this unconditionally. Previously wakeUpRts
was for the threaded rts only.
- - - - -
6c43daa1 by Duncan Coutts at 2026-09-27T18:43:03+01:00
Enable idle GC by default on non-threaded RTS
The behaviour is now uniform between the threaded and non-threaded RTS
ways. The deadlock detection now relies on idle GC for both threaded
and non-threaded ways. Previously deadlock detection did not rely on
idle GC for the non-threaded way.
Also tweak test T7275 to account for idle GC. This test's output is
sensitive to the number of major GCs run. Since this commit enables idle
GC for the non-threaded RTS, for this test that increases the number of
major GCs, since the test program is frequently idle for more than 300ms.
- - - - -
32e5b061 by Duncan Coutts at 2026-09-27T18:43:03+01:00
Fix state of idle GC control vars with +RTS -V0
Currently when the user uses +RTS -I0, then doIdleGC is set to false.
But if the master tick interval -V is set to 0 then the idleGCDelayTime
was being set to 0 but doIdleGC was not being set to false, which is
inconsistent, and almost certainly buggy.
- - - - -
e2cdb1a0 by Duncan Coutts at 2026-09-27T18:43:03+01:00
Add a long Note [Deadlock detection]
It describes the historical and modern designs and their trade-offs.
The point is we've now unified the code for deadlock detection between
the threaded and non-threaded ways, by changing the non-threaded to
follow the same design as the threaded.
- - - - -
9f15516c by Duncan Coutts at 2026-09-27T18:43:03+01:00
Refactor the utils providing the I/O timeout for different system APIs
We have to provide a timeout in three different time formats (int
milliseconds, struct timeval *, and struct timespec *). These are for
platform APIs including poll, select, ppoll, pselect, epoll_wait,
epoll_wait2 and in future kevent and io_uring.
Previously we had one util for each format that both worked out what
the timeout should be and converted it into the particular format. This
duplicates some logic and it also makes it awkward to modify the timeout
value (since there's no common format).
Modifying the timeout value is exactly what we want to do, in a
subsequent commit, where we want to adjust the timeout to take account
of idle GC.
- - - - -
bbf4dbb7 by Duncan Coutts at 2026-09-27T18:43:03+01:00
Update the old select I/O manager to use the new timeout utils
This will make it much easier to adjust the timeout to take account of
idle GC.
- - - - -
97a922ba by Duncan Coutts at 2026-09-27T18:43:03+01:00
Add opportunistic deadlock detection without idle GC
Our general design is now that comprehensive deadlock detection relies
on idle GC. This allows us to detect partial deadlocks that are not
system deadlocks. But it's not prompt.
It's a nice to have feature to be able to promptly detect the cases that
are system deadlocks. There's no hope to do this in the threaded RTS
with -N > 1, but it can be done for the non-threaded RTS and _could_ be
done for the threaded RTS with -N1.
We can integrate this feature into the general design without too many
contortions. We just need the I/O managers to check before sleeping if
they would be about to sleep indefinately with no I/O and no registered
signal handlers. If so, they can notify the idle GC that we're
deadlocked and we schedule and idle GC.
- - - - -
aada868f by Duncan Coutts at 2026-09-27T18:43:03+01:00
Add idle GC mode for !HAVE_PREEMPTION
We have to do idle GC (and thus deadlock detection) differently when we
do not have the ticker and the ability to interrupt the I/O manager
when it's blocking.
- - - - -
e81a7fd7 by Duncan Coutts at 2026-09-27T18:43:03+01:00
Add and use USED_IF_PREEMPTION to avoid warnings
- - - - -
aade1797 by Duncan Coutts at 2026-09-27T18:43:03+01:00
Add performDeadlockDetection and export via ghc-experimental
When documenting the idle GC and deadlock detection behaviour in the
user guide, it becomes obiovus that in some cases one would want manual
control over deadlock detection.
In particular, for applications that disable idle GC, for debugging or
robustness, they may want to invoke deadlock detecting GC manually.
Bind it in ghc-internal GHC.Internal.System.Mem and re-export via
ghc-experimental in System.Mem.Experimental. It is clearly
implementation specific behaviour, especially since in GHC the deadlock
detection is tied to doing a major GC.
- - - - -
d0af4f3d by Duncan Coutts at 2026-09-27T18:43:03+01:00
Add tests for deadlock detection, issue #26408
We have three tests:
T26408a: make sure that we can detect a /partial deadlock/ that is not
also a /system deadlock/
T26408b: make sure that we can detect a /system deadlock/ promptly
without relying on idle GC
T26408c: make sure that we do not falsely detect a deadlock when there is
nothing to do except wait on signals
- - - - -
e5743b87 by Duncan Coutts at 2026-09-27T18:43:03+01:00
Update the user guide with the revised idle GC behaviour
It's now not just for the threaded RTS, but general.
Add a new section in the RTS chapter: Idle GC and deadlock detection.
Provide general documentation on both topics. In particular, document
the behaviour of deadlock detection and how it depends on idle GC.
Move the existing entries for -I and -Iw to this section.
Add a changelog entry.
- - - - -
78 changed files:
- .gitlab-ci.yml
- + changelog.d/idle-gc-and-deadlock-detection
- compiler/GHC/Core/Opt/WorkWrap/Utils.hs
- compiler/GHC/Driver/Downsweep.hs
- compiler/GHC/Driver/Errors/Ppr.hs
- compiler/GHC/Driver/Errors/Types.hs
- compiler/GHC/Parser.y
- compiler/GHC/Parser/Annotation.hs
- compiler/GHC/Parser/PostProcess.hs
- compiler/GHC/Unit/Env.hs
- compiler/GHC/Unit/State.hs
- docs/users_guide/profiling.rst
- docs/users_guide/runtime_control.rst
- hadrian/ghci-multi-cabal.in
- libraries/ghc-experimental/src/System/Mem/Experimental.hs
- libraries/ghc-internal/src/GHC/Internal/System/Mem.hs
- rts/Capability.c
- rts/IOManager.c
- rts/IOManager.h
- + rts/IdleGC.c
- + rts/IdleGC.h
- rts/Linker.c
- rts/RtsFlags.c
- rts/RtsSignals.h
- rts/RtsStartup.c
- rts/RtsSymbols.c
- rts/Schedule.c
- rts/Schedule.h
- rts/Timer.c
- rts/include/rts/OSThreads.h
- rts/include/rts/storage/GC.h
- rts/posix/Poll.c
- rts/posix/Poll.h
- rts/posix/Select.c
- rts/posix/SelectBis.c
- rts/posix/SelectBis.h
- rts/posix/Signals.c
- − rts/posix/Signals.h
- rts/posix/Timeout.c
- rts/posix/Timeout.h
- rts/rts.cabal
- rts/win32/AsyncMIO.c
- rts/win32/AsyncMIO.h
- rts/win32/AsyncWinIO.c
- rts/win32/AsyncWinIO.h
- rts/win32/AwaitEvent.c
- rts/win32/ConsoleHandler.c
- rts/win32/ConsoleHandler.h
- testsuite/tests/driver/multipleHomeUnits/mhu-closure/Makefile
- testsuite/tests/driver/multipleHomeUnits/mhu-closure/mhu-closure.stderr
- testsuite/tests/driver/multipleHomeUnits/mhu-closure/mhu-closure.stdout
- testsuite/tests/ghc-api/exactprint/T22919.stderr
- testsuite/tests/ghc-api/exactprint/ZeroWidthSemi.stderr
- testsuite/tests/interface-stability/ghc-experimental-exports.stdout
- testsuite/tests/interface-stability/ghc-experimental-exports.stdout-mingw32
- testsuite/tests/module/mod185.stderr
- testsuite/tests/parser/should_compile/DumpParsedAst.stderr
- testsuite/tests/parser/should_compile/DumpParsedAstComments.stderr
- testsuite/tests/parser/should_compile/DumpRenamedAst.stderr
- testsuite/tests/parser/should_compile/DumpSemis.stderr
- testsuite/tests/parser/should_compile/KindSigs.stderr
- testsuite/tests/parser/should_compile/T15279.stderr
- testsuite/tests/parser/should_compile/T20718.stderr
- testsuite/tests/parser/should_compile/T20846.stderr
- testsuite/tests/printer/Test20297.stdout
- testsuite/tests/printer/Test24533.stdout
- testsuite/tests/profiling/should_run/Makefile
- testsuite/tests/rts/Makefile
- + testsuite/tests/rts/T26408a.hs
- + testsuite/tests/rts/T26408a.stderr
- + testsuite/tests/rts/T26408b.hs
- + testsuite/tests/rts/T26408b.stderr
- + testsuite/tests/rts/T26408c.hs
- + testsuite/tests/rts/T26408c.stdout
- testsuite/tests/rts/all.T
- utils/check-exact/ExactPrint.hs
- utils/check-exact/Main.hs
- utils/check-exact/Transform.hs
The diff was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f3468063892902795d7fd9fc26b2b7…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f3468063892902795d7fd9fc26b2b7…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
Simon Peyton Jones pushed to branch wip/T26543b at Glasgow Haskell Compiler / GHC
Commits:
96bc4a6e by Simon Peyton Jones at 2026-09-27T18:39:25+01:00
Wibbles
- - - - -
9 changed files:
- compiler/GHC/Core/TyCo/FVs.hs
- compiler/GHC/Tc/Gen/App.hs
- compiler/GHC/Tc/Utils/Unify.hs
- testsuite/tests/default/default-fail05.stderr
- testsuite/tests/ghci.debugger/scripts/T14628.stderr
- testsuite/tests/indexed-types/should_fail/T1897b.stderr
- testsuite/tests/typecheck/should_fail/T22707.stderr
- + testsuite/tests/typecheck/should_fail/T26543a.hs
- testsuite/tests/typecheck/should_fail/all.T
Changes:
=====================================
compiler/GHC/Core/TyCo/FVs.hs
=====================================
@@ -55,7 +55,6 @@ module GHC.Core.TyCo.FVs
) where
import GHC.Prelude
-import GHC.Exts( inline )
import {-# SOURCE #-} GHC.Core.Type( partitionInvisibleTypes, coreView, rewriterView )
@@ -852,8 +851,11 @@ invisibleVarsOfTypes = foldr (unionVarSet . invisibleVarsOfType) emptyVarSet
{- Note [Any-free-var folder]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The "any-free-var" folders take a `check_fv` predicate (TyCoVar -> Bool) and
-check if any free variable of the type/coercion satisfies it. Our main folders
-look like:
+check if any /deep/ free variable of the type/coercion satisfies it. So
+
+ anyFreeVarsOfType p ty = anyVarSet p (tyCoVarsOfType ty)
+
+Our main folders look like:
afv_type :: Type -> FV (BoundVars, TyVar -> Bool) Any
@@ -871,7 +873,7 @@ nice tight loops.
{-# INLINE afvFolder #-} -- See Note [Any-free-var folder]
afvFolder :: TyCoFolder (FV (BoundVars, TyCoVar -> Bool) DM.Any)
-- 'afvFolder' is short for "any-free-var folder", good for checking
--- if any shallow free var of a type satisfies a predicate `check_fv`
+-- if any /deep/ free var of a type satisfies a predicate `check_fv`
-- The "env" of the (FV env Any) is a pair (bvs,pred) of
-- the bound variables (which are definitely not free), and
-- an "interesting var" predicate which the client supplies
@@ -881,8 +883,11 @@ afvFolder = TyCoFolder { tcf_view = noView -- See Note [Free vars and synonyms]
, tcf_hole = do_hole
, tcf_tycobinder = addBndrSelectiveFV }
where
- do_tcv tv = MkFV $ \ (bvs,check_fv) ->
- Any (not (tv `elemVarSet` bvs) && check_fv tv)
+ -- Check the variable itself with check_fv,
+ -- and check its kind (this is a deep folder)
+ do_tcv tcv = MkFV $ \ (bvs,check_fv) ->
+ Any (not (tcv `elemVarSet` bvs) &&
+ (check_fv tcv || runAny check_fv (afv_type (varType tcv))))
do_hole hole = do_tcv (coHoleCoVar hole)
afv_type :: Type -> FV (BoundVars, TyVar -> Bool) DM.Any
@@ -892,7 +897,7 @@ afv_co :: Coercion -> FV (BoundVars, TyVar -> Bool) DM.Any
runAny :: (TyVar -> Bool) -> FV (BoundVars, TyCoVar -> Bool) DM.Any -> Bool
{-# INLINE runAny #-}
-runAny check_fv fvs = DM.getAny $ runFV fvs (emptyVarSet, check_fv)
+runAny check_fv fvs = DM.getAny (runFV fvs (emptyVarSet, check_fv))
anyFreeVarsOfType :: (TyCoVar -> Bool) -> Type -> Bool
{-# INLINE anyFreeVarsOfType #-}
=====================================
compiler/GHC/Tc/Gen/App.hs
=====================================
@@ -2196,10 +2196,9 @@ qlUnify ty1 ty2
-- the main point of QuickLook (allowing meta-variables to be unified
-- with qualified types).
- go' (TyVarTy tv) ty2
- | isQLInstTyVar tv = go_kappa tv ty2
- go' ty1 (TyVarTy tv)
- | isQLInstTyVar tv = go_kappa tv ty1
+ -- Type variables; see (UQL6) in Note [QuickLook unification]
+ go' (TyVarTy tv) ty2 | isQLInstTyVar tv = go_kappa tv ty2
+ go' ty1 (TyVarTy tv) | isQLInstTyVar tv = go_kappa tv ty1
go' (CastTy ty1 _) ty2 = go ty1 ty2
go' ty1 (CastTy ty2 _) = go ty1 ty2
@@ -2242,6 +2241,8 @@ qlUnify ty1 ty2
----------------
go_flexi kappa ty2 -- ty2 is zonked
+ | isConcreteTyVar kappa -- See (UQL7) in Note [QuickLook unification]
+ = return ()
| anyFreeVarsOfType (== kappa) ty2
= return () -- Occurs check
| otherwise
@@ -2340,6 +2341,22 @@ That is the entire point of qlUnify! Wrinkles:
constraint arrows =>, so that (UQL4) applies to them as well.
See Note [Multiplicity in deep subsumption].
+(UQL6) What if `qlUnify` sees a regular (monotyped) unification variable, `alpha`, rather
+ than an instantiation variable `kappa`? Thus (alpha ~ ty). It's tempting to just
+ unify it, but the regular, on-the-fly unifier has lots of careful checks (levels,
+ concreteness etc). In regular unification it doesn't matter whether we unify on-the-fly
+ or later -- it's just an efficiency issue -- but here it matters because `qlUnify` has
+ /user-visible/ consequences. This led to #26543.
+
+ The simplest thing is to say that `qlUnify` unifies /only/ instantiation variables
+ (see calls to `isQLInstTyVar`). We could be more ambitious, perhaps, in future.
+
+(UQL7) If `qlUnify` sees a concrete `kaapa`, thus
+ kappa[conc] ~ some-type
+ we just give up. It's tiresome to do all the checks for concreteness; and concrete
+ type variables are never unified with polytypes, so it's fine to leave it for the
+ regular unifier.
+
Sadly discarded design alternative
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is very tempting to use `unifyType` rather than `qlUnify`, killing off the
=====================================
compiler/GHC/Tc/Utils/Unify.hs
=====================================
@@ -3596,7 +3596,6 @@ matchExpectedFunKind hs_ty n k = go n k
data UnifyCheckCaller
= UC_OnTheFly -- Called from the on-the-fly unifier
- | UC_QuickLook -- Called from Quick Look
| UC_Solver -- Called from constraint solver
-- | The result type of 'simpleUnifyCheck'.
@@ -3649,9 +3648,7 @@ simpleUnifyCheck caller given_eq_lvl lhs_tv rhs
lhs_tv_is_concrete = isConcreteTyVar lhs_tv
lhs_tv_nm = tyVarName lhs_tv
- forall_ok = case caller of
- UC_QuickLook -> isQLInstTyVar lhs_tv
- _ -> isRuntimeUnkTyVar lhs_tv
+ forall_ok = isRuntimeUnkTyVar lhs_tv
-- This fam_ok thing relates to a very specific perf problem
-- See Note [Prevent unification with type families]
@@ -3660,7 +3657,6 @@ simpleUnifyCheck caller given_eq_lvl lhs_tv rhs
-- see if it bites us)
fam_ok = case caller of
UC_Solver -> True
- UC_QuickLook -> True
UC_OnTheFly -> False
rhs_tcv_is_bad tcv
=====================================
testsuite/tests/default/default-fail05.stderr
=====================================
@@ -1,45 +1,45 @@
default-fail05.hs:11:10: error: [GHC-39999]
- • Ambiguous type variable ‘f0’ arising from a use of ‘toList’
- prevents the constraint ‘(Foldable f0)’ from being solved.
- Probable fix: use a type annotation to specify what ‘f0’ should be.
+ • Ambiguous type variable ‘t0’ arising from a use of ‘toList’
+ prevents the constraint ‘(Foldable t0)’ from being solved.
+ Probable fix: use a type annotation to specify what ‘t0’ should be.
Potentially matching instances:
instance Foldable (Either a)
-- Defined in ‘GHC.Internal.Data.Foldable’
instance Foldable Maybe -- Defined in ‘GHC.Internal.Data.Foldable’
...plus three others
- ...plus 26 instances involving out-of-scope types
+ ...plus 11 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the first argument of ‘($)’, namely ‘toList’
In the first argument of ‘print’, namely ‘(toList $ pure 21)’
In a stmt of a 'do' block: print (toList $ pure 21)
default-fail05.hs:11:19: error: [GHC-39999]
- • Ambiguous type variable ‘f0’ arising from a use of ‘pure’
- prevents the constraint ‘(Applicative f0)’ from being solved.
- Probable fix: use a type annotation to specify what ‘f0’ should be.
+ • Ambiguous type variable ‘t0’ arising from a use of ‘pure’
+ prevents the constraint ‘(Applicative t0)’ from being solved.
+ Probable fix: use a type annotation to specify what ‘t0’ should be.
Potentially matching instances:
instance Applicative IO -- Defined in ‘GHC.Internal.Base’
instance Applicative Maybe -- Defined in ‘GHC.Internal.Base’
...plus six others
- ...plus 9 instances involving out-of-scope types
+ ...plus five instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the second argument of ‘($)’, namely ‘pure 21’
In the first argument of ‘print’, namely ‘(toList $ pure 21)’
In a stmt of a 'do' block: print (toList $ pure 21)
default-fail05.hs:12:3: error: [GHC-39999]
- • Ambiguous type variable ‘t0’ arising from a use of ‘traverse’
- prevents the constraint ‘(Traversable t0)’ from being solved.
+ • Ambiguous type variable ‘t1’ arising from a use of ‘traverse’
+ prevents the constraint ‘(Traversable t1)’ from being solved.
Relevant bindings include
- main :: IO (t0 ()) (bound at default-fail05.hs:10:1)
- Probable fix: use a type annotation to specify what ‘t0’ should be.
+ main :: IO (t1 ()) (bound at default-fail05.hs:10:1)
+ Probable fix: use a type annotation to specify what ‘t1’ should be.
Potentially matching instances:
instance Traversable (Either a)
-- Defined in ‘GHC.Internal.Data.Traversable’
instance Traversable Maybe
-- Defined in ‘GHC.Internal.Data.Traversable’
...plus three others
- ...plus 28 instances involving out-of-scope types
+ ...plus 13 instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In a stmt of a 'do' block: traverse print (pure 42)
In the expression:
@@ -51,16 +51,16 @@ default-fail05.hs:12:3: error: [GHC-39999]
traverse print (pure 42)
default-fail05.hs:12:19: error: [GHC-39999]
- • Ambiguous type variable ‘t0’ arising from a use of ‘pure’
- prevents the constraint ‘(Applicative t0)’ from being solved.
+ • Ambiguous type variable ‘t1’ arising from a use of ‘pure’
+ prevents the constraint ‘(Applicative t1)’ from being solved.
Relevant bindings include
- main :: IO (t0 ()) (bound at default-fail05.hs:10:1)
- Probable fix: use a type annotation to specify what ‘t0’ should be.
+ main :: IO (t1 ()) (bound at default-fail05.hs:10:1)
+ Probable fix: use a type annotation to specify what ‘t1’ should be.
Potentially matching instances:
instance Applicative IO -- Defined in ‘GHC.Internal.Base’
instance Applicative Maybe -- Defined in ‘GHC.Internal.Base’
...plus six others
- ...plus 9 instances involving out-of-scope types
+ ...plus five instances involving out-of-scope types
(use -fprint-potential-instances to see them all)
• In the second argument of ‘traverse’, namely ‘(pure 42)’
In a stmt of a 'do' block: traverse print (pure 42)
=====================================
testsuite/tests/ghci.debugger/scripts/T14628.stderr
=====================================
@@ -1,9 +1,9 @@
-
-<interactive>:4:17: error: [GHC-25897]
+<interactive>:4:7: error: [GHC-25897]
• Couldn't match type ‘m’ with ‘(,) a0’
- Expected: StateT Int ((,) a0) ()
- Actual: StateT Int m ()
+ Expected: (a0, ((), Int))
+ Actual: m ((), Int)
‘m’ is an interactive-debugger skolem
- • In the first argument of ‘runStateT’, namely ‘_result’
- In the second argument of ‘($)’, namely ‘runStateT _result 0’
+ • In the second argument of ‘($)’, namely ‘runStateT _result 0’
In the expression: snd $ runStateT _result 0
+ In an equation for ‘it’: it = snd $ runStateT _result 0
+
=====================================
testsuite/tests/indexed-types/should_fail/T1897b.stderr
=====================================
@@ -1,14 +1,14 @@
T1897b.hs:16:1: error: [GHC-83865]
- • Couldn't match type: Depend b0
- with: Depend b
- Expected: t (Depend b) -> Bool
- Actual: t (Depend b0) -> Bool
+ • Couldn't match type: Depend a0
+ with: Depend a
+ Expected: t (Depend a) -> Bool
+ Actual: t (Depend a0) -> Bool
Note: ‘Depend’ is a non-injective type family.
- The type variable ‘b0’ is ambiguous
+ The type variable ‘a0’ is ambiguous
• In the ambiguity check for the inferred type for ‘isValid’
To defer the ambiguity check to use sites, enable AllowAmbiguousTypes
When checking the inferred type
- isValid :: forall {t :: * -> *} {b}.
- (Foldable t, Bug b) =>
- t (Depend b) -> Bool
+ isValid :: forall {t :: * -> *} {a}.
+ (Foldable t, Bug a) =>
+ t (Depend a) -> Bool
=====================================
testsuite/tests/typecheck/should_fail/T22707.stderr
=====================================
@@ -1,38 +1,11 @@
-T22707.hs:6:59: error: [GHC-25897]
- • Couldn't match expected type ‘i1’
- with actual type ‘m0
- GHC.Internal.Types.LiftedRep GHC.Internal.Types.LiftedRep b0’
- ‘i1’ is a rigid type variable bound by
- the type signature for:
- t1 :: forall i2 o i1 a. Cont (i2 -> o) i1 a -> Cont o i2 (a -> i1)
- at T22707.hs:5:1-47
- • In the expression: evalCont (t1 c) >>== \ ati1 -> return ati1 xa
- In the second argument of ‘($)’, namely
- ‘\ xa -> evalCont (t1 c) >>== \ ati1 -> return ati1 xa’
- In the second argument of ‘runCont’, namely
- ‘(ati1tti2 $ \ xa -> evalCont (t1 c) >>== \ ati1 -> return ...)’
- • Relevant bindings include
- ati1tti2 :: (a -> i1) -> i2 (bound at T22707.hs:6:16)
- c :: Cont (i2 -> o) i1 a (bound at T22707.hs:6:4)
- t1 :: Cont (i2 -> o) i1 a -> Cont o i2 (a -> i1)
- (bound at T22707.hs:6:1)
-
T22707.hs:6:72: error: [GHC-25897]
- • Couldn't match type ‘o’
- with ‘m0
- GHC.Internal.Types.LiftedRep
- GHC.Internal.Types.LiftedRep
- (m0 GHC.Internal.Types.LiftedRep GHC.Internal.Types.LiftedRep b0)’
+ • Couldn't match type ‘i2’ with ‘a -> i1’
Expected: Cont
- ((a -> i1)
- -> m0
- GHC.Internal.Types.LiftedRep
- GHC.Internal.Types.LiftedRep
- (m0 GHC.Internal.Types.LiftedRep GHC.Internal.Types.LiftedRep b0))
+ ((a -> i1) -> m0 (a -> i1) (a -> i1) (m0 (a -> i1) (a -> i1) b0))
i1
a
Actual: Cont (i2 -> o) i1 a
- ‘o’ is a rigid type variable bound by
+ ‘i2’ is a rigid type variable bound by
the type signature for:
t1 :: forall i2 o i1 a. Cont (i2 -> o) i1 a -> Cont o i2 (a -> i1)
at T22707.hs:5:1-47
@@ -40,6 +13,9 @@ T22707.hs:6:72: error: [GHC-25897]
In the first argument of ‘evalCont’, namely ‘(t1 c)’
In the first argument of ‘(>>==)’, namely ‘evalCont (t1 c)’
• Relevant bindings include
+ xa :: a (bound at T22707.hs:6:53)
+ ati1tti2 :: (a -> m0 (a -> i1) (a -> i1) b0) -> a -> i1
+ (bound at T22707.hs:6:16)
c :: Cont (i2 -> o) i1 a (bound at T22707.hs:6:4)
t1 :: Cont (i2 -> o) i1 a -> Cont o i2 (a -> i1)
(bound at T22707.hs:6:1)
=====================================
testsuite/tests/typecheck/should_fail/T26543a.hs
=====================================
@@ -0,0 +1,18 @@
+{-# LANGUAGE ImpredicativeTypes, PolyKinds #-}
+
+-- Should be rejected via an occurs-check in the kinds,
+-- but was erroneously accpeted with ImpredicativeTypes
+-- See !16566
+
+module T26543 where
+
+import Data.Kind
+import Data.Proxy
+
+h :: forall k (m :: k -> Type) (a :: k). (Proxy k, Proxy (m a))
+h = (Proxy, Proxy)
+
+use2 :: forall t. (Proxy t, Proxy t) -> ()
+use2 _ = ()
+
+test = use2 h
=====================================
testsuite/tests/typecheck/should_fail/all.T
=====================================
@@ -763,3 +763,4 @@ test('T26861', normal, compile_fail, [''])
test('T26862', normal, compile_fail, [''])
test('T27210', normal, compile_fail, [''])
test('T26532', normal, compile_fail, [''])
+test('T26543a', normal, compile_fail, [''])
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/96bc4a6e5f7285e907e682705867b4f…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/commit/96bc4a6e5f7285e907e682705867b4f…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
27 Sep '26
Simon Peyton Jones pushed to branch wip/T27844 at Glasgow Haskell Compiler / GHC
Commits:
0f5ff61c by Simon Peyton Jones at 2026-09-27T17:31:10+01:00
Further wibbles
- - - - -
26bd3aa5 by Simon Peyton Jones at 2026-09-27T18:37:50+01:00
More wibbles
...incl a new assert in the _no_unification case
- - - - -
4 changed files:
- compiler/GHC/Tc/Solver/Equality.hs
- compiler/GHC/Tc/Solver/Monad.hs
- compiler/GHC/Tc/Utils/Unify.hs
- testsuite/tests/indexed-types/should_fail/T9662.stderr
Changes:
=====================================
compiler/GHC/Tc/Solver/Equality.hs
=====================================
@@ -1824,7 +1824,7 @@ Ticket #10009, a very nasty example:
g :: forall a. (UnF (F a) ~ a) => a -> ()
g _ = f (undefined :: F a)
-For g we get [G] g1 : UnF (F a) ~ a
+For g we get [G] g1 : UnF (F a) ~ a
[W] w1 : UnF (F beta) ~ beta
[W] w2 : F a ~ F beta
@@ -2213,6 +2213,9 @@ canEqCanLHSFinish_try_unification ev eq_rel swapped lhs rhs
-- We have a touchable unification variable on the left,
-- and the top-shape check succeeded. These are both guaranteed
-- by the fact that simpleUnifyCheck did not return SUC_CannotUnify.
+ --
+ -- Next, do a fully `checkTyEqRhs` check to see if we can unify
+ -- See Note [checkTyEqRhs] in GHC.Tc.Utils.Unify
do { let flags = unifyingLHSMetaTyVar_TEFTask ev lhs_tv
; check_result <- wrapTcS (checkTyEqRhs flags rhs)
; traceTcS "canEqCanLHSFinish_try_unification" $
@@ -2229,21 +2232,19 @@ canEqCanLHSFinish_try_unification ev eq_rel swapped lhs rhs
= finish_no_unify
where
- -- We can't unify, but we can see if this equality can go in the inert set
- -- and be used to rewrite other constraints.
- finish_no_unify = canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
-
- -- We can't unify, and this equality should not be used to rewrite
- -- other constraints (e.g. because it has an occurs check).
- -- So add it to the inert Irreds.
--- try_irred reason = tryIrredInstead reason ev eq_rel swapped lhs rhs
-
- -- We can't unify as-is, and want to flip the equality around.
- -- Example: alpha ~ F tys, flip it around to become the canonical
- -- equality f tys ~ alpha.
--- swap_and_finish tv can_rhs = swapAndFinish ev eq_rel swapped (mkTyVarTy tv) can_rhs
+ -- finish_no_unify: abandon the attempt to unify, and instead hand off to
+ -- canEqCanLHSFinish_no_unification
+ -- First, though, if we have (tv ~ F tys)
+ -- swap it over so we get (F tys ~ tv), with `F tys` on the LHS
+ -- See Note [Orienting TyVarLHS/TyFamLHS]
+ finish_no_unify
+ | TyVarLHS lhs_tv <- lhs
+ , Just can_rhs <- canTyFamEqLHS_maybe rhs
+ = canEqCanLHSFinish_no_unification ev eq_rel (flipSwap swapped) can_rhs (mkTyVarTy lhs_tv)
+ | otherwise
+ = canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
- -- Phew! Finally! We can unify; go ahead and do so.
+ -- do_unification: Phew! Finally! We can unify; go ahead and do so.
do_unification tv rhs_redn =
do { -- In the common case where rhs_redn is Refl, we don't need to rewrite
-- the evidence, even if swapped=IsSwapped. Suppose the original was
@@ -2280,39 +2281,41 @@ canEqCanLHSFinish_try_unification ev eq_rel swapped lhs rhs
; return (Stop new_ev (text "Solved by unification")) }
---------------------------
--- Unification is off the table
canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
+ -- We can't unify, but we can see if this equality can go in the inert set
+ -- and be used to rewrite other constraints.
+ -- See Note [Equalities where unification fails]
= do { -- Do checkTypeEq to guarantee (TyEq:OC), (TyEq:F)
-- Must do the occurs check even on tyvar/tyvar equalities,
-- in case have x ~ (y :: ..x...); this is #12593.
; check_result <- checkTypeEq ev eq_rel lhs rhs
-
- ; let lhs_ty = canEqLHSType lhs
; case check_result of
PuFail reason
-
- -- If we had F a ~ G (F a), which gives an occurs check,
- -- then swap it to G (F a) ~ F a, which does not
- -- See Note [Orienting TyVarLHS/TyFamLHS]
- -- ToDo: fix me ... also needed for a ~ G (F a)
- -- However `swap_for_size` above will orient it with (G (F a)) on
- -- the left anwyway. `swap_for_rewriting` "wins", but that doesn't
- -- matter: in the occurs check case swap_for_rewriting will be moot.
- -- TL;DR: the next four lines of code are redundant
- -- I'm leaving them here in case they become relevant again
- | Just can_rhs <- canTyFamEqLHS_maybe rhs
- , reason `cterHasOnlyProblem` cteSolubleOccurs
- -> finishWith ev eq_rel (flipSwap swapped) can_rhs lhs_ty
-
| reason `cterHasOnlyProblems` do_not_prevent_rewriting
- -> finishWith ev eq_rel swapped lhs rhs
+ -> do { new_ev <- rewriteEqEvidenceSwapOnly ev eq_rel swapped lhs rhs
+ ; continueWith $ Right $
+ EqCt { eq_ev = new_ev, eq_eq_rel = eq_rel
+ , eq_lhs = lhs , eq_rhs = rhs } }
| otherwise
- -> tryIrredInstead reason ev eq_rel swapped lhs rhs
+ -> -- We can't unify, and this equality should not be used to rewrite
+ -- other constraints (e.g. because it has an occurs check).
+ -- So add it to the inert Irreds.
+ do { new_ev <- rewriteEqEvidenceSwapOnly ev eq_rel swapped lhs rhs
+ ; finishCanWithIrred (NonCanonicalReason reason) new_ev }
PuOK new_eqs rhs_redn
- -> do { emitWork new_eqs
+ -> -- Claim: there should be no new_eqs
+ -- The non-unification check does non promotion nor type-family
+ -- flattening for Wanteds
+ assertPpr (isEmptyCts new_eqs)
+ (vcat [ text "new_eqs" <+> ppr new_eqs
+ , text "rhs_redn" <+> ppr rhs_redn
+ , text "lhs" <+> ppr lhs
+ , text "rhs" <+> ppr rhs ]) $
+ do { emitWork new_eqs
; let new_holes = rewriterSetFromCts new_eqs
+ lhs_ty = canEqLHSType lhs
lhs_redn = mkReflRedn (eqRelRole eq_rel) lhs_ty
; new_ev <- rewriteEqEvidence ev swapped lhs_redn rhs_redn new_holes
@@ -2336,45 +2339,7 @@ canEqCanLHSFinish_no_unification ev eq_rel swapped lhs rhs
do_not_prevent_rewriting :: CheckTyEqResult
do_not_prevent_rewriting = cteProblem cteSkolemEscape S.<>
cteProblem cteConcrete
-
-----------------------
-finishWith :: CtEvidence -> EqRel -> SwapFlag
- -> CanEqLHS -> TcType
- -> TcS (StopOrContinue (Either unused EqCt))
-finishWith ev eq_rel swapped can_lhs rhs_ty
- = do { new_ev <- rewriteEqEvidenceSwapOnly ev eq_rel swapped can_lhs rhs_ty
- ; continueWith $ Right $
- EqCt { eq_ev = new_ev, eq_eq_rel = eq_rel
- , eq_lhs = can_lhs , eq_rhs = rhs_ty } }
-
-{-
-swapAndFinish :: CtEvidence -> EqRel -> SwapFlag
- -> TcType -> CanEqLHS -- ty ~ F tys
- -> TcS (StopOrContinue (Either unused EqCt))
--- We have an equality alpha ~ F tys, that we can't unify e.g because 'tys'
--- mentions alpha, it would not be a canonical constraint as-is.
--- We want to flip it to (F tys ~ a), whereupon it is canonical
-swapAndFinish ev eq_rel swapped lhs_ty can_rhs
- = do { new_ev <- rewriteEqEvidenceSwapOnly ev eq_rel (flipSwap swapped) can_rhs lhs_ty
- ; continueWith $ Right $
- EqCt { eq_ev = new_ev, eq_eq_rel = eq_rel
- , eq_lhs = can_rhs, eq_rhs = lhs_ty } }
--}
-
----------------------
-tryIrredInstead :: CheckTyEqResult -> CtEvidence
- -> EqRel -> SwapFlag -> CanEqLHS -> TcType
- -> TcS (StopOrContinue (Either IrredCt unused))
--- We have a non-canonical equality
--- We still swap it if 'swapped' says so, so that it is oriented
--- in the direction that the error-reporting machinery
--- expects it; e.g. (m ~ t m) rather than (t m ~ m)
--- This is not very important, and only affects error reporting.
-tryIrredInstead reason ev eq_rel swapped lhs rhs
- = do { traceTcS "cantMakeCanonical" (ppr reason $$ ppr lhs $$ ppr rhs)
- ; new_ev <- rewriteEqEvidenceSwapOnly ev eq_rel swapped lhs rhs
- ; finishCanWithIrred (NonCanonicalReason reason) new_ev }
-
finishCanWithIrred :: CtIrredReason -> CtEvidence
-> TcS (StopOrContinue (Either IrredCt a))
finishCanWithIrred reason ev
@@ -2394,8 +2359,26 @@ canEqReflexive ev eq_rel ty
= do { setEqIfWanted ev (mkReflCPH eq_rel ty)
; stopWith ev "Solved by reflexivity" }
-{- Note [Equalities with heterogeneous kinds]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+{- Note [Equalities where unification fails]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+If we can't unify, but the equality is canonical, we still want to add it to the
+inert set so that it can be used to rewrite other Wanteds. (There are many
+reasons by unification might not happen: the LHS is not a touchable unification
+variable, it has rewriters, it's a Given etc.)
+
+* In this case we call `checkTypeEq`to see if it is canonical (which in turn
+ calls `checkTypeEqRhs`).
+
+* Even if `checkTypeEq` returns `PuFail`, we may /still/ be able to add it to the inert
+ set, depending on the reason for failure; examples are given in `do_not_prevent_rewriting`.
+
+* NB: we do /not/ try to take advantage of the case where `canEqCanLHSFinish_try_unification`
+ has already called `checkTyEqRhs`; doing so caused #27844. (The first call did fam-app
+ breaking, which concealed a genuine occurs check.) It's just too clever; better to do
+ the simple thing.
+
+Note [Equalities with heterogeneous kinds]
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
What do we do when we have an equality
(tv :: k1) ~ (rhs :: k2)
@@ -3066,8 +3049,6 @@ inertsEqsCanDischarge inerts (EqCt { eq_lhs = lhs_w, eq_rhs = rhs_w
inertsEqsCanDischarge _ _ = Nothing
-
-
{- Note [Do not unify Givens]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Consider this GADT match
@@ -3133,34 +3114,6 @@ Wrinkles:
we should kick out `co` so that we can now unify it, which might
unlock other stuff. See `kickOutAfterFillingCoercionHole` in
GHC.Tc.Solver.Monad.
-
-Note [Solve by unification]
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-If we solve
- alpha[n] ~ ty
-by unification, there are two cases to consider
-
-* TouchableSameLevel: if the ambient level is 'n', then
- we can simply update alpha := ty, and do nothing else
-
-* TouchableOuterLevel free_metas n: if the ambient level is greater than
- 'n' (the level of alpha), in addition to setting alpha := ty we must
- do two other things:
-
- 1. Promote all the free meta-vars of 'ty' to level n. After all,
- alpha[n] is at level n, and so if we set, say,
- alpha[n] := Maybe beta[m],
- we must ensure that when unifying beta we do skolem-escape checks
- etc relevant to level n. Simple way to do that: promote beta to
- level n.
-
- 2. Set the Unification Level Flag to record that a level-n unification has
- taken place. See Note [WhatUnifications] in GHC.Tc.Utils.Unify
-
-NB: TouchableSameLevel is just an optimisation for TouchableOuterLevel. Promotion
-would be a no-op, and setting the unification flag unnecessarily would just
-make the solver iterate more often. (We don't need to iterate when unifying
-at the ambient level because of the kick-out mechanism.)
-}
=====================================
compiler/GHC/Tc/Solver/Monad.hs
=====================================
@@ -2333,7 +2333,7 @@ checkTypeEq ev eq_rel lhs rhs =
given_flags :: TyEqFlags TcM (TcTyVar, TcType)
given_flags = wanted_flags { tef_fam_app = mkTEFA_Break ev eq_rel BreakGiven }
-- TEFA_Break used for: [G] a ~ Maybe (F a)
- -- or [W] F a ~ Maybe (F a)
+ -- or [G] F a ~ Maybe (F a)
-- occ_prob: see Note [Occurs check and representational equality]
occ_prob = case eq_rel of
=====================================
compiler/GHC/Tc/Utils/Unify.hs
=====================================
@@ -4203,7 +4203,7 @@ famAppArgFlags flags = case flags of
The key function `checkTyEqRhs ty_eq_flags rhs` is called on the
RHS of a type equality
lhs ~ rhs
-and checks to see if `rhs` satisfies, or can be made to satisfy,
+and checks to see if `rhs` satisfies, /or can be made to satisfy/,
invariants described by `ty_eq_flags`. It can succeded or fail; in
the latter case it returns a `CheckTyEqResult` that describes why it
failed.
@@ -4224,6 +4224,15 @@ Notably, it can check for things like:
* Concreteness error
e.g. alpha[conc] ~ r[sk]
+Note "or can be made to satisfy the preconditions"! `checkTyEqRhs` may transform
+the RHS by
+
+ * Turning a type-family application into a type variable, perhaps to avoid
+ an occurs check. See Note Note [Family applications in canonical constraints]
+
+ * Promoting any nested unification variables, in order to satisfy the
+ level invariant. See Note [Promotion and level-checking]
+
Its specific behaviour is governed by the `TyEqFlags` that are passed
to it; see Note [TyEqFlags].
=====================================
testsuite/tests/indexed-types/should_fail/T9662.stderr
=====================================
@@ -1,12 +1,12 @@
T9662.hs:49:8: error: [GHC-25897]
- • Couldn't match type ‘n’ with ‘Int’
+ • Couldn't match type ‘m’ with ‘Int’
Expected: Exp (((sh :. k) :. m) :. n)
-> Exp (((sh :. m) :. n) :. k)
Actual: Exp
(Tuple (((Atom a0 :. Atom Int) :. Atom Int) :. Atom Int))
-> Exp
(Plain (((Unlifted (Atom a0) :. Exp Int) :. Exp Int) :. Exp Int))
- ‘n’ is a rigid type variable bound by
+ ‘m’ is a rigid type variable bound by
the type signature for:
test :: forall sh k m n.
Shape (((sh :. k) :. m) :. n) -> Shape (((sh :. m) :. n) :. k)
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f3b36989d97f7839876ec4f47142cf…
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/f3b36989d97f7839876ec4f47142cf…
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0
[Git][ghc/ghc] Pushed new branch wip/az/epa-tidy-locatedxxx-26
by Alan Zimmerman (@alanz) 27 Sep '26
by Alan Zimmerman (@alanz) 27 Sep '26
27 Sep '26
Alan Zimmerman pushed new branch wip/az/epa-tidy-locatedxxx-26 at Glasgow Haskell Compiler / GHC
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/tree/wip/az/epa-tidy-locatedxxx-26
You're receiving this email because of your account on gitlab.haskell.org. Manage all notifications: https://gitlab.haskell.org/-/profile/notifications | Help: https://gitlab.haskell.org/help
1
0