The macro fast paths only reference GetProfiler from -O0 code, direct
calls, and ON_DEMAND builds: the single GetProfiler() call on the macro
path sits behind the constexpr-dead callstack guard and is folded away
at -O1+, so Release builds carried no config fingerprint and mismatched
clients linked silently.
GetToken is the hot-path binding of every queueing macro and is
referenced at all optimization levels; mangle it the same way so
mismatches fail at link time in Release builds too.
The gpu backends each used a bare 255 as the sentinel for a
not-yet-initialized context id. Define InvalidGpuContextId (-1) in
TracyQueue.hpp and replace the scattered 255s and their asserts
with it.
Id exhaustion is handled by an error message and an assert in the
new NextGpuContextId() getter. Non-assert builds continue execution,
at which point they are no longer valid. Handling this code path
is out of scope here. Various attempts at handling the exhaustion
problem have been otherwise purged from the API implementations.
The decoder recovered offset-encoded 16-bit string lengths into a
uint16_t, so sz += ProtocolOffset8Bit truncated lengths in [65536,
65791] back into [0, 255] in release builds, desynchronizing the
stream. Read the wire value into uint16_t sz16 and recover into a
uint32_t sz, relying on automatic promotion for the addition.
Align the client asserts with the encoder's actual capacity
(ProtocolOffset8Bit + uint16 max).
gcc breaks on this:
error: function ‘static uint32_t tracy::Profiler::SectionEnter(const char*, ...)’ can never be inlined because it uses variable argument lists
Bug (High Severity): Wrong queue type in MemDiscardCallstack
In the callstack path of MemDiscardCallstack, the wrong queue type is
sent:
SendMemDiscard( QueueType::MemDiscard, thread, name );
Every other callstack variant correctly uses its callstack queue type
(MemAllocCallstack, MemFreeCallstack, etc.), but this one uses the
non-callstack type. The SendMemDiscard assertion at line 1026 confirms
MemDiscardCallstack is a valid value.
Impact: The callstack captured by SendCallstackSerial() will be orphaned.
The server processes the event via the non-callstack handler, leaving the
callstack serial data unconsumed, which desynchronizes the serial queue
and corrupts all subsequent events.
A zone emitted from a shared object initializer runs before the
executable's constructors, so its timestamp precedes s_initTime, which
the server uses as the trace epoch (baseTime). Such a zone converts to
negative trace time and its end no longer satisfies IsEndValid(), which
excludes it from statistics reconstruction and makes it render as
never-ending.
Record the current time when a producer token is created before
s_initTime is constructed and use it as the init time, ensuring no event
timestamp precedes the trace epoch.
ELF init_priority only orders constructors within a single module. All of
a shared object's initializers run before any of the executable's, so an
instrumented dependency .so emitting a zone from its static initializer
creates the main thread producer token against the zero-initialized
s_queue. The queue constructor then resets the producer list, orphaning
that producer: every zone emitted on the main thread from that point on
is enqueued into blocks no consumer ever iterates and silently lost,
while sampling (worker thread producer) keeps working.
Re-link such a producer right after the queue is constructed. In the
common case, where nothing was emitted during shared object init, this
merely constructs the main thread token eagerly.