Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
-
d046b5ab
by Simon Hengel at 2025-07-24T06:12:05-04:00
-
d2b89603
by Ben Gamari at 2025-07-24T06:12:47-04:00
-
8d3ce71a
by Sebastian Graf at 2025-07-24T09:48:46-04:00
-
c8c9416e
by Sylvain Henry at 2025-07-24T09:49:08-04:00
20 changed files:
- compiler/GHC/Core/Opt/CprAnal.hs
- compiler/GHC/Core/Opt/WorkWrap/Utils.hs
- compiler/GHC/Driver/Errors.hs
- compiler/GHC/Types/Error.hs
- compiler/GHC/Utils/Logger.hs
- docs/users_guide/9.14.1-notes.rst
- + docs/users_guide/diagnostics-as-json-schema-1_2.json
- docs/users_guide/using.rst
- rts/Interpreter.c
- rts/PrimOps.cmm
- rts/RaiseAsync.c
- rts/STM.c
- + testsuite/tests/cpranal/sigs/T25944.hs
- + testsuite/tests/cpranal/sigs/T25944.stderr
- testsuite/tests/cpranal/sigs/all.T
- testsuite/tests/driver/json.stderr
- testsuite/tests/driver/json_warn.stderr
- + testsuite/tests/lib/stm/T26028.hs
- + testsuite/tests/lib/stm/T26028.stdout
- + testsuite/tests/lib/stm/all.T
Changes:
| 1 | +{-# LANGUAGE MultiWayIf #-}
|
|
| 1 | 2 | |
| 2 | 3 | -- | Constructed Product Result analysis. Identifies functions that surely
|
| 3 | 4 | -- return heap-allocated records on every code path, so that we can eliminate
|
| ... | ... | @@ -22,12 +23,15 @@ import GHC.Types.Demand |
| 22 | 23 | import GHC.Types.Cpr
|
| 23 | 24 | import GHC.Types.Unique.MemoFun
|
| 24 | 25 | |
| 26 | +import GHC.Core
|
|
| 25 | 27 | import GHC.Core.FamInstEnv
|
| 26 | 28 | import GHC.Core.DataCon
|
| 27 | 29 | import GHC.Core.Type
|
| 28 | 30 | import GHC.Core.Utils
|
| 29 | -import GHC.Core
|
|
| 31 | +import GHC.Core.Coercion
|
|
| 32 | +import GHC.Core.Reduction
|
|
| 30 | 33 | import GHC.Core.Seq
|
| 34 | +import GHC.Core.TyCon
|
|
| 31 | 35 | import GHC.Core.Opt.WorkWrap.Utils
|
| 32 | 36 | |
| 33 | 37 | import GHC.Data.Graph.UnVar -- for UnVarSet
|
| ... | ... | @@ -216,9 +220,13 @@ cprAnal' _ (Type ty) = (topCprType, Type ty) -- Doesn't happen, in fact |
| 216 | 220 | cprAnal' _ (Coercion co) = (topCprType, Coercion co)
|
| 217 | 221 | |
| 218 | 222 | cprAnal' env (Cast e co)
|
| 219 | - = (cpr_ty, Cast e' co)
|
|
| 223 | + = (cpr_ty', Cast e' co)
|
|
| 220 | 224 | where
|
| 221 | 225 | (cpr_ty, e') = cprAnal env e
|
| 226 | + cpr_ty'
|
|
| 227 | + | cpr_ty == topCprType = topCprType -- cheap case first
|
|
| 228 | + | isRecNewTyConApp env (coercionRKind co) = topCprType -- See Note [CPR for recursive data constructors]
|
|
| 229 | + | otherwise = cpr_ty
|
|
| 222 | 230 | |
| 223 | 231 | cprAnal' env (Tick t e)
|
| 224 | 232 | = (cpr_ty, Tick t e')
|
| ... | ... | @@ -391,6 +399,19 @@ cprTransformDataConWork env con args |
| 391 | 399 | mAX_CPR_SIZE :: Arity
|
| 392 | 400 | mAX_CPR_SIZE = 10
|
| 393 | 401 | |
| 402 | +isRecNewTyConApp :: AnalEnv -> Type -> Bool
|
|
| 403 | +-- See Note [CPR for recursive newtype constructors]
|
|
| 404 | +isRecNewTyConApp env ty
|
|
| 405 | + --- | pprTrace "isRecNewTyConApp" (ppr ty) False = undefined
|
|
| 406 | + | Just (tc, tc_args) <- splitTyConApp_maybe ty =
|
|
| 407 | + if | Just (HetReduction (Reduction _ rhs) _) <- topReduceTyFamApp_maybe (ae_fam_envs env) tc tc_args
|
|
| 408 | + -> isRecNewTyConApp env rhs
|
|
| 409 | + | Just dc <- newTyConDataCon_maybe tc
|
|
| 410 | + -> ae_rec_dc env dc == DefinitelyRecursive
|
|
| 411 | + | otherwise
|
|
| 412 | + -> False
|
|
| 413 | + | otherwise = False
|
|
| 414 | + |
|
| 394 | 415 | --
|
| 395 | 416 | -- * Bindings
|
| 396 | 417 | --
|
| ... | ... | @@ -414,12 +435,18 @@ cprFix orig_env orig_pairs |
| 414 | 435 | | otherwise = orig_pairs
|
| 415 | 436 | init_env = extendSigEnvFromIds orig_env (map fst init_pairs)
|
| 416 | 437 | |
| 438 | + -- If fixed-point iteration does not yield a result we use this instead
|
|
| 439 | + -- See Note [Safe abortion in the fixed-point iteration]
|
|
| 440 | + abort :: (AnalEnv, [(Id,CoreExpr)])
|
|
| 441 | + abort = step (nonVirgin orig_env) [(setIdCprSig id topCprSig, rhs) | (id, rhs) <- orig_pairs ]
|
|
| 442 | + |
|
| 417 | 443 | -- The fixed-point varies the idCprSig field of the binders and and their
|
| 418 | 444 | -- entries in the AnalEnv, and terminates if that annotation does not change
|
| 419 | 445 | -- any more.
|
| 420 | 446 | loop :: Int -> AnalEnv -> [(Id,CoreExpr)] -> (AnalEnv, [(Id,CoreExpr)])
|
| 421 | 447 | loop n env pairs
|
| 422 | 448 | | found_fixpoint = (reset_env', pairs')
|
| 449 | + | n == 10 = pprTraceUserWarning (text "cprFix aborts. This is not terrible, but worth reporting a GHC issue." <+> ppr (map fst pairs)) $ abort
|
|
| 423 | 450 | | otherwise = loop (n+1) env' pairs'
|
| 424 | 451 | where
|
| 425 | 452 | -- In all but the first iteration, delete the virgin flag
|
| ... | ... | @@ -519,8 +546,9 @@ cprAnalBind env id rhs |
| 519 | 546 | -- possibly trim thunk CPR info
|
| 520 | 547 | rhs_ty'
|
| 521 | 548 | -- See Note [CPR for thunks]
|
| 522 | - | stays_thunk = trimCprTy rhs_ty
|
|
| 523 | - | otherwise = rhs_ty
|
|
| 549 | + | rhs_ty == topCprType = topCprType -- cheap case first
|
|
| 550 | + | stays_thunk = trimCprTy rhs_ty
|
|
| 551 | + | otherwise = rhs_ty
|
|
| 524 | 552 | -- See Note [Arity trimming for CPR signatures]
|
| 525 | 553 | sig = mkCprSigForArity (idArity id) rhs_ty'
|
| 526 | 554 | -- See Note [OPAQUE pragma]
|
| ... | ... | @@ -639,7 +667,7 @@ data AnalEnv |
| 639 | 667 | , ae_fam_envs :: FamInstEnvs
|
| 640 | 668 | -- ^ Needed when expanding type families and synonyms of product types.
|
| 641 | 669 | , ae_rec_dc :: DataCon -> IsRecDataConResult
|
| 642 | - -- ^ Memoised result of 'GHC.Core.Opt.WorkWrap.Utils.isRecDataCon'
|
|
| 670 | + -- ^ Memoised result of 'GHC.Core.Opt.WorkWrap.Utils.isRecDataType
|
|
| 643 | 671 | }
|
| 644 | 672 | |
| 645 | 673 | instance Outputable AnalEnv where
|
| ... | ... | @@ -1042,10 +1070,11 @@ Eliminating the shared 'c' binding in the process. And then |
| 1042 | 1070 | |
| 1043 | 1071 | What can we do about it?
|
| 1044 | 1072 | |
| 1045 | - A. Don't CPR functions that return a *recursive data type* (the list in this
|
|
| 1046 | - case). This is the solution we adopt. Rationale: the benefit of CPR on
|
|
| 1047 | - recursive data structures is slight, because it only affects the outer layer
|
|
| 1048 | - of a potentially massive data structure.
|
|
| 1073 | + A. Don't give recursive data constructors or casts representing recursive newtype constructors
|
|
| 1074 | + the CPR property (the list in this case). This is the solution we adopt.
|
|
| 1075 | + Rationale: the benefit of CPR on recursive data structures is slight,
|
|
| 1076 | + because it only affects the outer layer of a potentially massive data
|
|
| 1077 | + structure.
|
|
| 1049 | 1078 | B. Don't CPR any *recursive function*. That would be quite conservative, as it
|
| 1050 | 1079 | would also affect e.g. the factorial function.
|
| 1051 | 1080 | C. Flat CPR only for recursive functions. This prevents the asymptotic
|
| ... | ... | @@ -1055,10 +1084,15 @@ What can we do about it? |
| 1055 | 1084 | `c` in the second eqn of `replicateC`). But we'd need to know which paths
|
| 1056 | 1085 | were hot. We want such static branch frequency estimates in #20378.
|
| 1057 | 1086 | |
| 1058 | -We adopt solution (A) It is ad-hoc, but appears to work reasonably well.
|
|
| 1059 | -Deciding what a "recursive data constructor" is is quite tricky and ad-hoc, too:
|
|
| 1060 | -See Note [Detecting recursive data constructors]. We don't have to be perfect
|
|
| 1061 | -and can simply keep on unboxing if unsure.
|
|
| 1087 | +We adopt solution (A). It is ad-hoc, but appears to work reasonably well.
|
|
| 1088 | +Specifically:
|
|
| 1089 | + |
|
| 1090 | +* For data constructors, in `cprTransformDataConWork` we check for a recursive
|
|
| 1091 | + data constructor by calling `ae_rec_dc env`, which is just a memoised version
|
|
| 1092 | + of `isRecDataCon`. See Note [Detecting recursive data constructors]
|
|
| 1093 | +* For newtypes, in the `Cast` case of `cprAnal`, we check for a recursive newtype
|
|
| 1094 | + by calling `isRecNewTyConApp`, which in turn calls `ae_rec_dc env`.
|
|
| 1095 | + See Note [CPR for recursive newtype constructors]
|
|
| 1062 | 1096 | |
| 1063 | 1097 | Note [Detecting recursive data constructors]
|
| 1064 | 1098 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| ... | ... | @@ -1075,12 +1109,15 @@ looks inside the following class of types, represented by `ty` (and responds |
| 1075 | 1109 | types of its data constructors and check `tc_args` for recursion.
|
| 1076 | 1110 | C. If `ty = F tc_args`, `F` is a `FamTyCon` and we can reduce `F tc_args` to
|
| 1077 | 1111 | `rhs`, look into the `rhs` type.
|
| 1112 | + D. If `ty = f a`, then look into `f` and `a`
|
|
| 1113 | + E. If `ty = ty' |> co`, then look into `ty'`
|
|
| 1078 | 1114 | |
| 1079 | 1115 | A few perhaps surprising points:
|
| 1080 | 1116 | |
| 1081 | 1117 | 1. It deems any function type as non-recursive, because it's unlikely that
|
| 1082 | 1118 | a recursion through a function type builds up a recursive data structure.
|
| 1083 | - 2. It doesn't look into kinds or coercion types because there's nothing to unbox.
|
|
| 1119 | + 2. It doesn't look into kinds, literals or coercion types because we are
|
|
| 1120 | + ultimately looking for value-level recursion.
|
|
| 1084 | 1121 | Same for promoted data constructors.
|
| 1085 | 1122 | 3. We don't care whether an AlgTyCon app `T tc_args` is fully saturated or not;
|
| 1086 | 1123 | we simply look at its definition/DataCons and its field tys and look for
|
| ... | ... | @@ -1153,6 +1190,22 @@ I've played with the idea to make points (1) through (3) of 'isRecDataCon' |
| 1153 | 1190 | configurable like (4) to enable more re-use throughout the compiler, but haven't
|
| 1154 | 1191 | found a killer app for that yet, so ultimately didn't do that.
|
| 1155 | 1192 | |
| 1193 | +Note [CPR for recursive newtype constructors]
|
|
| 1194 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 1195 | +A newtype constructor is considered recursive iff the data constructor of the
|
|
| 1196 | +equivalent datatype definition is recursive.
|
|
| 1197 | +See Note [CPR for recursive data constructors].
|
|
| 1198 | +Detection is a bit complicated by the fact that newtype constructor applications
|
|
| 1199 | +reflect as Casts in Core:
|
|
| 1200 | + |
|
| 1201 | + newtype List a = C (Maybe (a, List a))
|
|
| 1202 | + xs = C (Just (0, C Nothing))
|
|
| 1203 | + ==> {desugar to Core}
|
|
| 1204 | + xs = Just (0, Nothing |> sym N:List) |> sym N:List
|
|
| 1205 | + |
|
| 1206 | +So the check for `isRecNewTyConApp` is in the Cast case of `cprAnal` rather than
|
|
| 1207 | +in `cprTransformDataConWork` as for data constructors.
|
|
| 1208 | + |
|
| 1156 | 1209 | Note [CPR examples]
|
| 1157 | 1210 | ~~~~~~~~~~~~~~~~~~~
|
| 1158 | 1211 | Here are some examples (stranal/should_compile/T10482a) of the
|
| ... | ... | @@ -63,6 +63,7 @@ import Data.List ( unzip4 ) |
| 63 | 63 | |
| 64 | 64 | import GHC.Types.RepType
|
| 65 | 65 | import GHC.Unit.Types
|
| 66 | +import GHC.Core.TyCo.Rep
|
|
| 66 | 67 | |
| 67 | 68 | {-
|
| 68 | 69 | ************************************************************************
|
| ... | ... | @@ -1426,23 +1427,29 @@ isRecDataCon fam_envs fuel orig_dc |
| 1426 | 1427 | | arg_ty <- map scaledThing (dataConRepArgTys dc) ]
|
| 1427 | 1428 | |
| 1428 | 1429 | go_arg_ty :: IntWithInf -> TyConSet -> Type -> IsRecDataConResult
|
| 1429 | - go_arg_ty fuel visited_tcs ty
|
|
| 1430 | - --- | pprTrace "arg_ty" (ppr ty) False = undefined
|
|
| 1430 | + go_arg_ty fuel visited_tcs ty = -- pprTrace "arg_ty" (ppr ty) $
|
|
| 1431 | + case coreFullView ty of
|
|
| 1432 | + TyConApp tc tc_args -> go_tc_app fuel visited_tcs tc tc_args
|
|
| 1433 | + -- See Note [Detecting recursive data constructors], points (B) and (C)
|
|
| 1431 | 1434 | |
| 1432 | - | Just (_tcv, ty') <- splitForAllTyCoVar_maybe ty
|
|
| 1433 | - = go_arg_ty fuel visited_tcs ty'
|
|
| 1435 | + ForAllTy _ ty' -> go_arg_ty fuel visited_tcs ty'
|
|
| 1434 | 1436 | -- See Note [Detecting recursive data constructors], point (A)
|
| 1435 | 1437 | |
| 1436 | - | Just (tc, tc_args) <- splitTyConApp_maybe ty
|
|
| 1437 | - = go_tc_app fuel visited_tcs tc tc_args
|
|
| 1438 | + CastTy ty' _ -> go_arg_ty fuel visited_tcs ty'
|
|
| 1438 | 1439 | |
| 1439 | - | otherwise
|
|
| 1440 | - = NonRecursiveOrUnsure
|
|
| 1440 | + AppTy f a -> go_arg_ty fuel visited_tcs f `combineIRDCR` go_arg_ty fuel visited_tcs a
|
|
| 1441 | + -- See Note [Detecting recursive data constructors], point (D)
|
|
| 1442 | + |
|
| 1443 | + FunTy{} -> NonRecursiveOrUnsure
|
|
| 1444 | + -- See Note [Detecting recursive data constructors], point (1)
|
|
| 1445 | + |
|
| 1446 | + -- (TyVarTy{} | LitTy{} | CastTy{})
|
|
| 1447 | + _ -> NonRecursiveOrUnsure
|
|
| 1441 | 1448 | |
| 1442 | 1449 | go_tc_app :: IntWithInf -> TyConSet -> TyCon -> [Type] -> IsRecDataConResult
|
| 1443 | 1450 | go_tc_app fuel visited_tcs tc tc_args =
|
| 1444 | 1451 | case tyConDataCons_maybe tc of
|
| 1445 | - --- | pprTrace "tc_app" (vcat [ppr tc, ppr tc_args]) False = undefined
|
|
| 1452 | + ---_ | pprTrace "tc_app" (vcat [ppr tc, ppr tc_args]) False -> undefined
|
|
| 1446 | 1453 | _ | Just (HetReduction (Reduction _ rhs) _) <- topReduceTyFamApp_maybe fam_envs tc tc_args
|
| 1447 | 1454 | -- This is the only place where we look at tc_args, which might have
|
| 1448 | 1455 | -- See Note [Detecting recursive data constructors], point (C) and (5)
|
| ... | ... | @@ -12,6 +12,7 @@ import GHC.Prelude |
| 12 | 12 | import GHC.Types.SrcLoc
|
| 13 | 13 | import GHC.Types.SourceError
|
| 14 | 14 | import GHC.Types.Error
|
| 15 | +import GHC.Utils.Json
|
|
| 15 | 16 | import GHC.Utils.Error
|
| 16 | 17 | import GHC.Utils.Outputable
|
| 17 | 18 | import GHC.Utils.Logger
|
| ... | ... | @@ -46,9 +47,22 @@ printMessages logger msg_opts opts = mapM_ (printMessage logger msg_opts opts) . |
| 46 | 47 | |
| 47 | 48 | printMessage :: forall a. (Diagnostic a) => Logger -> DiagnosticOpts a -> DiagOpts -> MsgEnvelope a -> IO ()
|
| 48 | 49 | printMessage logger msg_opts opts message
|
| 49 | - | log_diags_as_json = logJsonMsg logger messageClass message
|
|
| 50 | + | log_diags_as_json = do
|
|
| 51 | + decorated <- decorateDiagnostic logflags messageClass location doc
|
|
| 52 | + let
|
|
| 53 | + rendered :: String
|
|
| 54 | + rendered = renderWithContext (log_default_user_context logflags) decorated
|
|
| 55 | + |
|
| 56 | + jsonMessage :: JsonDoc
|
|
| 57 | + jsonMessage = jsonDiagnostic rendered message
|
|
| 58 | + |
|
| 59 | + logJsonMsg logger messageClass jsonMessage
|
|
| 60 | + |
|
| 50 | 61 | | otherwise = logMsg logger messageClass location doc
|
| 51 | 62 | where
|
| 63 | + logflags :: LogFlags
|
|
| 64 | + logflags = logFlags logger
|
|
| 65 | + |
|
| 52 | 66 | doc :: SDoc
|
| 53 | 67 | doc = updSDocContext (\_ -> ctx) (messageWithHints diagnostic)
|
| 54 | 68 |
| ... | ... | @@ -73,6 +73,9 @@ module GHC.Types.Error |
| 73 | 73 | , mkLocMessage
|
| 74 | 74 | , mkLocMessageWarningGroups
|
| 75 | 75 | , getCaretDiagnostic
|
| 76 | + |
|
| 77 | + , jsonDiagnostic
|
|
| 78 | + |
|
| 76 | 79 | -- * Queries
|
| 77 | 80 | , isIntrinsicErrorMessage
|
| 78 | 81 | , isExtrinsicErrorMessage
|
| ... | ... | @@ -109,7 +112,7 @@ import GHC.Utils.Panic |
| 109 | 112 | |
| 110 | 113 | import GHC.Version (cProjectVersion)
|
| 111 | 114 | import Data.Bifunctor
|
| 112 | -import Data.Foldable ( fold, toList )
|
|
| 115 | +import Data.Foldable
|
|
| 113 | 116 | import Data.List.NonEmpty ( NonEmpty (..) )
|
| 114 | 117 | import qualified Data.List.NonEmpty as NE
|
| 115 | 118 | import Data.List ( intercalate )
|
| ... | ... | @@ -171,9 +174,6 @@ instance Diagnostic e => Outputable (Messages e) where |
| 171 | 174 | pprDiagnostic (errMsgDiagnostic envelope)
|
| 172 | 175 | ]
|
| 173 | 176 | |
| 174 | -instance (Diagnostic e) => ToJson (Messages e) where
|
|
| 175 | - json msgs = JSArray . toList $ json <$> getMessages msgs
|
|
| 176 | - |
|
| 177 | 177 | {- Note [Discarding Messages]
|
| 178 | 178 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 179 | 179 | |
| ... | ... | @@ -573,7 +573,7 @@ instance ToJson DiagnosticCode where |
| 573 | 573 | {- Note [Diagnostic Message JSON Schema]
|
| 574 | 574 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
| 575 | 575 | The below instance of ToJson must conform to the JSON schema
|
| 576 | -specified in docs/users_guide/diagnostics-as-json-schema-1_1.json.
|
|
| 576 | +specified in docs/users_guide/diagnostics-as-json-schema-1_2.json.
|
|
| 577 | 577 | When the schema is altered, please bump the version.
|
| 578 | 578 | If the content is altered in a backwards compatible way,
|
| 579 | 579 | update the minor version (e.g. 1.3 ~> 1.4).
|
| ... | ... | @@ -586,15 +586,17 @@ https://json-schema.org |
| 586 | 586 | -}
|
| 587 | 587 | |
| 588 | 588 | schemaVersion :: String
|
| 589 | -schemaVersion = "1.1"
|
|
| 589 | +schemaVersion = "1.2"
|
|
| 590 | + |
|
| 590 | 591 | -- See Note [Diagnostic Message JSON Schema] before editing!
|
| 591 | -instance Diagnostic e => ToJson (MsgEnvelope e) where
|
|
| 592 | - json m = JSObject $ [
|
|
| 592 | +jsonDiagnostic :: forall e. Diagnostic e => String -> MsgEnvelope e -> JsonDoc
|
|
| 593 | +jsonDiagnostic rendered m = JSObject $ [
|
|
| 593 | 594 | ("version", JSString schemaVersion),
|
| 594 | 595 | ("ghcVersion", JSString $ "ghc-" ++ cProjectVersion),
|
| 595 | 596 | ("span", json $ errMsgSpan m),
|
| 596 | 597 | ("severity", json $ errMsgSeverity m),
|
| 597 | 598 | ("code", maybe JSNull json (diagnosticCode diag)),
|
| 599 | + ("rendered", JSString rendered),
|
|
| 598 | 600 | ("message", JSArray $ map renderToJSString diagMsg),
|
| 599 | 601 | ("hints", JSArray $ map (renderToJSString . ppr) (diagnosticHints diag) ) ]
|
| 600 | 602 | ++ [ ("reason", reasonJson)
|
| ... | ... | @@ -62,6 +62,8 @@ module GHC.Utils.Logger |
| 62 | 62 | , logJsonMsg
|
| 63 | 63 | , logDumpMsg
|
| 64 | 64 | |
| 65 | + , decorateDiagnostic
|
|
| 66 | + |
|
| 65 | 67 | -- * Dumping
|
| 66 | 68 | , defaultDumpAction
|
| 67 | 69 | , putDumpFile
|
| ... | ... | @@ -419,26 +421,62 @@ defaultLogActionWithHandles out err logflags msg_class srcSpan msg |
| 419 | 421 | MCInfo -> printErrs msg
|
| 420 | 422 | MCFatal -> printErrs msg
|
| 421 | 423 | MCDiagnostic SevIgnore _ _ -> pure () -- suppress the message
|
| 422 | - MCDiagnostic _sev _rea _code -> printDiagnostics
|
|
| 424 | + MCDiagnostic _sev _rea _code -> decorateDiagnostic logflags msg_class srcSpan msg >>= printErrs
|
|
| 423 | 425 | where
|
| 424 | 426 | printOut = defaultLogActionHPrintDoc logflags False out
|
| 425 | 427 | printErrs = defaultLogActionHPrintDoc logflags False err
|
| 426 | 428 | putStrSDoc = defaultLogActionHPutStrDoc logflags False out
|
| 429 | + |
|
| 430 | +-- This function is used by `defaultLogActionWithHandles` for non-JSON output,
|
|
| 431 | +-- and also by `GHC.Driver.Errors.printMessages` to produce the `rendered`
|
|
| 432 | +-- message on `-fdiagnostics-as-json`.
|
|
| 433 | +--
|
|
| 434 | +-- We would want to eventually consolidate this. However, this is currently
|
|
| 435 | +-- not feasible for the following reasons:
|
|
| 436 | +--
|
|
| 437 | +-- 1. Some parts of the compiler sidestep `printMessages`, for that reason we
|
|
| 438 | +-- can not decorate the message in `printMessages`.
|
|
| 439 | +--
|
|
| 440 | +-- 2. GHC uses two different code paths for JSON and non-JSON diagnostics. For
|
|
| 441 | +-- that reason we can not decorate the message in `defaultLogActionWithHandles`.
|
|
| 442 | +--
|
|
| 443 | +-- See also Note [JSON Error Messages]:
|
|
| 444 | +--
|
|
| 445 | +-- `jsonLogAction` should be removed along with -ddump-json
|
|
| 446 | +--
|
|
| 447 | +-- Also note that (1) is the reason why some parts of the compiler produce
|
|
| 448 | +-- diagnostics that don't respect `-fdiagnostics-as-json`.
|
|
| 449 | +--
|
|
| 450 | +-- The plan as I see it is as follows:
|
|
| 451 | +--
|
|
| 452 | +-- 1. Refactor all places in the compiler that report diagnostics to go
|
|
| 453 | +-- through `GHC.Driver.Errors.printMessages`.
|
|
| 454 | +--
|
|
| 455 | +-- (It's easy to find all those places by looking for who creates
|
|
| 456 | +-- MCDiagnostic, either directly or via `mkMCDiagnostic` or
|
|
| 457 | +-- `errorDiagnostic`.)
|
|
| 458 | +--
|
|
| 459 | +-- 2. Get rid of `-ddump-json`, `jsonLogAction` and consolidate message
|
|
| 460 | +-- decoration at one place (either `printMessages` or
|
|
| 461 | +-- `defaultLogActionWithHandles`)
|
|
| 462 | +--
|
|
| 463 | +-- This story is tracked by #24113.
|
|
| 464 | +decorateDiagnostic :: LogFlags -> MessageClass -> SrcSpan -> SDoc -> IO SDoc
|
|
| 465 | +decorateDiagnostic logflags msg_class srcSpan msg = addCaret
|
|
| 466 | + where
|
|
| 427 | 467 | -- Pretty print the warning flag, if any (#10752)
|
| 468 | + message :: SDoc
|
|
| 428 | 469 | message = mkLocMessageWarningGroups (log_show_warn_groups logflags) msg_class srcSpan msg
|
| 429 | 470 | |
| 430 | - printDiagnostics = do
|
|
| 471 | + addCaret :: IO SDoc
|
|
| 472 | + addCaret = do
|
|
| 431 | 473 | caretDiagnostic <-
|
| 432 | 474 | if log_show_caret logflags
|
| 433 | 475 | then getCaretDiagnostic msg_class srcSpan
|
| 434 | 476 | else pure empty
|
| 435 | - printErrs $ getPprStyle $ \style ->
|
|
| 477 | + return $ getPprStyle $ \style ->
|
|
| 436 | 478 | withPprStyle (setStyleColoured True style)
|
| 437 | 479 | (message $+$ caretDiagnostic $+$ blankLine)
|
| 438 | - -- careful (#2302): printErrs prints in UTF-8,
|
|
| 439 | - -- whereas converting to string first and using
|
|
| 440 | - -- hPutStr would just emit the low 8 bits of
|
|
| 441 | - -- each unicode char.
|
|
| 442 | 480 | |
| 443 | 481 | -- | Like 'defaultLogActionHPutStrDoc' but appends an extra newline.
|
| 444 | 482 | defaultLogActionHPrintDoc :: LogFlags -> Bool -> Handle -> SDoc -> IO ()
|
| ... | ... | @@ -603,8 +641,8 @@ defaultTraceAction logflags title doc x = |
| 603 | 641 | logMsg :: Logger -> MessageClass -> SrcSpan -> SDoc -> IO ()
|
| 604 | 642 | logMsg logger mc loc msg = putLogMsg logger (logFlags logger) mc loc msg
|
| 605 | 643 | |
| 606 | -logJsonMsg :: ToJson a => Logger -> MessageClass -> a -> IO ()
|
|
| 607 | -logJsonMsg logger mc d = putJsonLogMsg logger (logFlags logger) mc (json d)
|
|
| 644 | +logJsonMsg :: Logger -> MessageClass -> JsonDoc -> IO ()
|
|
| 645 | +logJsonMsg logger mc = putJsonLogMsg logger (logFlags logger) mc
|
|
| 608 | 646 | |
| 609 | 647 | -- | Dump something
|
| 610 | 648 | logDumpFile :: Logger -> PprStyle -> DumpFlag -> String -> DumpFormat -> SDoc -> IO ()
|
| ... | ... | @@ -147,6 +147,11 @@ Compiler |
| 147 | 147 | integer operations. Also, ``shuffleFloatX4#`` and ``shuffleDoubleX2#`` no longer
|
| 148 | 148 | require ``-mavx``.
|
| 149 | 149 | |
| 150 | +- JSON diagnostics produced with (:ghc-flag:`-fdiagnostics-as-json`) now
|
|
| 151 | + include the `rendered` diagnostics message, in the exact same format as what
|
|
| 152 | + GHC would have produced without -fdiagnostics-as-json (including ANSI escape
|
|
| 153 | + sequences).
|
|
| 154 | + |
|
| 150 | 155 | GHCi
|
| 151 | 156 | ~~~~
|
| 152 | 157 |
| 1 | +{
|
|
| 2 | + "$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
| 3 | + "title": "JSON Diagnostic Schema",
|
|
| 4 | + "description": "A Schema for specifying GHC diagnostics output as JSON",
|
|
| 5 | + "type": "object",
|
|
| 6 | + "properties": {
|
|
| 7 | + "version": {
|
|
| 8 | + "description": "The current JSON schema version this object conforms to",
|
|
| 9 | + "type": "string"
|
|
| 10 | + },
|
|
| 11 | + "ghcVersion": {
|
|
| 12 | + "description": "The GHC version",
|
|
| 13 | + "type": "string"
|
|
| 14 | + },
|
|
| 15 | + "span": {
|
|
| 16 | + "oneOf": [
|
|
| 17 | + { "$ref": "#/$defs/span" },
|
|
| 18 | + { "type": "null" }
|
|
| 19 | + ]
|
|
| 20 | + },
|
|
| 21 | + "severity": {
|
|
| 22 | + "description": "The diagnostic severity",
|
|
| 23 | + "type": "string",
|
|
| 24 | + "enum": [
|
|
| 25 | + "Warning",
|
|
| 26 | + "Error"
|
|
| 27 | + ]
|
|
| 28 | + },
|
|
| 29 | + "code": {
|
|
| 30 | + "description": "The diagnostic code (if it exists)",
|
|
| 31 | + "type": [
|
|
| 32 | + "integer",
|
|
| 33 | + "null"
|
|
| 34 | + ]
|
|
| 35 | + },
|
|
| 36 | + "rendered": {
|
|
| 37 | + "description": "The rendered diagnostics message, in the exact same format as what GHC would have produced without -fdiagnostics-as-json (including ANSI escape sequences)",
|
|
| 38 | + "type": "string"
|
|
| 39 | + },
|
|
| 40 | + "message": {
|
|
| 41 | + "description": "The string output of the diagnostic message by GHC",
|
|
| 42 | + "type": "array",
|
|
| 43 | + "items": {
|
|
| 44 | + "type": "string"
|
|
| 45 | + }
|
|
| 46 | + },
|
|
| 47 | + "hints": {
|
|
| 48 | + "description": "The suggested fixes",
|
|
| 49 | + "type": "array",
|
|
| 50 | + "items": {
|
|
| 51 | + "type": "string"
|
|
| 52 | + }
|
|
| 53 | + },
|
|
| 54 | + "reason" : {
|
|
| 55 | + "description": "The GHC flag that was responsible for the emission of the diagnostic message",
|
|
| 56 | + "oneOf": [
|
|
| 57 | + {
|
|
| 58 | + "type": "object",
|
|
| 59 | + "description": "The diagnostic message was controlled by one or more GHC flags",
|
|
| 60 | + "properties": {
|
|
| 61 | + "flags": {
|
|
| 62 | + "type": "array",
|
|
| 63 | + "items": {
|
|
| 64 | + "description": "The name of a GHC flag controlling the diagnostic message",
|
|
| 65 | + "type": "string"
|
|
| 66 | + },
|
|
| 67 | + "minItems": 1
|
|
| 68 | + }
|
|
| 69 | + },
|
|
| 70 | + "required": ["flags"]
|
|
| 71 | + },
|
|
| 72 | + {
|
|
| 73 | + "type": "object",
|
|
| 74 | + "description": "The diagnostic message was controlled by a GHC diagnostic message category",
|
|
| 75 | + "properties": {
|
|
| 76 | + "category": {
|
|
| 77 | + "description": "The name of the GHC diagnostic message category controlling the diagnostic message",
|
|
| 78 | + "type": "string"
|
|
| 79 | + }
|
|
| 80 | + },
|
|
| 81 | + "required": ["category"]
|
|
| 82 | + }
|
|
| 83 | + ]
|
|
| 84 | + }
|
|
| 85 | + },
|
|
| 86 | + |
|
| 87 | + "$comment": "NOTE: \"rendered\" is not a required field so that the schema is backward compatible with version 1.1. If you bump the schema version to 2.0 the please also add \"rendered\" to the \"required\" fields.",
|
|
| 88 | + "required": [
|
|
| 89 | + "version",
|
|
| 90 | + "ghcVersion",
|
|
| 91 | + "span",
|
|
| 92 | + "severity",
|
|
| 93 | + "code",
|
|
| 94 | + "message",
|
|
| 95 | + "hints"
|
|
| 96 | + ],
|
|
| 97 | + |
|
| 98 | + "additionalProperties": false,
|
|
| 99 | + "$defs": {
|
|
| 100 | + "span": {
|
|
| 101 | + "description": "The span of the diagnostic",
|
|
| 102 | + "type": "object",
|
|
| 103 | + "properties": {
|
|
| 104 | + "file": {
|
|
| 105 | + "description": "The file in which the diagnostic occurs",
|
|
| 106 | + "type": "string"
|
|
| 107 | + },
|
|
| 108 | + "start": {
|
|
| 109 | + "description": "The start location of the diagnostic",
|
|
| 110 | + "$ref": "#/$defs/location"
|
|
| 111 | + },
|
|
| 112 | + "end": {
|
|
| 113 | + "description": "The end location of the diagnostic",
|
|
| 114 | + "$ref": "#/$defs/location"
|
|
| 115 | + }
|
|
| 116 | + },
|
|
| 117 | + "required": [
|
|
| 118 | + "file",
|
|
| 119 | + "start",
|
|
| 120 | + "end"
|
|
| 121 | + ],
|
|
| 122 | + "additionalProperties": false
|
|
| 123 | + },
|
|
| 124 | + "location": {
|
|
| 125 | + "description": "A location in a text file",
|
|
| 126 | + "type": "object",
|
|
| 127 | + "properties": {
|
|
| 128 | + "line": {
|
|
| 129 | + "description": "The line number",
|
|
| 130 | + "type": "integer"
|
|
| 131 | + },
|
|
| 132 | + "column": {
|
|
| 133 | + "description": "The column number",
|
|
| 134 | + "type": "integer"
|
|
| 135 | + }
|
|
| 136 | + },
|
|
| 137 | + "required": [
|
|
| 138 | + "line",
|
|
| 139 | + "column"
|
|
| 140 | + ],
|
|
| 141 | + "additionalProperties": false
|
|
| 142 | + }
|
|
| 143 | + }
|
|
| 144 | +} |
| ... | ... | @@ -1428,7 +1428,7 @@ messages and in GHCi: |
| 1428 | 1428 | a new line.
|
| 1429 | 1429 | |
| 1430 | 1430 | The structure of the output is described by a `JSON Schema <https://json-schema.org/>`_.
|
| 1431 | - The schema can be downloaded :download:`here <diagnostics-as-json-schema-1_1.json>`.
|
|
| 1431 | + The schema can be downloaded :download:`here <diagnostics-as-json-schema-1_2.json>`.
|
|
| 1432 | 1432 | |
| 1433 | 1433 | .. ghc-flag:: -fdiagnostics-color=⟨always|auto|never⟩
|
| 1434 | 1434 | :shortdesc: Use colors in error messages
|
| ... | ... | @@ -473,6 +473,72 @@ void interp_shutdown( void ){ |
| 473 | 473 | |
| 474 | 474 | #endif
|
| 475 | 475 | |
| 476 | +const StgPtr ctoi_tuple_infos[] = {
|
|
| 477 | + (StgPtr) &stg_ctoi_t0_info,
|
|
| 478 | + (StgPtr) &stg_ctoi_t1_info,
|
|
| 479 | + (StgPtr) &stg_ctoi_t2_info,
|
|
| 480 | + (StgPtr) &stg_ctoi_t3_info,
|
|
| 481 | + (StgPtr) &stg_ctoi_t4_info,
|
|
| 482 | + (StgPtr) &stg_ctoi_t5_info,
|
|
| 483 | + (StgPtr) &stg_ctoi_t6_info,
|
|
| 484 | + (StgPtr) &stg_ctoi_t7_info,
|
|
| 485 | + (StgPtr) &stg_ctoi_t8_info,
|
|
| 486 | + (StgPtr) &stg_ctoi_t9_info,
|
|
| 487 | + (StgPtr) &stg_ctoi_t10_info,
|
|
| 488 | + (StgPtr) &stg_ctoi_t11_info,
|
|
| 489 | + (StgPtr) &stg_ctoi_t12_info,
|
|
| 490 | + (StgPtr) &stg_ctoi_t13_info,
|
|
| 491 | + (StgPtr) &stg_ctoi_t14_info,
|
|
| 492 | + (StgPtr) &stg_ctoi_t15_info,
|
|
| 493 | + (StgPtr) &stg_ctoi_t16_info,
|
|
| 494 | + (StgPtr) &stg_ctoi_t17_info,
|
|
| 495 | + (StgPtr) &stg_ctoi_t18_info,
|
|
| 496 | + (StgPtr) &stg_ctoi_t19_info,
|
|
| 497 | + (StgPtr) &stg_ctoi_t20_info,
|
|
| 498 | + (StgPtr) &stg_ctoi_t21_info,
|
|
| 499 | + (StgPtr) &stg_ctoi_t22_info,
|
|
| 500 | + (StgPtr) &stg_ctoi_t23_info,
|
|
| 501 | + (StgPtr) &stg_ctoi_t24_info,
|
|
| 502 | + (StgPtr) &stg_ctoi_t25_info,
|
|
| 503 | + (StgPtr) &stg_ctoi_t26_info,
|
|
| 504 | + (StgPtr) &stg_ctoi_t27_info,
|
|
| 505 | + (StgPtr) &stg_ctoi_t28_info,
|
|
| 506 | + (StgPtr) &stg_ctoi_t29_info,
|
|
| 507 | + (StgPtr) &stg_ctoi_t30_info,
|
|
| 508 | + (StgPtr) &stg_ctoi_t31_info,
|
|
| 509 | + (StgPtr) &stg_ctoi_t32_info,
|
|
| 510 | + (StgPtr) &stg_ctoi_t33_info,
|
|
| 511 | + (StgPtr) &stg_ctoi_t34_info,
|
|
| 512 | + (StgPtr) &stg_ctoi_t35_info,
|
|
| 513 | + (StgPtr) &stg_ctoi_t36_info,
|
|
| 514 | + (StgPtr) &stg_ctoi_t37_info,
|
|
| 515 | + (StgPtr) &stg_ctoi_t38_info,
|
|
| 516 | + (StgPtr) &stg_ctoi_t39_info,
|
|
| 517 | + (StgPtr) &stg_ctoi_t40_info,
|
|
| 518 | + (StgPtr) &stg_ctoi_t41_info,
|
|
| 519 | + (StgPtr) &stg_ctoi_t42_info,
|
|
| 520 | + (StgPtr) &stg_ctoi_t43_info,
|
|
| 521 | + (StgPtr) &stg_ctoi_t44_info,
|
|
| 522 | + (StgPtr) &stg_ctoi_t45_info,
|
|
| 523 | + (StgPtr) &stg_ctoi_t46_info,
|
|
| 524 | + (StgPtr) &stg_ctoi_t47_info,
|
|
| 525 | + (StgPtr) &stg_ctoi_t48_info,
|
|
| 526 | + (StgPtr) &stg_ctoi_t49_info,
|
|
| 527 | + (StgPtr) &stg_ctoi_t50_info,
|
|
| 528 | + (StgPtr) &stg_ctoi_t51_info,
|
|
| 529 | + (StgPtr) &stg_ctoi_t52_info,
|
|
| 530 | + (StgPtr) &stg_ctoi_t53_info,
|
|
| 531 | + (StgPtr) &stg_ctoi_t54_info,
|
|
| 532 | + (StgPtr) &stg_ctoi_t55_info,
|
|
| 533 | + (StgPtr) &stg_ctoi_t56_info,
|
|
| 534 | + (StgPtr) &stg_ctoi_t57_info,
|
|
| 535 | + (StgPtr) &stg_ctoi_t58_info,
|
|
| 536 | + (StgPtr) &stg_ctoi_t59_info,
|
|
| 537 | + (StgPtr) &stg_ctoi_t60_info,
|
|
| 538 | + (StgPtr) &stg_ctoi_t61_info,
|
|
| 539 | + (StgPtr) &stg_ctoi_t62_info,
|
|
| 540 | +};
|
|
| 541 | + |
|
| 476 | 542 | #if defined(PROFILING)
|
| 477 | 543 | |
| 478 | 544 | //
|
| ... | ... | @@ -1828,82 +1894,11 @@ run_BCO: |
| 1828 | 1894 | SpW(-1) = BCO_PTR(o_tuple_bco);
|
| 1829 | 1895 | SpW(-2) = tuple_info;
|
| 1830 | 1896 | SpW(-3) = BCO_PTR(o_bco);
|
| 1831 | - W_ ctoi_t_offset;
|
|
| 1832 | 1897 | int tuple_stack_words = (tuple_info >> 24) & 0xff;
|
| 1833 | - switch(tuple_stack_words) {
|
|
| 1834 | - case 0: ctoi_t_offset = (W_)&stg_ctoi_t0_info; break;
|
|
| 1835 | - case 1: ctoi_t_offset = (W_)&stg_ctoi_t1_info; break;
|
|
| 1836 | - case 2: ctoi_t_offset = (W_)&stg_ctoi_t2_info; break;
|
|
| 1837 | - case 3: ctoi_t_offset = (W_)&stg_ctoi_t3_info; break;
|
|
| 1838 | - case 4: ctoi_t_offset = (W_)&stg_ctoi_t4_info; break;
|
|
| 1839 | - case 5: ctoi_t_offset = (W_)&stg_ctoi_t5_info; break;
|
|
| 1840 | - case 6: ctoi_t_offset = (W_)&stg_ctoi_t6_info; break;
|
|
| 1841 | - case 7: ctoi_t_offset = (W_)&stg_ctoi_t7_info; break;
|
|
| 1842 | - case 8: ctoi_t_offset = (W_)&stg_ctoi_t8_info; break;
|
|
| 1843 | - case 9: ctoi_t_offset = (W_)&stg_ctoi_t9_info; break;
|
|
| 1844 | - |
|
| 1845 | - case 10: ctoi_t_offset = (W_)&stg_ctoi_t10_info; break;
|
|
| 1846 | - case 11: ctoi_t_offset = (W_)&stg_ctoi_t11_info; break;
|
|
| 1847 | - case 12: ctoi_t_offset = (W_)&stg_ctoi_t12_info; break;
|
|
| 1848 | - case 13: ctoi_t_offset = (W_)&stg_ctoi_t13_info; break;
|
|
| 1849 | - case 14: ctoi_t_offset = (W_)&stg_ctoi_t14_info; break;
|
|
| 1850 | - case 15: ctoi_t_offset = (W_)&stg_ctoi_t15_info; break;
|
|
| 1851 | - case 16: ctoi_t_offset = (W_)&stg_ctoi_t16_info; break;
|
|
| 1852 | - case 17: ctoi_t_offset = (W_)&stg_ctoi_t17_info; break;
|
|
| 1853 | - case 18: ctoi_t_offset = (W_)&stg_ctoi_t18_info; break;
|
|
| 1854 | - case 19: ctoi_t_offset = (W_)&stg_ctoi_t19_info; break;
|
|
| 1855 | - |
|
| 1856 | - case 20: ctoi_t_offset = (W_)&stg_ctoi_t20_info; break;
|
|
| 1857 | - case 21: ctoi_t_offset = (W_)&stg_ctoi_t21_info; break;
|
|
| 1858 | - case 22: ctoi_t_offset = (W_)&stg_ctoi_t22_info; break;
|
|
| 1859 | - case 23: ctoi_t_offset = (W_)&stg_ctoi_t23_info; break;
|
|
| 1860 | - case 24: ctoi_t_offset = (W_)&stg_ctoi_t24_info; break;
|
|
| 1861 | - case 25: ctoi_t_offset = (W_)&stg_ctoi_t25_info; break;
|
|
| 1862 | - case 26: ctoi_t_offset = (W_)&stg_ctoi_t26_info; break;
|
|
| 1863 | - case 27: ctoi_t_offset = (W_)&stg_ctoi_t27_info; break;
|
|
| 1864 | - case 28: ctoi_t_offset = (W_)&stg_ctoi_t28_info; break;
|
|
| 1865 | - case 29: ctoi_t_offset = (W_)&stg_ctoi_t29_info; break;
|
|
| 1866 | - |
|
| 1867 | - case 30: ctoi_t_offset = (W_)&stg_ctoi_t30_info; break;
|
|
| 1868 | - case 31: ctoi_t_offset = (W_)&stg_ctoi_t31_info; break;
|
|
| 1869 | - case 32: ctoi_t_offset = (W_)&stg_ctoi_t32_info; break;
|
|
| 1870 | - case 33: ctoi_t_offset = (W_)&stg_ctoi_t33_info; break;
|
|
| 1871 | - case 34: ctoi_t_offset = (W_)&stg_ctoi_t34_info; break;
|
|
| 1872 | - case 35: ctoi_t_offset = (W_)&stg_ctoi_t35_info; break;
|
|
| 1873 | - case 36: ctoi_t_offset = (W_)&stg_ctoi_t36_info; break;
|
|
| 1874 | - case 37: ctoi_t_offset = (W_)&stg_ctoi_t37_info; break;
|
|
| 1875 | - case 38: ctoi_t_offset = (W_)&stg_ctoi_t38_info; break;
|
|
| 1876 | - case 39: ctoi_t_offset = (W_)&stg_ctoi_t39_info; break;
|
|
| 1877 | - |
|
| 1878 | - case 40: ctoi_t_offset = (W_)&stg_ctoi_t40_info; break;
|
|
| 1879 | - case 41: ctoi_t_offset = (W_)&stg_ctoi_t41_info; break;
|
|
| 1880 | - case 42: ctoi_t_offset = (W_)&stg_ctoi_t42_info; break;
|
|
| 1881 | - case 43: ctoi_t_offset = (W_)&stg_ctoi_t43_info; break;
|
|
| 1882 | - case 44: ctoi_t_offset = (W_)&stg_ctoi_t44_info; break;
|
|
| 1883 | - case 45: ctoi_t_offset = (W_)&stg_ctoi_t45_info; break;
|
|
| 1884 | - case 46: ctoi_t_offset = (W_)&stg_ctoi_t46_info; break;
|
|
| 1885 | - case 47: ctoi_t_offset = (W_)&stg_ctoi_t47_info; break;
|
|
| 1886 | - case 48: ctoi_t_offset = (W_)&stg_ctoi_t48_info; break;
|
|
| 1887 | - case 49: ctoi_t_offset = (W_)&stg_ctoi_t49_info; break;
|
|
| 1888 | - |
|
| 1889 | - case 50: ctoi_t_offset = (W_)&stg_ctoi_t50_info; break;
|
|
| 1890 | - case 51: ctoi_t_offset = (W_)&stg_ctoi_t51_info; break;
|
|
| 1891 | - case 52: ctoi_t_offset = (W_)&stg_ctoi_t52_info; break;
|
|
| 1892 | - case 53: ctoi_t_offset = (W_)&stg_ctoi_t53_info; break;
|
|
| 1893 | - case 54: ctoi_t_offset = (W_)&stg_ctoi_t54_info; break;
|
|
| 1894 | - case 55: ctoi_t_offset = (W_)&stg_ctoi_t55_info; break;
|
|
| 1895 | - case 56: ctoi_t_offset = (W_)&stg_ctoi_t56_info; break;
|
|
| 1896 | - case 57: ctoi_t_offset = (W_)&stg_ctoi_t57_info; break;
|
|
| 1897 | - case 58: ctoi_t_offset = (W_)&stg_ctoi_t58_info; break;
|
|
| 1898 | - case 59: ctoi_t_offset = (W_)&stg_ctoi_t59_info; break;
|
|
| 1899 | - |
|
| 1900 | - case 60: ctoi_t_offset = (W_)&stg_ctoi_t60_info; break;
|
|
| 1901 | - case 61: ctoi_t_offset = (W_)&stg_ctoi_t61_info; break;
|
|
| 1902 | - case 62: ctoi_t_offset = (W_)&stg_ctoi_t62_info; break;
|
|
| 1903 | - |
|
| 1904 | - default: barf("unsupported tuple size %d", tuple_stack_words);
|
|
| 1898 | + if (tuple_stack_words > 62) {
|
|
| 1899 | + barf("unsupported tuple size %d", tuple_stack_words);
|
|
| 1905 | 1900 | }
|
| 1906 | - |
|
| 1901 | + W_ ctoi_t_offset = (W_) ctoi_tuple_infos[tuple_stack_words];
|
|
| 1907 | 1902 | SpW(-4) = ctoi_t_offset;
|
| 1908 | 1903 | Sp_subW(4);
|
| 1909 | 1904 | goto nextInsn;
|
| ... | ... | @@ -1211,16 +1211,27 @@ INFO_TABLE_RET(stg_catch_retry_frame, CATCH_RETRY_FRAME, |
| 1211 | 1211 | gcptr trec, outer, arg;
|
| 1212 | 1212 | |
| 1213 | 1213 | trec = StgTSO_trec(CurrentTSO);
|
| 1214 | - outer = StgTRecHeader_enclosing_trec(trec);
|
|
| 1215 | - (r) = ccall stmCommitNestedTransaction(MyCapability() "ptr", trec "ptr");
|
|
| 1216 | - if (r != 0) {
|
|
| 1217 | - // Succeeded (either first branch or second branch)
|
|
| 1218 | - StgTSO_trec(CurrentTSO) = outer;
|
|
| 1219 | - return (ret);
|
|
| 1220 | - } else {
|
|
| 1221 | - // Did not commit: abort and restart.
|
|
| 1222 | - StgTSO_trec(CurrentTSO) = outer;
|
|
| 1223 | - jump stg_abort();
|
|
| 1214 | + if (running_alt_code != 1) {
|
|
| 1215 | + // When exiting the lhs code of catchRetry# lhs rhs, we need to cleanup
|
|
| 1216 | + // the nested transaction.
|
|
| 1217 | + // See Note [catchRetry# implementation]
|
|
| 1218 | + outer = StgTRecHeader_enclosing_trec(trec);
|
|
| 1219 | + (r) = ccall stmCommitNestedTransaction(MyCapability() "ptr", trec "ptr");
|
|
| 1220 | + if (r != 0) {
|
|
| 1221 | + // Succeeded in first branch
|
|
| 1222 | + StgTSO_trec(CurrentTSO) = outer;
|
|
| 1223 | + return (ret);
|
|
| 1224 | + } else {
|
|
| 1225 | + // Did not commit: abort and restart.
|
|
| 1226 | + StgTSO_trec(CurrentTSO) = outer;
|
|
| 1227 | + jump stg_abort();
|
|
| 1228 | + }
|
|
| 1229 | + }
|
|
| 1230 | + else {
|
|
| 1231 | + // nothing to do in the rhs code of catchRetry# lhs rhs, it's already
|
|
| 1232 | + // using the parent transaction (not a nested one).
|
|
| 1233 | + // See Note [catchRetry# implementation]
|
|
| 1234 | + return (ret);
|
|
| 1224 | 1235 | }
|
| 1225 | 1236 | }
|
| 1226 | 1237 | |
| ... | ... | @@ -1453,21 +1464,26 @@ retry_pop_stack: |
| 1453 | 1464 | outer = StgTRecHeader_enclosing_trec(trec);
|
| 1454 | 1465 | |
| 1455 | 1466 | if (frame_type == CATCH_RETRY_FRAME) {
|
| 1456 | - // The retry reaches a CATCH_RETRY_FRAME before the atomic frame
|
|
| 1457 | - ASSERT(outer != NO_TREC);
|
|
| 1458 | - // Abort the transaction attempting the current branch
|
|
| 1459 | - ccall stmAbortTransaction(MyCapability() "ptr", trec "ptr");
|
|
| 1460 | - ccall stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr");
|
|
| 1467 | + // The retry reaches a CATCH_RETRY_FRAME before the ATOMICALLY_FRAME
|
|
| 1468 | + |
|
| 1461 | 1469 | if (!StgCatchRetryFrame_running_alt_code(frame) != 0) {
|
| 1462 | - // Retry in the first branch: try the alternative
|
|
| 1463 | - ("ptr" trec) = ccall stmStartTransaction(MyCapability() "ptr", outer "ptr");
|
|
| 1464 | - StgTSO_trec(CurrentTSO) = trec;
|
|
| 1470 | + // Retrying in the lhs of catchRetry# lhs rhs, i.e. in a nested
|
|
| 1471 | + // transaction. See Note [catchRetry# implementation]
|
|
| 1472 | + |
|
| 1473 | + // check that we have a parent transaction
|
|
| 1474 | + ASSERT(outer != NO_TREC);
|
|
| 1475 | + |
|
| 1476 | + // Abort the nested transaction
|
|
| 1477 | + ccall stmAbortTransaction(MyCapability() "ptr", trec "ptr");
|
|
| 1478 | + ccall stmFreeAbortedTRec(MyCapability() "ptr", trec "ptr");
|
|
| 1479 | + |
|
| 1480 | + // As we are retrying in the lhs code, we must now try the rhs code
|
|
| 1481 | + StgTSO_trec(CurrentTSO) = outer;
|
|
| 1465 | 1482 | StgCatchRetryFrame_running_alt_code(frame) = 1 :: CInt; // true;
|
| 1466 | 1483 | R1 = StgCatchRetryFrame_alt_code(frame);
|
| 1467 | 1484 | jump stg_ap_v_fast [R1];
|
| 1468 | 1485 | } else {
|
| 1469 | - // Retry in the alternative code: propagate the retry
|
|
| 1470 | - StgTSO_trec(CurrentTSO) = outer;
|
|
| 1486 | + // Retry in the rhs code: propagate the retry
|
|
| 1471 | 1487 | Sp = Sp + SIZEOF_StgCatchRetryFrame;
|
| 1472 | 1488 | goto retry_pop_stack;
|
| 1473 | 1489 | }
|
| ... | ... | @@ -1043,8 +1043,7 @@ raiseAsync(Capability *cap, StgTSO *tso, StgClosure *exception, |
| 1043 | 1043 | }
|
| 1044 | 1044 | |
| 1045 | 1045 | case CATCH_STM_FRAME:
|
| 1046 | - case CATCH_RETRY_FRAME:
|
|
| 1047 | - // CATCH frames within an atomically block: abort the
|
|
| 1046 | + // CATCH_STM frame within an atomically block: abort the
|
|
| 1048 | 1047 | // inner transaction and continue. Eventually we will
|
| 1049 | 1048 | // hit the outer transaction that will get frozen (see
|
| 1050 | 1049 | // above).
|
| ... | ... | @@ -1056,14 +1055,40 @@ raiseAsync(Capability *cap, StgTSO *tso, StgClosure *exception, |
| 1056 | 1055 | {
|
| 1057 | 1056 | StgTRecHeader *trec = tso -> trec;
|
| 1058 | 1057 | StgTRecHeader *outer = trec -> enclosing_trec;
|
| 1059 | - debugTraceCap(DEBUG_stm, cap,
|
|
| 1060 | - "found atomically block delivering async exception");
|
|
| 1058 | + debugTraceCap(DEBUG_stm, cap, "raiseAsync: traversing CATCH_STM frame");
|
|
| 1061 | 1059 | stmAbortTransaction(cap, trec);
|
| 1062 | 1060 | stmFreeAbortedTRec(cap, trec);
|
| 1063 | 1061 | tso -> trec = outer;
|
| 1064 | 1062 | break;
|
| 1065 | 1063 | };
|
| 1066 | 1064 | |
| 1065 | + case CATCH_RETRY_FRAME:
|
|
| 1066 | + // CATCH_RETY frame within an atomically block: if we're executing
|
|
| 1067 | + // the lhs code, abort the inner transaction and continue; if we're
|
|
| 1068 | + // executing thr rhs, continue (no nested transaction to abort. See
|
|
| 1069 | + // Note [catchRetry# implementation]). Eventually we will hit the
|
|
| 1070 | + // outer transaction that will get frozen (see above).
|
|
| 1071 | + //
|
|
| 1072 | + // As for the CATCH_STM_FRAME case above, we do not care
|
|
| 1073 | + // whether the transaction is valid or not because its
|
|
| 1074 | + // possible validity cannot have caused the exception
|
|
| 1075 | + // and will not be visible after the abort.
|
|
| 1076 | + {
|
|
| 1077 | + if (!((StgCatchRetryFrame *)frame) -> running_alt_code) {
|
|
| 1078 | + debugTraceCap(DEBUG_stm, cap, "raiseAsync: traversing CATCH_RETRY frame (lhs)");
|
|
| 1079 | + StgTRecHeader *trec = tso -> trec;
|
|
| 1080 | + StgTRecHeader *outer = trec -> enclosing_trec;
|
|
| 1081 | + stmAbortTransaction(cap, trec);
|
|
| 1082 | + stmFreeAbortedTRec(cap, trec);
|
|
| 1083 | + tso -> trec = outer;
|
|
| 1084 | + }
|
|
| 1085 | + else
|
|
| 1086 | + {
|
|
| 1087 | + debugTraceCap(DEBUG_stm, cap, "raiseAsync: traversing CATCH_RETRY frame (rhs)");
|
|
| 1088 | + }
|
|
| 1089 | + break;
|
|
| 1090 | + };
|
|
| 1091 | + |
|
| 1067 | 1092 | default:
|
| 1068 | 1093 | // see Note [Update async masking state on unwind] in Schedule.c
|
| 1069 | 1094 | if (*frame == (W_)&stg_unmaskAsyncExceptionszh_ret_info) {
|
| ... | ... | @@ -1505,3 +1505,30 @@ void stmWriteTVar(Capability *cap, |
| 1505 | 1505 | }
|
| 1506 | 1506 | |
| 1507 | 1507 | /*......................................................................*/
|
| 1508 | + |
|
| 1509 | + |
|
| 1510 | + |
|
| 1511 | +/*
|
|
| 1512 | + |
|
| 1513 | +Note [catchRetry# implementation]
|
|
| 1514 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 1515 | +catchRetry# creates a nested transaction for its lhs:
|
|
| 1516 | +- if the lhs transaction succeeds:
|
|
| 1517 | + - the lhs transaction is committed
|
|
| 1518 | + - its read-variables are merged with those of the parent transaction
|
|
| 1519 | + - the rhs code is ignored
|
|
| 1520 | +- if the lhs transaction retries:
|
|
| 1521 | + - the lhs transaction is aborted
|
|
| 1522 | + - its read-variables are merged with those of the parent transaction
|
|
| 1523 | + - the rhs code is executed directly in the parent transaction (see #26028).
|
|
| 1524 | + |
|
| 1525 | +So note that:
|
|
| 1526 | +- lhs code uses a nested transaction
|
|
| 1527 | +- rhs code doesn't use a nested transaction
|
|
| 1528 | + |
|
| 1529 | +We have to take which case we're in into account (using the running_alt_code
|
|
| 1530 | +field of the catchRetry frame) in catchRetry's entry code, in retry#
|
|
| 1531 | +implementation, and also when an async exception is received (to cleanup the
|
|
| 1532 | +right number of transactions).
|
|
| 1533 | + |
|
| 1534 | +*/ |
| 1 | +{-# LANGUAGE UndecidableInstances, LambdaCase #-}
|
|
| 2 | + |
|
| 3 | +-- | This file starts with a small reproducer for #25944 that is easy to debug
|
|
| 4 | +-- and then continues with a much larger MWE that is faithful to the original
|
|
| 5 | +-- issue.
|
|
| 6 | +module T25944 (foo, bar, popMinOneT, popMinOne) where
|
|
| 7 | + |
|
| 8 | +import Data.Functor.Identity ( Identity(..) )
|
|
| 9 | +import Data.Coerce
|
|
| 10 | + |
|
| 11 | +data ListCons a b = Nil | a :- !b
|
|
| 12 | +newtype Fix f = Fix (f (Fix f)) -- Rec
|
|
| 13 | + |
|
| 14 | +foo :: Fix (ListCons a) -> Fix (ListCons a) -> Fix (ListCons a)
|
|
| 15 | +foo a b = go a
|
|
| 16 | + where
|
|
| 17 | + -- The outer loop arranges it so that the base case `go as` of `go2` is
|
|
| 18 | + -- bottom on the first iteration of the loop.
|
|
| 19 | + go (Fix Nil) = Fix Nil
|
|
| 20 | + go (Fix (a :- as)) = Fix (a :- go2 b)
|
|
| 21 | + where
|
|
| 22 | + go2 (Fix Nil) = go as
|
|
| 23 | + go2 (Fix (b :- bs)) = Fix (b :- go2 bs)
|
|
| 24 | + |
|
| 25 | +bar :: Int -> (Fix (ListCons Int), Int)
|
|
| 26 | +bar n = (foo (Fix Nil) (Fix Nil), n) -- should still have CPR property
|
|
| 27 | + |
|
| 28 | +-- Now the actual reproducer from #25944:
|
|
| 29 | + |
|
| 30 | +newtype ListT m a = ListT { runListT :: m (ListCons a (ListT m a)) }
|
|
| 31 | + |
|
| 32 | +cons :: Applicative m => a -> ListT m a -> ListT m a
|
|
| 33 | +cons x xs = ListT (pure (x :- xs))
|
|
| 34 | + |
|
| 35 | +nil :: Applicative m => ListT m a
|
|
| 36 | +nil = ListT (pure Nil)
|
|
| 37 | + |
|
| 38 | +instance Functor m => Functor (ListT m) where
|
|
| 39 | + fmap f (ListT m) = ListT (go <$> m)
|
|
| 40 | + where
|
|
| 41 | + go Nil = Nil
|
|
| 42 | + go (a :- m) = f a :- (f <$> m)
|
|
| 43 | + |
|
| 44 | +foldListT :: ((ListCons a (ListT m a) -> c) -> m (ListCons a (ListT m a)) -> b)
|
|
| 45 | + -> (a -> b -> c)
|
|
| 46 | + -> c
|
|
| 47 | + -> ListT m a -> b
|
|
| 48 | +foldListT r c n = r h . runListT
|
|
| 49 | + where
|
|
| 50 | + h Nil = n
|
|
| 51 | + h (x :- ListT xs) = c x (r h xs)
|
|
| 52 | +{-# INLINE foldListT #-}
|
|
| 53 | + |
|
| 54 | +mapListT :: forall a m b. Monad m => (a -> ListT m b -> ListT m b) -> ListT m b -> ListT m a -> ListT m b
|
|
| 55 | +mapListT =
|
|
| 56 | + foldListT
|
|
| 57 | + ((coerce ::
|
|
| 58 | + ((ListCons a (ListT m a) -> m (ListCons b (ListT m b))) -> m (ListCons a (ListT m a)) -> m (ListCons b (ListT m b))) ->
|
|
| 59 | + ((ListCons a (ListT m a) -> ListT m b) -> m (ListCons a (ListT m a)) -> ListT m b))
|
|
| 60 | + (=<<))
|
|
| 61 | +{-# INLINE mapListT #-}
|
|
| 62 | + |
|
| 63 | +instance Monad m => Applicative (ListT m) where
|
|
| 64 | + pure x = cons x nil
|
|
| 65 | + {-# INLINE pure #-}
|
|
| 66 | + liftA2 f xs ys = mapListT (\x zs -> mapListT (cons . f x) zs ys) nil xs
|
|
| 67 | + {-# INLINE liftA2 #-}
|
|
| 68 | + |
|
| 69 | +instance Monad m => Monad (ListT m) where
|
|
| 70 | + xs >>= f = mapListT (flip (mapListT cons) . f) nil xs
|
|
| 71 | + {-# INLINE (>>=) #-}
|
|
| 72 | + |
|
| 73 | +infixr 5 :<
|
|
| 74 | +data Node w a b = Leaf a | !w :< b
|
|
| 75 | + deriving (Functor)
|
|
| 76 | + |
|
| 77 | +bimapNode f g (Leaf x) = Leaf (f x)
|
|
| 78 | +bimapNode f g (x :< xs) = x :< g xs
|
|
| 79 | + |
|
| 80 | +newtype HeapT w m a = HeapT { runHeapT :: ListT m (Node w a (HeapT w m a)) }
|
|
| 81 | + |
|
| 82 | +-- | The 'Heap' type, specialised to the 'Identity' monad.
|
|
| 83 | +type Heap w = HeapT w Identity
|
|
| 84 | + |
|
| 85 | +instance Functor m => Functor (HeapT w m) where
|
|
| 86 | + fmap f = HeapT . fmap (bimapNode f (fmap f)) . runHeapT
|
|
| 87 | + |
|
| 88 | +instance Monad m => Applicative (HeapT w m) where
|
|
| 89 | + pure = HeapT . pure . Leaf
|
|
| 90 | + (<*>) = liftA2 id
|
|
| 91 | + |
|
| 92 | +instance Monad m => Monad (HeapT w m) where
|
|
| 93 | + HeapT m >>= f = HeapT (m >>= g)
|
|
| 94 | + where
|
|
| 95 | + g (Leaf x) = runHeapT (f x)
|
|
| 96 | + g (w :< xs) = pure (w :< (xs >>= f))
|
|
| 97 | + |
|
| 98 | +popMinOneT :: forall w m a. (Monoid w, Monad m) => HeapT w m a -> m (Maybe ((a, w), HeapT w m a))
|
|
| 99 | +popMinOneT = go mempty [] . runHeapT
|
|
| 100 | + where
|
|
| 101 | + go' :: w -> Maybe (w, HeapT w m a) -> m (Maybe ((a, w), HeapT w m a))
|
|
| 102 | + go' a Nothing = pure Nothing
|
|
| 103 | + go' a (Just (w, HeapT xs)) = go (a <> w) [] xs
|
|
| 104 | + |
|
| 105 | + go :: w -> [(w, HeapT w m a)] -> ListT m (Node w a (HeapT w m a)) -> m (Maybe ((a, w), HeapT w m a))
|
|
| 106 | + go w a (ListT xs) = xs >>= \case
|
|
| 107 | + Nil -> go' w (undefined)
|
|
| 108 | + Leaf x :- xs -> pure (Just ((x, w), undefined >> HeapT (foldl (\ys (yw,y) -> ListT (pure ((yw :< y) :- ys))) xs a)))
|
|
| 109 | + (u :< x) :- xs -> go w ((u,x) : a) xs
|
|
| 110 | +{-# INLINE popMinOneT #-}
|
|
| 111 | + |
|
| 112 | +popMinOne :: Monoid w => Heap w a -> Maybe ((a, w), Heap w a)
|
|
| 113 | +popMinOne = runIdentity . popMinOneT
|
|
| 114 | +{-# INLINE popMinOne #-} |
| 1 | + |
|
| 2 | +==================== Cpr signatures ====================
|
|
| 3 | +T25944.$fApplicativeHeapT:
|
|
| 4 | +T25944.$fApplicativeListT:
|
|
| 5 | +T25944.$fFunctorHeapT:
|
|
| 6 | +T25944.$fFunctorListT:
|
|
| 7 | +T25944.$fFunctorNode:
|
|
| 8 | +T25944.$fMonadHeapT:
|
|
| 9 | +T25944.$fMonadListT:
|
|
| 10 | +T25944.bar: 1
|
|
| 11 | +T25944.foo:
|
|
| 12 | +T25944.popMinOne: 2(1(1,))
|
|
| 13 | +T25944.popMinOneT:
|
|
| 14 | +T25944.runHeapT:
|
|
| 15 | +T25944.runListT:
|
|
| 16 | + |
|
| 17 | + |
| ... | ... | @@ -12,3 +12,4 @@ test('T16040', normal, compile, ['']) |
| 12 | 12 | test('T19232', normal, compile, [''])
|
| 13 | 13 | test('T19398', normal, compile, [''])
|
| 14 | 14 | test('T19822', normal, compile, [''])
|
| 15 | +test('T25944', normal, compile, ['']) |
| 1 | -{"version":"1.1","ghcVersion":"ghc-9.13.20250529","span":{"file":"json.hs","start":{"line":9,"column":11},"end":{"line":9,"column":21}},"severity":"Error","code":48010,"message":["Empty list of alternatives in case expression"],"hints":["Perhaps you intended to use the \u2018EmptyCase\u2019 extension"]} |
|
| 1 | +{"version":"1.2","ghcVersion":"ghc-9.13.20250627","span":{"file":"json.hs","start":{"line":9,"column":11},"end":{"line":9,"column":21}},"severity":"Error","code":48010,"rendered":"json.hs:9:11: error: [GHC-48010]\n Empty list of alternatives in case expression\n Suggested fix:\n Perhaps you intended to use the \u2018EmptyCase\u2019 extension\n","message":["Empty list of alternatives in case expression"],"hints":["Perhaps you intended to use the \u2018EmptyCase\u2019 extension"]} |
| 1 | -{"version":"1.1","ghcVersion":"ghc-9.13.20250529","span":{"file":"json_warn.hs","start":{"line":4,"column":3},"end":{"line":4,"column":4}},"severity":"Warning","code":40910,"message":["Defined but not used: \u2018x\u2019"],"hints":[],"reason":{"flags":["unused-matches"]}}
|
|
| 2 | -{"version":"1.1","ghcVersion":"ghc-9.13.20250529","span":{"file":"json_warn.hs","start":{"line":7,"column":5},"end":{"line":7,"column":9}},"severity":"Warning","code":63394,"message":["In the use of \u2018head\u2019\n(imported from Prelude, but defined in GHC.Internal.List):\n\"This is a partial function, it throws an error on empty lists. Use pattern matching, 'Data.List.uncons' or 'Data.Maybe.listToMaybe' instead. Consider refactoring to use \"Data.List.NonEmpty\".\""],"hints":[],"reason":{"category":"x-partial"}} |
|
| 1 | +{"version":"1.2","ghcVersion":"ghc-9.13.20250627","span":{"file":"json_warn.hs","start":{"line":4,"column":3},"end":{"line":4,"column":4}},"severity":"Warning","code":40910,"rendered":"json_warn.hs:4:3: warning: [GHC-40910] [-Wunused-matches (in -Wextra)]\n Defined but not used: \u2018x\u2019\n","message":["Defined but not used: \u2018x\u2019"],"hints":[],"reason":{"flags":["unused-matches"]}}
|
|
| 2 | +{"version":"1.2","ghcVersion":"ghc-9.13.20250627","span":{"file":"json_warn.hs","start":{"line":7,"column":5},"end":{"line":7,"column":9}},"severity":"Warning","code":63394,"rendered":"json_warn.hs:7:5: warning: [GHC-63394] [-Wx-partial (in -Wextended-warnings)]\n In the use of \u2018head\u2019\n (imported from Prelude, but defined in GHC.Internal.List):\n \"This is a partial function, it throws an error on empty lists. Use pattern matching, 'Data.List.uncons' or 'Data.Maybe.listToMaybe' instead. Consider refactoring to use \"Data.List.NonEmpty\".\"\n","message":["In the use of \u2018head\u2019\n(imported from Prelude, but defined in GHC.Internal.List):\n\"This is a partial function, it throws an error on empty lists. Use pattern matching, 'Data.List.uncons' or 'Data.Maybe.listToMaybe' instead. Consider refactoring to use \"Data.List.NonEmpty\".\""],"hints":[],"reason":{"category":"x-partial"}} |
| 1 | +module Main where
|
|
| 2 | + |
|
| 3 | +import GHC.Conc
|
|
| 4 | + |
|
| 5 | +forever :: IO String
|
|
| 6 | +forever = delay 10 >> forever
|
|
| 7 | + |
|
| 8 | +terminates :: IO String
|
|
| 9 | +terminates = delay 1 >> pure "terminates"
|
|
| 10 | + |
|
| 11 | +delay s = threadDelay (1000000 * s)
|
|
| 12 | + |
|
| 13 | +async :: IO a -> IO (STM a)
|
|
| 14 | +async a = do
|
|
| 15 | + var <- atomically (newTVar Nothing)
|
|
| 16 | + forkIO (a >>= atomically . writeTVar var . Just)
|
|
| 17 | + pure (readTVar var >>= maybe retry pure)
|
|
| 18 | + |
|
| 19 | +main :: IO ()
|
|
| 20 | +main = do
|
|
| 21 | + x <- mapM async $ terminates : replicate 50000 forever
|
|
| 22 | + r <- atomically (foldr1 orElse x)
|
|
| 23 | + print r |
| 1 | +"terminates" |
| 1 | +test('T26028', only_ways(['threaded1']), compile_and_run, ['-O2']) |