Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
-
1cb1d672
by Wen Kokke at 2026-05-06T09:53:40-04:00
-
9d54dc94
by Wen Kokke at 2026-05-06T09:53:40-04:00
-
418d737b
by Wen Kokke at 2026-05-06T09:53:40-04:00
-
99f4afa4
by Wen Kokke at 2026-05-06T09:53:40-04:00
-
7e9eb8b9
by Wen Kokke at 2026-05-06T09:53:40-04:00
-
3a3045fb
by Wen Kokke at 2026-05-06T09:53:41-04:00
-
a3b339a4
by Teo Camarasu at 2026-05-06T09:54:25-04:00
-
eb922183
by Duncan Coutts at 2026-05-07T14:28:50+01:00
-
01b0e233
by Duncan Coutts at 2026-05-07T14:28:50+01:00
-
bc41d646
by Duncan Coutts at 2026-05-07T14:28:50+01:00
-
4ed9a386
by Duncan Coutts at 2026-05-07T14:28:50+01:00
-
97504fa6
by Duncan Coutts at 2026-05-07T14:28:50+01:00
-
79330223
by Facundo Domínguez at 2026-05-07T13:40:13-04:00
18 changed files:
- + changelog.d/dynamic-trace-flags
- + changelog.d/so_inline_is_a_predicate
- compiler/GHC/Core/SimpleOpt.hs
- compiler/GHC/Driver/Config.hs
- rts/RtsSymbols.c
- rts/Trace.c
- rts/Trace.h
- rts/include/rts/EventLogWriter.h
- + rts/posix/FdWakeup.c
- + rts/posix/FdWakeup.h
- rts/posix/Ticker.c
- − rts/posix/ticker/Pthread.c
- − rts/posix/ticker/TimerFd.c
- rts/rts.cabal
- rts/sm/NonMoving.c
- + testsuite/tests/ghc-api/T24386.hs
- testsuite/tests/ghc-api/all.T
- − testsuite/tests/interface-stability/base-exports.stdout-ws-32
Changes:
| 1 | +section: compiler
|
|
| 2 | +synopsis: Support dynamic trace flags in RTS
|
|
| 3 | +issues: #27186
|
|
| 4 | +mrs: !15936
|
|
| 5 | + |
|
| 6 | +description: {
|
|
| 7 | + The RTS API now exposes the `RUNTIME_TRACE_FLAG` type and
|
|
| 8 | + the `getTraceFlags` and `setTraceFlags` functions that can be used to
|
|
| 9 | + change the trace flags at runtime.
|
|
| 10 | +} |
| 1 | +section: ghc-lib
|
|
| 2 | +synopsis: Generalize the ``so_inline`` option of the simple optimizer to a predicate
|
|
| 3 | + that selects the bindings to preserve.
|
|
| 4 | + |
|
| 5 | +issues: #24386
|
|
| 6 | +mrs: !15988
|
|
| 7 | + |
|
| 8 | +description: {
|
|
| 9 | + The ``so_inline`` option of the simple optimizer was a boolean and now it is a
|
|
| 10 | + predicate taking a binding ``Id`` and returning a boolean. ``const b`` has the
|
|
| 11 | + same effect as formerly setting ``b``.
|
|
| 12 | +} |
| ... | ... | @@ -108,6 +108,93 @@ unfolding-info to the scrutinee's Id.) |
| 108 | 108 | * Bad bad bad: then the x in case x of ... may be replaced with a version that has an unfolding.
|
| 109 | 109 | |
| 110 | 110 | See ticket #25790
|
| 111 | + |
|
| 112 | +Note [Controlling inlining in the simple optimiser]
|
|
| 113 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
| 114 | +Sometimes, plugins that analyse Core programs may want to prevent the
|
|
| 115 | +inlining of certain bindings. While they could avoid running the simple
|
|
| 116 | +optimiser at all, that would leave plenty of generated bindings that do not
|
|
| 117 | +have a direct correspondence to the source code.
|
|
| 118 | + |
|
| 119 | +For example, consider the following Haskell code:
|
|
| 120 | + |
|
| 121 | + foo = z
|
|
| 122 | + where
|
|
| 123 | + z = z1 + z2
|
|
| 124 | + z1 = 42
|
|
| 125 | + z2 = 1
|
|
| 126 | + |
|
| 127 | +Before the simple optimizer runs, the Core programs is roughly:
|
|
| 128 | + |
|
| 129 | + foo =
|
|
| 130 | + let
|
|
| 131 | + foo_aIb =
|
|
| 132 | + let
|
|
| 133 | + z2
|
|
| 134 | + = let
|
|
| 135 | + z2_aHG = 1
|
|
| 136 | + in
|
|
| 137 | + z2_aHG
|
|
| 138 | + in
|
|
| 139 | + let
|
|
| 140 | + z1 =
|
|
| 141 | + let
|
|
| 142 | + z1_aHR = 42
|
|
| 143 | + in
|
|
| 144 | + z1_aHR
|
|
| 145 | + in
|
|
| 146 | + let
|
|
| 147 | + z =
|
|
| 148 | + let
|
|
| 149 | + z_aI5 = z1 + z2
|
|
| 150 | + in
|
|
| 151 | + z_aI5
|
|
| 152 | + in
|
|
| 153 | + z
|
|
| 154 | + in
|
|
| 155 | + foo_aIb
|
|
| 156 | + |
|
| 157 | +After the simple optimizer runs, the Core program is:
|
|
| 158 | + |
|
| 159 | + foo = 42 + 1
|
|
| 160 | + |
|
| 161 | +And the bindings for `z`, `z1`, and `z2` are all gone. If a plugin wanted to
|
|
| 162 | +analyse those bindings, it would have to deal with the unsimplified Core, but
|
|
| 163 | +cope with the generated bindings `z2_aHG`, `z1_aHR`, `z_aI5`, and `foo_aIb`,
|
|
| 164 | +all of which have no direct correspondence to the source code.
|
|
| 165 | + |
|
| 166 | +Fortunately, a plugin can still improve the output by using the `so_inline`
|
|
| 167 | +field of `SimpleOpts`. The `so_inline` field is a /function/ of type
|
|
| 168 | +`(Id -> Bool)` that tells the simple optimiser whether or not to inline the `Id`.
|
|
| 169 | +The client of the GHC can thereby control precisely which bindings are inlined
|
|
| 170 | +and which are not. For instance,
|
|
| 171 | + |
|
| 172 | + simplOptPgm
|
|
| 173 | + (defaultSimpleOpts { so_inline = (`notElem` ["z", "z1", "z2"]) })
|
|
| 174 | + ...
|
|
| 175 | + |
|
| 176 | +produces the following Core program:
|
|
| 177 | + |
|
| 178 | + foo =
|
|
| 179 | + let
|
|
| 180 | + z2 = 1
|
|
| 181 | + in
|
|
| 182 | + let
|
|
| 183 | + z1 = 42
|
|
| 184 | + in
|
|
| 185 | + let
|
|
| 186 | + z = z1 + z2
|
|
| 187 | + in
|
|
| 188 | + z
|
|
| 189 | + |
|
| 190 | +which contains the bindings of interest and little else.
|
|
| 191 | + |
|
| 192 | +For the specifics of how this affects a concrete plugin (Liquid Haskell), see
|
|
| 193 | +the discussion in https://gitlab.haskell.org/ghc/ghc/-/issues/24386
|
|
| 194 | + |
|
| 195 | +In addition to supporting clients of the GHC API, there is another use of
|
|
| 196 | +`so_inline` mentioned in 'simpleOptExprNoInline'.
|
|
| 197 | + |
|
| 111 | 198 | -}
|
| 112 | 199 | |
| 113 | 200 | -- | Simple optimiser options
|
| ... | ... | @@ -115,8 +202,11 @@ data SimpleOpts = SimpleOpts |
| 115 | 202 | { so_uf_opts :: !UnfoldingOpts -- ^ Unfolding options
|
| 116 | 203 | , so_co_opts :: !OptCoercionOpts -- ^ Coercion optimiser options
|
| 117 | 204 | , so_eta_red :: !Bool -- ^ Eta reduction on?
|
| 118 | - , so_inline :: !Bool -- ^ False <=> do no inlining whatsoever,
|
|
| 119 | - -- even for trivial or used-once things
|
|
| 205 | + , so_inline :: !(Var -> Bool) -- ^ False <=> do no inline the given
|
|
| 206 | + -- binding whatsoever, even for trivial or
|
|
| 207 | + -- used-once things
|
|
| 208 | + --
|
|
| 209 | + -- See Note [Controlling inlining in the simple optimiser]
|
|
| 120 | 210 | }
|
| 121 | 211 | |
| 122 | 212 | -- | Default options for the Simple optimiser.
|
| ... | ... | @@ -125,7 +215,7 @@ defaultSimpleOpts = SimpleOpts |
| 125 | 215 | { so_uf_opts = defaultUnfoldingOpts
|
| 126 | 216 | , so_co_opts = OptCoercionOpts { optCoercionEnabled = False }
|
| 127 | 217 | , so_eta_red = False
|
| 128 | - , so_inline = True
|
|
| 218 | + , so_inline = const True
|
|
| 129 | 219 | }
|
| 130 | 220 | |
| 131 | 221 | simpleOptExpr :: HasDebugCallStack => SimpleOpts -> CoreExpr -> CoreExpr
|
| ... | ... | @@ -170,7 +260,7 @@ simpleOptExprNoInline :: HasDebugCallStack => SimpleOpts -> CoreExpr -> CoreExpr |
| 170 | 260 | simpleOptExprNoInline opts expr
|
| 171 | 261 | = simple_opt_expr init_env expr
|
| 172 | 262 | where
|
| 173 | - init_opts = opts { so_inline = False }
|
|
| 263 | + init_opts = opts { so_inline = const False }
|
|
| 174 | 264 | init_env = (emptyEnv init_opts) { soe_subst = init_subst }
|
| 175 | 265 | init_subst = mkEmptySubst (mkInScopeSet (exprFreeVars expr))
|
| 176 | 266 | |
| ... | ... | @@ -639,12 +729,12 @@ simple_bind_pair env@(SOE { soe_inl = inl_env, soe_subst = subst, soe_opts = opt |
| 639 | 729 | |
| 640 | 730 | pre_inline_unconditionally :: Bool
|
| 641 | 731 | pre_inline_unconditionally
|
| 642 | - | not (so_inline opts) = False -- Not if so_inline is False
|
|
| 643 | - | isExportedId in_bndr = False
|
|
| 644 | - | stable_unf = False
|
|
| 645 | - | not active = False -- Note [Inline prag in simplOpt]
|
|
| 646 | - | not (safe_to_inline occ) = False
|
|
| 647 | - | otherwise = True
|
|
| 732 | + | not (so_inline opts in_bndr) = False -- Not if so_inline is False
|
|
| 733 | + | isExportedId in_bndr = False
|
|
| 734 | + | stable_unf = False
|
|
| 735 | + | not active = False -- Note [Inline prag in simplOpt]
|
|
| 736 | + | not (safe_to_inline occ) = False
|
|
| 737 | + | otherwise = True
|
|
| 648 | 738 | |
| 649 | 739 | -- Unconditionally safe to inline
|
| 650 | 740 | safe_to_inline :: OccInfo -> Bool
|
| ... | ... | @@ -711,15 +801,15 @@ simple_out_bind_pair env@(SOE { soe_subst = subst, soe_opts = opts }) |
| 711 | 801 | |
| 712 | 802 | post_inline_unconditionally :: Bool
|
| 713 | 803 | post_inline_unconditionally
|
| 714 | - | not (so_inline opts) = False -- Not if so_inline is False
|
|
| 715 | - | isExportedId in_bndr = False -- Note [Exported Ids and trivial RHSs]
|
|
| 716 | - | stable_unf = False -- Note [Stable unfoldings and postInlineUnconditionally]
|
|
| 717 | - | not active = False -- in GHC.Core.Opt.Simplify.Utils
|
|
| 718 | - | is_loop_breaker = False -- If it's a loop-breaker of any kind, don't inline
|
|
| 719 | - -- because it might be referred to "earlier"
|
|
| 720 | - | exprIsTrivial out_rhs = True
|
|
| 721 | - | coercible_hack = True
|
|
| 722 | - | otherwise = False
|
|
| 804 | + | not (so_inline opts in_bndr) = False -- Not if so_inline is False
|
|
| 805 | + | isExportedId in_bndr = False -- Note [Exported Ids and trivial RHSs]
|
|
| 806 | + | stable_unf = False -- Note [Stable unfoldings and postInlineUnconditionally]
|
|
| 807 | + | not active = False -- in GHC.Core.Opt.Simplify.Utils
|
|
| 808 | + | is_loop_breaker = False -- If it's a loop-breaker of any kind, don't inline
|
|
| 809 | + -- because it might be referred to "earlier"
|
|
| 810 | + | exprIsTrivial out_rhs = True
|
|
| 811 | + | coercible_hack = True
|
|
| 812 | + | otherwise = False
|
|
| 723 | 813 | |
| 724 | 814 | is_loop_breaker = isWeakLoopBreaker occ_info
|
| 725 | 815 |
| ... | ... | @@ -26,7 +26,7 @@ initSimpleOpts dflags = SimpleOpts |
| 26 | 26 | { so_uf_opts = unfoldingOpts dflags
|
| 27 | 27 | , so_co_opts = initOptCoercionOpts dflags
|
| 28 | 28 | , so_eta_red = gopt Opt_DoEtaReduction dflags
|
| 29 | - , so_inline = True
|
|
| 29 | + , so_inline = const True
|
|
| 30 | 30 | }
|
| 31 | 31 | |
| 32 | 32 | -- | Instruct the interpreter evaluation to break...
|
| ... | ... | @@ -540,7 +540,12 @@ extern char **environ; |
| 540 | 540 | SymI_HasProto(__word_encodeFloat) \
|
| 541 | 541 | SymI_HasDataProto(stg_atomicallyzh) \
|
| 542 | 542 | SymI_HasProto(barf) \
|
| 543 | + SymI_HasProto(startEventLogging) \
|
|
| 544 | + SymI_HasProto(endEventLogging) \
|
|
| 543 | 545 | SymI_HasProto(flushEventLog) \
|
| 546 | + SymI_HasProto(flushEventLog) \
|
|
| 547 | + SymI_HasProto(getTraceFlag) \
|
|
| 548 | + SymI_HasProto(setTraceFlag) \
|
|
| 544 | 549 | SymI_HasProto(deRefStablePtr) \
|
| 545 | 550 | SymI_HasProto(debugBelch) \
|
| 546 | 551 | SymI_HasProto(errorBelch) \
|
| ... | ... | @@ -29,14 +29,54 @@ |
| 29 | 29 | #include <unistd.h>
|
| 30 | 30 | #endif
|
| 31 | 31 | |
| 32 | -// events
|
|
| 33 | -uint8_t TRACE_sched;
|
|
| 34 | -uint8_t TRACE_gc;
|
|
| 35 | -uint8_t TRACE_nonmoving_gc;
|
|
| 36 | -uint8_t TRACE_spark_sampled;
|
|
| 37 | -uint8_t TRACE_spark_full;
|
|
| 38 | -uint8_t TRACE_user;
|
|
| 39 | -uint8_t TRACE_cap;
|
|
| 32 | +RUNTIME_TRACE_FLAG_CACHE RuntimeTraceFlagCache = {0};
|
|
| 33 | + |
|
| 34 | +bool getTraceFlag(RUNTIME_TRACE_FLAG flag) {
|
|
| 35 | + switch (flag) {
|
|
| 36 | + case TRACE_SCHEDULER:
|
|
| 37 | + return RuntimeTraceFlagCache.scheduler;
|
|
| 38 | + case TRACE_GC:
|
|
| 39 | + return RuntimeTraceFlagCache.gc;
|
|
| 40 | + case TRACE_NONMOVING_GC:
|
|
| 41 | + return RuntimeTraceFlagCache.nonmoving_gc;
|
|
| 42 | + case TRACE_SPARK_SAMPLED:
|
|
| 43 | + return RuntimeTraceFlagCache.spark_sampled;
|
|
| 44 | + case TRACE_SPARK_FULL:
|
|
| 45 | + return RuntimeTraceFlagCache.spark_full;
|
|
| 46 | + case TRACE_USER:
|
|
| 47 | + return RuntimeTraceFlagCache.user;
|
|
| 48 | + case TRACE_CAP:
|
|
| 49 | + return RuntimeTraceFlagCache.cap;
|
|
| 50 | + default:
|
|
| 51 | + return false;
|
|
| 52 | + }
|
|
| 53 | +}
|
|
| 54 | + |
|
| 55 | +void setTraceFlag(RUNTIME_TRACE_FLAG flag, bool value) {
|
|
| 56 | + switch (flag) {
|
|
| 57 | + case TRACE_SCHEDULER:
|
|
| 58 | + RuntimeTraceFlagCache.scheduler = value;
|
|
| 59 | + break;
|
|
| 60 | + case TRACE_GC:
|
|
| 61 | + RuntimeTraceFlagCache.gc = value;
|
|
| 62 | + break;
|
|
| 63 | + case TRACE_NONMOVING_GC:
|
|
| 64 | + RuntimeTraceFlagCache.nonmoving_gc = value;
|
|
| 65 | + break;
|
|
| 66 | + case TRACE_SPARK_SAMPLED:
|
|
| 67 | + RuntimeTraceFlagCache.spark_sampled = value;
|
|
| 68 | + break;
|
|
| 69 | + case TRACE_SPARK_FULL:
|
|
| 70 | + RuntimeTraceFlagCache.spark_full = value;
|
|
| 71 | + break;
|
|
| 72 | + case TRACE_USER:
|
|
| 73 | + RuntimeTraceFlagCache.user = value;
|
|
| 74 | + break;
|
|
| 75 | + case TRACE_CAP:
|
|
| 76 | + RuntimeTraceFlagCache.cap = value;
|
|
| 77 | + break;
|
|
| 78 | + }
|
|
| 79 | +}
|
|
| 40 | 80 | |
| 41 | 81 | #if defined(THREADED_RTS)
|
| 42 | 82 | static Mutex trace_utx;
|
| ... | ... | @@ -51,43 +91,41 @@ static void traceCap_stderr(Capability *cap, char *msg, ...); |
| 51 | 91 | --------------------------------------------------------------------------- */
|
| 52 | 92 | |
| 53 | 93 | /*
|
| 54 | - * Update the TRACE_* globals. Must be called whenever RtsFlags.TraceFlags is
|
|
| 55 | - * modified.
|
|
| 94 | + * Initialise the runtime trace flags from RtsFlags.TraceFlags.
|
|
| 56 | 95 | */
|
| 57 | -static void updateTraceFlagCache (void)
|
|
| 58 | -{
|
|
| 59 | - // -Ds turns on scheduler tracing too
|
|
| 60 | - TRACE_sched =
|
|
| 61 | - RtsFlags.TraceFlags.scheduler ||
|
|
| 62 | - RtsFlags.DebugFlags.scheduler;
|
|
| 63 | - |
|
| 64 | - // -Dg turns on gc tracing too
|
|
| 65 | - TRACE_gc =
|
|
| 66 | - RtsFlags.TraceFlags.gc ||
|
|
| 67 | - RtsFlags.DebugFlags.gc ||
|
|
| 68 | - RtsFlags.DebugFlags.scheduler;
|
|
| 69 | - |
|
| 70 | - TRACE_nonmoving_gc =
|
|
| 71 | - RtsFlags.TraceFlags.nonmoving_gc;
|
|
| 72 | - |
|
| 73 | - TRACE_spark_sampled =
|
|
| 74 | - RtsFlags.TraceFlags.sparks_sampled;
|
|
| 75 | - |
|
| 76 | - // -Dr turns on full spark tracing
|
|
| 77 | - TRACE_spark_full =
|
|
| 78 | - RtsFlags.TraceFlags.sparks_full ||
|
|
| 79 | - RtsFlags.DebugFlags.sparks;
|
|
| 80 | - |
|
| 81 | - TRACE_user =
|
|
| 82 | - RtsFlags.TraceFlags.user;
|
|
| 83 | - |
|
| 84 | - // We trace cap events if we're tracing anything else
|
|
| 85 | - TRACE_cap =
|
|
| 86 | - TRACE_sched ||
|
|
| 87 | - TRACE_gc ||
|
|
| 88 | - TRACE_spark_sampled ||
|
|
| 89 | - TRACE_spark_full ||
|
|
| 90 | - TRACE_user;
|
|
| 96 | +static void updateTraceFlagCache(void) {
|
|
| 97 | + // -Ds turns on scheduler tracing too
|
|
| 98 | + RuntimeTraceFlagCache.scheduler =
|
|
| 99 | + RtsFlags.TraceFlags.scheduler ||
|
|
| 100 | + RtsFlags.DebugFlags.scheduler;
|
|
| 101 | + |
|
| 102 | + // -Dg turns on gc tracing too
|
|
| 103 | + RuntimeTraceFlagCache.gc =
|
|
| 104 | + RtsFlags.TraceFlags.gc ||
|
|
| 105 | + RtsFlags.DebugFlags.gc ||
|
|
| 106 | + RtsFlags.DebugFlags.scheduler;
|
|
| 107 | + |
|
| 108 | + RuntimeTraceFlagCache.nonmoving_gc =
|
|
| 109 | + RtsFlags.TraceFlags.nonmoving_gc;
|
|
| 110 | + |
|
| 111 | + RuntimeTraceFlagCache.spark_sampled =
|
|
| 112 | + RtsFlags.TraceFlags.sparks_sampled;
|
|
| 113 | + |
|
| 114 | + // -Dr turns on full spark tracing
|
|
| 115 | + RuntimeTraceFlagCache.spark_full =
|
|
| 116 | + RtsFlags.TraceFlags.sparks_full ||
|
|
| 117 | + RtsFlags.DebugFlags.sparks;
|
|
| 118 | + |
|
| 119 | + RuntimeTraceFlagCache.user =
|
|
| 120 | + RtsFlags.TraceFlags.user;
|
|
| 121 | + |
|
| 122 | + // We trace cap events if we're tracing anything else
|
|
| 123 | + RuntimeTraceFlagCache.cap =
|
|
| 124 | + TRACE_sched ||
|
|
| 125 | + TRACE_gc ||
|
|
| 126 | + TRACE_spark_sampled ||
|
|
| 127 | + TRACE_spark_full ||
|
|
| 128 | + TRACE_user;
|
|
| 91 | 129 | }
|
| 92 | 130 | |
| 93 | 131 | void initTracing (void)
|
| ... | ... | @@ -880,59 +918,65 @@ void traceThreadLabel_(Capability *cap, |
| 880 | 918 | }
|
| 881 | 919 | }
|
| 882 | 920 | |
| 883 | -void traceConcMarkBegin(void)
|
|
| 921 | +void traceNonmovingGcEvent_ (EventTypeNum tag)
|
|
| 884 | 922 | {
|
| 885 | - if (eventlog_enabled)
|
|
| 886 | - postEventNoCap(EVENT_CONC_MARK_BEGIN);
|
|
| 923 | +#if defined(DEBUG)
|
|
| 924 | + if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
|
|
| 925 | + /* nothing - no string representation for nonmoving GC events */
|
|
| 926 | + } else
|
|
| 927 | +#endif
|
|
| 928 | + {
|
|
| 929 | + /* currently most non-moving GC events are nullary events */
|
|
| 930 | + postEventNoCap(tag);
|
|
| 931 | + }
|
|
| 887 | 932 | }
|
| 888 | 933 | |
| 889 | -void traceConcMarkEnd(StgWord32 marked_obj_count)
|
|
| 934 | +void traceConcMarkEnd_(StgWord32 marked_obj_count)
|
|
| 890 | 935 | {
|
| 891 | - if (eventlog_enabled)
|
|
| 936 | +#if defined(DEBUG)
|
|
| 937 | + if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
|
|
| 938 | + /* nothing - no string representation for nonmoving GC events */
|
|
| 939 | + } else
|
|
| 940 | +#endif
|
|
| 941 | + {
|
|
| 892 | 942 | postConcMarkEnd(marked_obj_count);
|
| 943 | + }
|
|
| 893 | 944 | }
|
| 894 | 945 | |
| 895 | -void traceConcSyncBegin(void)
|
|
| 896 | -{
|
|
| 897 | - if (eventlog_enabled)
|
|
| 898 | - postEventNoCap(EVENT_CONC_SYNC_BEGIN);
|
|
| 899 | -}
|
|
| 900 | - |
|
| 901 | -void traceConcSyncEnd(void)
|
|
| 902 | -{
|
|
| 903 | - if (eventlog_enabled)
|
|
| 904 | - postEventNoCap(EVENT_CONC_SYNC_END);
|
|
| 905 | -}
|
|
| 906 | - |
|
| 907 | -void traceConcSweepBegin(void)
|
|
| 908 | -{
|
|
| 909 | - if (eventlog_enabled)
|
|
| 910 | - postEventNoCap(EVENT_CONC_SWEEP_BEGIN);
|
|
| 911 | -}
|
|
| 912 | - |
|
| 913 | -void traceConcSweepEnd(void)
|
|
| 914 | -{
|
|
| 915 | - if (eventlog_enabled)
|
|
| 916 | - postEventNoCap(EVENT_CONC_SWEEP_END);
|
|
| 917 | -}
|
|
| 918 | - |
|
| 919 | -void traceConcUpdRemSetFlush(Capability *cap)
|
|
| 946 | +void traceConcUpdRemSetFlush_(Capability *cap)
|
|
| 920 | 947 | {
|
| 921 | - if (eventlog_enabled)
|
|
| 948 | +#if defined(DEBUG)
|
|
| 949 | + if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
|
|
| 950 | + /* nothing - no string representation for nonmoving GC events */
|
|
| 951 | + } else
|
|
| 952 | +#endif
|
|
| 953 | + {
|
|
| 922 | 954 | postConcUpdRemSetFlush(cap);
|
| 955 | + }
|
|
| 923 | 956 | }
|
| 924 | 957 | |
| 925 | -void traceNonmovingHeapCensus(uint16_t blk_size,
|
|
| 926 | - const struct NonmovingAllocCensus *census)
|
|
| 958 | +void traceNonmovingHeapCensus_(uint16_t blk_size, const struct NonmovingAllocCensus *census)
|
|
| 927 | 959 | {
|
| 928 | - if (eventlog_enabled && TRACE_nonmoving_gc)
|
|
| 960 | +#if defined(DEBUG)
|
|
| 961 | + if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
|
|
| 962 | + /* nothing - no string representation for nonmoving GC events */
|
|
| 963 | + } else
|
|
| 964 | +#endif
|
|
| 965 | + {
|
|
| 929 | 966 | postNonmovingHeapCensus(blk_size, census);
|
| 967 | + }
|
|
| 930 | 968 | }
|
| 931 | 969 | |
| 932 | -void traceNonmovingPrunedSegments(uint32_t pruned_segments, uint32_t free_segments)
|
|
| 970 | +void traceNonmovingPrunedSegments_(uint32_t pruned_segments, uint32_t free_segments)
|
|
| 933 | 971 | {
|
| 934 | - if (eventlog_enabled && TRACE_nonmoving_gc)
|
|
| 972 | +#if defined(DEBUG)
|
|
| 973 | + if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
|
|
| 974 | + /* nothing - no string representation for nonmoving GC events */
|
|
| 975 | + } else
|
|
| 976 | +#endif
|
|
| 977 | + {
|
|
| 935 | 978 | postNonmovingPrunedSegments(pruned_segments, free_segments);
|
| 979 | + }
|
|
| 936 | 980 | }
|
| 937 | 981 | |
| 938 | 982 | void traceThreadStatus_ (StgTSO *tso USED_IF_DEBUG)
|
| ... | ... | @@ -70,16 +70,35 @@ enum CapsetType { CapsetTypeCustom = CAPSET_TYPE_CUSTOM, |
| 70 | 70 | #define DEBUG_continuation RtsFlags.DebugFlags.continuation
|
| 71 | 71 | #define DEBUG_iomanager RtsFlags.DebugFlags.iomanager
|
| 72 | 72 | |
| 73 | -// Event-enabled flags
|
|
| 74 | -// These semantically booleans but we use a dense packing to minimize their
|
|
| 75 | -// cache impact.
|
|
| 76 | -extern uint8_t TRACE_sched;
|
|
| 77 | -extern uint8_t TRACE_gc;
|
|
| 78 | -extern uint8_t TRACE_nonmoving_gc;
|
|
| 79 | -extern uint8_t TRACE_spark_sampled;
|
|
| 80 | -extern uint8_t TRACE_spark_full;
|
|
| 81 | -extern uint8_t TRACE_cap;
|
|
| 82 | -/* extern uint8_t TRACE_user; */ // only used in Trace.c
|
|
| 73 | +// These trace flags are shorthand for the members of the RuntimeTraceFlagCache
|
|
| 74 | +// struct. Within the RTS, these should be treated as read-only variables.
|
|
| 75 | +#define TRACE_sched ((const bool)RuntimeTraceFlagCache.scheduler)
|
|
| 76 | +#define TRACE_gc ((const bool)RuntimeTraceFlagCache.gc)
|
|
| 77 | +#define TRACE_nonmoving_gc ((const bool)RuntimeTraceFlagCache.nonmoving_gc)
|
|
| 78 | +#define TRACE_spark_sampled ((const bool)RuntimeTraceFlagCache.spark_sampled)
|
|
| 79 | +#define TRACE_spark_full ((const bool)RuntimeTraceFlagCache.spark_full)
|
|
| 80 | +#define TRACE_user ((const bool)RuntimeTraceFlagCache.user)
|
|
| 81 | +#define TRACE_cap ((const bool)RuntimeTraceFlagCache.cap)
|
|
| 82 | + |
|
| 83 | +/*
|
|
| 84 | + * Runtime trace flags.
|
|
| 85 | + */
|
|
| 86 | +typedef struct {
|
|
| 87 | + bool scheduler;
|
|
| 88 | + bool gc;
|
|
| 89 | + bool nonmoving_gc;
|
|
| 90 | + bool spark_sampled;
|
|
| 91 | + bool spark_full;
|
|
| 92 | + bool user;
|
|
| 93 | + bool cap;
|
|
| 94 | +} RUNTIME_TRACE_FLAG_CACHE;
|
|
| 95 | + |
|
| 96 | +/*
|
|
| 97 | + * These flags should be used to determine whether or not some value should
|
|
| 98 | + * be traced at runtime, rather than the values in RtsFlags. These flags can
|
|
| 99 | + * be modified at runtime using setTraceFlag in `rts/EventLogWriter.h`.
|
|
| 100 | + */
|
|
| 101 | +extern RUNTIME_TRACE_FLAG_CACHE RuntimeTraceFlagCache;
|
|
| 83 | 102 | |
| 84 | 103 | // -----------------------------------------------------------------------------
|
| 85 | 104 | // Posting events
|
| ... | ... | @@ -136,6 +155,52 @@ void traceGcEvent_ (Capability *cap, EventTypeNum tag); |
| 136 | 155 | |
| 137 | 156 | void traceGcEventAtT_ (Capability *cap, StgWord64 ts, EventTypeNum tag);
|
| 138 | 157 | |
| 158 | +/*
|
|
| 159 | + * Record a nonmoving GC event.
|
|
| 160 | + */
|
|
| 161 | +#define traceConcMarkBegin() \
|
|
| 162 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
|
|
| 163 | + traceNonmovingGcEvent_(EVENT_CONC_MARK_BEGIN); \
|
|
| 164 | + }
|
|
| 165 | +#define traceConcMarkEnd(marked_obj_count) \
|
|
| 166 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
|
|
| 167 | + traceConcMarkEnd_(marked_obj_count); \
|
|
| 168 | + }
|
|
| 169 | +#define traceConcSyncBegin() \
|
|
| 170 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
|
|
| 171 | + traceNonmovingGcEvent_(EVENT_CONC_SYNC_BEGIN); \
|
|
| 172 | + }
|
|
| 173 | +#define traceConcSyncEnd() \
|
|
| 174 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
|
|
| 175 | + traceNonmovingGcEvent_(EVENT_CONC_SYNC_END); \
|
|
| 176 | + }
|
|
| 177 | +#define traceConcSweepBegin() \
|
|
| 178 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
|
|
| 179 | + traceNonmovingGcEvent_(EVENT_CONC_SWEEP_BEGIN); \
|
|
| 180 | + }
|
|
| 181 | +#define traceConcSweepEnd() \
|
|
| 182 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
|
|
| 183 | + traceNonmovingGcEvent_(EVENT_CONC_SWEEP_END); \
|
|
| 184 | + }
|
|
| 185 | +#define traceConcUpdRemSetFlush(cap) \
|
|
| 186 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
|
|
| 187 | + traceConcUpdRemSetFlush_(cap); \
|
|
| 188 | + }
|
|
| 189 | +#define traceNonmovingHeapCensus(blk_size, census) \
|
|
| 190 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
|
|
| 191 | + traceNonmovingHeapCensus_(blk_size, census); \
|
|
| 192 | + }
|
|
| 193 | +#define traceNonmovingPrunedSegments(pruned_segments, free_segments) \
|
|
| 194 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
|
|
| 195 | + traceNonmovingPrunedSegments_(pruned_segments, free_segments); \
|
|
| 196 | + }
|
|
| 197 | + |
|
| 198 | +void traceNonmovingGcEvent_ (EventTypeNum tag);
|
|
| 199 | +void traceConcMarkEnd_(StgWord32 marked_obj_count);
|
|
| 200 | +void traceConcUpdRemSetFlush_(Capability *cap);
|
|
| 201 | +void traceNonmovingHeapCensus_(uint16_t blk_size, const struct NonmovingAllocCensus *census);
|
|
| 202 | +void traceNonmovingPrunedSegments_(uint32_t pruned_segments, uint32_t free_segments);
|
|
| 203 | + |
|
| 139 | 204 | /*
|
| 140 | 205 | * Record a heap event
|
| 141 | 206 | */
|
| ... | ... | @@ -321,17 +386,6 @@ void traceProfSampleCostCentre(Capability *cap, |
| 321 | 386 | void traceProfBegin(void);
|
| 322 | 387 | #endif /* PROFILING */
|
| 323 | 388 | |
| 324 | -void traceConcMarkBegin(void);
|
|
| 325 | -void traceConcMarkEnd(StgWord32 marked_obj_count);
|
|
| 326 | -void traceConcSyncBegin(void);
|
|
| 327 | -void traceConcSyncEnd(void);
|
|
| 328 | -void traceConcSweepBegin(void);
|
|
| 329 | -void traceConcSweepEnd(void);
|
|
| 330 | -void traceConcUpdRemSetFlush(Capability *cap);
|
|
| 331 | -void traceNonmovingHeapCensus(uint16_t blk_size,
|
|
| 332 | - const struct NonmovingAllocCensus *census);
|
|
| 333 | -void traceNonmovingPrunedSegments(uint32_t pruned_segments, uint32_t free_segments);
|
|
| 334 | - |
|
| 335 | 389 | void traceIPE(const InfoProvEnt *ipe);
|
| 336 | 390 | void flushTrace(void);
|
| 337 | 391 | |
| ... | ... | @@ -384,6 +438,7 @@ void flushTrace(void); |
| 384 | 438 | #define traceConcSweepEnd() /* nothing */
|
| 385 | 439 | #define traceConcUpdRemSetFlush(cap) /* nothing */
|
| 386 | 440 | #define traceNonmovingHeapCensus(blk_size, census) /* nothing */
|
| 441 | +#define traceNonmovingPrunedSegments(pruned_segments, free_segments) /* nothing */
|
|
| 387 | 442 | |
| 388 | 443 | #define flushTrace() /* nothing */
|
| 389 | 444 |
| ... | ... | @@ -78,3 +78,34 @@ void endEventLogging(void); |
| 78 | 78 | * Flush the eventlog. cap can be NULL if one is not held.
|
| 79 | 79 | */
|
| 80 | 80 | void flushEventLog(Capability **cap);
|
| 81 | + |
|
| 82 | +/*
|
|
| 83 | + * An enumeration for the runtime trace flags.
|
|
| 84 | + */
|
|
| 85 | +typedef enum {
|
|
| 86 | + TRACE_SCHEDULER,
|
|
| 87 | + TRACE_GC,
|
|
| 88 | + TRACE_NONMOVING_GC,
|
|
| 89 | + TRACE_SPARK_SAMPLED,
|
|
| 90 | + TRACE_SPARK_FULL,
|
|
| 91 | + TRACE_USER,
|
|
| 92 | + TRACE_CAP,
|
|
| 93 | +} RUNTIME_TRACE_FLAG;
|
|
| 94 | + |
|
| 95 | +/*
|
|
| 96 | + * Get the value of the given runtime trace flag.
|
|
| 97 | + *
|
|
| 98 | + * Warning: The trace flag cache is not thread-safe. After initialisation, the
|
|
| 99 | + * RTS never writes to these values, but concurrently using getTraceFlag and
|
|
| 100 | + * setTraceFlag for the same flag is a race condition.
|
|
| 101 | + */
|
|
| 102 | +bool getTraceFlag(RUNTIME_TRACE_FLAG flag);
|
|
| 103 | + |
|
| 104 | +/*
|
|
| 105 | + * Set the value of the given runtime trace flag.
|
|
| 106 | + *
|
|
| 107 | + * Warning: The trace flag cache is not thread-safe. After initialisation, the
|
|
| 108 | + * RTS never writes to these values. However, inconsistent reads may lead to
|
|
| 109 | + * incorrect tracing for a short time after setting a trace flag.
|
|
| 110 | + */
|
|
| 111 | +void setTraceFlag(RUNTIME_TRACE_FLAG flag, bool value); |
| 1 | +/* -----------------------------------------------------------------------------
|
|
| 2 | + *
|
|
| 3 | + * (c) The GHC Team 2025
|
|
| 4 | + *
|
|
| 5 | + * Utilities for a simple fd-based cross-thread wakeup mechanism.
|
|
| 6 | + *
|
|
| 7 | + * This is used to provide a mechanism to wake a thread when it is blocked
|
|
| 8 | + * waiting on fds and timeouts. The mechanism works by including the read end
|
|
| 9 | + * fd into the set of fds the thread waits on, and when a wake up is needed,
|
|
| 10 | + * the write end fd is used.
|
|
| 11 | + *
|
|
| 12 | + * This is implemented using either eventfd() or pipe().
|
|
| 13 | + *
|
|
| 14 | + * Linux 2.6.22+ and FreeBSD 13+ support eventfd. It is a single fd with a
|
|
| 15 | + * 64bit counter. It uses fewer resources than a pipe (less memory and one
|
|
| 16 | + * rather than two fds), and is a tad faster (on the order of 5-10%). Using
|
|
| 17 | + * write() adds to the counter, while read() reads and resets it. Thus
|
|
| 18 | + * multiple writes are combined automatically into a single corresponding
|
|
| 19 | + * read.
|
|
| 20 | + *
|
|
| 21 | + * Otherwise we use a classic unix pipe.
|
|
| 22 | + *
|
|
| 23 | + * In both implementations, multiple sendFdWakeup notifcations (without
|
|
| 24 | + * interleaved collectFdWakeup) are combined to a single notification. This
|
|
| 25 | + * is automatic given the semantics of eventfd, while for pipe we implement
|
|
| 26 | + * it explicitly by draining the pipe in collectFdWakeup.
|
|
| 27 | + *
|
|
| 28 | + * -------------------------------------------------------------------------*/
|
|
| 29 | + |
|
| 30 | +#include "rts/PosixSource.h"
|
|
| 31 | +#include "Rts.h"
|
|
| 32 | + |
|
| 33 | +#include "FdWakeup.h"
|
|
| 34 | + |
|
| 35 | +#include <fcntl.h>
|
|
| 36 | +#include <unistd.h>
|
|
| 37 | + |
|
| 38 | +#ifdef HAVE_SYS_EVENTFD_H
|
|
| 39 | +#include <sys/eventfd.h>
|
|
| 40 | +#endif
|
|
| 41 | + |
|
| 42 | +#if !defined(HAVE_EVENTFD) \
|
|
| 43 | + || (defined(HAVE_EVENTFD) && !(defined(EFD_CLOEXEC) && defined(EFD_NONBLOCK)))
|
|
| 44 | +static void fcntl_CLOEXEC_NONBLOCK(int fd)
|
|
| 45 | +{
|
|
| 46 | + int res1 = fcntl(fd, F_SETFD, FD_CLOEXEC);
|
|
| 47 | + int res2 = fcntl(fd, F_SETFL, O_NONBLOCK);
|
|
| 48 | + if (RTS_UNLIKELY(res1 < 0 || res2 < 0)) {
|
|
| 49 | + sysErrorBelch("newFdWakeup fcntl()");
|
|
| 50 | + stg_exit(EXIT_FAILURE);
|
|
| 51 | + }
|
|
| 52 | +}
|
|
| 53 | +#endif
|
|
| 54 | + |
|
| 55 | +void newFdWakeup(int *wakeup_fd_r, int *wakeup_fd_w)
|
|
| 56 | +{
|
|
| 57 | +#if defined(HAVE_EVENTFD)
|
|
| 58 | + int wakeup_fd;
|
|
| 59 | +#if defined(EFD_CLOEXEC) && defined(EFD_NONBLOCK)
|
|
| 60 | + wakeup_fd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK);
|
|
| 61 | +#else
|
|
| 62 | + wakeup_fd = eventfd(0, 0);
|
|
| 63 | + if (wakeup_fd >= 0) fcntl_CLOEXEC_NONBLOCK(wakeup_fd);
|
|
| 64 | +#endif
|
|
| 65 | + if (RTS_UNLIKELY(wakeup_fd < 0)) {
|
|
| 66 | + sysErrorBelch("newFdWakeup eventfd()");
|
|
| 67 | + stg_exit(EXIT_FAILURE);
|
|
| 68 | + }
|
|
| 69 | + /* eventfd uses the same fd for each end */
|
|
| 70 | + *wakeup_fd_r = wakeup_fd;
|
|
| 71 | + *wakeup_fd_w = wakeup_fd;
|
|
| 72 | +#else
|
|
| 73 | + int pipefd[2];
|
|
| 74 | + int res;
|
|
| 75 | + res = pipe(pipefd);
|
|
| 76 | + if (RTS_UNLIKELY(res < 0)) {
|
|
| 77 | + sysErrorBelch("newFdWakeup pipe");
|
|
| 78 | + stg_exit(EXIT_FAILURE);
|
|
| 79 | + }
|
|
| 80 | + fcntl_CLOEXEC_NONBLOCK(pipefd[0]);
|
|
| 81 | + fcntl_CLOEXEC_NONBLOCK(pipefd[1]);
|
|
| 82 | + *wakeup_fd_r = pipefd[0]; /* read end */
|
|
| 83 | + *wakeup_fd_w = pipefd[1]; /* write end */
|
|
| 84 | +#endif
|
|
| 85 | +}
|
|
| 86 | + |
|
| 87 | +void closeFdWakeup(int wakeup_fd_r, int wakeup_fd_w)
|
|
| 88 | +{
|
|
| 89 | +#if defined(HAVE_EVENTFD)
|
|
| 90 | + ASSERT(wakeup_fd_r == wakeup_fd_w);
|
|
| 91 | + close(wakeup_fd_r);
|
|
| 92 | +#else
|
|
| 93 | + ASSERT(wakeup_fd_r != wakeup_fd_w);
|
|
| 94 | + close(wakeup_fd_r);
|
|
| 95 | + close(wakeup_fd_w);
|
|
| 96 | +#endif
|
|
| 97 | +}
|
|
| 98 | + |
|
| 99 | +/* This is safe to use from a signal handler. Using write() to a pipe
|
|
| 100 | + * or eventfd is fine. */
|
|
| 101 | +void sendFdWakeup(int wakeup_fd_w)
|
|
| 102 | +{
|
|
| 103 | + int res;
|
|
| 104 | +#if defined(HAVE_EVENTFD)
|
|
| 105 | + uint64_t val = 1;
|
|
| 106 | + res = write(wakeup_fd_w, &val, 8);
|
|
| 107 | +#else
|
|
| 108 | + unsigned char buf = 1;
|
|
| 109 | + res = write(wakeup_fd_w, &buf, 1);
|
|
| 110 | +#endif
|
|
| 111 | + if (RTS_UNLIKELY(res < 0)) {
|
|
| 112 | + /* Unlikely the pipe buffer will fill, but it would not be an error. */
|
|
| 113 | + if (errno == EAGAIN) return;
|
|
| 114 | + sysErrorBelch("sendFdWakeup write");
|
|
| 115 | + stg_exit(EXIT_FAILURE);
|
|
| 116 | + }
|
|
| 117 | +}
|
|
| 118 | + |
|
| 119 | +void collectFdWakeup(int wakeup_fd_r)
|
|
| 120 | +{
|
|
| 121 | + int res;
|
|
| 122 | +#if defined(HAVE_EVENTFD)
|
|
| 123 | + uint64_t buf;
|
|
| 124 | + /* eventfd combines events into one counter, so a single read is enough */
|
|
| 125 | + res = read(wakeup_fd_r, &buf, 8);
|
|
| 126 | +#else
|
|
| 127 | + /* Drain the pipe buffer. Multiple wakeup notifications could
|
|
| 128 | + * have been sent before we have a chance to collect them.
|
|
| 129 | + */
|
|
| 130 | + uint64_t buf;
|
|
| 131 | + do {
|
|
| 132 | + res = read(wakeup_fd_r, &buf, 8);
|
|
| 133 | + } while (res == 8);
|
|
| 134 | +#endif
|
|
| 135 | + if (RTS_UNLIKELY(res < 0)) {
|
|
| 136 | + /* After the first pipe read, it could block */
|
|
| 137 | + if (errno == EAGAIN) return;
|
|
| 138 | + sysErrorBelch("collectFdWakeup read");
|
|
| 139 | + stg_exit(EXIT_FAILURE);
|
|
| 140 | + }
|
|
| 141 | +} |
| 1 | +/* -----------------------------------------------------------------------------
|
|
| 2 | + *
|
|
| 3 | + * (c) The GHC Team 2025
|
|
| 4 | + *
|
|
| 5 | + * Utilities for a simple fd-based cross-thread wakeup mechanism.
|
|
| 6 | + *
|
|
| 7 | + * It provides a mechanism for a thread that block on fds to add a simple
|
|
| 8 | + * wakeup/notification feature.
|
|
| 9 | + *
|
|
| 10 | + * Start with newFdWakeup, and pass the fd_r to the thread that needs the
|
|
| 11 | + * wakeup feature. The thread that needs to be woken should include the fd_r
|
|
| 12 | + * into the set of fds that the thread waits on (e.g. using poll or similar).
|
|
| 13 | + * If this fd becomes ready for read, the thread must call collectFdWakeup,
|
|
| 14 | + * and when a wake up is needed, the write end fd is used. In any other thread
|
|
| 15 | + * (or in a signal handler), call sendFdWakeup(fd_w) to (asynchronously) cause
|
|
| 16 | + * the wakeup.
|
|
| 17 | + *
|
|
| 18 | + * There is no message payload. Multiple wakeups may be combined (if they're
|
|
| 19 | + * sent multiple times before the notified thread can wake and call
|
|
| 20 | + * collectFdWakeup).
|
|
| 21 | + *
|
|
| 22 | + * The implementation uses pipe() or eventfd() on supported OSs.
|
|
| 23 | + *
|
|
| 24 | + * Prototypes for functions in FdWakeup.c
|
|
| 25 | + *
|
|
| 26 | + * -------------------------------------------------------------------------*/
|
|
| 27 | + |
|
| 28 | +#pragma once
|
|
| 29 | + |
|
| 30 | +#include "BeginPrivate.h"
|
|
| 31 | + |
|
| 32 | +void newFdWakeup(int *fd_r, int *fd_w);
|
|
| 33 | +void closeFdWakeup(int fd_r, int fd_w);
|
|
| 34 | + |
|
| 35 | +/* This is safe to use from a signal handler */
|
|
| 36 | +void sendFdWakeup(int fd_w);
|
|
| 37 | +void collectFdWakeup(int fd_r);
|
|
| 38 | + |
|
| 39 | +#include "EndPrivate.h"
|
|
| 40 | + |
| 1 | 1 | /* -----------------------------------------------------------------------------
|
| 2 | 2 | *
|
| 3 | - * (c) The GHC Team, 1995-2007
|
|
| 3 | + * (c) The GHC Team, 1995-2026
|
|
| 4 | 4 | *
|
| 5 | - * Posix implementation(s) of the interval timer for profiling and pre-emptive
|
|
| 6 | - * scheduling.
|
|
| 5 | + * The posix implementation of the interval timer, used for pre-emptive
|
|
| 6 | + * scheduling of Haskell threads, and for sample based profiling.
|
|
| 7 | + *
|
|
| 8 | + * This file defines the "ticker": the platform-specific service to install and
|
|
| 9 | + * run the timer. See rts/Timer.c for the platform-dependent view of interval
|
|
| 10 | + * timing.
|
|
| 7 | 11 | *
|
| 8 | 12 | * ---------------------------------------------------------------------------*/
|
| 9 | 13 | |
| 10 | -/* The interval timer is used for profiling and for context switching.
|
|
| 11 | - * This file defines the platform-specific services to install and run the
|
|
| 12 | - * timers, and we call this the ticker. See rts/Timer.c for the
|
|
| 13 | - * platform-dependent view of interval timing.
|
|
| 14 | +/* This implementation uses a posix thread which repeatedly blocks on a timeout
|
|
| 15 | + * using either the ppoll() or select() API. This lets it also block on a file
|
|
| 16 | + * descriptor for early wakeup.
|
|
| 17 | + *
|
|
| 18 | + * The design uses a simple relative time delay with no catchup. That is, time
|
|
| 19 | + * spent by the ticker thread itself (e.g. flushing eventlog buffers) is not
|
|
| 20 | + * accounted for, and the next tick is delayed by that much (modulo wakeup
|
|
| 21 | + * jitter). This is probably the right thing to do: generally in realtime
|
|
| 22 | + * systems one does not want to try to catch up when behind, since that tends
|
|
| 23 | + * towards oversubscribing resources. Graceful degredation is usually
|
|
| 24 | + * preferable.
|
|
| 25 | + *
|
|
| 26 | + * Experimental results (on Linux 6.18 on x86-64) to measure the typical
|
|
| 27 | + * difference between the requested wakeup time and actual wakeup time for
|
|
| 28 | + * different delay intervals:
|
|
| 29 | + *
|
|
| 30 | + * interval typical actual wakeup time after due time
|
|
| 31 | + * 10000us 340 -- 400us (this is the default interval)
|
|
| 32 | + * 1000us 55 -- 100us
|
|
| 33 | + * 100us 55us
|
|
| 34 | + * 10us 55us
|
|
| 35 | + *
|
|
| 36 | + * While there's quite a bit of variance to these numbers, the results do not
|
|
| 37 | + * vary significantly between using select, ppoll or nanosleep.
|
|
| 38 | + *
|
|
| 39 | + * On Linux at least, for longer delays the kernel allows itself lower wakeup
|
|
| 40 | + * accuracy (which allows it to save power by coalescing multiple wakeups).
|
|
| 41 | + * Similarly, the reason for 55us on the low end is that the default thread
|
|
| 42 | + * timer slack on Linux is 50us, and context switch time accounts for the
|
|
| 43 | + * remainder.
|
|
| 44 | + *
|
|
| 45 | + * In conclusion, on Linux at least, the accuracy is fine, both for the
|
|
| 46 | + * default interval (10ms, 10000us) and for shorter intervals used during
|
|
| 47 | + * profiling.
|
|
| 14 | 48 | *
|
| 15 | 49 | * Historically we had ticker implementations using signals. This was always a
|
| 16 | - * rather shakey thing to do but we had few alternatives.
|
|
| 50 | + * rather shakey thing to do but we originally had few alternatives.
|
|
| 17 | 51 | * - One problem with using signals is that there are severe limits on what
|
| 18 | 52 | * code can be called from signal handlers. In particular it's not possible
|
| 19 | 53 | * to take locks in a signal handler contex. This was enough for contex
|
| ... | ... | @@ -23,17 +57,245 @@ |
| 23 | 57 | * calls (#10840) or can be overwritten by user code.
|
| 24 | 58 | */
|
| 25 | 59 | |
| 26 | -/* Select a ticker implementation to use:
|
|
| 27 | - *
|
|
| 28 | - * On modern Linux, FreeBSD and NetBSD we can use timerfd_create and a thread
|
|
| 29 | - * that waits on it using poll. Linux has had timerfd since version 2.6.25.
|
|
| 30 | - * NetBSD has had timerfd since version 10, and FreeBSD since version 15.
|
|
| 31 | - *
|
|
| 32 | - * For older version of linux/bsd without timerfd, and for all other posix
|
|
| 33 | - * platforms, we use the implementation using posix pthreads and nanosleep().
|
|
| 60 | +#include "rts/PosixSource.h"
|
|
| 61 | +#include "Rts.h"
|
|
| 62 | + |
|
| 63 | +#include "Ticker.h"
|
|
| 64 | +#include "RtsUtils.h"
|
|
| 65 | +#include "Proftimer.h"
|
|
| 66 | +#include "Schedule.h"
|
|
| 67 | +#include "posix/Clock.h"
|
|
| 68 | +#include "posix/FdWakeup.h"
|
|
| 69 | + |
|
| 70 | +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
|
|
| 71 | +/* We prefer the ppoll() function if available since it allows sanely waiting
|
|
| 72 | + * on a single fd with precise timeouts (nanosecond precision). It is not in
|
|
| 73 | + * the posix standard however and some platforms (notably glibc and freebsd)
|
|
| 74 | + * need special CPP defines to make it available:
|
|
| 75 | + */
|
|
| 76 | +#define _GNU_SOURCE 1
|
|
| 77 | +#define __BSD_VISIBLE 1
|
|
| 78 | +#include <signal.h>
|
|
| 79 | +#include <poll.h>
|
|
| 80 | +#else
|
|
| 81 | +/* Otherwise we use the classic select(), which does have microsecond
|
|
| 82 | + * precision, but requires we build three whole 1024 bit (128 byte) fd sets
|
|
| 83 | + * just to wait on one fd.
|
|
| 34 | 84 | */
|
| 35 | -#if defined(HAVE_SYS_TIMERFD_H)
|
|
| 36 | -#include "ticker/TimerFd.c"
|
|
| 85 | +#include <sys/select.h>
|
|
| 86 | +#endif
|
|
| 87 | + |
|
| 88 | +#include <time.h>
|
|
| 89 | +#if HAVE_SYS_TIME_H
|
|
| 90 | +# include <sys/time.h>
|
|
| 91 | +#endif
|
|
| 92 | + |
|
| 93 | +#if defined(HAVE_SIGNAL_H)
|
|
| 94 | +# include <signal.h>
|
|
| 95 | +#endif
|
|
| 96 | + |
|
| 97 | +#include <string.h>
|
|
| 98 | + |
|
| 99 | +#include <pthread.h>
|
|
| 100 | +#if defined(HAVE_PTHREAD_NP_H)
|
|
| 101 | +#include <pthread_np.h>
|
|
| 102 | +#endif
|
|
| 103 | +#include <unistd.h>
|
|
| 104 | +#include <fcntl.h>
|
|
| 105 | + |
|
| 106 | +static Time itimer_interval = DEFAULT_TICK_INTERVAL;
|
|
| 107 | + |
|
| 108 | +// Should we be firing ticks?
|
|
| 109 | +// Writers to this must hold the mutex below.
|
|
| 110 | +static bool stopped = false;
|
|
| 111 | + |
|
| 112 | +// should the ticker thread exit?
|
|
| 113 | +// This can be set without holding the mutex.
|
|
| 114 | +static bool exited = true;
|
|
| 115 | + |
|
| 116 | +// Signaled when we want to (re)start the timer
|
|
| 117 | +static Condition start_cond;
|
|
| 118 | +static Mutex mutex;
|
|
| 119 | +static OSThreadId thread;
|
|
| 120 | + |
|
| 121 | +// fds for interrupting the ticker
|
|
| 122 | +static int interruptfd_r = -1, interruptfd_w = -1;
|
|
| 123 | + |
|
| 124 | +static void *itimer_thread_func(void *_handle_tick)
|
|
| 125 | +{
|
|
| 126 | + TickProc handle_tick = _handle_tick;
|
|
| 127 | + |
|
| 128 | +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
|
|
| 129 | + struct pollfd pollfds[1];
|
|
| 130 | + |
|
| 131 | + pollfds[0].fd = interruptfd_r;
|
|
| 132 | + pollfds[0].events = POLLIN;
|
|
| 133 | + |
|
| 134 | + struct timespec ts = { .tv_sec = TimeToSeconds(itimer_interval)
|
|
| 135 | + , .tv_nsec = TimeToNS(itimer_interval) % 1000000000
|
|
| 136 | + };
|
|
| 37 | 137 | #else
|
| 38 | -#include "ticker/Pthread.c"
|
|
| 138 | + fd_set selectfds;
|
|
| 139 | + FD_ZERO(&selectfds);
|
|
| 140 | + FD_SET(interruptfd_r, &selectfds);
|
|
| 141 | + |
|
| 142 | + struct timeval tv = { .tv_sec = TimeToSeconds(itimer_interval)
|
|
| 143 | + /* convert remainder time in nanoseconds
|
|
| 144 | + to microseconds, rounding up: */
|
|
| 145 | + , .tv_usec = ((TimeToNS(itimer_interval) % 1000000000)
|
|
| 146 | + + 999) / 1000
|
|
| 147 | + };
|
|
| 148 | +#endif
|
|
| 149 | + |
|
| 150 | + // Relaxed is sufficient: If we don't see that exited was set in one iteration we will
|
|
| 151 | + // see it next time.
|
|
| 152 | + while (!RELAXED_LOAD_ALWAYS(&exited)) {
|
|
| 153 | + |
|
| 154 | +#if defined(HAVE_DECL_PPOLL) && HAVE_DECL_PPOLL == 1
|
|
| 155 | + int nfds = 1;
|
|
| 156 | + int nready = ppoll(pollfds, nfds, &ts, NULL);
|
|
| 157 | +#else
|
|
| 158 | + struct timeval tv_tmp = tv; // copy since select may change this value.
|
|
| 159 | + int nfds = interruptfd_r+1;
|
|
| 160 | + int nready = select(nfds, &selectfds, NULL, NULL, &tv_tmp);
|
|
| 161 | +#endif
|
|
| 162 | + // In either case (ppoll or select), the result nready is the number
|
|
| 163 | + // of fds that are ready.
|
|
| 164 | + if (RTS_LIKELY(nready == 0)) {
|
|
| 165 | + // Timer expired, not interrupted, continue.
|
|
| 166 | + } else if (nready > 0) {
|
|
| 167 | + // We only monitor one fd (the interruptfd_r), so we know
|
|
| 168 | + // it is that fd that is ready without any further checks.
|
|
| 169 | + collectFdWakeup(interruptfd_r);
|
|
| 170 | + // No further action needed, continue on to handling the final tick
|
|
| 171 | + // and then stop.
|
|
| 172 | + |
|
| 173 | + // Note that we rely on sendFdWakeup and select/poll to provide the
|
|
| 174 | + // happens-before relation. So if 'exited' was set before calling
|
|
| 175 | + // sendFdWakeup, then we should be able to reliably read it after.
|
|
| 176 | + // And thus reading 'exited' in the while loop guard is ok.
|
|
| 177 | + } else {
|
|
| 178 | + // While the RTS attempts to mask signals, some foreign libraries
|
|
| 179 | + // that rely on signal delivery may unmask them. Consequently we
|
|
| 180 | + // may see EINTR. See #24610.
|
|
| 181 | + if (errno != EINTR) {
|
|
| 182 | + sysErrorBelch("Ticker: poll failed: %s", strerror(errno));
|
|
| 183 | + }
|
|
| 184 | + }
|
|
| 185 | + |
|
| 186 | + // first try a cheap test
|
|
| 187 | + if (RELAXED_LOAD_ALWAYS(&stopped)) {
|
|
| 188 | + OS_ACQUIRE_LOCK(&mutex);
|
|
| 189 | + // should we really stop?
|
|
| 190 | + if (stopped) {
|
|
| 191 | + waitCondition(&start_cond, &mutex);
|
|
| 192 | + }
|
|
| 193 | + OS_RELEASE_LOCK(&mutex);
|
|
| 194 | + } else {
|
|
| 195 | + handle_tick(0);
|
|
| 196 | + }
|
|
| 197 | + }
|
|
| 198 | + |
|
| 199 | + return NULL;
|
|
| 200 | +}
|
|
| 201 | + |
|
| 202 | +void
|
|
| 203 | +initTicker (Time interval, TickProc handle_tick)
|
|
| 204 | +{
|
|
| 205 | + itimer_interval = interval;
|
|
| 206 | + stopped = true;
|
|
| 207 | + exited = false;
|
|
| 208 | +#if defined(HAVE_SIGNAL_H)
|
|
| 209 | + sigset_t mask, omask;
|
|
| 210 | + int sigret;
|
|
| 211 | +#endif
|
|
| 212 | + int ret;
|
|
| 213 | + |
|
| 214 | + initCondition(&start_cond);
|
|
| 215 | + initMutex(&mutex);
|
|
| 216 | + |
|
| 217 | + /* Open the interrupt fd synchronously.
|
|
| 218 | + *
|
|
| 219 | + * We used to do it in itimer_thread_func (i.e. in the timer thread) but it
|
|
| 220 | + * meant that some user code could run before it and get confused by the
|
|
| 221 | + * allocation of the timerfd.
|
|
| 222 | + *
|
|
| 223 | + * See hClose002 which unsafely closes a file descriptor twice expecting an
|
|
| 224 | + * exception the second time: it sometimes failed when the second call to
|
|
| 225 | + * "close" closed our own timerfd which inadvertently reused the same file
|
|
| 226 | + * descriptor closed by the first call! (see #20618)
|
|
| 227 | + */
|
|
| 228 | + |
|
| 229 | + if (interruptfd_r != -1) {
|
|
| 230 | + // don't leak the old file descriptors after a fork (#25280)
|
|
| 231 | + closeFdWakeup(interruptfd_r, interruptfd_w);
|
|
| 232 | + }
|
|
| 233 | + newFdWakeup(&interruptfd_r, &interruptfd_w);
|
|
| 234 | + |
|
| 235 | + /*
|
|
| 236 | + * Create the thread with all blockable signals blocked, leaving signal
|
|
| 237 | + * handling to the main and/or other threads. This is especially useful in
|
|
| 238 | + * the non-threaded runtime, where applications might expect sigprocmask(2)
|
|
| 239 | + * to effectively block signals.
|
|
| 240 | + */
|
|
| 241 | +#if defined(HAVE_SIGNAL_H)
|
|
| 242 | + sigfillset(&mask);
|
|
| 243 | + sigret = pthread_sigmask(SIG_SETMASK, &mask, &omask);
|
|
| 244 | +#endif
|
|
| 245 | + ret = createAttachedOSThread(&thread, "ghc_ticker", itimer_thread_func, (void*)handle_tick);
|
|
| 246 | +#if defined(HAVE_SIGNAL_H)
|
|
| 247 | + if (sigret == 0)
|
|
| 248 | + pthread_sigmask(SIG_SETMASK, &omask, NULL);
|
|
| 39 | 249 | #endif
|
| 250 | + |
|
| 251 | + if (ret != 0) {
|
|
| 252 | + barf("Ticker: Failed to spawn thread: %s", strerror(errno));
|
|
| 253 | + }
|
|
| 254 | +}
|
|
| 255 | + |
|
| 256 | +void
|
|
| 257 | +startTicker(void)
|
|
| 258 | +{
|
|
| 259 | + OS_ACQUIRE_LOCK(&mutex);
|
|
| 260 | + RELAXED_STORE(&stopped, false);
|
|
| 261 | + signalCondition(&start_cond);
|
|
| 262 | + OS_RELEASE_LOCK(&mutex);
|
|
| 263 | +}
|
|
| 264 | + |
|
| 265 | +/* There may be at most one additional tick fired after a call to this */
|
|
| 266 | +void
|
|
| 267 | +stopTicker(void)
|
|
| 268 | +{
|
|
| 269 | + OS_ACQUIRE_LOCK(&mutex);
|
|
| 270 | + RELAXED_STORE(&stopped, true);
|
|
| 271 | + OS_RELEASE_LOCK(&mutex);
|
|
| 272 | +}
|
|
| 273 | + |
|
| 274 | +/* There may be at most one additional tick fired after a call to this */
|
|
| 275 | +void
|
|
| 276 | +exitTicker (bool wait)
|
|
| 277 | +{
|
|
| 278 | + ASSERT(!SEQ_CST_LOAD(&exited));
|
|
| 279 | + SEQ_CST_STORE(&exited, true);
|
|
| 280 | + // ensure that ticker wakes up if stopped
|
|
| 281 | + startTicker();
|
|
| 282 | + sendFdWakeup(interruptfd_w);
|
|
| 283 | + |
|
| 284 | + // wait for ticker to terminate if necessary
|
|
| 285 | + if (wait) {
|
|
| 286 | + if (pthread_join(thread, NULL)) {
|
|
| 287 | + sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
|
|
| 288 | + }
|
|
| 289 | + closeFdWakeup(interruptfd_r, interruptfd_w);
|
|
| 290 | + closeMutex(&mutex);
|
|
| 291 | + closeCondition(&start_cond);
|
|
| 292 | + } else {
|
|
| 293 | + pthread_detach(thread);
|
|
| 294 | + }
|
|
| 295 | +}
|
|
| 296 | + |
|
| 297 | +int
|
|
| 298 | +rtsTimerSignal(void)
|
|
| 299 | +{
|
|
| 300 | + return SIGALRM;
|
|
| 301 | +} |
| 1 | -/* -----------------------------------------------------------------------------
|
|
| 2 | - *
|
|
| 3 | - * (c) The GHC Team, 1995-2007
|
|
| 4 | - *
|
|
| 5 | - * Interval timer for profiling and pre-emptive scheduling.
|
|
| 6 | - *
|
|
| 7 | - * ---------------------------------------------------------------------------*/
|
|
| 8 | - |
|
| 9 | -/*
|
|
| 10 | - * We use a realtime timer by default. I found this much more
|
|
| 11 | - * reliable than a CPU timer:
|
|
| 12 | - *
|
|
| 13 | - * Experiments with different frequencies: using
|
|
| 14 | - * CLOCK_REALTIME/CLOCK_MONOTONIC on Linux 2.6.32,
|
|
| 15 | - * 1000us has <1% impact on runtime
|
|
| 16 | - * 100us has ~2% impact on runtime
|
|
| 17 | - * 10us has ~40% impact on runtime
|
|
| 18 | - *
|
|
| 19 | - * using CLOCK_PROCESS_CPUTIME_ID on Linux 2.6.32,
|
|
| 20 | - * I cannot get it to tick faster than 10ms (10000us)
|
|
| 21 | - * which isn't great for profiling.
|
|
| 22 | - *
|
|
| 23 | - * In the threaded RTS, we can't tick in CPU time because the thread
|
|
| 24 | - * which has the virtual timer might be idle, so the tick would never
|
|
| 25 | - * fire. Therefore we used to tick in realtime in the threaded RTS and
|
|
| 26 | - * in CPU time otherwise, but now we always tick in realtime, for
|
|
| 27 | - * several reasons:
|
|
| 28 | - *
|
|
| 29 | - * - resolution (see above)
|
|
| 30 | - * - consistency (-threaded is the same as normal)
|
|
| 31 | - * - more consistency: Windows only has a realtime timer
|
|
| 32 | - *
|
|
| 33 | - * Note we want to use CLOCK_MONOTONIC rather than CLOCK_REALTIME,
|
|
| 34 | - * because the latter may jump around (NTP adjustments, leap seconds
|
|
| 35 | - * etc.).
|
|
| 36 | - */
|
|
| 37 | - |
|
| 38 | -#include "rts/PosixSource.h"
|
|
| 39 | -#include "Rts.h"
|
|
| 40 | - |
|
| 41 | -#include "Ticker.h"
|
|
| 42 | -#include "RtsUtils.h"
|
|
| 43 | -#include "Proftimer.h"
|
|
| 44 | -#include "Schedule.h"
|
|
| 45 | -#include "posix/Clock.h"
|
|
| 46 | -#include <poll.h>
|
|
| 47 | - |
|
| 48 | -#include <time.h>
|
|
| 49 | -#if HAVE_SYS_TIME_H
|
|
| 50 | -# include <sys/time.h>
|
|
| 51 | -#endif
|
|
| 52 | - |
|
| 53 | -#if defined(HAVE_SIGNAL_H)
|
|
| 54 | -# include <signal.h>
|
|
| 55 | -#endif
|
|
| 56 | - |
|
| 57 | -#include <string.h>
|
|
| 58 | - |
|
| 59 | -#include <pthread.h>
|
|
| 60 | -#if defined(HAVE_PTHREAD_NP_H)
|
|
| 61 | -#include <pthread_np.h>
|
|
| 62 | -#endif
|
|
| 63 | -#include <unistd.h>
|
|
| 64 | -#include <fcntl.h>
|
|
| 65 | - |
|
| 66 | -/*
|
|
| 67 | - * TFD_CLOEXEC has been added in Linux 2.6.26.
|
|
| 68 | - * If it is not available, we use fcntl(F_SETFD).
|
|
| 69 | - */
|
|
| 70 | -#if !defined(TFD_CLOEXEC)
|
|
| 71 | -#define TFD_CLOEXEC 0
|
|
| 72 | -#endif
|
|
| 73 | - |
|
| 74 | -static Time itimer_interval = DEFAULT_TICK_INTERVAL;
|
|
| 75 | - |
|
| 76 | -// Should we be firing ticks?
|
|
| 77 | -// Writers to this must hold the mutex below.
|
|
| 78 | -static bool stopped = false;
|
|
| 79 | - |
|
| 80 | -// should the ticker thread exit?
|
|
| 81 | -// This can be set without holding the mutex.
|
|
| 82 | -static bool exited = true;
|
|
| 83 | - |
|
| 84 | -// Signaled when we want to (re)start the timer
|
|
| 85 | -static Condition start_cond;
|
|
| 86 | -static Mutex mutex;
|
|
| 87 | -static OSThreadId thread;
|
|
| 88 | - |
|
| 89 | -static void *itimer_thread_func(void *_handle_tick)
|
|
| 90 | -{
|
|
| 91 | - TickProc handle_tick = _handle_tick;
|
|
| 92 | - |
|
| 93 | - // Relaxed is sufficient: If we don't see that exited was set in one iteration we will
|
|
| 94 | - // see it next time.
|
|
| 95 | - while (!RELAXED_LOAD_ALWAYS(&exited)) {
|
|
| 96 | - if (rtsSleep(itimer_interval) != 0) {
|
|
| 97 | - sysErrorBelch("Ticker: sleep failed: %s", strerror(errno));
|
|
| 98 | - }
|
|
| 99 | - |
|
| 100 | - // first try a cheap test
|
|
| 101 | - if (RELAXED_LOAD_ALWAYS(&stopped)) {
|
|
| 102 | - OS_ACQUIRE_LOCK(&mutex);
|
|
| 103 | - // should we really stop?
|
|
| 104 | - if (stopped) {
|
|
| 105 | - waitCondition(&start_cond, &mutex);
|
|
| 106 | - }
|
|
| 107 | - OS_RELEASE_LOCK(&mutex);
|
|
| 108 | - } else {
|
|
| 109 | - handle_tick(0);
|
|
| 110 | - }
|
|
| 111 | - }
|
|
| 112 | - |
|
| 113 | - return NULL;
|
|
| 114 | -}
|
|
| 115 | - |
|
| 116 | -void
|
|
| 117 | -initTicker (Time interval, TickProc handle_tick)
|
|
| 118 | -{
|
|
| 119 | - itimer_interval = interval;
|
|
| 120 | - stopped = true;
|
|
| 121 | - exited = false;
|
|
| 122 | -#if defined(HAVE_SIGNAL_H)
|
|
| 123 | - sigset_t mask, omask;
|
|
| 124 | - int sigret;
|
|
| 125 | -#endif
|
|
| 126 | - int ret;
|
|
| 127 | - |
|
| 128 | - initCondition(&start_cond);
|
|
| 129 | - initMutex(&mutex);
|
|
| 130 | - |
|
| 131 | - /*
|
|
| 132 | - * Create the thread with all blockable signals blocked, leaving signal
|
|
| 133 | - * handling to the main and/or other threads. This is especially useful in
|
|
| 134 | - * the non-threaded runtime, where applications might expect sigprocmask(2)
|
|
| 135 | - * to effectively block signals.
|
|
| 136 | - */
|
|
| 137 | -#if defined(HAVE_SIGNAL_H)
|
|
| 138 | - sigfillset(&mask);
|
|
| 139 | - sigret = pthread_sigmask(SIG_SETMASK, &mask, &omask);
|
|
| 140 | -#endif
|
|
| 141 | - ret = createAttachedOSThread(&thread, "ghc_ticker", itimer_thread_func, (void*)handle_tick);
|
|
| 142 | -#if defined(HAVE_SIGNAL_H)
|
|
| 143 | - if (sigret == 0)
|
|
| 144 | - pthread_sigmask(SIG_SETMASK, &omask, NULL);
|
|
| 145 | -#endif
|
|
| 146 | - |
|
| 147 | - if (ret != 0) {
|
|
| 148 | - barf("Ticker: Failed to spawn thread: %s", strerror(errno));
|
|
| 149 | - }
|
|
| 150 | -}
|
|
| 151 | - |
|
| 152 | -void
|
|
| 153 | -startTicker(void)
|
|
| 154 | -{
|
|
| 155 | - OS_ACQUIRE_LOCK(&mutex);
|
|
| 156 | - RELAXED_STORE(&stopped, false);
|
|
| 157 | - signalCondition(&start_cond);
|
|
| 158 | - OS_RELEASE_LOCK(&mutex);
|
|
| 159 | -}
|
|
| 160 | - |
|
| 161 | -/* There may be at most one additional tick fired after a call to this */
|
|
| 162 | -void
|
|
| 163 | -stopTicker(void)
|
|
| 164 | -{
|
|
| 165 | - OS_ACQUIRE_LOCK(&mutex);
|
|
| 166 | - RELAXED_STORE(&stopped, true);
|
|
| 167 | - OS_RELEASE_LOCK(&mutex);
|
|
| 168 | -}
|
|
| 169 | - |
|
| 170 | -/* There may be at most one additional tick fired after a call to this */
|
|
| 171 | -void
|
|
| 172 | -exitTicker (bool wait)
|
|
| 173 | -{
|
|
| 174 | - ASSERT(!SEQ_CST_LOAD(&exited));
|
|
| 175 | - SEQ_CST_STORE(&exited, true);
|
|
| 176 | - // ensure that ticker wakes up if stopped
|
|
| 177 | - startTicker();
|
|
| 178 | - |
|
| 179 | - // wait for ticker to terminate if necessary
|
|
| 180 | - if (wait) {
|
|
| 181 | - if (pthread_join(thread, NULL)) {
|
|
| 182 | - sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
|
|
| 183 | - }
|
|
| 184 | - closeMutex(&mutex);
|
|
| 185 | - closeCondition(&start_cond);
|
|
| 186 | - } else {
|
|
| 187 | - pthread_detach(thread);
|
|
| 188 | - }
|
|
| 189 | -}
|
|
| 190 | - |
|
| 191 | -int
|
|
| 192 | -rtsTimerSignal(void)
|
|
| 193 | -{
|
|
| 194 | - return SIGALRM;
|
|
| 195 | -} |
| 1 | -/* -----------------------------------------------------------------------------
|
|
| 2 | - *
|
|
| 3 | - * (c) The GHC Team, 1995-2023
|
|
| 4 | - *
|
|
| 5 | - * Interval timer for profiling and pre-emptive scheduling.
|
|
| 6 | - *
|
|
| 7 | - * ---------------------------------------------------------------------------*/
|
|
| 8 | - |
|
| 9 | -/*
|
|
| 10 | - * We use a realtime timer by default. I found this much more
|
|
| 11 | - * reliable than a CPU timer:
|
|
| 12 | - *
|
|
| 13 | - * Experiments with different frequencies: using
|
|
| 14 | - * CLOCK_REALTIME/CLOCK_MONOTONIC on Linux 2.6.32,
|
|
| 15 | - * 1000us has <1% impact on runtime
|
|
| 16 | - * 100us has ~2% impact on runtime
|
|
| 17 | - * 10us has ~40% impact on runtime
|
|
| 18 | - *
|
|
| 19 | - * using CLOCK_PROCESS_CPUTIME_ID on Linux 2.6.32,
|
|
| 20 | - * I cannot get it to tick faster than 10ms (10000us)
|
|
| 21 | - * which isn't great for profiling.
|
|
| 22 | - *
|
|
| 23 | - * In the threaded RTS, we can't tick in CPU time because the thread
|
|
| 24 | - * which has the virtual timer might be idle, so the tick would never
|
|
| 25 | - * fire. Therefore we used to tick in realtime in the threaded RTS and
|
|
| 26 | - * in CPU time otherwise, but now we always tick in realtime, for
|
|
| 27 | - * several reasons:
|
|
| 28 | - *
|
|
| 29 | - * - resolution (see above)
|
|
| 30 | - * - consistency (-threaded is the same as normal)
|
|
| 31 | - * - more consistency: Windows only has a realtime timer
|
|
| 32 | - *
|
|
| 33 | - * Note we want to use CLOCK_MONOTONIC rather than CLOCK_REALTIME,
|
|
| 34 | - * because the latter may jump around (NTP adjustments, leap seconds
|
|
| 35 | - * etc.).
|
|
| 36 | - */
|
|
| 37 | - |
|
| 38 | -#include "rts/PosixSource.h"
|
|
| 39 | -#include "Rts.h"
|
|
| 40 | - |
|
| 41 | -#include "Ticker.h"
|
|
| 42 | -#include "RtsUtils.h"
|
|
| 43 | -#include "Proftimer.h"
|
|
| 44 | -#include "Schedule.h"
|
|
| 45 | -#include "posix/Clock.h"
|
|
| 46 | -#include <poll.h>
|
|
| 47 | - |
|
| 48 | -#include <time.h>
|
|
| 49 | -#if HAVE_SYS_TIME_H
|
|
| 50 | -# include <sys/time.h>
|
|
| 51 | -#endif
|
|
| 52 | - |
|
| 53 | -#if defined(HAVE_SIGNAL_H)
|
|
| 54 | -# include <signal.h>
|
|
| 55 | -#endif
|
|
| 56 | - |
|
| 57 | -#include <string.h>
|
|
| 58 | - |
|
| 59 | -#include <pthread.h>
|
|
| 60 | -#if defined(HAVE_PTHREAD_NP_H)
|
|
| 61 | -#include <pthread_np.h>
|
|
| 62 | -#endif
|
|
| 63 | -#include <unistd.h>
|
|
| 64 | -#include <fcntl.h>
|
|
| 65 | - |
|
| 66 | -#include <sys/timerfd.h>
|
|
| 67 | - |
|
| 68 | - |
|
| 69 | -/*
|
|
| 70 | - * TFD_CLOEXEC has been added in Linux 2.6.26.
|
|
| 71 | - * If it is not available, we use fcntl(F_SETFD).
|
|
| 72 | - */
|
|
| 73 | -#if !defined(TFD_CLOEXEC)
|
|
| 74 | -#define TFD_CLOEXEC 0
|
|
| 75 | -#endif
|
|
| 76 | - |
|
| 77 | -static Time itimer_interval = DEFAULT_TICK_INTERVAL;
|
|
| 78 | - |
|
| 79 | -// Should we be firing ticks?
|
|
| 80 | -// Writers to this must hold the mutex below.
|
|
| 81 | -static bool stopped = false;
|
|
| 82 | - |
|
| 83 | -// should the ticker thread exit?
|
|
| 84 | -// This can be set without holding the mutex.
|
|
| 85 | -static bool exited = true;
|
|
| 86 | - |
|
| 87 | -// Signaled when we want to (re)start the timer
|
|
| 88 | -static Condition start_cond;
|
|
| 89 | -static Mutex mutex;
|
|
| 90 | -static OSThreadId thread;
|
|
| 91 | - |
|
| 92 | -// file descriptor for the timer (Linux only)
|
|
| 93 | -static int timerfd = -1;
|
|
| 94 | - |
|
| 95 | -// pipe for signaling exit
|
|
| 96 | -static int pipefds[2];
|
|
| 97 | - |
|
| 98 | -static void *itimer_thread_func(void *_handle_tick)
|
|
| 99 | -{
|
|
| 100 | - TickProc handle_tick = _handle_tick;
|
|
| 101 | - uint64_t nticks;
|
|
| 102 | - ssize_t r = 0;
|
|
| 103 | - struct pollfd pollfds[2];
|
|
| 104 | - |
|
| 105 | - pollfds[0].fd = pipefds[0];
|
|
| 106 | - pollfds[0].events = POLLIN;
|
|
| 107 | - pollfds[1].fd = timerfd;
|
|
| 108 | - pollfds[1].events = POLLIN;
|
|
| 109 | - |
|
| 110 | - // Relaxed is sufficient: If we don't see that exited was set in one iteration we will
|
|
| 111 | - // see it next time.
|
|
| 112 | - while (!RELAXED_LOAD_ALWAYS(&exited)) {
|
|
| 113 | - if (poll(pollfds, 2, -1) == -1) {
|
|
| 114 | - // While the RTS attempts to mask signals, some foreign libraries
|
|
| 115 | - // may rely on signal delivery may unmask them. Consequently we may
|
|
| 116 | - // see EINTR. See #24610.
|
|
| 117 | - if (errno != EINTR) {
|
|
| 118 | - sysErrorBelch("Ticker: poll failed: %s", strerror(errno));
|
|
| 119 | - }
|
|
| 120 | - }
|
|
| 121 | - |
|
| 122 | - // We check the pipe first, even though the timerfd may also have triggered.
|
|
| 123 | - if (pollfds[0].revents & POLLIN) {
|
|
| 124 | - // the pipe is ready for reading, the only possible reason is that we're exiting
|
|
| 125 | - exited = true; // set this again to make sure even RELAXED_LOAD will read the proper value
|
|
| 126 | - // no further action needed, skip ahead to handling the final tick and then stopping
|
|
| 127 | - }
|
|
| 128 | - else if (pollfds[1].revents & POLLIN) { // the timerfd is ready for reading
|
|
| 129 | - r = read(timerfd, &nticks, sizeof(nticks)); // this should never block now
|
|
| 130 | - |
|
| 131 | - if ((r == 0) && (errno == 0)) {
|
|
| 132 | - /* r == 0 is expected only for non-blocking fd (in which case
|
|
| 133 | - * errno should be EAGAIN) but we use a blocking fd.
|
|
| 134 | - *
|
|
| 135 | - * Due to a kernel bug (cf https://lkml.org/lkml/2019/8/16/335)
|
|
| 136 | - * on some platforms we could see r == 0 and errno == 0.
|
|
| 137 | - */
|
|
| 138 | - IF_DEBUG(scheduler, debugBelch("read(timerfd) returned 0 with errno=0. This is a known kernel bug. We just ignore it."));
|
|
| 139 | - }
|
|
| 140 | - else if (r != sizeof(nticks) && errno != EINTR) {
|
|
| 141 | - barf("Ticker: read(timerfd) failed with %s and returned %zd", strerror(errno), r);
|
|
| 142 | - }
|
|
| 143 | - }
|
|
| 144 | - |
|
| 145 | - // first try a cheap test
|
|
| 146 | - if (RELAXED_LOAD_ALWAYS(&stopped)) {
|
|
| 147 | - OS_ACQUIRE_LOCK(&mutex);
|
|
| 148 | - // should we really stop?
|
|
| 149 | - if (stopped) {
|
|
| 150 | - waitCondition(&start_cond, &mutex);
|
|
| 151 | - }
|
|
| 152 | - OS_RELEASE_LOCK(&mutex);
|
|
| 153 | - } else {
|
|
| 154 | - handle_tick(0);
|
|
| 155 | - }
|
|
| 156 | - }
|
|
| 157 | - |
|
| 158 | - close(timerfd);
|
|
| 159 | - return NULL;
|
|
| 160 | -}
|
|
| 161 | - |
|
| 162 | -void
|
|
| 163 | -initTicker (Time interval, TickProc handle_tick)
|
|
| 164 | -{
|
|
| 165 | - itimer_interval = interval;
|
|
| 166 | - stopped = true;
|
|
| 167 | - exited = false;
|
|
| 168 | -#if defined(HAVE_SIGNAL_H)
|
|
| 169 | - sigset_t mask, omask;
|
|
| 170 | - int sigret;
|
|
| 171 | -#endif
|
|
| 172 | - int ret;
|
|
| 173 | - |
|
| 174 | - initCondition(&start_cond);
|
|
| 175 | - initMutex(&mutex);
|
|
| 176 | - |
|
| 177 | - /* Open the file descriptor for the timer synchronously.
|
|
| 178 | - *
|
|
| 179 | - * We used to do it in itimer_thread_func (i.e. in the timer thread) but it
|
|
| 180 | - * meant that some user code could run before it and get confused by the
|
|
| 181 | - * allocation of the timerfd.
|
|
| 182 | - *
|
|
| 183 | - * See hClose002 which unsafely closes a file descriptor twice expecting an
|
|
| 184 | - * exception the second time: it sometimes failed when the second call to
|
|
| 185 | - * "close" closed our own timerfd which inadvertently reused the same file
|
|
| 186 | - * descriptor closed by the first call! (see #20618)
|
|
| 187 | - */
|
|
| 188 | - struct itimerspec it;
|
|
| 189 | - it.it_value.tv_sec = TimeToSeconds(itimer_interval);
|
|
| 190 | - it.it_value.tv_nsec = TimeToNS(itimer_interval) % 1000000000;
|
|
| 191 | - it.it_interval = it.it_value;
|
|
| 192 | - |
|
| 193 | - if (timerfd != -1) {
|
|
| 194 | - // don't leak the old file descriptors after a fork (#25280)
|
|
| 195 | - close(timerfd);
|
|
| 196 | - close(pipefds[0]);
|
|
| 197 | - close(pipefds[1]);
|
|
| 198 | - timerfd = -1;
|
|
| 199 | - }
|
|
| 200 | - |
|
| 201 | - timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
|
|
| 202 | - if (timerfd == -1) {
|
|
| 203 | - barf("timerfd_create: %s", strerror(errno));
|
|
| 204 | - }
|
|
| 205 | - if (!TFD_CLOEXEC) {
|
|
| 206 | - fcntl(timerfd, F_SETFD, FD_CLOEXEC);
|
|
| 207 | - }
|
|
| 208 | - if (timerfd_settime(timerfd, 0, &it, NULL)) {
|
|
| 209 | - barf("timerfd_settime: %s", strerror(errno));
|
|
| 210 | - }
|
|
| 211 | - |
|
| 212 | - if (pipe(pipefds) < 0) {
|
|
| 213 | - barf("pipe: %s", strerror(errno));
|
|
| 214 | - }
|
|
| 215 | - |
|
| 216 | - /*
|
|
| 217 | - * Create the thread with all blockable signals blocked, leaving signal
|
|
| 218 | - * handling to the main and/or other threads. This is especially useful in
|
|
| 219 | - * the non-threaded runtime, where applications might expect sigprocmask(2)
|
|
| 220 | - * to effectively block signals.
|
|
| 221 | - */
|
|
| 222 | -#if defined(HAVE_SIGNAL_H)
|
|
| 223 | - sigfillset(&mask);
|
|
| 224 | - sigret = pthread_sigmask(SIG_SETMASK, &mask, &omask);
|
|
| 225 | -#endif
|
|
| 226 | - ret = createAttachedOSThread(&thread, "ghc_ticker", itimer_thread_func, (void*)handle_tick);
|
|
| 227 | -#if defined(HAVE_SIGNAL_H)
|
|
| 228 | - if (sigret == 0)
|
|
| 229 | - pthread_sigmask(SIG_SETMASK, &omask, NULL);
|
|
| 230 | -#endif
|
|
| 231 | - |
|
| 232 | - if (ret != 0) {
|
|
| 233 | - barf("Ticker: Failed to spawn thread: %s", strerror(errno));
|
|
| 234 | - }
|
|
| 235 | -}
|
|
| 236 | - |
|
| 237 | -void
|
|
| 238 | -startTicker(void)
|
|
| 239 | -{
|
|
| 240 | - OS_ACQUIRE_LOCK(&mutex);
|
|
| 241 | - RELAXED_STORE(&stopped, false);
|
|
| 242 | - signalCondition(&start_cond);
|
|
| 243 | - OS_RELEASE_LOCK(&mutex);
|
|
| 244 | -}
|
|
| 245 | - |
|
| 246 | -/* There may be at most one additional tick fired after a call to this */
|
|
| 247 | -void
|
|
| 248 | -stopTicker(void)
|
|
| 249 | -{
|
|
| 250 | - OS_ACQUIRE_LOCK(&mutex);
|
|
| 251 | - RELAXED_STORE(&stopped, true);
|
|
| 252 | - OS_RELEASE_LOCK(&mutex);
|
|
| 253 | -}
|
|
| 254 | - |
|
| 255 | -/* There may be at most one additional tick fired after a call to this */
|
|
| 256 | -void
|
|
| 257 | -exitTicker (bool wait)
|
|
| 258 | -{
|
|
| 259 | - ASSERT(!SEQ_CST_LOAD(&exited));
|
|
| 260 | - SEQ_CST_STORE(&exited, true);
|
|
| 261 | - // ensure that ticker wakes up if stopped
|
|
| 262 | - startTicker();
|
|
| 263 | - |
|
| 264 | - // wait for ticker to terminate if necessary
|
|
| 265 | - if (wait) {
|
|
| 266 | - // write anything to the pipe to trigger poll() in the ticker thread
|
|
| 267 | - if (write(pipefds[1], "stop", 5) < 0) {
|
|
| 268 | - sysErrorBelch("Ticker: Failed to write to pipe: %s", strerror(errno));
|
|
| 269 | - }
|
|
| 270 | - |
|
| 271 | - if (pthread_join(thread, NULL)) {
|
|
| 272 | - sysErrorBelch("Ticker: Failed to join: %s", strerror(errno));
|
|
| 273 | - }
|
|
| 274 | - |
|
| 275 | - // These need to happen AFTER the ticker thread has finished to prevent a race condition
|
|
| 276 | - // where the ticker thread closes the read end of the pipe before we're done writing to it.
|
|
| 277 | - close(pipefds[0]);
|
|
| 278 | - close(pipefds[1]);
|
|
| 279 | - |
|
| 280 | - closeMutex(&mutex);
|
|
| 281 | - closeCondition(&start_cond);
|
|
| 282 | - } else {
|
|
| 283 | - pthread_detach(thread);
|
|
| 284 | - }
|
|
| 285 | -}
|
|
| 286 | - |
|
| 287 | -int
|
|
| 288 | -rtsTimerSignal(void)
|
|
| 289 | -{
|
|
| 290 | - return SIGALRM;
|
|
| 291 | -} |
| ... | ... | @@ -582,11 +582,9 @@ library |
| 582 | 582 | posix/Ticker.c
|
| 583 | 583 | posix/OSMem.c
|
| 584 | 584 | posix/OSThreads.c
|
| 585 | + posix/FdWakeup.c
|
|
| 585 | 586 | posix/Poll.c
|
| 586 | 587 | posix/Select.c
|
| 587 | 588 | posix/Signals.c
|
| 588 | 589 | posix/Timeout.c
|
| 589 | 590 | posix/TTY.c |
| 590 | - -- ticker/*.c
|
|
| 591 | - -- We don't want to compile posix/ticker/*.c, these will be #included
|
|
| 592 | - -- from Ticker.c |
| ... | ... | @@ -1339,7 +1339,7 @@ concurrent_marking: |
| 1339 | 1339 | nonmovingPrintAllocatorCensus(!concurrent);
|
| 1340 | 1340 | #endif
|
| 1341 | 1341 | #if defined(TRACING)
|
| 1342 | - if (RtsFlags.TraceFlags.nonmoving_gc)
|
|
| 1342 | + if (RTS_UNLIKELY(TRACE_nonmoving_gc))
|
|
| 1343 | 1343 | nonmovingTraceAllocatorCensus();
|
| 1344 | 1344 | #endif
|
| 1345 | 1345 |
| 1 | + |
|
| 2 | +-- This test checks that bindings are preserved when configuring the simple
|
|
| 3 | +-- optimizer to not inline bindings with names selected by a predicate.
|
|
| 4 | +--
|
|
| 5 | +-- This feature is important for the LiquidHaskell plugin, which relies on the
|
|
| 6 | +-- simple optimizer to make core programs easier to read, but needs to preserve
|
|
| 7 | +-- bindings that are relevant for verification.
|
|
| 8 | +--
|
|
| 9 | +-- See https://gitlab.haskell.org/ghc/ghc/-/issues/24386 for the full discussion.
|
|
| 10 | +--
|
|
| 11 | + |
|
| 12 | +import Control.Monad
|
|
| 13 | +import Data.List (find)
|
|
| 14 | +import Data.Time (getCurrentTime)
|
|
| 15 | +import GHC
|
|
| 16 | +import GHC.Core
|
|
| 17 | +import GHC.Core.SimpleOpt
|
|
| 18 | +import GHC.Data.StringBuffer
|
|
| 19 | +import GHC.Driver.Config
|
|
| 20 | +import GHC.Driver.DynFlags
|
|
| 21 | +import GHC.Driver.Env.Types
|
|
| 22 | +import GHC.Types.Name
|
|
| 23 | +import GHC.Unit.Module.ModGuts
|
|
| 24 | +import GHC.Unit.Types
|
|
| 25 | +import GHC.Utils.Error
|
|
| 26 | +import GHC.Utils.Outputable
|
|
| 27 | + |
|
| 28 | +import System.Environment (getArgs)
|
|
| 29 | + |
|
| 30 | + |
|
| 31 | +main :: IO ()
|
|
| 32 | +main =
|
|
| 33 | + testLocalBindingsDesugaring
|
|
| 34 | + |
|
| 35 | +testLocalBindingsDesugaring :: IO ()
|
|
| 36 | +testLocalBindingsDesugaring = do
|
|
| 37 | + let inputSource = unlines
|
|
| 38 | + [ "module LocalBindingsDesugaring where"
|
|
| 39 | + , "f :: ()"
|
|
| 40 | + , "f = z"
|
|
| 41 | + , " where"
|
|
| 42 | + , " z = ()"
|
|
| 43 | + ]
|
|
| 44 | + |
|
| 45 | + isExpectedDesugaring p = case findExpr "f" p of
|
|
| 46 | + Just (Let (NonRec b _) _)
|
|
| 47 | + -> isIdNamed "z" b
|
|
| 48 | + _ -> False
|
|
| 49 | + |
|
| 50 | + isIdNamed name v = occNameString (occName v) == name
|
|
| 51 | + |
|
| 52 | + coreProgram <-
|
|
| 53 | + compileToCore
|
|
| 54 | + (not . isIdNamed "z")
|
|
| 55 | + "LocalBindingsDesugaring"
|
|
| 56 | + inputSource
|
|
| 57 | + unless (isExpectedDesugaring coreProgram) $
|
|
| 58 | + fail $ unlines $
|
|
| 59 | + "Unexpected desugaring: No local binding for `z` found in the Core program."
|
|
| 60 | + : map showPprQualified coreProgram
|
|
| 61 | + |
|
| 62 | +-- | Find the Core expression bound to the given name.
|
|
| 63 | +findExpr :: String -> CoreProgram -> Maybe CoreExpr
|
|
| 64 | +findExpr _ [] =
|
|
| 65 | + Nothing
|
|
| 66 | +findExpr name (p:ps) = case p of
|
|
| 67 | + NonRec b e
|
|
| 68 | + | occNameString (occName b) == name
|
|
| 69 | + -> Just e
|
|
| 70 | + Rec binds
|
|
| 71 | + | Just (_, e) <- find (\(b, _e) -> occNameString (occName b) == name) binds
|
|
| 72 | + -> Just e
|
|
| 73 | + _ -> findExpr name ps
|
|
| 74 | + |
|
| 75 | +showPprQualified :: Outputable a => a -> String
|
|
| 76 | +showPprQualified = showSDocQualified . ppr
|
|
| 77 | + |
|
| 78 | +showSDocQualified :: SDoc -> String
|
|
| 79 | +showSDocQualified = renderWithContext ctx
|
|
| 80 | + where
|
|
| 81 | + ctx = defaultSDocContext { sdocStyle = cmdlineParserStyle }
|
|
| 82 | + |
|
| 83 | + |
|
| 84 | + |
|
| 85 | +compileToCore :: (Id -> Bool) -> String -> String -> IO [CoreBind]
|
|
| 86 | +compileToCore keepBindings modName inputSource = do
|
|
| 87 | + [libdir] <- getArgs
|
|
| 88 | + now <- getCurrentTime
|
|
| 89 | + runGhc (Just libdir) $ do
|
|
| 90 | + df1 <- getSessionDynFlags
|
|
| 91 | + GHC.setSessionDynFlags $ df1 { GHC.backend = GHC.bytecodeBackend }
|
|
| 92 | + let target = Target {
|
|
| 93 | + targetId = TargetFile (modName ++ ".hs") Nothing
|
|
| 94 | + , targetUnitId = homeUnitId_ df1
|
|
| 95 | + , targetAllowObjCode = False
|
|
| 96 | + , targetContents = Just (stringToStringBuffer inputSource, now)
|
|
| 97 | + }
|
|
| 98 | + setTargets [target]
|
|
| 99 | + void $ GHC.depanal [] False
|
|
| 100 | + |
|
| 101 | + dsMod <- getModSummary
|
|
| 102 | + (mkModule mainUnit (mkModuleName modName))
|
|
| 103 | + >>= parseModule
|
|
| 104 | + >>= typecheckModule NoTcMPlugins
|
|
| 105 | + >>= desugarModule
|
|
| 106 | + hsc_env <- getSession
|
|
| 107 | + return $ mg_binds $ simpleOptimize keepBindings hsc_env $ dm_core_module dsMod
|
|
| 108 | + |
|
| 109 | +-- Run the simple optimizer
|
|
| 110 | +simpleOptimize :: (Id -> Bool) -> GHC.HscEnv -> ModGuts -> ModGuts
|
|
| 111 | +simpleOptimize keepBindings hsc_env guts@(ModGuts
|
|
| 112 | + { mg_module = mgmod
|
|
| 113 | + , mg_binds = binds
|
|
| 114 | + , mg_rules = rules
|
|
| 115 | + }) =
|
|
| 116 | + let dflags = hsc_dflags hsc_env
|
|
| 117 | + simpl_opts = (initSimpleOpts dflags) { so_inline = keepBindings }
|
|
| 118 | + (binds2, rules2, _occ_anald_binds) =
|
|
| 119 | + simpleOptPgm simpl_opts mgmod binds rules
|
|
| 120 | + in guts
|
|
| 121 | + { mg_binds = binds2
|
|
| 122 | + , mg_rules = rules2
|
|
| 123 | + } |
| ... | ... | @@ -81,3 +81,4 @@ test('T26910', [ extra_run_opts(f'"{config.libdir}"') |
| 81 | 81 | test('TypeMapStringLiteral', normal, compile_and_run, ['-package ghc'])
|
| 82 | 82 | |
| 83 | 83 | test('T25121_status', normal, compile_and_run, ['-package ghc'])
|
| 84 | +test('T24386', [extra_run_opts(f'"{config.libdir}"')], compile_and_run, ['-package ghc']) |