Marge Bot pushed to branch wip/marge_bot_batch_merge_job at Glasgow Haskell Compiler / GHC
Commits:
6ff44d56 by Wen Kokke at 2026-05-06T05:23:13-04:00
rts: Add dynamic trace flags API
This commit adds an API to the RTS (exposed via Rts.h) that allows users to dynamically change the trace flags.
Prior to this commit, users were able to stop and start the profiling and heap profiling timers (via startProfTimer/stopProfTimer and startHeapProfTimer/stopHeapProfTimer).
This extends that functionality to also cover the core event types.
The getTraceFlag/setTraceFlag functions read and write the values of the trace flag cache, which is allocated by Trace.c, rather than modifying the members of RtsFlags.TraceFlags.
This is done under the assumption that the members of RtsFlags should not be modified after RTS initialisation.
Consequently, if the user modifies the trace flags using setTraceFlag, the object returned by getTraceFlags (from base) will not reflect these changes.
The trace flags are not protected by locks of any sort.
Hence, these functions are not thread-safe.
However, the trace flags are not modified by the RTS after initialisation, only read, so the race conditions introduced by one user modifying them are most likely benign.
This PR also puts the trace flag cache in a single global struct, as opposed to a collection of global variables, and changes the types of the individual flags from uint8_t to bool, as these have the same size on both Clang and GCC and are a better semantic match.
Prior to the change to uint8_t, they had type int, see 42c47cd6.
Even with its deprecation in C23, I don't think there should be any issue depending on stdbool.h.
The TRACE_X macros are redefined to access the global struct, with values cast to const bool to ensure they are read-only.
- - - - -
9b66c2e4 by Wen Kokke at 2026-05-06T05:23:13-04:00
rts: Ensure TRACE_X values are used in place of RtsFlags.TraceFlags.X
- - - - -
8f42ce61 by Wen Kokke at 2026-05-06T05:23:13-04:00
rts: Fix nonmoving-GC tracing
The current nonmoving-GC tracing functions were written in a different
style from the other tracing functions. They were directly implemented
as, e.g., a traceConcMarkEnd function that called postConcMarkEnd.
The other tracing functions are implemented as, e.g., traceThreadLabel_,
a function that posts the thread label event, and traceThreadLabel, a
macro that checks whether TRACE_scheduler is set. This commit fixes that
implementation, and ensures that the nonmoving-GC tracing functions only
emit events if nonmoving-GC tracing is enabled.
- - - - -
a16ece7a by Wen Kokke at 2026-05-06T05:23:13-04:00
rts: Add SymI_HasProto for get/setTraceFlag
- - - - -
0ddddde9 by Wen Kokke at 2026-05-06T05:23:13-04:00
rts: Add SymI_HasProto for start/endEventLogging
- - - - -
0b2b3eeb by Wen Kokke at 2026-05-06T05:23:13-04:00
rts: Add changelog entry
- - - - -
be21a344 by Teo Camarasu at 2026-05-06T05:23:14-04:00
interface-stability/base: don't distinguish ws-32
The interface of base is identical when the Word size is 32bits.
Therefore, there is no need to have another file for this case.
So, we delete it.
Step towards: #26752
- - - - -
7 changed files:
- + changelog.d/dynamic-trace-flags
- rts/RtsSymbols.c
- rts/Trace.c
- rts/Trace.h
- rts/include/rts/EventLogWriter.h
- rts/sm/NonMoving.c
- − testsuite/tests/interface-stability/base-exports.stdout-ws-32
Changes:
=====================================
changelog.d/dynamic-trace-flags
=====================================
@@ -0,0 +1,10 @@
+section: compiler
+synopsis: Support dynamic trace flags in RTS
+issues: #27186
+mrs: !15936
+
+description: {
+ The RTS API now exposes the `RUNTIME_TRACE_FLAG` type and
+ the `getTraceFlags` and `setTraceFlags` functions that can be used to
+ change the trace flags at runtime.
+}
=====================================
rts/RtsSymbols.c
=====================================
@@ -540,7 +540,12 @@ extern char **environ;
SymI_HasProto(__word_encodeFloat) \
SymI_HasDataProto(stg_atomicallyzh) \
SymI_HasProto(barf) \
+ SymI_HasProto(startEventLogging) \
+ SymI_HasProto(endEventLogging) \
SymI_HasProto(flushEventLog) \
+ SymI_HasProto(flushEventLog) \
+ SymI_HasProto(getTraceFlag) \
+ SymI_HasProto(setTraceFlag) \
SymI_HasProto(deRefStablePtr) \
SymI_HasProto(debugBelch) \
SymI_HasProto(errorBelch) \
=====================================
rts/Trace.c
=====================================
@@ -29,14 +29,54 @@
#include
#endif
-// events
-uint8_t TRACE_sched;
-uint8_t TRACE_gc;
-uint8_t TRACE_nonmoving_gc;
-uint8_t TRACE_spark_sampled;
-uint8_t TRACE_spark_full;
-uint8_t TRACE_user;
-uint8_t TRACE_cap;
+RUNTIME_TRACE_FLAG_CACHE RuntimeTraceFlagCache = {0};
+
+bool getTraceFlag(RUNTIME_TRACE_FLAG flag) {
+ switch (flag) {
+ case TRACE_SCHEDULER:
+ return RuntimeTraceFlagCache.scheduler;
+ case TRACE_GC:
+ return RuntimeTraceFlagCache.gc;
+ case TRACE_NONMOVING_GC:
+ return RuntimeTraceFlagCache.nonmoving_gc;
+ case TRACE_SPARK_SAMPLED:
+ return RuntimeTraceFlagCache.spark_sampled;
+ case TRACE_SPARK_FULL:
+ return RuntimeTraceFlagCache.spark_full;
+ case TRACE_USER:
+ return RuntimeTraceFlagCache.user;
+ case TRACE_CAP:
+ return RuntimeTraceFlagCache.cap;
+ default:
+ return false;
+ }
+}
+
+void setTraceFlag(RUNTIME_TRACE_FLAG flag, bool value) {
+ switch (flag) {
+ case TRACE_SCHEDULER:
+ RuntimeTraceFlagCache.scheduler = value;
+ break;
+ case TRACE_GC:
+ RuntimeTraceFlagCache.gc = value;
+ break;
+ case TRACE_NONMOVING_GC:
+ RuntimeTraceFlagCache.nonmoving_gc = value;
+ break;
+ case TRACE_SPARK_SAMPLED:
+ RuntimeTraceFlagCache.spark_sampled = value;
+ break;
+ case TRACE_SPARK_FULL:
+ RuntimeTraceFlagCache.spark_full = value;
+ break;
+ case TRACE_USER:
+ RuntimeTraceFlagCache.user = value;
+ break;
+ case TRACE_CAP:
+ RuntimeTraceFlagCache.cap = value;
+ break;
+ }
+}
#if defined(THREADED_RTS)
static Mutex trace_utx;
@@ -51,43 +91,41 @@ static void traceCap_stderr(Capability *cap, char *msg, ...);
--------------------------------------------------------------------------- */
/*
- * Update the TRACE_* globals. Must be called whenever RtsFlags.TraceFlags is
- * modified.
+ * Initialise the runtime trace flags from RtsFlags.TraceFlags.
*/
-static void updateTraceFlagCache (void)
-{
- // -Ds turns on scheduler tracing too
- TRACE_sched =
- RtsFlags.TraceFlags.scheduler ||
- RtsFlags.DebugFlags.scheduler;
-
- // -Dg turns on gc tracing too
- TRACE_gc =
- RtsFlags.TraceFlags.gc ||
- RtsFlags.DebugFlags.gc ||
- RtsFlags.DebugFlags.scheduler;
-
- TRACE_nonmoving_gc =
- RtsFlags.TraceFlags.nonmoving_gc;
-
- TRACE_spark_sampled =
- RtsFlags.TraceFlags.sparks_sampled;
-
- // -Dr turns on full spark tracing
- TRACE_spark_full =
- RtsFlags.TraceFlags.sparks_full ||
- RtsFlags.DebugFlags.sparks;
-
- TRACE_user =
- RtsFlags.TraceFlags.user;
-
- // We trace cap events if we're tracing anything else
- TRACE_cap =
- TRACE_sched ||
- TRACE_gc ||
- TRACE_spark_sampled ||
- TRACE_spark_full ||
- TRACE_user;
+static void updateTraceFlagCache(void) {
+ // -Ds turns on scheduler tracing too
+ RuntimeTraceFlagCache.scheduler =
+ RtsFlags.TraceFlags.scheduler ||
+ RtsFlags.DebugFlags.scheduler;
+
+ // -Dg turns on gc tracing too
+ RuntimeTraceFlagCache.gc =
+ RtsFlags.TraceFlags.gc ||
+ RtsFlags.DebugFlags.gc ||
+ RtsFlags.DebugFlags.scheduler;
+
+ RuntimeTraceFlagCache.nonmoving_gc =
+ RtsFlags.TraceFlags.nonmoving_gc;
+
+ RuntimeTraceFlagCache.spark_sampled =
+ RtsFlags.TraceFlags.sparks_sampled;
+
+ // -Dr turns on full spark tracing
+ RuntimeTraceFlagCache.spark_full =
+ RtsFlags.TraceFlags.sparks_full ||
+ RtsFlags.DebugFlags.sparks;
+
+ RuntimeTraceFlagCache.user =
+ RtsFlags.TraceFlags.user;
+
+ // We trace cap events if we're tracing anything else
+ RuntimeTraceFlagCache.cap =
+ TRACE_sched ||
+ TRACE_gc ||
+ TRACE_spark_sampled ||
+ TRACE_spark_full ||
+ TRACE_user;
}
void initTracing (void)
@@ -880,59 +918,65 @@ void traceThreadLabel_(Capability *cap,
}
}
-void traceConcMarkBegin(void)
+void traceNonmovingGcEvent_ (EventTypeNum tag)
{
- if (eventlog_enabled)
- postEventNoCap(EVENT_CONC_MARK_BEGIN);
+#if defined(DEBUG)
+ if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
+ /* nothing - no string representation for nonmoving GC events */
+ } else
+#endif
+ {
+ /* currently most non-moving GC events are nullary events */
+ postEventNoCap(tag);
+ }
}
-void traceConcMarkEnd(StgWord32 marked_obj_count)
+void traceConcMarkEnd_(StgWord32 marked_obj_count)
{
- if (eventlog_enabled)
+#if defined(DEBUG)
+ if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
+ /* nothing - no string representation for nonmoving GC events */
+ } else
+#endif
+ {
postConcMarkEnd(marked_obj_count);
+ }
}
-void traceConcSyncBegin(void)
-{
- if (eventlog_enabled)
- postEventNoCap(EVENT_CONC_SYNC_BEGIN);
-}
-
-void traceConcSyncEnd(void)
-{
- if (eventlog_enabled)
- postEventNoCap(EVENT_CONC_SYNC_END);
-}
-
-void traceConcSweepBegin(void)
-{
- if (eventlog_enabled)
- postEventNoCap(EVENT_CONC_SWEEP_BEGIN);
-}
-
-void traceConcSweepEnd(void)
-{
- if (eventlog_enabled)
- postEventNoCap(EVENT_CONC_SWEEP_END);
-}
-
-void traceConcUpdRemSetFlush(Capability *cap)
+void traceConcUpdRemSetFlush_(Capability *cap)
{
- if (eventlog_enabled)
+#if defined(DEBUG)
+ if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
+ /* nothing - no string representation for nonmoving GC events */
+ } else
+#endif
+ {
postConcUpdRemSetFlush(cap);
+ }
}
-void traceNonmovingHeapCensus(uint16_t blk_size,
- const struct NonmovingAllocCensus *census)
+void traceNonmovingHeapCensus_(uint16_t blk_size, const struct NonmovingAllocCensus *census)
{
- if (eventlog_enabled && TRACE_nonmoving_gc)
+#if defined(DEBUG)
+ if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
+ /* nothing - no string representation for nonmoving GC events */
+ } else
+#endif
+ {
postNonmovingHeapCensus(blk_size, census);
+ }
}
-void traceNonmovingPrunedSegments(uint32_t pruned_segments, uint32_t free_segments)
+void traceNonmovingPrunedSegments_(uint32_t pruned_segments, uint32_t free_segments)
{
- if (eventlog_enabled && TRACE_nonmoving_gc)
+#if defined(DEBUG)
+ if (RtsFlags.TraceFlags.tracing == TRACE_STDERR) {
+ /* nothing - no string representation for nonmoving GC events */
+ } else
+#endif
+ {
postNonmovingPrunedSegments(pruned_segments, free_segments);
+ }
}
void traceThreadStatus_ (StgTSO *tso USED_IF_DEBUG)
=====================================
rts/Trace.h
=====================================
@@ -70,16 +70,35 @@ enum CapsetType { CapsetTypeCustom = CAPSET_TYPE_CUSTOM,
#define DEBUG_continuation RtsFlags.DebugFlags.continuation
#define DEBUG_iomanager RtsFlags.DebugFlags.iomanager
-// Event-enabled flags
-// These semantically booleans but we use a dense packing to minimize their
-// cache impact.
-extern uint8_t TRACE_sched;
-extern uint8_t TRACE_gc;
-extern uint8_t TRACE_nonmoving_gc;
-extern uint8_t TRACE_spark_sampled;
-extern uint8_t TRACE_spark_full;
-extern uint8_t TRACE_cap;
-/* extern uint8_t TRACE_user; */ // only used in Trace.c
+// These trace flags are shorthand for the members of the RuntimeTraceFlagCache
+// struct. Within the RTS, these should be treated as read-only variables.
+#define TRACE_sched ((const bool)RuntimeTraceFlagCache.scheduler)
+#define TRACE_gc ((const bool)RuntimeTraceFlagCache.gc)
+#define TRACE_nonmoving_gc ((const bool)RuntimeTraceFlagCache.nonmoving_gc)
+#define TRACE_spark_sampled ((const bool)RuntimeTraceFlagCache.spark_sampled)
+#define TRACE_spark_full ((const bool)RuntimeTraceFlagCache.spark_full)
+#define TRACE_user ((const bool)RuntimeTraceFlagCache.user)
+#define TRACE_cap ((const bool)RuntimeTraceFlagCache.cap)
+
+/*
+ * Runtime trace flags.
+ */
+typedef struct {
+ bool scheduler;
+ bool gc;
+ bool nonmoving_gc;
+ bool spark_sampled;
+ bool spark_full;
+ bool user;
+ bool cap;
+} RUNTIME_TRACE_FLAG_CACHE;
+
+/*
+ * These flags should be used to determine whether or not some value should
+ * be traced at runtime, rather than the values in RtsFlags. These flags can
+ * be modified at runtime using setTraceFlag in `rts/EventLogWriter.h`.
+ */
+extern RUNTIME_TRACE_FLAG_CACHE RuntimeTraceFlagCache;
// -----------------------------------------------------------------------------
// Posting events
@@ -136,6 +155,52 @@ void traceGcEvent_ (Capability *cap, EventTypeNum tag);
void traceGcEventAtT_ (Capability *cap, StgWord64 ts, EventTypeNum tag);
+/*
+ * Record a nonmoving GC event.
+ */
+#define traceConcMarkBegin() \
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
+ traceNonmovingGcEvent_(EVENT_CONC_MARK_BEGIN); \
+ }
+#define traceConcMarkEnd(marked_obj_count) \
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
+ traceConcMarkEnd_(marked_obj_count); \
+ }
+#define traceConcSyncBegin() \
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
+ traceNonmovingGcEvent_(EVENT_CONC_SYNC_BEGIN); \
+ }
+#define traceConcSyncEnd() \
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
+ traceNonmovingGcEvent_(EVENT_CONC_SYNC_END); \
+ }
+#define traceConcSweepBegin() \
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
+ traceNonmovingGcEvent_(EVENT_CONC_SWEEP_BEGIN); \
+ }
+#define traceConcSweepEnd() \
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
+ traceNonmovingGcEvent_(EVENT_CONC_SWEEP_END); \
+ }
+#define traceConcUpdRemSetFlush(cap) \
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
+ traceConcUpdRemSetFlush_(cap); \
+ }
+#define traceNonmovingHeapCensus(blk_size, census) \
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
+ traceNonmovingHeapCensus_(blk_size, census); \
+ }
+#define traceNonmovingPrunedSegments(pruned_segments, free_segments) \
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc)) { \
+ traceNonmovingPrunedSegments_(pruned_segments, free_segments); \
+ }
+
+void traceNonmovingGcEvent_ (EventTypeNum tag);
+void traceConcMarkEnd_(StgWord32 marked_obj_count);
+void traceConcUpdRemSetFlush_(Capability *cap);
+void traceNonmovingHeapCensus_(uint16_t blk_size, const struct NonmovingAllocCensus *census);
+void traceNonmovingPrunedSegments_(uint32_t pruned_segments, uint32_t free_segments);
+
/*
* Record a heap event
*/
@@ -321,17 +386,6 @@ void traceProfSampleCostCentre(Capability *cap,
void traceProfBegin(void);
#endif /* PROFILING */
-void traceConcMarkBegin(void);
-void traceConcMarkEnd(StgWord32 marked_obj_count);
-void traceConcSyncBegin(void);
-void traceConcSyncEnd(void);
-void traceConcSweepBegin(void);
-void traceConcSweepEnd(void);
-void traceConcUpdRemSetFlush(Capability *cap);
-void traceNonmovingHeapCensus(uint16_t blk_size,
- const struct NonmovingAllocCensus *census);
-void traceNonmovingPrunedSegments(uint32_t pruned_segments, uint32_t free_segments);
-
void traceIPE(const InfoProvEnt *ipe);
void flushTrace(void);
@@ -384,6 +438,7 @@ void flushTrace(void);
#define traceConcSweepEnd() /* nothing */
#define traceConcUpdRemSetFlush(cap) /* nothing */
#define traceNonmovingHeapCensus(blk_size, census) /* nothing */
+#define traceNonmovingPrunedSegments(pruned_segments, free_segments) /* nothing */
#define flushTrace() /* nothing */
=====================================
rts/include/rts/EventLogWriter.h
=====================================
@@ -78,3 +78,34 @@ void endEventLogging(void);
* Flush the eventlog. cap can be NULL if one is not held.
*/
void flushEventLog(Capability **cap);
+
+/*
+ * An enumeration for the runtime trace flags.
+ */
+typedef enum {
+ TRACE_SCHEDULER,
+ TRACE_GC,
+ TRACE_NONMOVING_GC,
+ TRACE_SPARK_SAMPLED,
+ TRACE_SPARK_FULL,
+ TRACE_USER,
+ TRACE_CAP,
+} RUNTIME_TRACE_FLAG;
+
+/*
+ * Get the value of the given runtime trace flag.
+ *
+ * Warning: The trace flag cache is not thread-safe. After initialisation, the
+ * RTS never writes to these values, but concurrently using getTraceFlag and
+ * setTraceFlag for the same flag is a race condition.
+ */
+bool getTraceFlag(RUNTIME_TRACE_FLAG flag);
+
+/*
+ * Set the value of the given runtime trace flag.
+ *
+ * Warning: The trace flag cache is not thread-safe. After initialisation, the
+ * RTS never writes to these values. However, inconsistent reads may lead to
+ * incorrect tracing for a short time after setting a trace flag.
+ */
+void setTraceFlag(RUNTIME_TRACE_FLAG flag, bool value);
=====================================
rts/sm/NonMoving.c
=====================================
@@ -1339,7 +1339,7 @@ concurrent_marking:
nonmovingPrintAllocatorCensus(!concurrent);
#endif
#if defined(TRACING)
- if (RtsFlags.TraceFlags.nonmoving_gc)
+ if (RTS_UNLIKELY(TRACE_nonmoving_gc))
nonmovingTraceAllocatorCensus();
#endif
=====================================
testsuite/tests/interface-stability/base-exports.stdout-ws-32 deleted
=====================================
The diff for this file was not included because it is too large.
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/d3ab500d9a3dc2ed07820a59ef5cb4b...
--
View it on GitLab: https://gitlab.haskell.org/ghc/ghc/-/compare/d3ab500d9a3dc2ed07820a59ef5cb4b...
You're receiving this email because of your account on gitlab.haskell.org.