From b44343df9d8ecddaa5941971f7e2824241d811fe Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 16:00:04 +0200 Subject: [PATCH 01/13] Clobber and sclock fixes --- .../docs/markers/SPEC.md | 99 ++++++++- .../rocprof_trace_decoder/cxx/funcmap.hpp | 90 ++++++-- .../rocprof-trace-decoder/markers/README.md | 25 ++- .../markers/SQTTAddressTrace.cpp | 128 +++-------- .../markers/SQTTFunctions.cpp | 9 +- .../markers/SQTTMarkers.cpp | 90 +++++++- .../markers/SQTTPass.cpp | 3 + .../rocprof-trace-decoder/markers/SQTTPass.h | 5 + .../rocprof-trace-decoder/python/att_tool.py | 10 + .../python/rocprof_trace_decoder/att.py | 140 +++++++++++- .../python/rocprof_trace_decoder/codegen.py | 173 +++++++++++++++ .../rocprof-trace-decoder/test/CMakeLists.txt | 23 ++ .../test/att_tool_test.py | 205 ++++++++++++++++++ .../test/codegen_test.py | 74 +++++++ .../test/markers/run_tests.py | 156 +++++++++---- .../test/unit/funcmap_test.cpp | 114 +++++++++- .../test/unit/markers_test.cpp | 114 +++++++++- 17 files changed, 1270 insertions(+), 188 deletions(-) create mode 100644 projects/rocprof-trace-decoder/test/att_tool_test.py create mode 100644 projects/rocprof-trace-decoder/test/codegen_test.py diff --git a/projects/rocprof-trace-decoder/docs/markers/SPEC.md b/projects/rocprof-trace-decoder/docs/markers/SPEC.md index 3f8adee242a6..b54270f766bd 100644 --- a/projects/rocprof-trace-decoder/docs/markers/SPEC.md +++ b/projects/rocprof-trace-decoder/docs/markers/SPEC.md @@ -85,7 +85,9 @@ Where `flags` is the OR of applicable bits: `EXIT_PREV=1`, `ENTER=2`. - If `encoded <= 0xFF`, the target supports it, and shader-clock packing is inactive: emit `s_ttracedata_imm` (8-bit immediate, no m0 setup, ~1 cycle faster) -- Otherwise: emit `s_ttracedata` (32-bit value loaded into m0) +- Otherwise: emit `s_ttracedata` through M0. On gfx10+, the pass explicitly + emits `s_mov_b32 m0, ` + `s_nop 0` + `s_ttracedata`; gfx9's backend + already provides the required hazard spacing for ordinary traces. With the 2-bit encoding, IDs 1-63 fit in `s_ttracedata_imm` (previously limited to IDs 1-15 with 4-bit flags). @@ -108,10 +110,43 @@ id = (val >> 2) & ((1 << (30 - clock_bits)) - 1) clock = val >> (32 - clock_bits) ``` +Only headers created by the pass use this packed form. Numeric point and enter +markers retain their legacy values so arbitrary user shaderdata is not +reinterpreted as a packed header. Bare exits use the packed form too: their +value is intrinsically indistinguishable from a pass-generated exit. +`att_tool.py` restores packed headers to the legacy form by default; +`--no-decode-markers` preserves the raw value. + The clock field is the shader clock sampled with `s_getreg`, not the realtime clock associated with `s_sendmsg`. The funcmap records the active layout with `M:shader_clock_bits=N;shader_clock_shift=S`. When `SQTT_SHADER_CLOCK_BITS=0`, -gfx12 uses the legacy marker layout and may use `s_ttracedata_imm`. +gfx12 uses the no-clock full-ID layout and may use `s_ttracedata_imm`. +A nonzero `shader_clock_bits` value requires `shader_clock_shift`; a malformed +packed row uses the no-clock full-ID layout. + +The sampled window lets a decoder compensate for the variable delay between +the trace instruction issuing and its shaderdata record appearing in SQTT. +For each **marker header** (never an `R:`-declared raw payload), restore the +sampled field to its source positions and calculate the delay modulo the +source-clock window: + +```text +arrival_window = uint32_t(record_time) & shader_clock_source_mask +sampled_window = packed_clock_field << shader_clock_shift +delay = (arrival_window - sampled_window) & shader_clock_source_mask +``` + +Only a relative delay is knowable from this truncated clock. Keep the smallest +observed delay for each shader-engine clock domain and compatible layout as the +reference, then shift every header earlier by `delay - minimum_delay`. This +preserves that stream's unknown common time origin while removing the extra +delivery latency. The actual delivery delay must be shorter than the sampled +clock window (the default 12-bit field at shift 4 has a 65,536-cycle window), +otherwise the modulo value is ambiguous. + +`att_tool.py` applies this correction while writing JSON by default; +`--no-decode-markers` preserves the packed values and arrival timestamps. It +rewrites each corrected header to the legacy `(id << 2) | flags` form. ### Semantic rules @@ -146,6 +181,44 @@ id = val >> 2 # or masked to 30 - shader_clock_bits when M: is present The marker type (function, user scope, point event) is resolved from the funcmap, not from encoding bits. +### C++ decoder marker encoding + +`` exposes the parsed funcmap and +its `MarkerEncoding`: + +```cpp +const auto& encoding = codeobjs.getFuncmap(codeobj_id).marker_encoding; +const auto marker = codeobjs.decodeMarkerValue(codeobj_id, raw_shaderdata_value); + +uint32_t id = marker.id; +uint32_t marker_id_mask = encoding.marker_id_mask(); +uint32_t packed_clock_mask = encoding.packed_shader_clock_mask(); +uint32_t source_clock_mask = encoding.shader_clock_source_mask(); + +// The sampled field is right-aligned. +uint32_t sampled_lo = 0; +if (encoding.has_shader_clock() && encoding.is_valid()) + sampled_lo = encoding.decode_shader_clock(raw_shaderdata_value) << encoding.shader_clock_shift; +``` + +`marker_id_mask()` and `packed_shader_clock_mask()` apply to the raw 32-bit +shaderdata value. `shader_clock_source_mask()` describes the matching low-word +window in `HW_REG_SHADER_CYCLES_LO` for timestamp comparison. +`decode_shader_clock()` is meaningful only after the record has been identified +as a packed header. + +`decode_marker_value(raw, funcmap)` (and the `CodeobjMap::decodeMarkerValue()` +wrapper) uses the packed layout for IDs known to the funcmap and for bare +exits; unregistered numeric point/enter values otherwise remain legacy. A +numeric value whose low packed ID collides with a registered ID is inherently +ambiguous from the 32-bit record alone. Call +`decode_marker_value(raw, encoding)` directly when the record is known to be a +packed header. + +With no `M:` row, `MarkerEncoding` uses the no-clock full-ID layout: +`marker_id_mask()` is `0xFFFFFFFC` and the two shader-clock masks and decoded +sampled field are zero. + --- ## ID Allocation @@ -493,6 +566,23 @@ M:shader_clock_bits=12;shader_clock_shift=4 -- gfx12 marker header clock pack The section is added to `llvm.used` to prevent linker stripping. +### `code.json` marker metadata + +For a packed code object, `att_tool.py` adds `sqtt_funcmap` data to +`code.json` using the established six-column row shape. Two additive top-level +tables carry metadata that cannot be represented in those rows: + +```json +"sqtt_funcmap_layout": [[codeobj_id, shader_clock_bits, shader_clock_shift]], +"sqtt_funcmap_payloads": [[codeobj_id, marker_id, extra_payload_count]] +``` + +`sqtt_funcmap_layout` contains rows only for code objects with shader-clock +packing; `sqtt_funcmap_payloads` contains their nonzero payload counts. Readers +that do not know these optional tables can continue using the unchanged +`sqtt_funcmap` rows. For a packed row, an absent payload count means zero; +legacy `code.json` output is unchanged. + ### Extracting the funcmap **Method 1: `sqtt_decode_funcmap.py` (recommended)** @@ -870,9 +960,8 @@ For each qualifying memory operation, the pass emits (before the op): 1. Allocate a unique ID from `NextEventID++` 2. Record the ID, kind name, and source location in `AddrTraceEntries` 3. Header point marker via `emitBareTrace()` with the unique ID -4. EXEC mask via inline asm: `s_mov_b32 $0, exec_lo` / `exec_hi` - (on gfx9, the exec read and `s_ttracedata` are fused into a single - inline asm block to satisfy the s_nop hazard requirement) +4. EXEC mask via inline asm: `s_mov_b32 m0, exec_lo` / `exec_hi`, then + `s_nop 0` and `s_ttracedata`. 5. For memory/LDS/atomics: `ptrtoint` of the pointer operand (i64 for memory, i32 for LDS) For buffers: extract rsrc_lo/hi, soffset as scalar values For permutes: extract the index operand (arg 0 of the intrinsic) diff --git a/projects/rocprof-trace-decoder/include/rocprof_trace_decoder/cxx/funcmap.hpp b/projects/rocprof-trace-decoder/include/rocprof_trace_decoder/cxx/funcmap.hpp index 6817c33d86f0..28ba770eb29b 100644 --- a/projects/rocprof-trace-decoder/include/rocprof_trace_decoder/cxx/funcmap.hpp +++ b/projects/rocprof-trace-decoder/include/rocprof_trace_decoder/cxx/funcmap.hpp @@ -106,16 +106,65 @@ struct FuncmapEntry uint32_t id{0}; // 0 for Kernel rows (no ID) std::string name{}; std::string source_loc{}; // empty if absent + uint64_t vaddr{0}; // resolved by CodeobjDecoderComponent; 0 if unresolved uint32_t extra_payload_count{0}; // following shaderdata records owned by this marker header - uint64_t vaddr{0}; // resolved by CodeobjDecoderComponent; 0 if unresolved }; +namespace detail +{ +constexpr uint32_t bit_mask(unsigned bits) noexcept +{ + return bits >= 32 ? 0xFFFFFFFFu : ((bits == 0) ? 0u : ((uint32_t(1) << bits) - 1u)); +} +} // namespace detail + struct MarkerEncoding { - uint32_t shader_clock_bits{0}; // 0 means legacy encoding + uint32_t shader_clock_bits{0}; // 0 means no packed shader-clock field uint32_t shader_clock_shift{0}; // source bit offset in shader_cycles_lo + // The instrumentation pass only emits layouts that retain at least one + // marker-ID bit and whose shader-clock window fits in a 32-bit source + // clock. + constexpr bool is_valid() const noexcept + { + return shader_clock_bits == 0 || + (shader_clock_bits <= 29 && shader_clock_shift < 32 && + shader_clock_bits <= 32 - shader_clock_shift); + } + constexpr bool has_shader_clock() const noexcept { return shader_clock_bits != 0; } + + /// Extract the right-aligned sampled shader-clock field from a raw marker + /// word. Malformed or legacy layouts return zero. + constexpr uint32_t decode_shader_clock(uint32_t raw) const noexcept + { + return (!has_shader_clock() || !is_valid()) + ? 0u + : (raw >> (32u - shader_clock_bits)) & detail::bit_mask(shader_clock_bits); + } + + /// Raw marker-word bits that contain the ID. The no-clock layout returns + /// 0xFFFFFFFC; malformed packed metadata falls back to that safe layout. + constexpr uint32_t marker_id_mask() const noexcept + { + return (!has_shader_clock() || !is_valid()) ? 0xFFFFFFFCu + : detail::bit_mask(30u - shader_clock_bits) << 2u; + } + + /// Raw marker-word bits occupied by the right-aligned sampled clock. + constexpr uint32_t packed_shader_clock_mask() const noexcept + { + return (!has_shader_clock() || !is_valid()) ? 0u + : detail::bit_mask(shader_clock_bits) << (32u - shader_clock_bits); + } + + /// Corresponding bit window in HW_REG_SHADER_CYCLES_LO. + constexpr uint32_t shader_clock_source_mask() const noexcept + { + return (!has_shader_clock() || !is_valid()) ? 0u + : detail::bit_mask(shader_clock_bits) << shader_clock_shift; + } }; struct FuncmapDiagnostic @@ -137,8 +186,8 @@ struct Funcmap std::vector entries{}; // owns rows, stable insertion order std::unordered_map by_id{}; // ID -> entry; last-writer-wins on dup uint32_t wave_size{0}; // 0 if no `W:` row - MarkerEncoding marker_encoding{}; std::vector diagnostics{}; // collected during parse / extraction + MarkerEncoding marker_encoding{}; // Returns the entry for `marker_id` (refcount bump, no string copy), or // nullptr if absent. @@ -150,7 +199,6 @@ struct MarkerValue uint32_t id; bool is_enter; bool exit_prev; - uint32_t shader_clock; }; // Parse the `.sqtt_funcmap` ASCII blob. Diagnostics go into @@ -185,31 +233,28 @@ inline Funcmap::EntryPtr Funcmap::find(uint32_t marker_id) const constexpr MarkerValue decode_marker_value(uint32_t v) noexcept { - return MarkerValue{v >> 2, bool((v >> 1) & 1u), bool(v & 1u), 0}; + return MarkerValue{v >> 2, bool((v >> 1) & 1u), bool(v & 1u)}; } -namespace detail -{ -constexpr uint32_t bit_mask(unsigned bits) noexcept -{ - return bits >= 32 ? 0xFFFFFFFFu : ((bits == 0) ? 0u : ((uint32_t(1) << bits) - 1u)); -} -} // namespace detail - constexpr MarkerValue decode_marker_value(uint32_t v, MarkerEncoding encoding) noexcept { - if (!encoding.has_shader_clock()) return decode_marker_value(v); + if (!encoding.has_shader_clock() || !encoding.is_valid()) return decode_marker_value(v); - uint32_t clock_bits = encoding.shader_clock_bits; - uint32_t id_bits = (clock_bits >= 30) ? 0 : (30 - clock_bits); - uint32_t clock = (clock_bits >= 32) ? v : ((v >> (32 - clock_bits)) & detail::bit_mask(clock_bits)); + uint32_t id_bits = 30 - encoding.shader_clock_bits; uint32_t id = (v >> 2) & detail::bit_mask(id_bits); - return MarkerValue{id, bool((v >> 1) & 1u), bool(v & 1u), clock}; + return MarkerValue{id, bool((v >> 1) & 1u), bool(v & 1u)}; } inline MarkerValue decode_marker_value(uint32_t v, const Funcmap& funcmap) noexcept { - return decode_marker_value(v, funcmap.marker_encoding); + MarkerValue marker = decode_marker_value(v, funcmap.marker_encoding); + if (!funcmap.marker_encoding.has_shader_clock() || !funcmap.marker_encoding.is_valid()) return marker; + + // Numeric point/enter markers intentionally retain their legacy values. + // A funcmap identifies the pass-generated headers that actually use the + // packed layout; a bare exit has no ID but is always packed. + if (funcmap.find(marker.id) || (marker.id == 0 && marker.exit_prev && !marker.is_enter)) return marker; + return decode_marker_value(v); } namespace detail @@ -363,6 +408,7 @@ inline Funcmap parse_funcmap_section(std::string_view blob, bool silent) { MarkerEncoding encoding{}; bool saw_clock_bits = false; + bool saw_clock_shift = false; bool ok = true; size_t attr_pos = 0; while (attr_pos <= payload.size()) @@ -391,13 +437,17 @@ inline Funcmap parse_funcmap_section(std::string_view blob, bool silent) if (!detail::parse_u32(value, parsed)) ok = false; else + { encoding.shader_clock_shift = parsed; + saw_clock_shift = true; + } } } if (attr_end >= payload.size()) break; attr_pos = attr_end + 1; } - if (!ok || (saw_clock_bits && encoding.shader_clock_bits > 29) || + if (!ok || (saw_clock_bits && encoding.shader_clock_bits > 0 && !saw_clock_shift) || + (saw_clock_bits && encoding.shader_clock_bits > 29) || (saw_clock_bits && encoding.shader_clock_shift > 32) || (saw_clock_bits && encoding.shader_clock_bits > 32 - encoding.shader_clock_shift)) { diff --git a/projects/rocprof-trace-decoder/markers/README.md b/projects/rocprof-trace-decoder/markers/README.md index 4125133516f5..eca447dc54c2 100644 --- a/projects/rocprof-trace-decoder/markers/README.md +++ b/projects/rocprof-trace-decoder/markers/README.md @@ -445,6 +445,11 @@ Marker values are packed into 2 flag bits so that small IDs can use the faster packing is inactive. Larger IDs fall back to `s_ttracedata` (32-bit m0, all targets). +For full M0-based traces, gfx10+ explicitly emits `s_mov_b32 m0`, `s_nop 0`, +then `s_ttracedata`; gfx9's backend already provides the required hazard +spacing for ordinary traces. Address EXEC tracing uses the explicit sequence +on every target. + ``` Both instructions share the same bit layout: @@ -467,8 +472,26 @@ On gfx12+, the default is to pack 12 shader-clock bits from `s_getreg_b32 hwreg(HW_REG_SHADER_CYCLES_LO, 4, 12)` into marker bits 31-20, leaving 18 marker ID bits plus the two low flags. Configure this with `SQTT_SHADER_CLOCK_BITS` and `SQTT_SHADER_CLOCK_SHIFT`; set -`SQTT_SHADER_CLOCK_BITS=0` to use the legacy layout on gfx12. The funcmap emits +`SQTT_SHADER_CLOCK_BITS=0` to use the no-clock full-ID layout on gfx12. The funcmap emits `M:shader_clock_bits=N;shader_clock_shift=S` whenever clock packing is active. +Numeric point and enter markers keep their legacy values; bare exits use the +packed form because their value is indistinguishable from a generated exit. + +The packed clock is a truncated sample at instruction issue. Compare it with +the record timestamp modulo its source-clock window and subtract only the +excess over the smallest delay for that shader engine. Apply this only to +headers, never `R:` payloads. `Funcmap::marker_encoding` exposes +`marker_id_mask()`, `packed_shader_clock_mask()`, and +`shader_clock_source_mask()`; without `M:` metadata those describe the legacy +full-width ID layout. + +`att_tool.py` applies that correction by default and masks each recognized +header to its ordinary legacy-form marker value for existing viewers. Use +`--no-decode-markers` to preserve packed values and arrival timestamps. + +For packed code objects, `att_tool.py` adds six-column `sqtt_funcmap` rows and +packed-layout/payload tables to `code.json`. Readers that do not know them +continue using the original JSON; legacy `code.json` output is unchanged. The marker type (function, user, barrier, memory) is determined by looking up the ID in the `.sqtt_funcmap` section, not from encoding bits. diff --git a/projects/rocprof-trace-decoder/markers/SQTTAddressTrace.cpp b/projects/rocprof-trace-decoder/markers/SQTTAddressTrace.cpp index 277903dc0fd7..91ae05ef7186 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTAddressTrace.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTAddressTrace.cpp @@ -28,6 +28,28 @@ using namespace llvm; +namespace +{ + +// Keep the explicit EXEC trace sequence together on every target. +void emitExecMaskTrace(IRBuilder<>& B, LLVMContext& Ctx, bool high) +{ + const char* Asm = high ? "s_mov_b32 m0, exec_hi\n" + "s_nop 0\n" + "s_ttracedata" + : "s_mov_b32 m0, exec_lo\n" + "s_nop 0\n" + "s_ttracedata"; + // Model the M0 definition as a fixed output. `m0` is reserved on AMDGPU, + // so a `~{m0}` clobber is diagnosed as undefined behavior by LLVM. + InlineAsm* trace = InlineAsm::get( + FunctionType::get(Type::getInt32Ty(Ctx), false), Asm, "={m0}", /*hasSideEffects=*/true + ); + B.CreateCall(trace); +} + +} // namespace + SQTTInstrumentPass::AddrTraceKind SQTTInstrumentPass::classifyAddrTraceOp( Instruction* I, bool traceMemory, bool traceLDS ) @@ -168,42 +190,8 @@ void SQTTInstrumentPass::emitAddressTrace( // 3. EXEC mask unsigned waveSize = getWaveSize(gen); - // gfx9: the ISA spec requires s_nop 0 between s_mov_b32 m0 and - // s_ttracedata. We can't insert a nop between the backend-generated - // s_mov_b32 m0 and s_ttracedata pair, so for exec reads we emit the - // entire sequence as a single inline asm block. - if (gen == GfxGen::GFX9) - { - InlineAsm* traceExecLo = InlineAsm::get( - FunctionType::get(Type::getVoidTy(Ctx), false), - "s_mov_b32 m0, exec_lo\n" - "s_nop 0\n" - "s_ttracedata", - "", - /*hasSideEffects=*/true - ); - B.CreateCall(traceExecLo); - - InlineAsm* traceExecHi = InlineAsm::get( - FunctionType::get(Type::getVoidTy(Ctx), false), - "s_mov_b32 m0, exec_hi\n" - "s_nop 0\n" - "s_ttracedata", - "", - /*hasSideEffects=*/true - ); - B.CreateCall(traceExecHi); - } - else - { - InlineAsm* readExecLo = - InlineAsm::get(FunctionType::get(I32, false), "s_mov_b32 $0, exec_lo", "=s", /*hasSideEffects=*/true); - emitBareTraceValue(B, B.CreateCall(readExecLo), M, gen); - - InlineAsm* readExecHi = - InlineAsm::get(FunctionType::get(I32, false), "s_mov_b32 $0, exec_hi", "=s", /*hasSideEffects=*/true); - emitBareTraceValue(B, B.CreateCall(readExecHi), M, gen); - } + emitExecMaskTrace(B, Ctx, /*high=*/false); + emitExecMaskTrace(B, Ctx, /*high=*/true); // 4. Readlane loop Function* ReadLane = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_readlane, {I32}); @@ -424,38 +412,8 @@ void SQTTInstrumentPass::emitBufferTrace(IRBuilder<>& B, CallInst* bufOp, uint32 // 2. EXEC mask (same as memory/LDS traces) unsigned waveSize = getWaveSize(gen); - if (gen == GfxGen::GFX9) - { - InlineAsm* traceExecLo = InlineAsm::get( - FunctionType::get(Type::getVoidTy(Ctx), false), - "s_mov_b32 m0, exec_lo\n" - "s_nop 0\n" - "s_ttracedata", - "", - /*hasSideEffects=*/true - ); - B.CreateCall(traceExecLo); - - InlineAsm* traceExecHi = InlineAsm::get( - FunctionType::get(Type::getVoidTy(Ctx), false), - "s_mov_b32 m0, exec_hi\n" - "s_nop 0\n" - "s_ttracedata", - "", - /*hasSideEffects=*/true - ); - B.CreateCall(traceExecHi); - } - else - { - InlineAsm* readExecLo = - InlineAsm::get(FunctionType::get(I32, false), "s_mov_b32 $0, exec_lo", "=s", /*hasSideEffects=*/true); - emitBareTraceValue(B, B.CreateCall(readExecLo), M, gen); - - InlineAsm* readExecHi = - InlineAsm::get(FunctionType::get(I32, false), "s_mov_b32 $0, exec_hi", "=s", /*hasSideEffects=*/true); - emitBareTraceValue(B, B.CreateCall(readExecHi), M, gen); - } + emitExecMaskTrace(B, Ctx, /*high=*/false); + emitExecMaskTrace(B, Ctx, /*high=*/true); // 3. rsrc base (lo/hi words) Value* rsrc = ops.Rsrc; @@ -556,38 +514,8 @@ void SQTTInstrumentPass::emitPermuteTrace( // 2. EXEC mask unsigned waveSize = getWaveSize(gen); - if (gen == GfxGen::GFX9) - { - InlineAsm* traceExecLo = InlineAsm::get( - FunctionType::get(Type::getVoidTy(Ctx), false), - "s_mov_b32 m0, exec_lo\n" - "s_nop 0\n" - "s_ttracedata", - "", - /*hasSideEffects=*/true - ); - B.CreateCall(traceExecLo); - - InlineAsm* traceExecHi = InlineAsm::get( - FunctionType::get(Type::getVoidTy(Ctx), false), - "s_mov_b32 m0, exec_hi\n" - "s_nop 0\n" - "s_ttracedata", - "", - /*hasSideEffects=*/true - ); - B.CreateCall(traceExecHi); - } - else - { - InlineAsm* readExecLo = - InlineAsm::get(FunctionType::get(I32, false), "s_mov_b32 $0, exec_lo", "=s", /*hasSideEffects=*/true); - emitBareTraceValue(B, B.CreateCall(readExecLo), M, gen); - - InlineAsm* readExecHi = - InlineAsm::get(FunctionType::get(I32, false), "s_mov_b32 $0, exec_hi", "=s", /*hasSideEffects=*/true); - emitBareTraceValue(B, B.CreateCall(readExecHi), M, gen); - } + emitExecMaskTrace(B, Ctx, /*high=*/false); + emitExecMaskTrace(B, Ctx, /*high=*/true); // 3. Per-lane readlane loop over index operand (arg 0) Value* index = permuteOp->getArgOperand(0); diff --git a/projects/rocprof-trace-decoder/markers/SQTTFunctions.cpp b/projects/rocprof-trace-decoder/markers/SQTTFunctions.cpp index 0889dfc5f30b..3b53bfb7a52c 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTFunctions.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTFunctions.cpp @@ -233,6 +233,7 @@ uint32_t SQTTInstrumentPass::compactFuncIDs(Module& M) auto IID = Callee->getIntrinsicID(); if (IID != Intrinsic::amdgcn_s_ttracedata && IID != Intrinsic::amdgcn_s_ttracedata_imm) continue; if (isRawPayloadTrace(CI)) continue; + if (!isMarkerHeaderTrace(CI)) continue; auto* Arg = dyn_cast(CI->getArgOperand(0)); if (!Arg) continue; uint32_t val = Arg->getZExtValue(); @@ -319,6 +320,7 @@ void SQTTInstrumentPass::rewriteMarkerIDs(Function& F, const std::mapgetIntrinsicID(); if (IID != Intrinsic::amdgcn_s_ttracedata && IID != Intrinsic::amdgcn_s_ttracedata_imm) continue; if (isRawPayloadTrace(CI)) continue; + if (!isMarkerHeaderTrace(CI)) continue; auto* Arg = dyn_cast(CI->getArgOperand(0)); if (!Arg) continue; @@ -353,16 +355,18 @@ void SQTTInstrumentPass::rewriteMarkerIDs(Function& F, const std::map B(CI); + CallInst* replacement; if (canUseImm(newVal) && supportsImmTrace(gen)) { Function* TTDImm = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_ttracedata_imm); - B.CreateCall(TTDImm, {ConstantInt::get(I16, newVal)}); + replacement = B.CreateCall(TTDImm, {ConstantInt::get(I16, newVal)}); } else { Function* TTD = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_ttracedata); - B.CreateCall(TTD, {ConstantInt::get(I32, newVal)}); + replacement = B.CreateCall(TTD, {ConstantInt::get(I32, newVal)}); } + replacement->copyMetadata(*CI); CI->eraseFromParent(); } } @@ -387,6 +391,7 @@ void SQTTInstrumentPass::removeFuncMarkersFromModule(Module& M, uint32_t id) auto IID = Callee->getIntrinsicID(); if (IID != Intrinsic::amdgcn_s_ttracedata && IID != Intrinsic::amdgcn_s_ttracedata_imm) continue; if (isRawPayloadTrace(CI)) continue; + if (!isMarkerHeaderTrace(CI)) continue; auto* Arg = dyn_cast(CI->getArgOperand(0)); if (!Arg) continue; diff --git a/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp b/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp index eaf15911097b..2fd1a4feea88 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp @@ -31,6 +31,7 @@ using namespace llvm; +static constexpr const char* MarkerHeaderMetadataName = "sqtt.marker_header"; static constexpr const char* RawPayloadMetadataName = "sqtt.raw_payload"; static constexpr uint32_t GFX12_SHADER_CYCLES_LO = 29; @@ -124,16 +125,18 @@ void SQTTInstrumentPass::insertTraceMarkerWithPayload( } emitMemBarrier(Builder, Config.MemBarrier); + CallInst* trace; if (canUseImm(markerID) && supportsImmTrace(gen)) { Function* TTDImm = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_ttracedata_imm); - Builder.CreateCall(TTDImm, {ConstantInt::get(I16, markerID)}); + trace = Builder.CreateCall(TTDImm, {ConstantInt::get(I16, markerID)}); } else { Function* TTD = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_ttracedata); - Builder.CreateCall(TTD, {ConstantInt::get(I32, markerID)}); + trace = Builder.CreateCall(TTD, {ConstantInt::get(I32, markerID)}); } + markMarkerHeaderTrace(trace); if (payload) { @@ -399,16 +402,19 @@ bool SQTTInstrumentPass::addBarriersToExistingMarkers(Function& F) CallInst* SQTTInstrumentPass::emitBareTrace(IRBuilder<>& B, uint32_t encoded, Module* M, GfxGen gen) { LLVMContext& Ctx = M->getContext(); + CallInst* CI; if (canUseImm(encoded) && supportsImmTrace(gen)) { Function* TTDImm = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_ttracedata_imm); - return B.CreateCall(TTDImm, {ConstantInt::get(Type::getInt16Ty(Ctx), encoded)}); + CI = B.CreateCall(TTDImm, {ConstantInt::get(Type::getInt16Ty(Ctx), encoded)}); } else { Function* TTD = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_ttracedata); - return B.CreateCall(TTD, {ConstantInt::get(Type::getInt32Ty(Ctx), encoded)}); + CI = B.CreateCall(TTD, {ConstantInt::get(Type::getInt32Ty(Ctx), encoded)}); } + markMarkerHeaderTrace(CI); + return CI; } CallInst* SQTTInstrumentPass::emitBareTraceValue(IRBuilder<>& B, Value* val, Module* M, GfxGen gen) @@ -422,6 +428,17 @@ CallInst* SQTTInstrumentPass::emitBareTraceValue(IRBuilder<>& B, Value* val, Mod CallInst* SQTTInstrumentPass::emitRawTracePayload(IRBuilder<>& B, Value* val, Module* M) { + // Full s_ttracedata lowers through M0, so its input must be scalar. The + // intrinsic lowering normally inserts this readfirstlane for a divergent + // named data value; retain that behavior before the explicit asm lowering. + Type* I32 = Type::getInt32Ty(M->getContext()); + if (val->getType() != I32) val = B.CreateZExtOrTrunc(val, I32); + if (!isa(val)) + { + Function* ReadFirstLane = + Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_readfirstlane, {I32}); + val = B.CreateCall(ReadFirstLane, {val}); + } Function* TTD = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_ttracedata); CallInst* CI = B.CreateCall(TTD, {val}); markRawPayloadTrace(CI); @@ -439,6 +456,61 @@ bool SQTTInstrumentPass::isRawPayloadTrace(CallInst* CI) return CI && CI->getMetadata(RawPayloadMetadataName) != nullptr; } +void SQTTInstrumentPass::markMarkerHeaderTrace(CallInst* CI) +{ + if (!CI) return; + CI->setMetadata(MarkerHeaderMetadataName, MDNode::get(CI->getContext(), {})); +} + +bool SQTTInstrumentPass::isMarkerHeaderTrace(CallInst* CI) +{ + return CI && CI->getMetadata(MarkerHeaderMetadataName) != nullptr; +} + +bool SQTTInstrumentPass::lowerFullTracesWithM0Nop(Function& F) +{ + SmallVector traces; + for (auto& BB : F) + { + for (auto& I : BB) + { + auto* CI = dyn_cast(&I); + if (!CI) continue; + Function* Callee = CI->getCalledFunction(); + if (Callee && Callee->getIntrinsicID() == Intrinsic::amdgcn_s_ttracedata) traces.push_back(CI); + } + } + if (traces.empty()) return false; + + LLVMContext& Ctx = F.getContext(); + Type* I32 = Type::getInt32Ty(Ctx); + // Model M0 as a fixed output instead of a clobber. M0 is reserved on + // AMDGPU and LLVM diagnoses `~{m0}` as undefined behavior. + FunctionType* TraceTy = FunctionType::get(I32, {I32}, false); + static constexpr const char TraceAsmText[] = + "s_mov_b32 m0, $1\n" + "s_nop 0\n" + "s_ttracedata"; + + // Constants can be encoded directly in the M0 move. Non-constant values + // reaching this helper are required to be scalar. + InlineAsm* ImmediateTrace = + InlineAsm::get(TraceTy, TraceAsmText, "={m0},i", /*hasSideEffects=*/true); + InlineAsm* ScalarTrace = InlineAsm::get(TraceTy, TraceAsmText, "={m0},s", /*hasSideEffects=*/true); + + for (CallInst* CI : traces) + { + Value* value = CI->getArgOperand(0); + InlineAsm* TraceAsm = isa(value) ? ImmediateTrace : ScalarTrace; + IRBuilder<> B(CI); + CallInst* replacement = B.CreateCall(TraceAsm, {value}); + replacement->setDebugLoc(CI->getDebugLoc()); + replacement->copyMetadata(*CI); + CI->eraseFromParent(); + } + return true; +} + bool SQTTInstrumentPass::applyShaderClockPacking(Function& F, GfxGen gen) { unsigned clockBits = getShaderClockBits(Config, gen); @@ -468,9 +540,13 @@ bool SQTTInstrumentPass::applyShaderClockPacking(Function& F, GfxGen gen) if (IID != Intrinsic::amdgcn_s_ttracedata && IID != Intrinsic::amdgcn_s_ttracedata_imm) continue; Value* encodedValue = CI->getArgOperand(0); + bool header = isMarkerHeaderTrace(CI); if (auto* Arg = dyn_cast(encodedValue)) { uint32_t encoded = Arg->getZExtValue(); + // A bare exit has no ID and cannot be distinguished from the + // numeric API in trace data, so keep all of them packed. + if (!header && encoded != FLAG_EXIT_PREV) continue; uint32_t markerID = encoded >> 2; if (markerID > maxID) { @@ -480,6 +556,8 @@ bool SQTTInstrumentPass::applyShaderClockPacking(Function& F, GfxGen gen) ); } } + else if (!header) + continue; Rewrites.push_back({CI, encodedValue}); } } @@ -502,7 +580,9 @@ bool SQTTInstrumentPass::applyShaderClockPacking(Function& F, GfxGen gen) Value* clock = B.CreateCall(SGetReg, {ConstantInt::get(I32, hwreg)}); Value* shiftedClock = B.CreateShl(clock, ConstantInt::get(I32, clockDestShift)); Value* packed = B.CreateOr(shiftedClock, markerAndFlags); - B.CreateCall(TTD, {packed}); + CallInst* packedTrace = B.CreateCall(TTD, {packed}); + packedTrace->setDebugLoc(CI->getDebugLoc()); + packedTrace->copyMetadata(*CI); CI->eraseFromParent(); } diff --git a/projects/rocprof-trace-decoder/markers/SQTTPass.cpp b/projects/rocprof-trace-decoder/markers/SQTTPass.cpp index e3c71e86451d..2b65bb695587 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTPass.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTPass.cpp @@ -179,6 +179,9 @@ PreservedAnalyses SQTTInstrumentPass::runLate(Module& M) if (!hadEarlyPass && Config.FunctionThreshold > 0 && !IsKernel) Changed |= instrumentFunctionDirect(F, Gen); Changed |= applyShaderClockPacking(F, Gen); + // gfx9's backend already provides the required M0 hazard spacing. + // RDNA/gfx12 need the explicit asm form. + if (Gen != GfxGen::GFX9) Changed |= lowerFullTracesWithM0Nop(F); } // Clean up sentinel declarations diff --git a/projects/rocprof-trace-decoder/markers/SQTTPass.h b/projects/rocprof-trace-decoder/markers/SQTTPass.h index c588cf9114fc..f26db1678a88 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTPass.h +++ b/projects/rocprof-trace-decoder/markers/SQTTPass.h @@ -177,6 +177,8 @@ class SQTTInstrumentPass : public llvm::PassInfoMixin llvm::CallInst* emitBareTrace(llvm::IRBuilder<>& B, uint32_t encoded, llvm::Module* M, GfxGen gen); llvm::CallInst* emitBareTraceValue(llvm::IRBuilder<>& B, llvm::Value* val, llvm::Module* M, GfxGen gen); llvm::CallInst* emitRawTracePayload(llvm::IRBuilder<>& B, llvm::Value* val, llvm::Module* M); + static void markMarkerHeaderTrace(llvm::CallInst* CI); + static bool isMarkerHeaderTrace(llvm::CallInst* CI); static void markRawPayloadTrace(llvm::CallInst* CI); static bool isRawPayloadTrace(llvm::CallInst* CI); @@ -274,6 +276,9 @@ class SQTTInstrumentPass : public llvm::PassInfoMixin uint32_t compactFuncIDs(llvm::Module& M); void rewriteMarkerIDs(llvm::Function& F, const std::map& IDMap, GfxGen gen); bool applyShaderClockPacking(llvm::Function& F, GfxGen gen); + // gfx10+ do not provide the needed M0 hazard spacing for this intrinsic. + // Lower their full traces after all marker transformations to explicit asm. + bool lowerFullTracesWithM0Nop(llvm::Function& F); void removeFuncMarkersFromModule(llvm::Module& M, uint32_t id); void removeAdjacentBarriers(llvm::CallInst* CI); diff --git a/projects/rocprof-trace-decoder/python/att_tool.py b/projects/rocprof-trace-decoder/python/att_tool.py index 40f524745d2a..cdd156810cdd 100644 --- a/projects/rocprof-trace-decoder/python/att_tool.py +++ b/projects/rocprof-trace-decoder/python/att_tool.py @@ -40,6 +40,7 @@ def generate_outputs_from_files( lib_path: str | Path | None = None, formats: str = "json,csv", base_name: str | None = None, + decode_markers: bool = True, ) -> list[Path]: inputs = _discover_inputs(files) if not inputs.att_files and not inputs.code_objects and inputs.code_json is None: @@ -91,6 +92,7 @@ def generate_outputs_from_files( formats=formats, base_name=output_base_name, on_warning=_print_warning, + decode_markers=decode_markers, ) if snapshot_source_dir: for output in outputs: @@ -214,6 +216,13 @@ def build_argparser() -> argparse.ArgumentParser: "--base-name", help="Base name for default ui_output_ and stats_<...>.csv output names.", ) + parser.add_argument( + "--no-decode-markers", + action="store_false", + dest="decode_markers", + default=True, + help="Leave packed marker IDs and JSON shaderdata timestamps unchanged.", + ) return parser @@ -225,6 +234,7 @@ def main(argv: list[str] | None = None) -> int: lib_path=args.lib, formats=args.formats, base_name=args.base_name, + decode_markers=args.decode_markers, ) for path in outputs: print(path) diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index c46d1110fc7a..e2448ae3a62d 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -9,6 +9,7 @@ from .bindings import Decoder from .code_index import CodeIndex from .codegen import CodeObject, generate_code_artifacts +from .records import ShaderDataFlags from .rcv import ( DEFAULT_RUN_BASE_NAME, RcvOutputWriter, @@ -45,6 +46,7 @@ def generate_att_outputs( formats: str | Iterable[str] = "json,csv", base_name: str = DEFAULT_RUN_BASE_NAME, on_warning: Callable[[Path, str], None] | None = None, + decode_markers: bool = True, ) -> list[Path]: output_formats = normalize_output_formats(formats) trace_list = [ @@ -92,6 +94,12 @@ def generate_att_outputs( ) writer = RcvOutputWriter(run_dir, run_code_index, formats=output_formats) + decode_json = ( + decode_markers + and "json" in output_formats + and bool(run_code_index.document.get("sqtt_funcmap_layout")) + ) + decoded = [] if decode_json else None with Decoder(lib_path) as decoder: for trace in run_traces: records = decoder.parse_file( @@ -101,7 +109,15 @@ def generate_att_outputs( if on_warning is not None: for info in records.info: on_warning(trace.path, decoder.info_string(info)) - writer.add_shader_records(trace.shader_engine, records) + if decoded is None: + writer.add_shader_records(trace.shader_engine, records) + else: + decoded.append((trace.shader_engine, records)) + + if decoded is not None: + _correct_marker_timestamps(decoded, run_code_index.document) + for shader_engine, records in decoded: + writer.add_shader_records(shader_engine, records) writer.finish() write_code_json(run_dir, run_code_index) @@ -114,6 +130,128 @@ def generate_att_outputs( return output_dirs +def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict) -> None: + """Correct packed marker headers in-place, leaving legacy streams alone.""" + layouts: dict[int, tuple[int, int]] = {} + for row in document.get("sqtt_funcmap_layout", []): + try: + codeobj, bits, shift = map(int, row[:3]) + except (TypeError, ValueError): + continue + if codeobj >= 0 and 0 < bits <= 29 and 0 <= shift and bits + shift <= 32: + layouts[codeobj] = (bits, shift) + if not layouts: + return + + marker_ids: dict[int, set[int]] = defaultdict(set) + for row in document.get("sqtt_funcmap", []): + try: + codeobj, marker_id, kind = int(row[0]), int(row[1]), str(row[2]) + except (IndexError, TypeError, ValueError): + continue + if codeobj in layouts and kind.lower() not in {"k", "kernel"} and marker_id >= 0: + marker_ids[codeobj].add(marker_id) + + payloads: dict[tuple[int, int], int] = {} + for row in document.get("sqtt_funcmap_payloads", []): + try: + codeobj, marker_id, count = map(int, row[:3]) + except (TypeError, ValueError): + continue + if marker_id in marker_ids.get(codeobj, ()) and count > 0: + payloads[codeobj, marker_id] = count + + shaderdata: dict[tuple[int, int, int, int], list[object]] = defaultdict(list) + occupancy: dict[tuple[int, int, int, int], list[object]] = defaultdict(list) + for se, records in decoded: + for record in records.shaderdata: + shaderdata[se, record.cu, record.simd, record.wave_id].append(record) + for record in records.occupancy: + occupancy[se, record.cu, record.simd, record.wave_id].append(record) + + headers: list[tuple[object, int, int, int, list[object]]] = [] + minimum_delay: dict[tuple[int, int], int] = {} + for location, stream in shaderdata.items(): + se = location[0] + stream.sort(key=lambda record: record.time) + intervals = _active_codeobj_intervals(occupancy.get(location, ())) + interval = 0 + payload_remaining = 0 + header_payloads: list[object] | None = None + for record in stream: + if payload_remaining: + if header_payloads is not None: + header_payloads.append(record) + payload_remaining -= 1 + continue + if record.flags & ((1 << ShaderDataFlags.IMM) | (1 << ShaderDataFlags.PRIV)): + continue + + while interval < len(intervals) and intervals[interval][1] < record.time: + interval += 1 + while interval + 1 < len(intervals) and intervals[interval + 1][0] <= record.time: + interval += 1 + codeobj = ( + intervals[interval][2] + if interval < len(intervals) and intervals[interval][0] <= record.time + else None + ) + layout = layouts.get(codeobj) + known_ids = marker_ids.get(codeobj, ()) + if layout is None: + continue + + bits, shift = layout + raw = int(record.value) & 0xFFFFFFFF + marker_id = (raw >> 2) & ((1 << (30 - bits)) - 1) + if marker_id not in known_ids and not (marker_id == 0 and (raw & 3) == 1): + continue + + record.value = (marker_id << 2) | (raw & 3) + header_payloads = [] + if marker_id in known_ids: + payload_remaining = payloads.get((codeobj, marker_id), 0) + mask = ((1 << bits) - 1) << shift + sampled = ((raw >> (32 - bits)) & ((1 << bits) - 1)) << shift + delay = ((int(record.time) & mask) - sampled) & mask + headers.append((record, se, mask, delay, header_payloads)) + key = se, mask + minimum_delay[key] = min(minimum_delay.get(key, delay), delay) + + changed = False + for record, se, mask, delay, header_payloads in headers: + correction = delay - minimum_delay[se, mask] + if correction: + record.time -= correction + # The output writer sorts shaderdata after correction. Move raw + # payloads with their header so declared header/payload blocks + # remain ordered for readers that consume R: metadata. + for payload in header_payloads: + payload.time -= correction + changed = True + if changed: + for _se, records in decoded: + records.shaderdata.sort(key=lambda record: record.time) + + +def _active_codeobj_intervals(occupancy: Iterable[object]) -> list[tuple[int, int, int]]: + events = sorted(occupancy, key=lambda record: (record.time, record.start)) + intervals: list[tuple[int, int, int]] = [] + begin = 0 + codeobj: int | None = None + for record in events: + if record.start: + if codeobj is not None: + intervals.append((begin, record.time, codeobj)) + begin, codeobj = record.time, record.pc.code_object_id + elif codeobj is not None: + intervals.append((begin, record.time, codeobj)) + codeobj = None + if codeobj is not None: + intervals.append((begin, (1 << 63) - 1, codeobj)) + return intervals + + def _group_traces(traces: Iterable[AttTrace]) -> dict[int, list[AttTrace]]: grouped: dict[int, list[AttTrace]] = defaultdict(list) for trace in traces: diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/codegen.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/codegen.py index 70cb50e51d0a..247f65672d68 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/codegen.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/codegen.py @@ -55,6 +55,22 @@ class CodeArtifacts: source_paths: tuple[Path, ...] +@dataclass(frozen=True) +class _SqttFuncmapEntry: + marker_id: int + kind: str + name: str + source_loc: str + + +@dataclass(frozen=True) +class _SqttFuncmap: + entries: tuple[_SqttFuncmapEntry, ...] + shader_clock_bits: int + shader_clock_shift: int + extra_payload_counts: dict[int, int] + + def _find_tool(name: str) -> str: candidates: list[Path] = [] for root in _rocm_roots(): @@ -107,6 +123,134 @@ def is_elf(path: str | Path) -> bool: return False +# --------------------------------------------------------------------------- +# SQTT marker metadata +# --------------------------------------------------------------------------- + + +def _parse_u32(value: str) -> int | None: + if not value or any(char < "0" or char > "9" for char in value): + return None + parsed = int(value) + return parsed if parsed <= 0xFFFFFFFF else None + + +def _split_funcmap_name(value: str) -> tuple[str, str]: + name, sep, source_loc = value.partition("@") + return (name, source_loc if sep else "") + + +def _parse_sqtt_funcmap(data: bytes) -> _SqttFuncmap: + """Parse the subset of ``.sqtt_funcmap`` needed by code.json. + + The output intentionally mirrors the C++ funcmap reader, but malformed + rows are ignored here: generating code metadata must remain best-effort + for ordinary code objects and older marker producers. + """ + + entries: list[_SqttFuncmapEntry] = [] + extra_payload_counts: dict[int, int] = {} + shader_clock_bits = 0 + shader_clock_shift = 0 + + for raw_line in re.split(r"[\n\x00]", data.decode("utf-8", errors="replace")): + line = raw_line.rstrip("\r \t") + if len(line) < 2 or line[1] != ":": + continue + + kind = line[0] + payload = line[2:] + if kind == "M": + bits = 0 + shift = 0 + saw_bits = False + saw_shift = False + valid = True + for attribute in payload.split(";"): + key, separator, value = attribute.partition("=") + if not separator: + continue + parsed = _parse_u32(value) + if key == "shader_clock_bits": + if parsed is None: + valid = False + else: + bits = parsed + saw_bits = True + elif key == "shader_clock_shift": + if parsed is None: + valid = False + else: + shift = parsed + saw_shift = True + + if valid and saw_bits and (bits == 0 or saw_shift) and bits <= 29 and ( + bits == 0 or (shift < 32 and bits <= 32 - shift) + ): + shader_clock_bits = bits + shader_clock_shift = shift if bits else 0 + continue + + if kind == "R": + marker_id_text, separator, attributes = payload.partition(":") + marker_id = _parse_u32(marker_id_text) if separator else None + if marker_id is None: + continue + for attribute in attributes.split(";"): + key, equals, value = attribute.partition("=") + if key != "extra_payload_count" or not equals: + continue + count = _parse_u32(value) + if count is not None: + extra_payload_counts[marker_id] = count + continue + + if kind == "K": + name, source_loc = _split_funcmap_name(payload) + if name: + entries.append(_SqttFuncmapEntry(0, kind, name, source_loc)) + continue + + if kind not in {"F", "U", "P"}: + continue + + marker_id_text, separator, name_and_source = payload.partition(":") + marker_id = _parse_u32(marker_id_text) if separator else None + if marker_id is None: + continue + name, source_loc = _split_funcmap_name(name_and_source) + if name: + entries.append(_SqttFuncmapEntry(marker_id, kind, name, source_loc)) + + return _SqttFuncmap( + entries=tuple(entries), + shader_clock_bits=shader_clock_bits, + shader_clock_shift=shader_clock_shift, + extra_payload_counts=extra_payload_counts, + ) + + +def _read_sqtt_funcmap(elf_path: str) -> _SqttFuncmap | None: + """Return embedded marker metadata, or ``None`` when no section exists.""" + + from elftools.common import exceptions as elftools_exceptions + from elftools.elf.elffile import ELFFile + + try: + with Path(elf_path).open("rb") as file: + section = ELFFile(file).get_section_by_name(".sqtt_funcmap") + if section is None: + return None + data = section.data() + except (OSError, elftools_exceptions.ELFError, ValueError): + return None + except Exception: + LOGGER.debug("Unexpected error reading .sqtt_funcmap from %s", elf_path, exc_info=True) + return None + + return _parse_sqtt_funcmap(data) if data else None + + # --------------------------------------------------------------------------- # DWARF helpers # --------------------------------------------------------------------------- @@ -569,6 +713,9 @@ def generate_code_artifacts(code_objects: Iterable[CodeObject]) -> CodeArtifacts rows: list[list] = [] comments: list[str] = [] kernels_out: list[dict] = [] + sqtt_funcmap_rows: list[list[object]] = [] + sqtt_funcmap_layouts: list[list[int]] = [] + sqtt_funcmap_payloads: list[list[int]] = [] line_no = 1 for code_object in normalized: @@ -577,6 +724,26 @@ def generate_code_artifacts(code_objects: Iterable[CodeObject]) -> CodeArtifacts range_map = build_address_ranges(elf_path) insts = parse_disassembly(elf_path) kernels, debug_labels = read_symbol_labels(elf_path) + funcmap = _read_sqtt_funcmap(elf_path) + + if funcmap is not None and funcmap.shader_clock_bits: + # Keep the existing sqtt_funcmap row shape used by rocprofv3 + # consumers. The two new side tables below are additive, so older + # readers can ignore them without losing their original schema. + sqtt_funcmap_layouts.append( + [codeobj_id, funcmap.shader_clock_bits, funcmap.shader_clock_shift] + ) + symbol_vaddrs: dict[str, int] = {} + for address, (name, _demangled) in sorted(kernels.items()): + symbol_vaddrs.setdefault(name, address) + for entry in funcmap.entries: + vaddr = symbol_vaddrs.get(entry.name, 0) if entry.kind in {"F", "K"} else 0 + sqtt_funcmap_rows.append( + [codeobj_id, entry.marker_id, entry.kind, entry.name, entry.source_loc, vaddr] + ) + payload_count = funcmap.extra_payload_counts.get(entry.marker_id, 0) + if entry.kind != "K" and payload_count: + sqtt_funcmap_payloads.append([codeobj_id, entry.marker_id, payload_count]) for vaddr, inst in insts: # Kernel and debug labels are emitted as code rows immediately @@ -639,6 +806,12 @@ def generate_code_artifacts(code_objects: Iterable[CodeObject]) -> CodeArtifacts "kernels": kernels_out, "hsaco": [str(code_object.path) for code_object in normalized], } + if sqtt_funcmap_rows: + code_doc["sqtt_funcmap"] = sqtt_funcmap_rows + if sqtt_funcmap_layouts: + code_doc["sqtt_funcmap_layout"] = sqtt_funcmap_layouts + if sqtt_funcmap_payloads: + code_doc["sqtt_funcmap_payloads"] = sqtt_funcmap_payloads source_paths = tuple(Path(path) for path in extract_paths_from_comments(comments)) return CodeArtifacts(CodeIndex.from_document(code_doc), source_paths) diff --git a/projects/rocprof-trace-decoder/test/CMakeLists.txt b/projects/rocprof-trace-decoder/test/CMakeLists.txt index b714fbf869bb..f4d6c0c08038 100644 --- a/projects/rocprof-trace-decoder/test/CMakeLists.txt +++ b/projects/rocprof-trace-decoder/test/CMakeLists.txt @@ -111,6 +111,8 @@ if(BUILD_INTEGRATION_TESTS) set(CSV_PY ${CMAKE_CURRENT_SOURCE_DIR}/csv_test.py) set(ATT_TOOL_PY ${CMAKE_CURRENT_SOURCE_DIR}/../python/att_tool.py) set(PYTHON_API_PY ${CMAKE_CURRENT_SOURCE_DIR}/python_api_test.py) + set(CODEGEN_TEST_PY ${CMAKE_CURRENT_SOURCE_DIR}/codegen_test.py) + set(ATT_TOOL_TEST_PY ${CMAKE_CURRENT_SOURCE_DIR}/att_tool_test.py) set(LIB_PATH ${CMAKE_BINARY_DIR}/lib/librocprof-trace-decoder${CMAKE_SHARED_LIBRARY_SUFFIX}) set(SANITIZED_LIB_PATH ${CMAKE_BINARY_DIR}/lib/librocprof-trace-decoder-sanitized${CMAKE_SHARED_LIBRARY_SUFFIX}) @@ -146,6 +148,27 @@ if(BUILD_INTEGRATION_TESTS) ENVIRONMENT "${PYTHON_API_TEST_ENV}") + add_test(NAME codegen-funcmap-metadata COMMAND ${Python3_EXECUTABLE} ${CODEGEN_TEST_PY}) + set_tests_properties( + codegen-funcmap-metadata + PROPERTIES TIMEOUT + 30 + LABELS + "integration-tests" + ENVIRONMENT + "PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/../python") + + add_test(NAME att-marker-clock-correction COMMAND ${Python3_EXECUTABLE} ${ATT_TOOL_TEST_PY}) + set_tests_properties( + att-marker-clock-correction + PROPERTIES + TIMEOUT + 30 + LABELS + "integration-tests" + ENVIRONMENT + "PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/../python") + if(DEFINED ROCPROF_TRACE_DECODER_PYTHON_INSTALL_DIR) configure_file(python_api_install_test.cmake.in python_api_install_test.cmake @ONLY) add_test( diff --git a/projects/rocprof-trace-decoder/test/att_tool_test.py b/projects/rocprof-trace-decoder/test/att_tool_test.py new file mode 100644 index 000000000000..895fd24c321a --- /dev/null +++ b/projects/rocprof-trace-decoder/test/att_tool_test.py @@ -0,0 +1,205 @@ +#!/usr/bin/env python3 +"""Focused checks for packed-marker JSON normalization and timestamp correction.""" + +from __future__ import annotations + +import sys +import unittest +from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "python")) + +from att_tool import build_argparser +from rocprof_trace_decoder.att import AttTrace, _correct_marker_timestamps, generate_att_outputs +from rocprof_trace_decoder.code_index import CodeIndex +from rocprof_trace_decoder.records import Occupancy, Pc, ShaderData, ShaderDataFlags, TraceRecords + + +def _shaderdata(time: int, value: int, flags: int = 0) -> ShaderData: + return ShaderData(time, value, cu=1, simd=2, wave_id=3, flags=flags, reserved=0) + + +def _occupancy(start: int, time: int, codeobj: int = 7) -> Occupancy: + return Occupancy(Pc(0, codeobj), time, 0, 1, 2, 3, start, 0, 0, 0, 0, 0) + + +class _Decoder: + def __init__(self, records: TraceRecords): + self.records = records + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def parse_file(self, *_args, **_kwargs): + return self.records + + +class _Writer: + records: TraceRecords | None = None + + def __init__(self, *_args, **_kwargs): + pass + + def add_shader_records(self, _se: int, records: TraceRecords): + type(self).records = records + + def finish(self): + pass + + +class MarkerTimestampCorrectionTest(unittest.TestCase): + def test_corrects_headers_and_moves_declared_payloads_with_their_header(self): + first_value = (0x100 << 20) | (3 << 2) + second_value = (0x100 << 20) | (4 << 2) + first = _shaderdata(0x1010, first_value) + second = _shaderdata(0x1080, second_value) + payload = _shaderdata(0x1090, first_value) + records = TraceRecords( + occupancy=[_occupancy(1, 0, 0), _occupancy(0, 0x2000, 0)], + shaderdata=[first, second, payload], + ) + document = { + "sqtt_funcmap": [[0, 3, "P", "first", "", 0], [0, 4, "P", "second", "", 0]], + "sqtt_funcmap_layout": [[0, 12, 4]], + "sqtt_funcmap_payloads": [[0, 4, 1]], + } + + _correct_marker_timestamps([(0, records)], document) + + self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x1010, 0x1020]) + self.assertIs(records.shaderdata[0], first) + self.assertIs(records.shaderdata[1], second) + self.assertIs(records.shaderdata[2], payload) + self.assertEqual([record.value for record in records.shaderdata], [12, 16, first_value]) + + def test_imm_record_is_not_a_packed_header(self): + record = _shaderdata(0x1010, 3 << 2, flags=1 << ShaderDataFlags.IMM) + records = TraceRecords(occupancy=[_occupancy(1, 0)], shaderdata=[record]) + + _correct_marker_timestamps( + [(0, records)], + {"sqtt_funcmap": [[7, 3, "P", "marker"]], "sqtt_funcmap_layout": [[7, 12, 4]]}, + ) + + self.assertEqual(record.time, 0x1010) + self.assertEqual(record.value, 3 << 2) + + def test_legacy_metadata_leaves_records_unchanged(self): + records = TraceRecords(shaderdata=[_shaderdata(20, 12), _shaderdata(10, 16)]) + original = list(records.shaderdata) + + _correct_marker_timestamps([(0, records)], {"sqtt_funcmap": [[7, 3, "P", "marker"]]}) + + self.assertEqual(records.shaderdata, original) + + def test_no_active_code_object_is_not_code_object_zero(self): + value = (0x100 << 20) | (3 << 2) + record = _shaderdata(0x1080, value) + records = TraceRecords(shaderdata=[record]) + + _correct_marker_timestamps( + [(0, records)], + {"sqtt_funcmap": [[0, 3, "P", "marker"]], "sqtt_funcmap_layout": [[0, 12, 4]]}, + ) + + self.assertEqual(record.time, 0x1080) + self.assertEqual(record.value, value) + + def test_exit_without_a_marker_row_is_decoded(self): + record = _shaderdata(0x1010, (0x100 << 20) | 1) + records = TraceRecords(occupancy=[_occupancy(1, 0)], shaderdata=[record]) + + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 0, "K", "kernel", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + }, + ) + + self.assertEqual(record.value, 1) + + def test_unknown_packed_values_are_unchanged(self): + records = TraceRecords( + occupancy=[_occupancy(1, 0)], + shaderdata=[_shaderdata(20, 4 << 20), _shaderdata(10, 4 << 20)], + ) + original = list(records.shaderdata) + + _correct_marker_timestamps( + [(0, records)], + {"sqtt_funcmap": [[7, 3, "P", "marker"]], "sqtt_funcmap_layout": [[7, 12, 4]]}, + ) + + self.assertEqual(records.shaderdata, original) + + def test_correction_is_default(self): + value = (0x100 << 20) | (3 << 2) + records = TraceRecords( + occupancy=[_occupancy(1, 0)], + shaderdata=[_shaderdata(0x1080, value), _shaderdata(0x1010, value)], + ) + code_index = CodeIndex.from_document( + { + "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + } + ) + + with TemporaryDirectory() as directory: + with patch("rocprof_trace_decoder.att.Decoder", return_value=_Decoder(records)), \ + patch("rocprof_trace_decoder.att.RcvOutputWriter", _Writer), \ + patch("rocprof_trace_decoder.att.write_code_json"): + generate_att_outputs( + [AttTrace(Path(directory) / "trace.att", shader_engine=0)], + code_index=code_index, + output_dir=directory, + formats="json", + ) + + self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x1010]) + self.assertEqual([record.value for record in records.shaderdata], [12, 12]) + + def test_no_decode_markers_preserves_packed_values(self): + value = (0x100 << 20) | (3 << 2) + records = TraceRecords( + occupancy=[_occupancy(1, 0)], + shaderdata=[_shaderdata(0x1080, value), _shaderdata(0x1010, value)], + ) + code_index = CodeIndex.from_document( + { + "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + } + ) + + _Writer.records = None + with TemporaryDirectory() as directory: + with patch("rocprof_trace_decoder.att.Decoder", return_value=_Decoder(records)), \ + patch("rocprof_trace_decoder.att.RcvOutputWriter", _Writer), \ + patch("rocprof_trace_decoder.att.write_code_json"): + generate_att_outputs( + [AttTrace(Path(directory) / "trace.att", shader_engine=0)], + code_index=code_index, + output_dir=directory, + formats="json", + decode_markers=False, + ) + + self.assertIs(_Writer.records, records) + self.assertEqual([record.time for record in records.shaderdata], [0x1080, 0x1010]) + self.assertEqual([record.value for record in records.shaderdata], [value, value]) + + def test_no_decode_markers_cli_flag(self): + parser = build_argparser() + self.assertTrue(parser.parse_args(["trace.att"]).decode_markers) + self.assertFalse(parser.parse_args(["--no-decode-markers", "trace.att"]).decode_markers) + + +if __name__ == "__main__": + unittest.main() diff --git a/projects/rocprof-trace-decoder/test/codegen_test.py b/projects/rocprof-trace-decoder/test/codegen_test.py new file mode 100644 index 000000000000..478056204f09 --- /dev/null +++ b/projects/rocprof-trace-decoder/test/codegen_test.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python3 +"""Focused schema checks for code.json SQTT funcmap metadata.""" + +from __future__ import annotations + +import sys +import unittest +from pathlib import Path +from unittest.mock import patch + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "python")) + +import rocprof_trace_decoder.codegen as codegen + + +class CodegenFuncmapTest(unittest.TestCase): + def generate(self, funcmaps: list[codegen._SqttFuncmap]): + code_objects = [ + codegen.CodeObject("packed.co", 10), + codegen.CodeObject("legacy.co", 11), + ][: len(funcmaps)] + with ( + patch.object(codegen, "_normalize_code_objects", return_value=code_objects), + patch.object(codegen, "build_address_ranges", return_value=[]), + patch.object(codegen, "parse_disassembly", return_value=[]), + patch.object(codegen, "read_symbol_labels", return_value=({}, {})), + patch.object(codegen, "_read_sqtt_funcmap", side_effect=funcmaps), + ): + return codegen.generate_code_artifacts(code_objects).code_index.document + + def test_adds_packed_metadata_without_changing_funcmap_rows(self): + doc = self.generate( + [ + codegen._parse_sqtt_funcmap( + b"M:shader_clock_bits=12;shader_clock_shift=4\n" + b"F:7:scope@source.cpp:1\n" + b"U:8:user\n" + b"R:8:extra_payload_count=1\n" + ) + ] + ) + + self.assertEqual( + doc["sqtt_funcmap"], + [[10, 7, "F", "scope", "source.cpp:1", 0], [10, 8, "U", "user", "", 0]], + ) + self.assertTrue(all(len(row) == 6 for row in doc["sqtt_funcmap"])) + self.assertEqual(doc["sqtt_funcmap_layout"], [[10, 12, 4]]) + self.assertEqual(doc["sqtt_funcmap_payloads"], [[10, 8, 1]]) + + def test_legacy_funcmap_leaves_code_json_unchanged(self): + doc = self.generate( + [codegen._parse_sqtt_funcmap(b"F:7:scope\nR:7:extra_payload_count=1\n")] + ) + + self.assertNotIn("sqtt_funcmap", doc) + self.assertNotIn("sqtt_funcmap_layout", doc) + self.assertNotIn("sqtt_funcmap_payloads", doc) + + def test_incomplete_packed_layout_is_ignored(self): + doc = self.generate([codegen._parse_sqtt_funcmap(b"M:shader_clock_bits=12\nF:7:scope\n")]) + + self.assertNotIn("sqtt_funcmap", doc) + self.assertNotIn("sqtt_funcmap_layout", doc) + + def test_packed_layout_does_not_require_named_entries(self): + doc = self.generate([codegen._parse_sqtt_funcmap(b"M:shader_clock_bits=12;shader_clock_shift=4\n")]) + + self.assertNotIn("sqtt_funcmap", doc) + self.assertEqual(doc["sqtt_funcmap_layout"], [[10, 12, 4]]) + + +if __name__ == "__main__": + unittest.main() diff --git a/projects/rocprof-trace-decoder/test/markers/run_tests.py b/projects/rocprof-trace-decoder/test/markers/run_tests.py index b1954cf8be55..f1ba4db32d80 100644 --- a/projects/rocprof-trace-decoder/test/markers/run_tests.py +++ b/projects/rocprof-trace-decoder/test/markers/run_tests.py @@ -119,15 +119,15 @@ def extract_funcmap(binary: str, tmpdir: str) -> str: # Find extracted code object import glob - cos = glob.glob(os.path.join(tmpdir, "*.hipv4-amdgcn-amd-amdhsa--*")) + cos = glob.glob(os.path.join(tmpdir, "*.hip*-amdgcn-amd-amdhsa--*")) if not cos: # llvm-objdump extracts relative to cwd or next to the binary base = os.path.basename(binary) - cos = glob.glob(os.path.join(tmpdir, f"{base}*.hipv4*")) + cos = glob.glob(os.path.join(tmpdir, f"{base}*.hip*-amdgcn-amd-amdhsa--*")) if not cos: # Try in the binary's directory bdir = os.path.dirname(binary) - cos = glob.glob(os.path.join(bdir, f"{os.path.basename(binary)}*.hipv4*")) + cos = glob.glob(os.path.join(bdir, f"{os.path.basename(binary)}*.hip*-amdgcn-amd-amdhsa--*")) if not cos: return "" @@ -368,8 +368,8 @@ def extract_funcmap(binary: str, tmpdir: str) -> str: "reject_funcmap": [], }, { - "name": "gfx9_addr_trace_nop_hazard", - "desc": "gfx9: s_nop 0 between s_ttracedata and s_mov_b32 m0 in addr trace", + "name": "gfx9_exec_m0_trace_spacing", + "desc": "gfx9: address EXEC traces use the explicit mov/nop/trace sequence", "env": {"SQTT_TRACE_ADDRESSES": "memory"}, "mode": "asm", "flags": ["--offload-arch=gfx90a"], @@ -379,7 +379,35 @@ def extract_funcmap(binary: str, tmpdir: str) -> str: "reject_ir": [], "expect_funcmap": [], "reject_funcmap": [], - "custom_asm_check": "nop_between_ttracedata_and_mov_m0", + "custom_asm_check": "gfx9_m0_trace_spacing", + }, + { + "name": "gfx10_full_m0_trace_nop", + "desc": "gfx10: every full M0 trace has s_nop 0 before s_ttracedata", + "env": {"SQTT_TRACE_ADDRESSES": "memory"}, + "mode": "asm", + "flags": ["--offload-arch=gfx1030"], + "expect_ir": [ + r"s_ttracedata\b", + ], + "reject_ir": [], + "expect_funcmap": [], + "reject_funcmap": [], + "custom_asm_check": "m0_nop_before_ttracedata", + }, + { + "name": "gfx12_full_m0_trace_nop", + "desc": "gfx12: every full M0 trace has s_nop 0 before s_ttracedata", + "env": {"SQTT_TRACE_ADDRESSES": "memory"}, + "mode": "asm", + "flags": ["--offload-arch=gfx1200"], + "expect_ir": [ + r"s_ttracedata\b", + ], + "reject_ir": [], + "expect_funcmap": [], + "reject_funcmap": [], + "custom_asm_check": "m0_nop_before_ttracedata", }, { "name": "addr_trace_unique_ids", @@ -504,6 +532,35 @@ def extract_funcmap(binary: str, tmpdir: str) -> str: "expect_funcmap": [], "reject_funcmap": [], }, + { + "name": "gfx12_shader_clock_disabled_asm", + "desc": "gfx12: disabled clock packing keeps immediate headers and dynamic payload traces", + "env": {"SQTT_SHADER_CLOCK_BITS": "0"}, + "mode": "asm", + "source": "marker", + "flags": ["--offload-arch=gfx1200"], + "expect_ir": [ + r"s_ttracedata_imm\b", + r"v_readfirstlane_b32\b", + ], + "reject_ir": [r"HW_REG_SHADER_CYCLES_LO"], + "expect_funcmap": [], + "reject_funcmap": [], + "custom_asm_check": "m0_nop_before_ttracedata", + }, + { + "name": "gfx12_shader_clock_disabled_funcmap", + "desc": "gfx12: disabled clock packing emits no funcmap layout row", + "env": {"SQTT_SHADER_CLOCK_BITS": "0"}, + "mode": "obj", + "source": "marker", + "flags": ["--offload-arch=gfx1200"], + "expect_ir": [], + "reject_ir": [], + "expect_funcmap": ["payload_value"], + "reject_funcmap": ["M:shader_clock_bits"], + "custom_funcmap_check": "data_marker_payload", + }, { "name": "gfx12_markers_no_global_inv", "desc": "gfx12 marker fences do not generate global cache invalidates", @@ -592,7 +649,7 @@ def run_test(test: dict, hipcc: str, verbose: bool) -> TestResult: try: if mode == "ir" or mode == "ir+obj": ir_path = os.path.join(tmpdir, f"{test['name']}.ll") - r = compile_test(env_vars, ["-S", "-emit-llvm"] + extra_flags, + r = compile_test(env_vars, ["-S", "-emit-llvm", "--offload-device-only"] + extra_flags, ir_path, hipcc, source=source) if r.returncode != 0: result.fail(f"Compilation failed (exit {r.returncode})") @@ -674,7 +731,7 @@ def run_test(test: dict, hipcc: str, verbose: bool) -> TestResult: ir_path = os.path.join(tmpdir, f"{test['name']}.ll") # Compile without pass plugin or SQTT_ENABLED env = os.environ.copy() - cmd = [hipcc, f"-I{INCLUDE_DIR}", "-S", "-emit-llvm", + cmd = [hipcc, f"-I{INCLUDE_DIR}", "-S", "-emit-llvm", "--offload-device-only", TEST_SOURCE, "-o", ir_path] r = subprocess.run(cmd, capture_output=True, text=True, env=env, timeout=120, cwd=PROJECT_ROOT) @@ -722,45 +779,52 @@ def check_funcmap(result: TestResult, funcmap: str, def check_custom_asm(result: TestResult, asm: str, check: str): """Run custom assembly validation checks.""" - if check == "nop_between_ttracedata_and_mov_m0": - # gfx9 hazard: s_mov_b32 m0 immediately followed by s_ttracedata - # requires an s_nop 0 in between. Extract instruction lines only. - insn_lines = [] - for line in asm.splitlines(): - stripped = line.strip() - if not stripped or stripped.startswith(";") or stripped.startswith("."): - continue - if stripped.endswith(":"): + if check not in {"m0_nop_before_ttracedata", "gfx9_m0_trace_spacing"}: + return + + # The pass emits direct M0 traces as mov/nop/trace. On gfx9, the backend + # may instead schedule an unrelated scalar instruction in that hazard + # slot, which is safe but means the final sequence is not contiguous. + strict_nop = check == "m0_nop_before_ttracedata" + insn_lines = [] + for line in asm.splitlines(): + stripped = line.strip() + if not stripped or stripped.startswith(";") or stripped.startswith("."): + continue + if stripped.endswith(":"): + continue + insn_lines.append(stripped) + + if strict_nop: + trace_count = 0 + for i, line in enumerate(insn_lines): + if not re.match(r"s_ttracedata\b", line): continue - insn_lines.append(stripped) - - violations = 0 - for i in range(len(insn_lines) - 1): - if re.match(r"s_mov_b32\s+m0\b", insn_lines[i]): - nxt = insn_lines[i + 1] - if re.match(r"s_ttracedata\b", nxt): - violations += 1 - result.fail( - f"s_mov_b32 m0 immediately followed by s_ttracedata " - f"(no s_nop): {insn_lines[i]!r} -> {nxt!r}") - if violations == 0: - # Verify we actually saw the pattern with the nop - saw_mov_m0 = False - saw_nop = False - for line in insn_lines: - if re.match(r"s_mov_b32\s+m0\b", line): - saw_mov_m0 = True - saw_nop = False - elif saw_mov_m0 and re.match(r"s_nop\b", line): - saw_nop = True - elif saw_mov_m0 and saw_nop and re.match(r"s_ttracedata\b", line): - break - else: - saw_mov_m0 = False - saw_nop = False - else: - result.fail("No s_mov_b32 m0 -> s_nop -> s_ttracedata sequence " - "found (expected at least one for exec mask hazard)") + trace_count += 1 + if (i < 2 or not re.match(r"s_nop\s+0\b", insn_lines[i - 1]) or + not re.match(r"s_mov_b32\s+m0\b", insn_lines[i - 2])): + before = insn_lines[max(0, i - 2):i + 1] + result.fail(f"full M0 trace does not use mov/nop 0/trace: {before!r}") + if trace_count == 0: + result.fail("No full M0 trace found") + return + + valid_sequences = 0 + for i in range(len(insn_lines) - 1): + if not re.match(r"s_mov_b32\s+m0\b", insn_lines[i]): + continue + nxt = insn_lines[i + 1] + if re.match(r"s_ttracedata\b", nxt): + result.fail( + f"s_mov_b32 m0 immediately followed by s_ttracedata " + f"(no spacing): {insn_lines[i]!r} -> {nxt!r}") + continue + if i + 2 < len(insn_lines) and re.match(r"s_nop\s+0\b", nxt) and re.match( + r"s_ttracedata\b", insn_lines[i + 2]): + valid_sequences += 1 + + if valid_sequences == 0: + result.fail("No s_mov_b32 m0 -> s_nop 0 -> s_ttracedata sequence found") def check_custom_funcmap(result: TestResult, funcmap: str, check: str): diff --git a/projects/rocprof-trace-decoder/test/unit/funcmap_test.cpp b/projects/rocprof-trace-decoder/test/unit/funcmap_test.cpp index c883b55ab3d5..46c39849a6cb 100644 --- a/projects/rocprof-trace-decoder/test/unit/funcmap_test.cpp +++ b/projects/rocprof-trace-decoder/test/unit/funcmap_test.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include using namespace rocprof_trace_decoder::codeobj; @@ -108,13 +109,111 @@ TEST(DecodeMarkerValue, ShaderClockEncoding) uint32_t raw = (0xABCu << 20) | (42u << 2) | 0b10u; auto m = decode_marker_value(raw, enc); EXPECT_EQ(m.id, 42u); - EXPECT_EQ(m.shader_clock, 0xABCu); + EXPECT_EQ(enc.decode_shader_clock(raw), 0xABCu); EXPECT_TRUE(m.is_enter); EXPECT_FALSE(m.exit_prev); Funcmap fm; fm.marker_encoding = enc; - EXPECT_EQ(decode_marker_value(raw, fm).id, 42u); + EXPECT_EQ(decode_marker_value(raw, fm).id, raw >> 2); +} + +TEST(MarkerEncoding, ExposesRawAndSourceClockMasks) +{ + MarkerEncoding enc{12, 4}; + + // The raw shaderdata word reserves bits [31:20] for the packed clock, + // bits [19:2] for the ID, and preserves the two low flags. + EXPECT_TRUE(enc.is_valid()); + EXPECT_TRUE(enc.has_shader_clock()); + EXPECT_EQ(enc.marker_id_mask(), 0x000FFFFCu); + EXPECT_EQ(enc.packed_shader_clock_mask(), 0xFFF00000u); + + // The sampled clock was HW_REG_SHADER_CYCLES_LO bits [15:4]. + EXPECT_EQ(enc.shader_clock_source_mask(), 0x0000FFF0u); + + uint32_t raw = (0xABCu << 20) | (42u << 2) | 0b10u; + EXPECT_EQ((raw & enc.marker_id_mask()) >> 2, 42u); + EXPECT_EQ(raw & enc.packed_shader_clock_mask(), 0xABC00000u); +} + +TEST(MarkerEncoding, LegacyEncodingRemainsFullWidthIdOnly) +{ + MarkerEncoding enc{}; + + EXPECT_TRUE(enc.is_valid()); + EXPECT_FALSE(enc.has_shader_clock()); + EXPECT_EQ(enc.marker_id_mask(), 0xFFFFFFFCu); + EXPECT_EQ(enc.packed_shader_clock_mask(), 0u); + EXPECT_EQ(enc.shader_clock_source_mask(), 0u); + + uint32_t raw = (0x3FFFFFFFu << 2) | 0b01u; + EXPECT_EQ(decode_marker_value(raw, enc).id, 0x3FFFFFFFu); + EXPECT_EQ(enc.decode_shader_clock(raw), 0u); + + MarkerEncoding invalid{30, 0}; + EXPECT_FALSE(invalid.is_valid()); + EXPECT_EQ(decode_marker_value(raw, invalid).id, 0x3FFFFFFFu); +} + +TEST(MarkerEncoding, SupportsBoundaryClockWindows) +{ + MarkerEncoding high_bit{1, 31}; + EXPECT_TRUE(high_bit.is_valid()); + EXPECT_EQ(high_bit.marker_id_mask(), 0x7FFFFFFCu); + EXPECT_EQ(high_bit.packed_shader_clock_mask(), 0x80000000u); + EXPECT_EQ(high_bit.shader_clock_source_mask(), 0x80000000u); + + MarkerEncoding wide{29, 3}; + EXPECT_TRUE(wide.is_valid()); + EXPECT_EQ(wide.marker_id_mask(), 0x00000004u); + EXPECT_EQ(wide.packed_shader_clock_mask(), 0xFFFFFFF8u); + EXPECT_EQ(wide.shader_clock_source_mask(), 0xFFFFFFF8u); +} + +TEST(DecodeMarkerValue, FuncmapLeavesUnregisteredValuesLegacy) +{ + Funcmap fm; + fm.marker_encoding = {12, 4}; + auto entry = std::make_shared(); + entry->kind = FuncmapEntryKind::Point; + entry->id = 42; + fm.entries.push_back(entry); + fm.by_id.emplace(entry->id, entry); + + uint32_t known = (0xABCu << 20) | (42u << 2); + EXPECT_EQ(decode_marker_value(known, fm).id, 42u); + EXPECT_EQ(fm.marker_encoding.decode_shader_clock(known), 0xABCu); + + uint32_t numeric = (0xABCu << 20) | (123u << 2); + EXPECT_EQ(decode_marker_value(numeric, fm).id, numeric >> 2); + + uint32_t exit = (0xABCu << 20) | 1u; + EXPECT_EQ(decode_marker_value(exit, fm).id, 0u); + EXPECT_EQ(fm.marker_encoding.decode_shader_clock(exit), 0xABCu); +} + +TEST(FuncmapCompatibility, ExistingAggregateInitializersStillWork) +{ + // Keep source compatibility for consumers that aggregate-initialize the + // public structs from the pre-packed-marker layout. + std::vector diagnostics; + Funcmap map{ + std::vector{}, + std::unordered_map{}, + 0, + diagnostics, + }; + FuncmapEntry entry{FuncmapEntryKind::Point, 7, "point", "source.cpp:1", 0x100000000ull}; + MarkerValue marker{7, true, false}; + + EXPECT_TRUE(map.diagnostics.empty()); + EXPECT_FALSE(map.marker_encoding.has_shader_clock()); + EXPECT_EQ(entry.vaddr, 0x100000000ull); + EXPECT_EQ(entry.extra_payload_count, 0u); + EXPECT_EQ(marker.id, 7u); + EXPECT_TRUE(marker.is_enter); + EXPECT_FALSE(marker.exit_prev); } // ─── parse_funcmap_section ────────────────────────────────────────────────── @@ -169,6 +268,17 @@ TEST(ParseFuncmap, ParsesExtraPayloadAndMarkerEncoding) EXPECT_EQ(p->extra_payload_count, 1u); EXPECT_EQ(m.marker_encoding.shader_clock_bits, 12u); EXPECT_EQ(m.marker_encoding.shader_clock_shift, 4u); + EXPECT_EQ(m.marker_encoding.marker_id_mask(), 0x000FFFFCu); + EXPECT_EQ(m.marker_encoding.packed_shader_clock_mask(), 0xFFF00000u); + EXPECT_EQ(m.marker_encoding.shader_clock_source_mask(), 0x0000FFF0u); +} + +TEST(ParseFuncmap, PackedMarkerEncodingRequiresClockShift) +{ + Funcmap m = parse_funcmap_section("M:shader_clock_bits=12\n", /*silent=*/true); + + EXPECT_FALSE(m.marker_encoding.has_shader_clock()); + EXPECT_TRUE(has_warning(m.diagnostics, "malformed M:")); } TEST(ParseFuncmap, ToleratesBlankLinesCRLFAndTrailingNUL) diff --git a/projects/rocprof-trace-decoder/test/unit/markers_test.cpp b/projects/rocprof-trace-decoder/test/unit/markers_test.cpp index 7d5d8ca4ecd5..edb2d6936587 100644 --- a/projects/rocprof-trace-decoder/test/unit/markers_test.cpp +++ b/projects/rocprof-trace-decoder/test/unit/markers_test.cpp @@ -29,6 +29,7 @@ #include "llvm/IR/Constants.h" #include "llvm/IR/Function.h" +#include "llvm/IR/InlineAsm.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/IntrinsicsAMDGPU.h" #include "llvm/IR/Metadata.h" @@ -138,13 +139,15 @@ void useFullScopeMasks(SQTTConfig& config) config.MemBarrier = MemBarrierMode::None; } -CallInst* insertTraceCallBefore(Instruction* insertPt, uint32_t encoded) +CallInst* insertTraceCallBefore(Instruction* insertPt, uint32_t encoded, bool passHeader = false) { Module* module = insertPt->getModule(); LLVMContext& ctx = module->getContext(); IRBuilder<> builder(insertPt); Function* trace = Intrinsic::getOrInsertDeclaration(module, Intrinsic::amdgcn_s_ttracedata); - return builder.CreateCall(trace, {ConstantInt::get(Type::getInt32Ty(ctx), encoded)}); + CallInst* call = builder.CreateCall(trace, {ConstantInt::get(Type::getInt32Ty(ctx), encoded)}); + if (passHeader) call->setMetadata("sqtt.marker_header", MDNode::get(ctx, {})); + return call; } void addEarlyFunctionMetadata(Function& function, uint32_t id, unsigned preOptSize, StringRef sourceLoc) @@ -255,6 +258,25 @@ std::vector traceMarkerValues(const Function& function) return values; } +const CallInst* findM0NopTrace(const Function& function) +{ + constexpr const char TraceAsm[] = + "s_mov_b32 m0, $1\n" + "s_nop 0\n" + "s_ttracedata"; + for (const BasicBlock& block : function) + { + for (const Instruction& inst : block) + { + auto* call = dyn_cast(&inst); + if (!call) continue; + auto* asmCall = dyn_cast(call->getCalledOperand()); + if (asmCall && asmCall->hasSideEffects() && asmCall->getAsmString() == TraceAsm) return call; + } + } + return nullptr; +} + bool hasPtrToIntFromAddressSpace(const Module& module, unsigned addressSpace, unsigned resultBits) { for (const Function& function : module) @@ -590,6 +612,80 @@ TEST(MarkerPass, Gfx9BufferAndPermuteAddressTracesUseInlineAsmExecProtocol) expectContains(ir, "s_mov_b32 m0, exec_lo"); expectContains(ir, "s_nop 0"); expectContains(ir, "s_ttracedata"); + expectContains(ir, "={m0}"); +} + +TEST(MarkerPass, Gfx10PlusFullM0TracesUseExplicitNopSequence) +{ + LLVMContext ctx; + auto module = makeModule(ctx); + const uint32_t markerValue = encodeMarker(64, false, false); + MDNode* traceMetadata = MDNode::get(ctx, {}); + + std::vector functions; + for (const auto& [name, cpu] : {std::pair{"gfx9_trace", "gfx90a"}, + std::pair{"gfx10_trace", "gfx1030"}, + std::pair{"gfx12_trace", "gfx1200"}}) + { + Function* function = makeVoidFunction(*module, name, cpu); + CallInst* trace = insertTraceCallBefore(function->getEntryBlock().getTerminator(), markerValue); + trace->setMetadata("sqtt.test.trace", traceMetadata); + functions.push_back(function); + } + + SQTTConfig config; + useFullScopeMasks(config); + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + + // gfx9's normal intrinsic lowering already inserts the M0 hazard nop. + // Keep that established path intact; gfx10+ needs the explicit sequence. + EXPECT_EQ(countIntrinsicCalls(*functions[0], Intrinsic::amdgcn_s_ttracedata), 1u); + EXPECT_EQ(findM0NopTrace(*functions[0]), nullptr); + for (size_t index = 1; index < functions.size(); ++index) + { + const CallInst* trace = findM0NopTrace(*functions[index]); + ASSERT_NE(trace, nullptr) << functions[index]->getName().str(); + auto* inlineAsm = dyn_cast(trace->getCalledOperand()); + ASSERT_NE(inlineAsm, nullptr); + EXPECT_EQ(inlineAsm->getConstraintString(), "={m0},i"); + EXPECT_EQ(trace->getMetadata("sqtt.test.trace"), traceMetadata); + } +} + +TEST(MarkerPass, Gfx12PacksOnlyPassGeneratedHeaders) +{ + LLVMContext ctx; + auto module = makeModule(ctx); + Type* i32 = Type::getInt32Ty(ctx); + Function* function = makeFunction(*module, "numeric_marker", "gfx1200", FunctionType::get(i32, {i32}, false)); + BasicBlock* entry = BasicBlock::Create(ctx, "entry", function); + IRBuilder<> builder(entry); + Value* value = builder.CreateAdd(function->getArg(0), ConstantInt::get(i32, 1)); + builder.CreateAdd(value, ConstantInt::get(i32, 2)); + Instruction* ret = builder.CreateRet(value); + + // This mirrors the numeric marker API: it has no funcmap entry and must + // keep its full legacy value even when the same module has packed headers. + insertTraceCallBefore(ret, encodeMarker(1u << 20, false, false)); + Function* trace = Intrinsic::getOrInsertDeclaration(module.get(), Intrinsic::amdgcn_s_ttracedata); + builder.SetInsertPoint(ret); + builder.CreateCall(trace, {value}); + + SQTTConfig config; + useFullScopeMasks(config); + config.FunctionThreshold = 1; + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + + // Entry and exit function markers read the shader clock; neither + // unregistered numeric marker reads it or trips the packed-ID range check. + EXPECT_EQ(countIntrinsicCalls(*function, Intrinsic::amdgcn_s_getreg), 2u); + expectContains(getFuncMap(*module), "M:shader_clock_bits=12;shader_clock_shift=4"); } TEST(MarkerPass, BarrierInstrumentationHandlesSplitAndStandaloneBarriers) @@ -689,8 +785,8 @@ TEST(MarkerPass, FunctionThresholdPrunesMarkersAndPreservesExistingLlvmUsed) Function* small = makeVoidFunction(*module, "small_function", "gfx1100"); Instruction* smallRet = small->getEntryBlock().getTerminator(); - insertTraceCallBefore(smallRet, encodeMarker(smallId, true, false)); - insertTraceCallBefore(smallRet, encodeMarker(smallId, false, true)); + insertTraceCallBefore(smallRet, encodeMarker(smallId, true, false), true); + insertTraceCallBefore(smallRet, encodeMarker(smallId, false, true), true); addEarlyFunctionMetadata(*small, smallId, 1, "small.hip:3"); Function* large = @@ -702,10 +798,15 @@ TEST(MarkerPass, FunctionThresholdPrunesMarkersAndPreservesExistingLlvmUsed) builder.CreateRetVoid(); Instruction* firstLargeInst = &*large->getEntryBlock().getFirstInsertionPt(); Instruction* largeRet = large->getEntryBlock().getTerminator(); - insertTraceCallBefore(firstLargeInst, encodeMarker(largeId, true, false)); - insertTraceCallBefore(largeRet, encodeMarker(largeId, false, true)); + insertTraceCallBefore(firstLargeInst, encodeMarker(largeId, true, false), true); + insertTraceCallBefore(largeRet, encodeMarker(largeId, false, true), true); addEarlyFunctionMetadata(*large, largeId, 40, "large.hip:17"); + // This unregistered numeric marker happens to use the pruned function's + // old ID. It must not be removed or rewritten with pass-owned headers. + Function* numeric = makeVoidFunction(*module, "numeric_marker", "gfx90a"); + insertTraceCallBefore(numeric->getEntryBlock().getTerminator(), encodeMarker(smallId, true, false)); + addEarlyFunctionMapEntry(*module, 99, "inlined_large_function", 40, "inlined.hip:21"); addEarlyFunctionMapEntry(*module, 100, "inlined_small_function", 1, "inlined.hip:4"); addExistingLlvmUsed(*module); @@ -727,4 +828,5 @@ TEST(MarkerPass, FunctionThresholdPrunesMarkersAndPreservesExistingLlvmUsed) EXPECT_EQ(countIntrinsicCalls(*small, Intrinsic::amdgcn_s_ttracedata), 0u); EXPECT_EQ(countIntrinsicCalls(*small, Intrinsic::amdgcn_s_ttracedata_imm), 0u); + EXPECT_EQ(traceMarkerValues(*numeric), std::vector{encodeMarker(smallId, true, false)}); } From 3adae6a5f5734ee2dfa45cd435adc607c914ed22 Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 16:56:14 +0200 Subject: [PATCH 02/13] Fix retirement and cross-SIMD --- .../python/rocprof_trace_decoder/att.py | 28 ++++--- .../test/att_tool_test.py | 74 ++++++++++++++++++- 2 files changed, 83 insertions(+), 19 deletions(-) diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index e2448ae3a62d..2f71c163649a 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -169,10 +169,9 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict for record in records.occupancy: occupancy[se, record.cu, record.simd, record.wave_id].append(record) - headers: list[tuple[object, int, int, int, list[object]]] = [] - minimum_delay: dict[tuple[int, int], int] = {} + headers: list[tuple[object, tuple[int, int, int, int, int], int, list[object]]] = [] + minimum_delay: dict[tuple[int, int, int, int, int], int] = {} for location, stream in shaderdata.items(): - se = location[0] stream.sort(key=lambda record: record.time) intervals = _active_codeobj_intervals(occupancy.get(location, ())) interval = 0 @@ -214,13 +213,15 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict mask = ((1 << bits) - 1) << shift sampled = ((raw >> (32 - bits)) & ((1 << bits) - 1)) << shift delay = ((int(record.time) & mask) - sampled) & mask - headers.append((record, se, mask, delay, header_payloads)) - key = se, mask - minimum_delay[key] = min(minimum_delay.get(key, delay), delay) + # shader_cycles_lo samples are only comparable on one physical + # SIMD. Keep differently encoded clock windows independent too. + clock_domain = (*location[:3], bits, shift) + headers.append((record, clock_domain, delay, header_payloads)) + minimum_delay[clock_domain] = min(minimum_delay.get(clock_domain, delay), delay) changed = False - for record, se, mask, delay, header_payloads in headers: - correction = delay - minimum_delay[se, mask] + for record, clock_domain, delay, header_payloads in headers: + correction = delay - minimum_delay[clock_domain] if correction: record.time -= correction # The output writer sorts shaderdata after correction. Move raw @@ -235,18 +236,15 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict def _active_codeobj_intervals(occupancy: Iterable[object]) -> list[tuple[int, int, int]]: - events = sorted(occupancy, key=lambda record: (record.time, record.start)) + """Associate a wave slot with its last launch until its next launch.""" + events = sorted((record for record in occupancy if record.start), key=lambda record: record.time) intervals: list[tuple[int, int, int]] = [] begin = 0 codeobj: int | None = None for record in events: - if record.start: - if codeobj is not None: - intervals.append((begin, record.time, codeobj)) - begin, codeobj = record.time, record.pc.code_object_id - elif codeobj is not None: + if codeobj is not None: intervals.append((begin, record.time, codeobj)) - codeobj = None + begin, codeobj = record.time, record.pc.code_object_id if codeobj is not None: intervals.append((begin, (1 << 63) - 1, codeobj)) return intervals diff --git a/projects/rocprof-trace-decoder/test/att_tool_test.py b/projects/rocprof-trace-decoder/test/att_tool_test.py index 895fd24c321a..fbd3526586ed 100644 --- a/projects/rocprof-trace-decoder/test/att_tool_test.py +++ b/projects/rocprof-trace-decoder/test/att_tool_test.py @@ -17,12 +17,12 @@ from rocprof_trace_decoder.records import Occupancy, Pc, ShaderData, ShaderDataFlags, TraceRecords -def _shaderdata(time: int, value: int, flags: int = 0) -> ShaderData: - return ShaderData(time, value, cu=1, simd=2, wave_id=3, flags=flags, reserved=0) +def _shaderdata(time: int, value: int, flags: int = 0, simd: int = 2) -> ShaderData: + return ShaderData(time, value, cu=1, simd=simd, wave_id=3, flags=flags, reserved=0) -def _occupancy(start: int, time: int, codeobj: int = 7) -> Occupancy: - return Occupancy(Pc(0, codeobj), time, 0, 1, 2, 3, start, 0, 0, 0, 0, 0) +def _occupancy(start: int, time: int, codeobj: int = 7, simd: int = 2) -> Occupancy: + return Occupancy(Pc(0, codeobj), time, 0, 1, simd, 3, start, 0, 0, 0, 0, 0) class _Decoder: @@ -165,6 +165,72 @@ def test_correction_is_default(self): self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x1010]) self.assertEqual([record.value for record in records.shaderdata], [12, 12]) + def test_correction_does_not_share_the_reference_between_simds(self): + value = (0x100 << 20) | (3 << 2) + slow_simd = _shaderdata(0x1080, value, simd=2) + fast_simd = _shaderdata(0x1010, value, simd=3) + records = TraceRecords( + occupancy=[_occupancy(1, 0, simd=2), _occupancy(1, 0, simd=3)], + shaderdata=[slow_simd, fast_simd], + ) + + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + }, + ) + + self.assertEqual(slow_simd.time, 0x1080) + self.assertEqual(fast_simd.time, 0x1010) + self.assertEqual([record.value for record in records.shaderdata], [12, 12]) + + def test_late_header_after_retirement_is_normalized_and_corrected(self): + value = (0x100 << 20) | (3 << 2) + before_retirement = _shaderdata(0x1010, value) + after_retirement = _shaderdata(0x1080, value) + records = TraceRecords( + occupancy=[_occupancy(1, 0), _occupancy(0, 0x1020)], + shaderdata=[before_retirement, after_retirement], + ) + + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + }, + ) + + self.assertEqual(before_retirement.time, 0x1010) + self.assertEqual(after_retirement.time, 0x1010) + self.assertEqual([record.value for record in records.shaderdata], [12, 12]) + + def test_later_launch_supersedes_a_retired_wave_slot(self): + value = (0x80 << 21) | (4 << 2) + first = _shaderdata(0x1020, value) + second = _shaderdata(0x1080, value) + records = TraceRecords( + occupancy=[ + _occupancy(1, 0, codeobj=7), + _occupancy(0, 0x20, codeobj=7), + _occupancy(1, 0x40, codeobj=8), + ], + shaderdata=[first, second], + ) + + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 3, "P", "first", "", 0], [8, 4, "P", "second", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4], [8, 11, 5]], + }, + ) + + self.assertEqual([record.time for record in records.shaderdata], [0x1020, 0x1020]) + self.assertEqual([record.value for record in records.shaderdata], [16, 16]) + def test_no_decode_markers_preserves_packed_values(self): value = (0x100 << 20) | (3 << 2) records = TraceRecords( From 4c0cfa332aedb45f474f159680bd693c79e05fc8 Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 17:22:47 +0200 Subject: [PATCH 03/13] Fixes --- .../docs/markers/SPEC.md | 59 ++- .../rocprof-trace-decoder/markers/README.md | 19 +- .../markers/SQTTAddressTrace.cpp | 19 +- .../markers/SQTTBarriers.cpp | 2 +- .../markers/SQTTFunctions.cpp | 10 + .../markers/SQTTMarkers.cpp | 20 + .../markers/SQTTNamedMarkers.cpp | 7 +- .../markers/SQTTPass.cpp | 30 +- .../rocprof-trace-decoder/markers/SQTTPass.h | 8 +- .../markers/SQTTTarget.h | 3 + .../python/rocprof_trace_decoder/att.py | 15 +- .../scripts/sqtt_data.py | 151 ++++--- .../scripts/sqtt_flamegraph.py | 33 +- .../scripts/sqtt_memory_trace.py | 29 +- .../scripts/sqtt_perfetto.py | 71 ++-- .../test/att_tool_test.py | 42 ++ .../test/markers/run_tests.py | 15 + .../test/unit/markers_test.cpp | 384 +++++++++++++++++- 18 files changed, 751 insertions(+), 166 deletions(-) diff --git a/projects/rocprof-trace-decoder/docs/markers/SPEC.md b/projects/rocprof-trace-decoder/docs/markers/SPEC.md index b54270f766bd..7537b8101ea7 100644 --- a/projects/rocprof-trace-decoder/docs/markers/SPEC.md +++ b/projects/rocprof-trace-decoder/docs/markers/SPEC.md @@ -85,9 +85,8 @@ Where `flags` is the OR of applicable bits: `EXIT_PREV=1`, `ENTER=2`. - If `encoded <= 0xFF`, the target supports it, and shader-clock packing is inactive: emit `s_ttracedata_imm` (8-bit immediate, no m0 setup, ~1 cycle faster) -- Otherwise: emit `s_ttracedata` through M0. On gfx10+, the pass explicitly - emits `s_mov_b32 m0, ` + `s_nop 0` + `s_ttracedata`; gfx9's backend - already provides the required hazard spacing for ordinary traces. +- Otherwise: emit `s_ttracedata` through M0. The pass explicitly emits + `s_mov_b32 m0, ` + `s_nop 0` + `s_ttracedata` on every target. With the 2-bit encoding, IDs 1-63 fit in `s_ttracedata_imm` (previously limited to IDs 1-15 with 4-bit flags). @@ -124,29 +123,41 @@ gfx12 uses the no-clock full-ID layout and may use `s_ttracedata_imm`. A nonzero `shader_clock_bits` value requires `shader_clock_shift`; a malformed packed row uses the no-clock full-ID layout. +Address trace blocks have `R:extra_payload_count > 1`. If a module emits any +such block, the automatic gfx12 clock width is `0`; an explicit nonzero +`SQTT_SHADER_CLOCK_BITS` is an error. A named `sqtt_marker_data()` record has +exactly one payload (`R:...=1`) and remains eligible for clock packing. + The sampled window lets a decoder compensate for the variable delay between the trace instruction issuing and its shaderdata record appearing in SQTT. For each **marker header** (never an `R:`-declared raw payload), restore the -sampled field to its source positions and calculate the delay modulo the -source-clock window: +sampled field to its source positions and calculate a safe lower bound for the +delay modulo the source-clock window: ```text -arrival_window = uint32_t(record_time) & shader_clock_source_mask +window_mask = (1ULL << (clock_bits + shader_clock_shift)) - 1 +bucket_size = 1ULL << shader_clock_shift +arrival_window = uint32_t(record_time) & window_mask sampled_window = packed_clock_field << shader_clock_shift -delay = (arrival_window - sampled_window) & shader_clock_source_mask +delta = (arrival_window - sampled_window) & window_mask +delay_lower_bound = max(0, delta - (bucket_size - 1)) ``` -Only a relative delay is knowable from this truncated clock. Keep the smallest -observed delay for each shader-engine clock domain and compatible layout as the -reference, then shift every header earlier by `delay - minimum_delay`. This -preserves that stream's unknown common time origin while removing the extra -delivery latency. The actual delivery delay must be shorter than the sampled -clock window (the default 12-bit field at shift 4 has a 65,536-cycle window), -otherwise the modulo value is ambiguous. +Only a relative delay is knowable from this truncated clock. The low +`shader_clock_shift` bits were not sampled, so the lower bound avoids moving a +record before any possible issue time; it leaves at most +`(1 << shader_clock_shift) - 1` cycles of residual jitter. Keep the smallest +observed lower bound for each shader-engine clock domain and compatible layout +as the reference, then shift every header earlier by +`delay_lower_bound - minimum_delay`. The actual delivery delay must be shorter +than the sampled clock window (the default 12-bit field at shift 4 has a +65,536-cycle window), otherwise the modulo value is ambiguous. `att_tool.py` applies this correction while writing JSON by default; `--no-decode-markers` preserves the packed values and arrival timestamps. It -rewrites each corrected header to the legacy `(id << 2) | flags` form. +rewrites each corrected header to the legacy `(id << 2) | flags` form. For an +externally produced packed multi-payload block, JSON output may normalize a +recognized header but does not apply timestamp correction. ### Semantic rules @@ -327,7 +338,7 @@ All variables are read at **compile time** by the pass plugin. | `SQTT_INSTRUMENT_FUNCTIONS`| `0` | Function entry/exit threshold (0=disabled) | | `SQTT_INSTRUMENT_MEMORY` | off | Memory op markers. Format: `N:M` (N=ops per marker, M=max gap) | | `SQTT_MEM_BARRIER` | `fence` | Reordering boundary around markers (`none`/`asm`/`fence` or `0`/`1`/`2`) | -| `SQTT_SHADER_CLOCK_BITS` | auto | Gfx12 shader clock bits in marker headers (`12` on gfx12, `0` elsewhere; `0` disables) | +| `SQTT_SHADER_CLOCK_BITS` | auto | Gfx12 shader clock bits (`12` on gfx12 unless an address block is emitted, then `0`; `0` disables). A nonzero explicit value is invalid with address blocks. | | `SQTT_SHADER_CLOCK_SHIFT` | `4` | Source bit offset in `HW_REG_SHADER_CYCLES_LO` for shader clock packing | ### Marker reorder boundary (`SQTT_MEM_BARRIER`) @@ -805,7 +816,7 @@ as cache line utilization, stride detection, and memory coalescing analysis. | Variable | Format | Default | Description | |---|---|---|---| -| `SQTT_TRACE_ADDRESSES` | `memory`, `lds`, or `memory,lds` | off | Trace per-lane addresses. `memory` = global/buffer/flat/atomics. `lds` = LDS (AS=3), LDS atomics, ds_permute/ds_bpermute. | +| `SQTT_TRACE_ADDRESSES` | `memory`, `lds`, or `memory,lds` | off | Trace per-lane addresses. `memory` = flat/global (AS=0/1), buffer, and matching atomics. `lds` = LDS (AS=3), LDS atomics, ds_permute/ds_bpermute. | `SQTT_TRACE_ADDRESSES` and `SQTT_INSTRUMENT_MEMORY` are mutually exclusive — setting both produces an error. Address tracing already emits a header marker @@ -823,7 +834,9 @@ for every memory op, so `SQTT_INSTRUMENT_MEMORY` is redundant. | `struct_buffer` | buffer | 32-bit | 2 (voffset+vindex) | 3 (rsrc_lo, rsrc_hi, soffset) | 67 | | `ds_permute`/`ds_bpermute` | LDS hw | 32-bit | 1 (index) | 0 | 32 | -Private/scratch (AS=5) is always excluded. All categories also emit exec_lo + exec_hi (2 tokens) and a header marker (1 token) before the data tokens shown above. +Pointer address spaces other than 0, 1, and selected LDS AS=3 (including +private/scratch AS=5) are excluded. All categories also emit exec_lo + exec_hi +(2 tokens) and a header marker (1 token) before the data tokens shown above. ### Wave size @@ -913,9 +926,9 @@ unified ID counter. The funcmap records each ID with: | Funcmap name prefix | Instruction type | Gated by | |---|---|---| -| `addr_trace_load` | Global/flat load | `memory` | -| `addr_trace_store` | Global/flat store | `memory` | -| `addr_trace_atomic` | Global/flat atomic (AtomicRMW, CmpXchg) | `memory` | +| `addr_trace_load` | Flat/global (AS=0/1) load | `memory` | +| `addr_trace_store` | Flat/global (AS=0/1) store | `memory` | +| `addr_trace_atomic` | Flat/global (AS=0/1) atomic (AtomicRMW, CmpXchg) | `memory` | | `addr_trace_lds_load` | LDS load | `lds` | | `addr_trace_lds_store` | LDS store | `lds` | | `addr_trace_lds_atomic` | LDS atomic | `lds` | @@ -987,11 +1000,13 @@ in post-processing. The pass handles operand layout differences across load, store, atomic, and cmpswap variants. The rsrc operand may be `<4 x i32>` (legacy) or `ptr addrspace(8)` (modern) — both are supported. +`*.buffer.load.lds` is excluded because its LDS-destination operand layout is +not this protocol. ### Atomic operations `AtomicRMWInst` and `AtomicCmpXchgInst` are classified by address space the -same way as loads/stores (AS=3 → LDS, AS=5 → excluded, otherwise → memory). +same way as loads/stores (AS=3 → LDS, AS=0/1 → memory, all others excluded). They receive distinct funcmap names (`addr_trace_atomic`, `addr_trace_lds_atomic`) rather than being folded into load/store categories, so the decoder can distinguish atomic access patterns. diff --git a/projects/rocprof-trace-decoder/markers/README.md b/projects/rocprof-trace-decoder/markers/README.md index eca447dc54c2..46200ca86729 100644 --- a/projects/rocprof-trace-decoder/markers/README.md +++ b/projects/rocprof-trace-decoder/markers/README.md @@ -177,6 +177,12 @@ to report only active lanes. The parser demultiplexes interleaved records from concurrent waves by grouping on `(cu, simd, wave_id)` before parsing each wave's address block. +Address blocks always have `extra_payload_count > 1`, so automatic gfx12 +shader-clock packing is disabled for the whole module when one is emitted. An +explicit nonzero `SQTT_SHADER_CLOCK_BITS` is an error in that configuration. +This does not apply to `sqtt_marker_data()`, whose single raw payload remains +eligible for clock packing. + ### Everything together All options compose. User markers, auto-function, auto-barrier, memory ops, @@ -280,9 +286,9 @@ directly via `getenv()`. | `SQTT_INSTRUMENT_FUNCTIONS` | `N` or `cost:N` | off | Instrument device functions exceeding threshold. `20` = instruction count > 20. `cost:100` = weighted cost > 100. | | `SQTT_INSTRUMENT_BARRIERS` | `0`, `1` | `0` | Instrument barriers. Consecutive signal+wait pairs fuse to a single marker. | | `SQTT_INSTRUMENT_MEMORY` | `N:M` | off | Instrument memory ops. N = ops per marker, M = max gap. `2:5` = 1 marker per 2 ops, sequence breaks at gap > 5. Covers global, buffer, flat (not LDS/scratch). | -| `SQTT_TRACE_ADDRESSES` | `memory`, `lds`, or both | off | Trace per-lane virtual addresses. Mutually exclusive with `SQTT_INSTRUMENT_MEMORY`. `memory` = global/buffer/flat, `lds` = LDS (AS=3). Expensive. | +| `SQTT_TRACE_ADDRESSES` | `memory`, `lds`, or both | off | Trace per-lane virtual addresses. Mutually exclusive with `SQTT_INSTRUMENT_MEMORY`. `memory` = flat/global (AS=0/1) and buffer, `lds` = LDS (AS=3). Expensive. | | `SQTT_MEM_BARRIER` | `none` / `asm` / `fence` (or `0` / `1` / `2`) | `fence` | Reordering boundary planted around every marker. `fence` (default) emits `fence syncscope("workgroup") acq_rel` tagged with AMDGPU local/LDS synchronization metadata, anchoring markers against optimizer and scheduler movement without marker-generated global cache invalidation. `asm` plants an empty `~{memory}` inline asm (IR/MIR-only constraint, no machine code). `none` disables both. Default favors marker accuracy; opt down for tight kernels. | -| `SQTT_SHADER_CLOCK_BITS` | unsigned integer | auto (`12` on gfx12, `0` elsewhere) | Number of gfx12 marker header high bits reserved for shader clock. `0` disables clock packing. | +| `SQTT_SHADER_CLOCK_BITS` | unsigned integer | auto (`12` on gfx12 unless an address block is emitted, then `0`) | Number of gfx12 marker header high bits reserved for shader clock. `0` disables clock packing; a nonzero explicit value is invalid with address blocks. | | `SQTT_SHADER_CLOCK_SHIFT` | unsigned integer | `4` | Source bit offset in `HW_REG_SHADER_CYCLES_LO` for the packed shader clock field. | ## Examples @@ -445,10 +451,9 @@ Marker values are packed into 2 flag bits so that small IDs can use the faster packing is inactive. Larger IDs fall back to `s_ttracedata` (32-bit m0, all targets). -For full M0-based traces, gfx10+ explicitly emits `s_mov_b32 m0`, `s_nop 0`, -then `s_ttracedata`; gfx9's backend already provides the required hazard -spacing for ordinary traces. Address EXEC tracing uses the explicit sequence -on every target. +For full M0-based traces, the pass explicitly emits `s_mov_b32 m0`, `s_nop 0`, +then `s_ttracedata` on every target. Address EXEC tracing uses the same +sequence. ``` Both instructions share the same bit layout: @@ -488,6 +493,8 @@ full-width ID layout. `att_tool.py` applies that correction by default and masks each recognized header to its ordinary legacy-form marker value for existing viewers. Use `--no-decode-markers` to preserve packed values and arrival timestamps. +For externally packed multi-payload blocks it may normalize a recognized +header, but does not apply timestamp correction. For packed code objects, `att_tool.py` adds six-column `sqtt_funcmap` rows and packed-layout/payload tables to `code.json`. Readers that do not know them diff --git a/projects/rocprof-trace-decoder/markers/SQTTAddressTrace.cpp b/projects/rocprof-trace-decoder/markers/SQTTAddressTrace.cpp index 91ae05ef7186..0a397e658a3b 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTAddressTrace.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTAddressTrace.cpp @@ -57,9 +57,12 @@ SQTTInstrumentPass::AddrTraceKind SQTTInstrumentPass::classifyAddrTraceOp( // Helper lambda for pointer-based address space classification auto classifyAS = [&](unsigned AS) -> AddrTraceKind { - if (AS == 5) return AddrTraceKind::None; // private if (AS == 3) return traceLDS ? AddrTraceKind::LDS : AddrTraceKind::None; - return traceMemory ? AddrTraceKind::Memory : AddrTraceKind::None; + // The memory address protocol is defined only for flat (0) and + // global (1) pointers. Other AMDGPU address spaces have different + // representations and must not be reinterpreted as global addresses. + if (AS == 0 || AS == 1) return traceMemory ? AddrTraceKind::Memory : AddrTraceKind::None; + return AddrTraceKind::None; }; if (auto* LI = dyn_cast(I)) return classifyAS(LI->getPointerAddressSpace()); @@ -72,7 +75,12 @@ SQTTInstrumentPass::AddrTraceKind SQTTInstrumentPass::classifyAddrTraceOp( Function* Callee = CI->getCalledFunction(); if (!Callee) return AddrTraceKind::None; StringRef Name = Callee->getName(); - if (traceMemory && isBufferOp(Name)) return AddrTraceKind::Buffer; + // buffer.load.lds has a distinct operand layout (including an LDS + // destination pointer), so the ordinary buffer component protocol is + // not valid for it. Leave it uninstrumented until it has its own + // protocol. + if (traceMemory && isBufferOp(Name) && !Name.ends_with(".buffer.load.lds")) + return AddrTraceKind::Buffer; auto IID = Callee->getIntrinsicID(); if (traceLDS && (IID == Intrinsic::amdgcn_ds_permute || IID == Intrinsic::amdgcn_ds_bpermute || IID == Intrinsic::amdgcn_ds_bpermute_fi_b32)) @@ -154,15 +162,19 @@ void SQTTInstrumentPass::emitAddressTrace( Type* I32 = Type::getInt32Ty(Ctx); Type* I64 = Type::getInt64Ty(Ctx); + emitTraceBlockBoundary(B, /*after=*/false); + // Buffer and permute ops use specialized trace protocols if (kind == AddrTraceKind::Buffer) { emitBufferTrace(B, cast(memOp), headerID, F, gen); + emitTraceBlockBoundary(B, /*after=*/true); return; } if (kind == AddrTraceKind::Permute) { emitPermuteTrace(B, cast(memOp), headerID, F, gen); + emitTraceBlockBoundary(B, /*after=*/true); return; } @@ -244,6 +256,7 @@ void SQTTInstrumentPass::emitAddressTrace( // Restore insertion point to AfterBB B.SetInsertPoint(AfterBB, AfterBB->begin()); + emitTraceBlockBoundary(B, /*after=*/true); } bool SQTTInstrumentPass::instrumentAddressTraces(Function& F, GfxGen gen) diff --git a/projects/rocprof-trace-decoder/markers/SQTTBarriers.cpp b/projects/rocprof-trace-decoder/markers/SQTTBarriers.cpp index 29b54e7a3866..6c9901d55b1c 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTBarriers.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTBarriers.cpp @@ -64,7 +64,7 @@ bool SQTTInstrumentPass::instrumentBarriers(Function& F, GfxGen gen) auto [CI, kind] = Barriers[i]; if (kind == BarrierKind::Signal && i + 1 < Barriers.size() && Barriers[i + 1].second == BarrierKind::Wait && - CI->getParent() == Barriers[i + 1].first->getParent()) + CI->getNextNode() == Barriers[i + 1].first) { CallInst* WaitCI = Barriers[i + 1].first; IRBuilder<> B(WaitCI); diff --git a/projects/rocprof-trace-decoder/markers/SQTTFunctions.cpp b/projects/rocprof-trace-decoder/markers/SQTTFunctions.cpp index 3b53bfb7a52c..3cee231f7ea3 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTFunctions.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTFunctions.cpp @@ -446,8 +446,18 @@ void SQTTInstrumentPass::removeAdjacentBarriers(CallInst* CI) } } +bool SQTTInstrumentPass::hasMustTailCall(const Function& F) +{ + for (const auto& BB : F) + for (const auto& I : BB) + if (const auto* CB = dyn_cast(&I); CB && CB->isMustTailCall()) return true; + return false; +} + bool SQTTInstrumentPass::instrumentFunctionDirect(Function& F, GfxGen gen) { + if (hasMustTailCall(F)) return false; + unsigned size = computeFunctionSize(F, Config.Mode); if (size <= Config.FunctionThreshold) return false; diff --git a/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp b/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp index 2fd1a4feea88..3de589a26e54 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp @@ -89,6 +89,26 @@ static void emitMemBarrier(IRBuilder<>& B, MemBarrierMode mode) F->setMetadata(LLVMContext::MD_mmra, MDNode::get(Ctx, LocalSyncAS)); } +void SQTTInstrumentPass::emitTraceBlockBoundary(IRBuilder<>& B, bool after) +{ + Module* M = B.GetInsertBlock()->getParent()->getParent(); + Type* I32 = Type::getInt32Ty(B.getContext()); + Function* SchedBarrier = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_sched_barrier); + + // Keep the complete address block between two scheduling boundaries, + // rather than adding one around every raw payload. + if (after) + { + emitMemBarrier(B, Config.MemBarrier); + B.CreateCall(SchedBarrier, {ConstantInt::get(I32, 0)}); + } + else + { + B.CreateCall(SchedBarrier, {ConstantInt::get(I32, 0)}); + emitMemBarrier(B, Config.MemBarrier); + } +} + void SQTTInstrumentPass::insertTraceMarker(IRBuilder<>& B, uint32_t markerID, Function& F, GfxGen gen) { insertTraceMarkerWithPayload(B, markerID, nullptr, F, gen); diff --git a/projects/rocprof-trace-decoder/markers/SQTTNamedMarkers.cpp b/projects/rocprof-trace-decoder/markers/SQTTNamedMarkers.cpp index f8adddbfb104..457bf242130f 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTNamedMarkers.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTNamedMarkers.cpp @@ -111,13 +111,12 @@ bool SQTTInstrumentPass::processMarkerCalls(Function& F, GfxGen gen, bool useBar auto& [CI, Type] = Calls[i]; if (!CI) continue; // already consumed by fusion - // Try to fuse exit+enter pairs: if this is an exit and the - // next call is an enter in the same basic block, emit a single - // marker with exit_prev=true. + // Fuse only directly adjacent exit+enter pairs. A marker boundary + // must not absorb work between the calls. if (Type == MarkerType::Exit && i + 1 < Calls.size()) { auto& [NextCI, NextType] = Calls[i + 1]; - if (NextCI && NextType == MarkerType::Enter && CI->getParent() == NextCI->getParent()) + if (NextCI && NextType == MarkerType::Enter && CI->getNextNode() == NextCI) { uint32_t enterEncoded = resolveMarkerString(NextCI, MarkerType::Enter); if (enterEncoded) diff --git a/projects/rocprof-trace-decoder/markers/SQTTPass.cpp b/projects/rocprof-trace-decoder/markers/SQTTPass.cpp index 2b65bb695587..9d8df7e90bbd 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTPass.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTPass.cpp @@ -27,6 +27,7 @@ #include "llvm/IR/Constants.h" #include "llvm/IR/InlineAsm.h" #include "llvm/Passes/PassBuilder.h" +#include "llvm/Support/ErrorHandling.h" #if __has_include("llvm/Plugins/PassPlugin.h") # include "llvm/Plugins/PassPlugin.h" #else @@ -112,11 +113,11 @@ PreservedAnalyses SQTTInstrumentPass::runEarly(Module& M) if (F.getCallingConv() == CallingConv::AMDGPU_KERNEL) continue; if (Config.FunctionThreshold == 0) continue; + if (hasMustTailCall(F)) continue; uint32_t id = NextEventID++; - - insertBareMarkers(F, id, Gen); storeFuncMetadata(F, id, Ctx); + insertBareMarkers(F, id, Gen); Changed = true; } @@ -166,7 +167,7 @@ PreservedAnalyses SQTTInstrumentPass::runLate(Module& M) if (Config.needsScopeCheck()) Changed |= wrapExistingMarkers(F, Gen); - if (hadEarlyPass) Changed |= addBarriersToExistingMarkers(F); + Changed |= addBarriersToExistingMarkers(F); Changed |= processNamedMarkers(F, Gen); @@ -177,11 +178,28 @@ PreservedAnalyses SQTTInstrumentPass::runLate(Module& M) if (Config.hasAddressTracing()) Changed |= instrumentAddressTraces(F, Gen); if (!hadEarlyPass && Config.FunctionThreshold > 0 && !IsKernel) Changed |= instrumentFunctionDirect(F, Gen); + } + // Address records have R:extra_payload_count > 1. Do not create a packed + // header layout whose JSON timestamp correction could split those blocks. + if (!AddrTraceEntries.empty()) + { + if (Config.ShaderClockBits == SQTTConfig::AutoShaderClockBits) + Config.ShaderClockBits = 0; + else if (Config.ShaderClockBits != 0) + report_fatal_error( + "SQTT_TRACE_ADDRESSES emits multi-payload records; set SQTT_SHADER_CLOCK_BITS=0" + ); + } + + // Packing must happen after the address decision for the entire module. + for (auto& F : M) + { + if (F.isDeclaration()) continue; + GfxGen Gen = getGfxGen(F); + if (Gen == GfxGen::Unknown) continue; Changed |= applyShaderClockPacking(F, Gen); - // gfx9's backend already provides the required M0 hazard spacing. - // RDNA/gfx12 need the explicit asm form. - if (Gen != GfxGen::GFX9) Changed |= lowerFullTracesWithM0Nop(F); + Changed |= lowerFullTracesWithM0Nop(F); } // Clean up sentinel declarations diff --git a/projects/rocprof-trace-decoder/markers/SQTTPass.h b/projects/rocprof-trace-decoder/markers/SQTTPass.h index f26db1678a88..1c7a4102e11a 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTPass.h +++ b/projects/rocprof-trace-decoder/markers/SQTTPass.h @@ -232,6 +232,9 @@ class SQTTInstrumentPass : public llvm::PassInfoMixin llvm::Function& F, GfxGen gen ); + // One ordering boundary on each side of a complete address-trace block. + // This deliberately does not put a boundary around every raw payload. + void emitTraceBlockBoundary(llvm::IRBuilder<>& B, bool after); // Source location for an instruction. Walks the inline chain via // DILocation::getInlinedAt() and joins entries with " -> " (innermost // first, then each outward call site). Matches the format used by @@ -276,8 +279,8 @@ class SQTTInstrumentPass : public llvm::PassInfoMixin uint32_t compactFuncIDs(llvm::Module& M); void rewriteMarkerIDs(llvm::Function& F, const std::map& IDMap, GfxGen gen); bool applyShaderClockPacking(llvm::Function& F, GfxGen gen); - // gfx10+ do not provide the needed M0 hazard spacing for this intrinsic. - // Lower their full traces after all marker transformations to explicit asm. + // Lower full traces after all marker transformations to explicit asm with + // the required M0 hazard spacing. bool lowerFullTracesWithM0Nop(llvm::Function& F); void removeFuncMarkersFromModule(llvm::Module& M, uint32_t id); void removeAdjacentBarriers(llvm::CallInst* CI); @@ -285,6 +288,7 @@ class SQTTInstrumentPass : public llvm::PassInfoMixin // ----------------------------------------------------------------- // -O0 fallback: direct function instrumentation // ----------------------------------------------------------------- + static bool hasMustTailCall(const llvm::Function& F); bool instrumentFunctionDirect(llvm::Function& F, GfxGen gen); // ----------------------------------------------------------------- diff --git a/projects/rocprof-trace-decoder/markers/SQTTTarget.h b/projects/rocprof-trace-decoder/markers/SQTTTarget.h index a2f6a5e08f5a..446a387c0d4a 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTTarget.h +++ b/projects/rocprof-trace-decoder/markers/SQTTTarget.h @@ -137,6 +137,9 @@ inline unsigned computeFunctionSize(const llvm::Function& F, CostMode mode) { for (auto& I : BB) { + // Pass-owned marker calls must not make a function appear large + // enough to retain the instrumentation that introduced them. + if (I.getMetadata("sqtt.marker_header") || I.getMetadata("sqtt.raw_payload")) continue; if (mode == CostMode::WeightedCost) total += instructionCost(I); else diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index 2f71c163649a..ee11663c4b30 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -153,6 +153,7 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict marker_ids[codeobj].add(marker_id) payloads: dict[tuple[int, int], int] = {} + multi_payload_codeobjs: set[int] = set() for row in document.get("sqtt_funcmap_payloads", []): try: codeobj, marker_id, count = map(int, row[:3]) @@ -160,6 +161,8 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict continue if marker_id in marker_ids.get(codeobj, ()) and count > 0: payloads[codeobj, marker_id] = count + if count > 1: + multi_payload_codeobjs.add(codeobj) shaderdata: dict[tuple[int, int, int, int], list[object]] = defaultdict(list) occupancy: dict[tuple[int, int, int, int], list[object]] = defaultdict(list) @@ -210,9 +213,17 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict header_payloads = [] if marker_id in known_ids: payload_remaining = payloads.get((codeobj, marker_id), 0) - mask = ((1 << bits) - 1) << shift + # New producers disable clock packing for multi-payload protocols. + # Keep old traces readable by normalizing their headers, but do + # not shift timestamps: sorting corrected headers independently + # can break the declared header/payload record adjacency. + if codeobj in multi_payload_codeobjs: + continue + window_mask = (1 << (bits + shift)) - 1 + bucket_size = 1 << shift sampled = ((raw >> (32 - bits)) & ((1 << bits) - 1)) << shift - delay = ((int(record.time) & mask) - sampled) & mask + delta = ((int(record.time) & window_mask) - sampled) & window_mask + delay = max(0, delta - (bucket_size - 1)) # shader_cycles_lo samples are only comparable on one physical # SIMD. Keep differently encoded clock windows independent too. clock_domain = (*location[:3], bits, shift) diff --git a/projects/rocprof-trace-decoder/scripts/sqtt_data.py b/projects/rocprof-trace-decoder/scripts/sqtt_data.py index 751cbdaba3be..ef7b422a1bde 100644 --- a/projects/rocprof-trace-decoder/scripts/sqtt_data.py +++ b/projects/rocprof-trace-decoder/scripts/sqtt_data.py @@ -139,7 +139,6 @@ def extra_payload_count(self, marker_id: int) -> int: """Returns extra records after this marker header; default is zero.""" return self.extra_payload_counts.get(marker_id, 0) - def merge_funcmaps(funcmaps: Iterable[FuncMap]) -> FuncMap: """Merge funcmaps while preserving all metadata used for marker decoding.""" merged = FuncMap() @@ -203,18 +202,24 @@ def parse_funcmap(raw: str) -> FuncMap: except ValueError: pass elif prefix == "M": + values = {} + valid = True for item in rest.split(";"): key, sep, value = item.partition("=") - if not sep: - continue - try: - parsed = int(value) - except ValueError: + key = key.strip() + if key not in ("shader_clock_bits", "shader_clock_shift"): continue - if key.strip() == "shader_clock_bits": - fm.shader_clock_bits = parsed - elif key.strip() == "shader_clock_shift": - fm.shader_clock_shift = parsed + if not sep or not value.strip().isdigit(): + valid = False + break + values[key] = int(value) + bits = values.get("shader_clock_bits") + shift = values.get("shader_clock_shift") + if valid and bits is not None and ( + bits == 0 or (shift is not None and bits <= 29 and shift < 32 and bits <= 32 - shift) + ): + fm.shader_clock_bits = bits + fm.shader_clock_shift = shift or 0 elif prefix == "K": name, loc = _split_source_loc(rest) fm.kernels.append(name) @@ -315,19 +320,25 @@ class ShaderRecord: simd: int wave_id: int flags: int + shader_engine: int = 0 def decode_marker(value: int, funcmap: FuncMap | None = None) -> tuple[int, bool, bool]: - """Decode marker value -> (id, enter, exit_prev).""" - exit_prev = bool(value & 0x1) - enter = bool(value & 0x2) - clock_bits = funcmap.shader_clock_bits if funcmap else 0 - if clock_bits: - id_bits = max(0, 30 - clock_bits) - marker_id = (value >> 2) & ((1 << id_bits) - 1 if id_bits else 0) - else: - marker_id = value >> 2 - return marker_id, enter, exit_prev + """Decode known packed headers/bare exits; keep numeric markers legacy.""" + raw = value & 0xFFFFFFFF + exit_prev = bool(raw & 0x1) + enter = bool(raw & 0x2) + legacy_id = raw >> 2 + bits = funcmap.shader_clock_bits if funcmap else 0 + shift = funcmap.shader_clock_shift if funcmap else 0 + if not (funcmap and 0 < bits <= 29 and 0 <= shift < 32 and bits <= 32 - shift): + return legacy_id, enter, exit_prev + + id_bits = 30 - bits + packed_id = legacy_id & ((1 << id_bits) - 1) + if packed_id in funcmap.markers or (packed_id == 0 and exit_prev and not enter): + return packed_id, enter, exit_prev + return legacy_id, enter, exit_prev def _load_shaderdata_from_dir(trace_dir: str) -> list[ShaderRecord]: @@ -335,23 +346,28 @@ def _load_shaderdata_from_dir(trace_dir: str) -> list[ShaderRecord]: records = [] filenames_path = os.path.join(trace_dir, "filenames.json") - shaderdata_files = [] + shaderdata_files: list[tuple[int, str]] = [] if os.path.exists(filenames_path): with open(filenames_path) as f: meta = json.load(f) sd_filenames = meta.get("shaderdata_filenames", {}) for se_id, file_list in sd_filenames.items(): + try: + shader_engine = int(se_id) + except (TypeError, ValueError): + continue for entry in file_list: fname = entry[0] if isinstance(entry, list) else entry - shaderdata_files.append(os.path.join(trace_dir, fname)) + shaderdata_files.append((shader_engine, os.path.join(trace_dir, fname))) else: - shaderdata_files = sorted( + paths = sorted( glob(os.path.join(trace_dir, "shaderdata_*.json")), key=lambda p: [int(x) for x in os.path.splitext(os.path.basename(p))[0].split("_")[1:]], ) + shaderdata_files = [(int(os.path.basename(path).split("_")[1]), path) for path in paths] - for path in shaderdata_files: + for shader_engine, path in shaderdata_files: if not os.path.exists(path): continue with open(path) as f: @@ -361,6 +377,7 @@ def _load_shaderdata_from_dir(trace_dir: str) -> list[ShaderRecord]: time=rec[0], value=rec[1], cu=rec[2], simd=rec[3], wave_id=rec[4], flags=rec[5], + shader_engine=shader_engine, )) return records @@ -391,7 +408,7 @@ def load_occupancy(trace_dir: str) -> tuple[dict[str, str], dict[tuple, list[Wav Returns: dispatches: dict mapping dispatch_id (str) -> kernel name/description - wave_spans: dict mapping (cu, simd, wave_slot) -> sorted list of WaveSpan + wave_spans: dict mapping (shader_engine, cu, simd, wave_slot) -> sorted list of WaveSpan """ path = os.path.join(trace_dir, "occupancy.json") if not os.path.exists(path): @@ -404,13 +421,17 @@ def load_occupancy(trace_dir: str) -> tuple[dict[str, str], dict[tuple, list[Wav events: dict[tuple, list[tuple]] = defaultdict(list) for key, val in data.items(): - if key in ("dispatches", "version") or not isinstance(val, list): + try: + shader_engine = int(key) + except (TypeError, ValueError): + continue + if not isinstance(val, list): continue for rec in val: - if len(rec) < 6: + if not isinstance(rec, list) or len(rec) < 6: continue time, cu, simd, wslot, is_launch, dispatch_id = rec[:6] - events[(cu, simd, wslot)].append((time, is_launch, dispatch_id)) + events[(shader_engine, cu, simd, wslot)].append((time, is_launch, dispatch_id)) wave_spans: dict[tuple, list[WaveSpan]] = {} for wave_key, evts in events.items(): @@ -458,6 +479,26 @@ def find_wave_span_at(spans: list[WaveSpan], time: int) -> Optional[WaveSpan]: return result +def make_funcmap_resolver( + per_co: dict[int, FuncMap], + dispatches: dict[str, str], + kernel_to_co: dict[str, int], + wave_spans: dict[tuple, list[WaveSpan]], + fallback: FuncMap, +): + """Resolve a late record to its latest launch until that slot relaunches.""" + def resolve(record: ShaderRecord) -> FuncMap: + span = find_wave_span_at( + wave_spans.get((record.shader_engine, record.cu, record.simd, record.wave_id), []), record.time + ) + if span is None: + return fallback + codeobj = kernel_to_co.get(dispatches.get(str(span.dispatch_id), "")) + return per_co.get(codeobj, fallback) + + return resolve + + def merge_folded(all_folded: list[dict[str, tuple[int, int]]]) -> dict[str, tuple[int, int]]: """Merge folded stack counts from multiple independent time domains.""" merged_cycles: dict[str, int] = defaultdict(int) @@ -490,6 +531,7 @@ class AddressTrace: addresses: list[int] # active lanes only; 64-bit for memory, 32-bit for LDS marker_id: int = 0 # unique per-op marker ID from funcmap source_loc: str = "" # "file.hip:42" if compiled with -g + shader_engine: int = 0 def active_lane_count(self) -> int: """Return count of active lanes (same as len(addresses)).""" @@ -632,6 +674,7 @@ def parse_address_block( addresses=addresses, marker_id=marker_id, source_loc=source_loc, + shader_engine=header_rec.shader_engine, ) return trace, i @@ -649,8 +692,8 @@ def _parse_buffer_block( """Parse buffer component block after exec mask has been read. Protocol: rsrc_lo, rsrc_hi, soffset, then per-lane voffset. - For struct buffers: additionally per-lane vindex. - Reconstructs full addresses: base + soffset + voffset[lane]. + Struct-buffer records contain vindex; consume them but do not guess an + address without a verified descriptor-stride decoder. """ is_struct = _is_struct_buffer(kind) @@ -672,15 +715,10 @@ def _parse_buffer_block( voffsets.append(records[i].value & 0xFFFFFFFF) i += 1 - # For struct buffers: per-lane vindex - vindices = None if is_struct: if i + wave_size > len(records): return None, len(records) - vindices = [] - for lane in range(wave_size): - vindices.append(records[i].value & 0xFFFFFFFF) - i += 1 + return None, i + wave_size # Reconstruct addresses for active lanes # Base address from rsrc descriptor: bits [47:0] @@ -702,6 +740,7 @@ def _parse_buffer_block( addresses=addresses, marker_id=marker_id, source_loc=source_loc, + shader_engine=header_rec.shader_engine, ) return trace, i @@ -709,13 +748,13 @@ def _parse_buffer_block( def preprocess_records( records: list[ShaderRecord], funcmap: FuncMap, + resolve_funcmap=None, ) -> tuple[list[ShaderRecord], list[AddressTrace]]: """Extract address trace blocks from the record stream. Address trace blocks span multiple consecutive s_ttracedata records (header + exec + addresses), but the raw shaderdata stream interleaves - records from all active waves. We demultiplex by (cu, simd, wave_id) - so each wave's records are contiguous before parsing address blocks. + records from all active waves. We demultiplex by (SE, CU, SIMD, wave). Returns: markers: all non-address-trace records (for build_stacks) @@ -730,26 +769,32 @@ def preprocess_records( # # A record is an address-trace header if decoding its value gives # a marker_id that resolves to an addr_trace_* funcmap entry. - # All subsequent records from the same (cu, simd, wave_id) until + # All subsequent records from the same wave stream until # the next header belong to the same address trace block. # Group all records by wave identity, preserving time order - per_wave: dict[tuple[int, int, int], list[ShaderRecord]] = defaultdict(list) - wave_has_addr_trace: set[tuple[int, int, int]] = set() + per_wave: dict[tuple[int, int, int, int], list[ShaderRecord]] = defaultdict(list) + wave_has_addr_trace: set[tuple[int, int, int, int]] = set() for rec in records: - key = (rec.cu, rec.simd, rec.wave_id) + if rec.flags & 2: # PRIV: generated by the trap handler + continue + key = (rec.shader_engine, rec.cu, rec.simd, rec.wave_id) per_wave[key].append(rec) + def map_for(record: ShaderRecord) -> FuncMap: + return resolve_funcmap(record) if resolve_funcmap is not None else funcmap + for key, wave_records in per_wave.items(): i = 0 while i < len(wave_records): - marker_id, _enter, _exit_prev = decode_marker(wave_records[i].value, funcmap) - name, _mtype = funcmap.resolve(marker_id) + current_map = map_for(wave_records[i]) + marker_id, _enter, _exit_prev = decode_marker(wave_records[i].value, current_map) + name, _mtype = current_map.resolve(marker_id) if _match_addr_trace(name) is not None: wave_has_addr_trace.add(key) break - i += 1 + funcmap.extra_payload_count(marker_id) + i += 1 + current_map.extra_payload_count(marker_id) # Process each wave's records for key, wave_records in per_wave.items(): @@ -759,27 +804,29 @@ def preprocess_records( i = 0 while i < len(wave_records): rec = wave_records[i] - marker_id, _enter, _exit_prev = decode_marker(rec.value, funcmap) + current_map = map_for(rec) + marker_id, _enter, _exit_prev = decode_marker(rec.value, current_map) markers.append(rec) - i += 1 + funcmap.extra_payload_count(marker_id) + i += 1 + current_map.extra_payload_count(marker_id) continue # Parse this wave's contiguous record stream i = 0 while i < len(wave_records): rec = wave_records[i] - marker_id, enter, exit_prev = decode_marker(rec.value, funcmap) - name, mtype = funcmap.resolve(marker_id) + current_map = map_for(rec) + marker_id, enter, exit_prev = decode_marker(rec.value, current_map) + name, mtype = current_map.resolve(marker_id) if _match_addr_trace(name) is not None: trace, i = parse_address_block( - wave_records, i, rec, name, marker_id, funcmap.wave_size, - funcmap.source_locs.get(marker_id, "")) + wave_records, i, rec, name, marker_id, current_map.wave_size, + current_map.source_locs.get(marker_id, "")) if trace: addr_traces.append(trace) else: markers.append(rec) - i += 1 + funcmap.extra_payload_count(marker_id) + i += 1 + current_map.extra_payload_count(marker_id) # Re-sort markers by time since we processed per-wave markers.sort(key=lambda r: r.time) diff --git a/projects/rocprof-trace-decoder/scripts/sqtt_flamegraph.py b/projects/rocprof-trace-decoder/scripts/sqtt_flamegraph.py index ab8c734e1dba..62167514243b 100755 --- a/projects/rocprof-trace-decoder/scripts/sqtt_flamegraph.py +++ b/projects/rocprof-trace-decoder/scripts/sqtt_flamegraph.py @@ -86,6 +86,7 @@ load_occupancy, load_shaderdata, build_kernel_to_co, + make_funcmap_resolver, merge_folded, merge_funcmaps, preprocess_records, @@ -97,7 +98,7 @@ # --------------------------------------------------------------------------- # A unique wave identity -WaveKey = tuple[int, int, int] # (cu, simd, wave_id) +WaveKey = tuple[int, int, int, int, int] # (se, cu, simd, wave_id, instance) def build_stacks( @@ -124,7 +125,7 @@ def build_stacks( # Merged funcmap for fallback resolution. merged = merge_funcmaps(per_co.values()) - # Build sorted launch times per (cu, simd, wave_slot) from occupancy. + # Build sorted launch times per (SE, CU, SIMD, wave_slot) from occupancy. slot_launches: dict[tuple, list[int]] = defaultdict(list) for wk, spans in wave_spans.items(): for span in spans: @@ -132,9 +133,9 @@ def build_stacks( for wk in slot_launches: slot_launches[wk].sort() - def _wave_instance(cu: int, simd: int, wave_id: int, time: int) -> int: + def _wave_instance(shader_engine: int, cu: int, simd: int, wave_id: int, time: int) -> int: """Find which wave instance (launch index) a record belongs to.""" - launches = slot_launches.get((cu, simd, wave_id), []) + launches = slot_launches.get((shader_engine, cu, simd, wave_id), []) if not launches: return 0 lo, hi = 0, len(launches) - 1 @@ -148,17 +149,19 @@ def _wave_instance(cu: int, simd: int, wave_id: int, time: int) -> int: hi = mid - 1 return result - # Group records by (cu, simd, wave_id, instance) + # Group records by (SE, CU, SIMD, wave_id, instance) wave_records: dict[tuple, list[ShaderRecord]] = defaultdict(list) for rec in records: + if rec.flags & 2: + continue if filter_cu is not None and rec.cu != filter_cu: continue if filter_simd is not None and rec.simd != filter_simd: continue if filter_wave is not None and rec.wave_id != filter_wave: continue - inst = _wave_instance(rec.cu, rec.simd, rec.wave_id, rec.time) - key = (rec.cu, rec.simd, rec.wave_id, inst) + inst = _wave_instance(rec.shader_engine, rec.cu, rec.simd, rec.wave_id, rec.time) + key = (rec.shader_engine, rec.cu, rec.simd, rec.wave_id, inst) wave_records[key].append(rec) # (total_cycles, exec_count) per stack @@ -193,15 +196,10 @@ def _flush(prefix, stack, last_time, now): cur_dispatch_id: Optional[int] = None cur_funcmap: FuncMap = merged - base_wk = wave_key[:3] + base_wk = wave_key[:4] spans = wave_spans.get(base_wk, []) for rec in recs: - marker_id, enter, exit_prev = decode_marker(rec.value, cur_funcmap) - - if marker_id == 0 and not exit_prev: - continue - # Check dispatch context span = find_wave_span_at(spans, rec.time) dispatch_id = span.dispatch_id if span else None @@ -212,6 +210,10 @@ def _flush(prefix, stack, last_time, now): stack = [] cur_prefix, cur_funcmap = _dispatch_prefix(dispatch_id) + marker_id, enter, exit_prev = decode_marker(rec.value, cur_funcmap) + if marker_id == 0 and not exit_prev: + continue + name, mtype = cur_funcmap.resolve(marker_id) # Point markers (barriers, memory ops, user points) carry no @@ -568,9 +570,10 @@ def main(): if not v.startswith("0 /")]) # Filter out address trace records before building stacks - records, addr_traces = preprocess_records(records, merged) + resolver = make_funcmap_resolver(per_co, dispatches, kernel_to_co, wave_spans, merged) + records, addr_traces = preprocess_records(records, merged, resolver) - waves = set((r.cu, r.simd, r.wave_id) for r in records) + waves = set((record.shader_engine, record.cu, record.simd, record.wave_id) for record in records) total_records += len(records) all_waves.update(waves) diff --git a/projects/rocprof-trace-decoder/scripts/sqtt_memory_trace.py b/projects/rocprof-trace-decoder/scripts/sqtt_memory_trace.py index c665071c0814..54c1d76346dd 100644 --- a/projects/rocprof-trace-decoder/scripts/sqtt_memory_trace.py +++ b/projects/rocprof-trace-decoder/scripts/sqtt_memory_trace.py @@ -52,10 +52,12 @@ from sqtt_data import ( AddressTrace, + build_kernel_to_co, discover_base_dir, load_funcmaps, load_occupancy, load_shaderdata, + make_funcmap_resolver, merge_funcmaps, preprocess_records, ) @@ -70,6 +72,7 @@ def format_address_trace(trace: AddressTrace) -> dict: "kind": trace.kind, "marker_id": trace.marker_id, "time": trace.time, + "shader_engine": trace.shader_engine, "cu": trace.cu, "simd": trace.simd, "wave": trace.wave_id, @@ -101,17 +104,20 @@ def print_summary(traces: list[AddressTrace], file=sys.stderr): # Global stats total_addrs = sum(len(t.addresses) for t in traces) + shader_engines = set() cus = set() simds = set() by_kind: dict[str, int] = {} for t in traces: by_kind[t.kind] = by_kind.get(t.kind, 0) + 1 + shader_engines.add(t.shader_engine) cus.add(t.cu) simds.add(t.simd) print(f"\nAddress trace summary:", file=file) print(f" Total operations: {len(traces)}", file=file) print(f" Total addresses: {total_addrs}", file=file) + print(f" Shader engines: {sorted(shader_engines)}", file=file) print(f" CUs seen: {sorted(cus)}", file=file) print(f" SIMDs seen: {sorted(simds)}", file=file) print(f" By kind:", file=file) @@ -141,15 +147,15 @@ def print_summary(traces: list[AddressTrace], file=sys.stderr): mid_traces = by_mid[mid] print(f" marker_id={mid} ({len(mid_traces)} samples)", file=file) - # Group by wave (CU, SIMD, wave_id) - by_wave: dict[tuple[int, int, int], list[AddressTrace]] = defaultdict(list) + # Group by wave (SE, CU, SIMD, wave_id) + by_wave: dict[tuple[int, int, int, int], list[AddressTrace]] = defaultdict(list) for t in mid_traces: - by_wave[(t.cu, t.simd, t.wave_id)].append(t) + by_wave[(t.shader_engine, t.cu, t.simd, t.wave_id)].append(t) - for (cu, simd, wid) in sorted(by_wave.keys()): - wave_traces = by_wave[(cu, simd, wid)] + for (se, cu, simd, wid) in sorted(by_wave.keys()): + wave_traces = by_wave[(se, cu, simd, wid)] is_64bit = wave_traces[0].kind in ("load", "store") - print(f" wave CU={cu} SIMD={simd} wave={wid} " + print(f" wave SE={se} CU={cu} SIMD={simd} wave={wid} " f"({len(wave_traces)} samples)", file=file) for t in wave_traces: @@ -217,8 +223,10 @@ def main(): print(f"Found {len(trace_dirs)} trace dir(s), " f"{len(code_objects)} code object(s)", file=sys.stderr) - # Load funcmaps - per_co = load_funcmaps(code_objects, do_demangle=args.demangle) + per_co_raw = load_funcmaps(code_objects, do_demangle=False) + kernel_to_co = build_kernel_to_co(per_co_raw) + per_co = (load_funcmaps(code_objects, do_demangle=True) + if args.demangle else per_co_raw) # Build merged funcmap for preprocessing. merged = merge_funcmaps(per_co.values()) @@ -233,7 +241,10 @@ def main(): continue total_records += len(records) - _, addr_traces = preprocess_records(records, merged) + dispatches, wave_spans = load_occupancy(td) + resolver = make_funcmap_resolver( + per_co, dispatches, kernel_to_co, wave_spans, merged) + _, addr_traces = preprocess_records(records, merged, resolver) # Apply filters for t in addr_traces: diff --git a/projects/rocprof-trace-decoder/scripts/sqtt_perfetto.py b/projects/rocprof-trace-decoder/scripts/sqtt_perfetto.py index 851d34b4c9ac..818a921025a2 100644 --- a/projects/rocprof-trace-decoder/scripts/sqtt_perfetto.py +++ b/projects/rocprof-trace-decoder/scripts/sqtt_perfetto.py @@ -68,6 +68,7 @@ load_funcmaps, load_occupancy, load_shaderdata, + make_funcmap_resolver, merge_funcmaps, preprocess_records, ) @@ -77,13 +78,13 @@ # Wave grouping helpers # --------------------------------------------------------------------------- -WaveKey = tuple[int, int, int, int] # (cu, simd, wave_id, instance) +WaveKey = tuple[int, int, int, int, int] # (se, cu, simd, wave_id, instance) def _build_slot_launches( wave_spans: dict[tuple, list[WaveSpan]], ) -> dict[tuple, list[int]]: - """Sorted launch times per (cu, simd, wave_slot). Used to assign instance + """Sorted launch times per (SE, CU, SIMD, wave_slot). Used to assign instance indices to records that share a physical wave slot across relaunches.""" slot_launches: dict[tuple, list[int]] = defaultdict(list) for wk, spans in wave_spans.items(): @@ -95,9 +96,9 @@ def _build_slot_launches( def _wave_instance(slot_launches: dict[tuple, list[int]], - cu: int, simd: int, wave_id: int, time: int) -> int: + shader_engine: int, cu: int, simd: int, wave_id: int, time: int) -> int: """Index of the most recent launch at or before `time` for this slot.""" - launches = slot_launches.get((cu, simd, wave_id), []) + launches = slot_launches.get((shader_engine, cu, simd, wave_id), []) if not launches: return 0 lo, hi = 0, len(launches) - 1 @@ -119,32 +120,34 @@ def group_by_wave_instance( filter_simd: Optional[int] = None, filter_wave: Optional[int] = None, ) -> dict[WaveKey, list[ShaderRecord]]: - """Group records by (cu, simd, wave_id, instance), preserving time order.""" + """Group records by (SE, CU, SIMD, wave_id, instance), preserving time order.""" slot_launches = _build_slot_launches(wave_spans) grouped: dict[WaveKey, list[ShaderRecord]] = defaultdict(list) for rec in records: + if rec.flags & 2: + continue if filter_cu is not None and rec.cu != filter_cu: continue if filter_simd is not None and rec.simd != filter_simd: continue if filter_wave is not None and rec.wave_id != filter_wave: continue - inst = _wave_instance(slot_launches, rec.cu, rec.simd, + inst = _wave_instance(slot_launches, rec.shader_engine, rec.cu, rec.simd, rec.wave_id, rec.time) - grouped[(rec.cu, rec.simd, rec.wave_id, inst)].append(rec) + grouped[(rec.shader_engine, rec.cu, rec.simd, rec.wave_id, inst)].append(rec) for k in grouped: grouped[k].sort(key=lambda r: r.time) return grouped -def pack_tid(cu: int, simd: int, wave_id: int, instance: int) -> int: - """Pack a wave key into a 32-bit thread id. +def pack_tid(shader_engine: int, cu: int, simd: int, wave_id: int, instance: int) -> int: + """Pack a shader-engine-aware wave key into a 64-bit thread id. - Layout: (cu << 20) | (simd << 16) | (wave_id << 8) | instance. - Collision-free for cu<4096, simd<16, wave_id<256, instance<256 -- all - well above hardware limits. + The lower 32 bits retain the old CU/SIMD/wave/instance layout; shader + engine occupies the high bits so equal wave slots on two engines do not + share a Perfetto track. """ - return (cu << 20) | (simd << 16) | (wave_id << 8) | instance + return (shader_engine << 32) | (cu << 20) | (simd << 16) | (wave_id << 8) | instance # --------------------------------------------------------------------------- @@ -195,9 +198,9 @@ def emit_wave_events( """ events: list[dict] = [] unmatched = 0 - cu, simd, wave_id, instance = wave_key - tid = pack_tid(cu, simd, wave_id, instance) - base_wk = (cu, simd, wave_id) + shader_engine, cu, simd, wave_id, instance = wave_key + tid = pack_tid(shader_engine, cu, simd, wave_id, instance) + base_wk = wave_key[:4] spans = wave_spans.get(base_wk, []) stack: list[tuple[str, str]] = [] # (name, mtype) pairs we've pushed @@ -208,10 +211,6 @@ def _ts(cycles: int) -> float: return cycles * ts_scale for rec in wave_records: - marker_id, enter, exit_prev = decode_marker(rec.value, cur_funcmap) - if marker_id == 0 and not exit_prev: - continue # noop / reserved value - # Update dispatch context. When the dispatch changes mid-stream # we synthesize E events for anything still on the stack -- a # function entered under dispatch A doesn't outlive A. @@ -228,6 +227,10 @@ def _ts(cycles: int) -> float: cur_funcmap = _funcmap_for_dispatch( dispatch_id, dispatches, kernel_to_co, per_co, merged_fm) + marker_id, enter, exit_prev = decode_marker(rec.value, cur_funcmap) + if marker_id == 0 and not exit_prev: + continue # noop / reserved value + name, mtype = cur_funcmap.resolve(marker_id) # Point markers (barriers, memory ops, user points) are instantaneous @@ -274,7 +277,7 @@ def _ts(cycles: int) -> float: def build_metadata_events( pids_seen: set[int], - tids_seen: set[tuple[int, int, tuple[int, int, int, int]]], + tids_seen: set[tuple], dispatches: dict[str, str], do_demangle: bool, ) -> list[dict]: @@ -282,7 +285,7 @@ def build_metadata_events( each thread (wave instance). tids_seen is a set of (pid, tid, wave_key) triples so we can recover - the (cu, simd, wave_id, instance) tuple for the thread name. + the (SE, CU, SIMD, wave_id, instance) tuple for the thread name. """ metadata: list[dict] = [] @@ -299,8 +302,8 @@ def build_metadata_events( }) for pid, tid, wave_key in sorted(tids_seen): - cu, simd, wave_id, instance = wave_key - thread_name = f"CU{cu}/SIMD{simd}/W{wave_id}#{instance}" + shader_engine, cu, simd, wave_id, instance = wave_key + thread_name = f"SE{shader_engine}/CU{cu}/SIMD{simd}/W{wave_id}#{instance}" metadata.append({ "ph": "M", "name": "thread_name", "pid": pid, "tid": tid, "args": {"name": thread_name}, @@ -333,12 +336,12 @@ def export_trace_dir( records = load_shaderdata(trace_dir) if not records: return None - records, _addr = preprocess_records(records, merged_fm) + dispatches, wave_spans = load_occupancy(trace_dir) + resolver = make_funcmap_resolver(per_co, dispatches, kernel_to_co, wave_spans, merged_fm) + records, _addr = preprocess_records(records, merged_fm, resolver) if not records: return None - dispatches, wave_spans = load_occupancy(trace_dir) - grouped = group_by_wave_instance( records, wave_spans, filter_cu=filter_cu, filter_simd=filter_simd, filter_wave=filter_wave) @@ -352,7 +355,7 @@ def export_trace_dir( all_events: list[dict] = [] pids_seen: set[int] = set() - tids_seen: set[tuple[int, int, tuple[int, int, int, int]]] = set() + tids_seen: set[tuple] = set() total_unmatched = 0 for wave_key, recs in grouped.items(): @@ -361,8 +364,8 @@ def export_trace_dir( wave_spans, ts_scale) total_unmatched += unmatched all_events.extend(evs) - cu, simd, wave_id, instance = wave_key - tid = pack_tid(cu, simd, wave_id, instance) + shader_engine, cu, simd, wave_id, instance = wave_key + tid = pack_tid(shader_engine, cu, simd, wave_id, instance) for ev in evs: pids_seen.add(ev["pid"]) tids_seen.add((ev["pid"], tid, wave_key)) @@ -424,19 +427,19 @@ def _self_test() -> int: exit_prev = 0x1 point_barrier = (2 << 2) - # One wave (cu=0, simd=1, wave_id=3) running dispatch 7. + # One wave (SE=0, cu=0, simd=1, wave_id=3) running dispatch 7. recs = [ ShaderRecord(time=100, value=enter_compute, cu=0, simd=1, wave_id=3, flags=0), ShaderRecord(time=200, value=point_barrier, cu=0, simd=1, wave_id=3, flags=0), ShaderRecord(time=300, value=exit_prev, cu=0, simd=1, wave_id=3, flags=0), ] - wave_spans = {(0, 1, 3): [WaveSpan(launch_time=0, retire_time=1000, dispatch_id=7)]} + wave_spans = {(0, 0, 1, 3): [WaveSpan(launch_time=0, retire_time=1000, dispatch_id=7)]} dispatches = {"7": "compute_kernel"} grouped = group_by_wave_instance(recs, wave_spans) assert len(grouped) == 1, f"expected 1 wave group, got {len(grouped)}" wave_key = next(iter(grouped)) - assert wave_key == (0, 1, 3, 0), wave_key + assert wave_key == (0, 0, 1, 3, 0), wave_key events, unmatched = emit_wave_events( grouped[wave_key], wave_key, dispatches, kernel_to_co={}, @@ -450,7 +453,7 @@ def _self_test() -> int: assert events[1]["name"] == "barrier" and events[1]["cat"] == "sqtt.point" assert events[1]["s"] == "t" assert all(e["pid"] == 7 for e in events) - expected_tid = pack_tid(0, 1, 3, 0) + expected_tid = pack_tid(0, 0, 1, 3, 0) assert all(e["tid"] == expected_tid for e in events) # B/E pairing (per (pid, tid)) diff --git a/projects/rocprof-trace-decoder/test/att_tool_test.py b/projects/rocprof-trace-decoder/test/att_tool_test.py index fbd3526586ed..a4ebec32d6be 100644 --- a/projects/rocprof-trace-decoder/test/att_tool_test.py +++ b/projects/rocprof-trace-decoder/test/att_tool_test.py @@ -186,6 +186,24 @@ def test_correction_does_not_share_the_reference_between_simds(self): self.assertEqual(fast_simd.time, 0x1010) self.assertEqual([record.value for record in records.shaderdata], [12, 12]) + def test_correction_does_not_precede_the_sampled_clock_at_wrap(self): + marker_id = 3 << 2 + reference = _shaderdata(0x43FF, (0x10 << 20) | marker_id) + wrapped = _shaderdata(0x400010, (0xFFF << 20) | marker_id) + records = TraceRecords( + occupancy=[_occupancy(1, 0)], shaderdata=[reference, wrapped]) + + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 10]], + }, + ) + + self.assertEqual([record.time for record in records.shaderdata], [0x43FF, 0x3FFFFF]) + self.assertEqual([record.value for record in records.shaderdata], [marker_id, marker_id]) + def test_late_header_after_retirement_is_normalized_and_corrected(self): value = (0x100 << 20) | (3 << 2) before_retirement = _shaderdata(0x1010, value) @@ -231,6 +249,30 @@ def test_later_launch_supersedes_a_retired_wave_slot(self): self.assertEqual([record.time for record in records.shaderdata], [0x1020, 0x1020]) self.assertEqual([record.value for record in records.shaderdata], [16, 16]) + def test_multi_payload_headers_are_normalized_without_clock_correction(self): + value = (0x100 << 20) | (3 << 2) + header = _shaderdata(0x1080, value) + payload0 = _shaderdata(0x1084, 0xDEADBEEF) + payload1 = _shaderdata(0x1088, 0xCAFEBABE) + records = TraceRecords( + occupancy=[_occupancy(1, 0)], + shaderdata=[header, payload0, payload1], + ) + + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 3, "P", "multi", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + "sqtt_funcmap_payloads": [[7, 3, 2]], + }, + ) + + self.assertEqual(header.value, 12) + self.assertEqual(header.time, 0x1080) + self.assertEqual([payload0.time, payload1.time], [0x1084, 0x1088]) + self.assertEqual([payload0.value, payload1.value], [0xDEADBEEF, 0xCAFEBABE]) + def test_no_decode_markers_preserves_packed_values(self): value = (0x100 << 20) | (3 << 2) records = TraceRecords( diff --git a/projects/rocprof-trace-decoder/test/markers/run_tests.py b/projects/rocprof-trace-decoder/test/markers/run_tests.py index f1ba4db32d80..140fadf1e070 100644 --- a/projects/rocprof-trace-decoder/test/markers/run_tests.py +++ b/projects/rocprof-trace-decoder/test/markers/run_tests.py @@ -381,6 +381,21 @@ def extract_funcmap(binary: str, tmpdir: str) -> str: "reject_funcmap": [], "custom_asm_check": "gfx9_m0_trace_spacing", }, + { + "name": "gfx9_full_m0_trace_nop", + "desc": "gfx9: every full M0 trace has s_nop 0 before s_ttracedata", + "env": {}, + "mode": "asm", + "source": "marker", + "flags": ["--offload-arch=gfx90a"], + "expect_ir": [ + r"s_ttracedata\b", + ], + "reject_ir": [], + "expect_funcmap": [], + "reject_funcmap": [], + "custom_asm_check": "m0_nop_before_ttracedata", + }, { "name": "gfx10_full_m0_trace_nop", "desc": "gfx10: every full M0 trace has s_nop 0 before s_ttracedata", diff --git a/projects/rocprof-trace-decoder/test/unit/markers_test.cpp b/projects/rocprof-trace-decoder/test/unit/markers_test.cpp index edb2d6936587..9158ba3c8837 100644 --- a/projects/rocprof-trace-decoder/test/unit/markers_test.cpp +++ b/projects/rocprof-trace-decoder/test/unit/markers_test.cpp @@ -150,6 +150,27 @@ CallInst* insertTraceCallBefore(Instruction* insertPt, uint32_t encoded, bool pa return call; } +Function* makeNamedMarkerSentinel(Module& module, StringRef name) +{ + LLVMContext& ctx = module.getContext(); + auto* type = FunctionType::get(Type::getVoidTy(ctx), {PointerType::get(ctx, 0)}, false); + return Function::Create(type, GlobalValue::ExternalLinkage, name, module); +} + +GlobalVariable* makeMarkerString(Module& module, StringRef value) +{ + LLVMContext& ctx = module.getContext(); + auto* initializer = ConstantDataArray::getString(ctx, value, true); + return new GlobalVariable( + module, + initializer->getType(), + true, + GlobalValue::PrivateLinkage, + initializer, + ".sqtt.marker.string" + ); +} + void addEarlyFunctionMetadata(Function& function, uint32_t id, unsigned preOptSize, StringRef sourceLoc) { Module* module = function.getParent(); @@ -238,6 +259,29 @@ size_t countIntrinsicCalls(const Function& function, Intrinsic::ID id) return count; } +size_t countFences(const Function& function) +{ + size_t count = 0; + for (const BasicBlock& block : function) + for (const Instruction& inst : block) + if (isa(inst)) ++count; + return count; +} + +std::optional earlyFunctionPreOptSize(const Module& module, StringRef name) +{ + const NamedMDNode* entries = module.getNamedMetadata("sqtt.funcmap.early"); + if (!entries) return std::nullopt; + for (const MDNode* entry : entries->operands()) + { + if (entry->getNumOperands() < 3) continue; + auto* entryName = dyn_cast(entry->getOperand(1)); + auto* size = mdconst::dyn_extract(entry->getOperand(2)); + if (entryName && size && entryName->getString() == name) return size->getZExtValue(); + } + return std::nullopt; +} + std::vector traceMarkerValues(const Function& function) { std::vector values; @@ -347,6 +391,25 @@ std::optional pointEntryId(const std::string& funcMap, StringRef name) return std::nullopt; } +size_t countPointEntries(const std::string& funcMap, StringRef name) +{ + size_t count = 0; + SmallVector lines; + StringRef(funcMap).split(lines, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/false); + for (StringRef line : lines) + { + line = line.rtrim("\r"); + if (!line.consume_front("P:")) continue; + auto [idText, rest] = line.split(':'); + unsigned id = 0; + if (idText.getAsInteger(10, id)) continue; + auto [entryName, sourceLoc] = rest.split('@'); + (void) sourceLoc; + if (entryName == name) ++count; + } + return count; +} + std::optional extraPayloadCountForId(const std::string& funcMap, unsigned markerId) { SmallVector lines; @@ -569,6 +632,176 @@ TEST(MarkerPass, AddressTracingHandlesBuffersAndPermutes) EXPECT_EQ(countIntrinsicCalls(*module, Intrinsic::amdgcn_readlane), 5u); } +TEST(MarkerPass, AddressTracingOnlyUsesFlatAndGlobalAddressSpaces) +{ + LLVMContext ctx; + auto module = makeModule(ctx); + Type* i32 = Type::getInt32Ty(ctx); + Type* voidTy = Type::getVoidTy(ctx); + FunctionType* type = FunctionType::get( + voidTy, + {PointerType::get(ctx, 0), + PointerType::get(ctx, 1), + PointerType::get(ctx, 2), + PointerType::get(ctx, 4), + PointerType::get(ctx, 5)}, + false + ); + Function* function = makeFunction(*module, "address_spaces", "gfx1100", type); + BasicBlock* entry = BasicBlock::Create(ctx, "entry", function); + IRBuilder<> builder(entry); + builder.CreateLoad(i32, function->getArg(0)); + builder.CreateStore(ConstantInt::get(i32, 1), function->getArg(1)); + builder.CreateLoad(i32, function->getArg(2)); + builder.CreateLoad(i32, function->getArg(3)); + builder.CreateLoad(i32, function->getArg(4)); + builder.CreateRetVoid(); + + SQTTConfig config; + useFullScopeMasks(config); + config.TraceMemoryAddrs = true; + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + + const std::string funcMap = getFuncMap(*module); + EXPECT_EQ(countPointEntries(funcMap, "addr_trace_load"), 1u); + EXPECT_EQ(countPointEntries(funcMap, "addr_trace_store"), 1u); + EXPECT_EQ(countPointEntries(funcMap, "addr_trace_atomic"), 0u); +} + +TEST(MarkerPass, AddressTracingSkipsBufferLoadLds) +{ + LLVMContext ctx; + auto module = makeModule(ctx); + Type* voidTy = Type::getVoidTy(ctx); + Function* function = makeVoidFunction(*module, "buffer_load_lds", "gfx1200"); + IRBuilder<> builder(function->getEntryBlock().getTerminator()); + Function* bufferLoadLds = Function::Create( + FunctionType::get(voidTy, false), + GlobalValue::ExternalLinkage, + "llvm.amdgcn.raw.buffer.load.lds", + module.get() + ); + builder.CreateCall(bufferLoadLds); + Function* point = makeNamedMarkerSentinel(*module, "__sqtt_named_marker_point"); + builder.CreateCall(point, {makeMarkerString(*module, "ordinary_point")}); + + SQTTConfig config; + useFullScopeMasks(config); + config.TraceMemoryAddrs = true; + // This would fail if .buffer.load.lds were misclassified as an address + // block during the preflight scan. + config.ShaderClockBits = 12; + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + + const std::string funcMap = getFuncMap(*module); + expectContains(funcMap, "M:shader_clock_bits=12;shader_clock_shift=4"); + expectNotContains(funcMap, "addr_trace_buffer_load"); + expectNotContains(funcMap, "W:"); +} + +TEST(MarkerPass, AddressTraceBlocksDisableAutomaticGfx12ClockPackingAndUseBlockBoundaries) +{ + LLVMContext ctx; + auto module = makeModule(ctx); + Type* i32 = Type::getInt32Ty(ctx); + Type* voidTy = Type::getVoidTy(ctx); + Function* function = makeFunction( + *module, + "gfx12_address_trace", + "gfx1200", + FunctionType::get(voidTy, {PointerType::get(ctx, 1)}, false) + ); + BasicBlock* entry = BasicBlock::Create(ctx, "entry", function); + IRBuilder<> builder(entry); + builder.CreateLoad(i32, function->getArg(0)); + builder.CreateRetVoid(); + + SQTTConfig config; + useFullScopeMasks(config); + config.MemBarrier = MemBarrierMode::Fence; + config.TraceMemoryAddrs = true; + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + + const std::string funcMap = getFuncMap(*module); + expectContains(funcMap, "W:32"); + expectPointEntryWithPayload(funcMap, "addr_trace_load", 66); + expectNotContains(funcMap, "M:shader_clock_bits="); + EXPECT_EQ(countIntrinsicCalls(*function, Intrinsic::amdgcn_s_getreg), 0u); + EXPECT_EQ(countFences(*function), 2u); + EXPECT_EQ(countIntrinsicCalls(*function, Intrinsic::amdgcn_sched_barrier), 2u); +} + +TEST(MarkerPass, ForcedGfx12ClockPackingFailsForAddressTraceBlocks) +{ + EXPECT_DEATH( + { + LLVMContext ctx; + auto module = makeModule(ctx); + Type* i32 = Type::getInt32Ty(ctx); + Type* voidTy = Type::getVoidTy(ctx); + Function* function = makeFunction( + *module, + "forced_clock_address_trace", + "gfx1200", + FunctionType::get(voidTy, {PointerType::get(ctx, 1)}, false) + ); + BasicBlock* entry = BasicBlock::Create(ctx, "entry", function); + IRBuilder<> builder(entry); + builder.CreateLoad(i32, function->getArg(0)); + builder.CreateRetVoid(); + + SQTTConfig config; + useFullScopeMasks(config); + config.TraceMemoryAddrs = true; + config.ShaderClockBits = 12; + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + }, + "SQTT_TRACE_ADDRESSES emits multi-payload records" + ); +} + +TEST(MarkerPass, OnePayloadNamedDataRemainsEligibleForGfx12ClockPacking) +{ + LLVMContext ctx; + auto module = makeModule(ctx); + Type* i32 = Type::getInt32Ty(ctx); + Type* voidTy = Type::getVoidTy(ctx); + Function* data = Function::Create( + FunctionType::get(voidTy, {PointerType::get(ctx, 0), i32}, false), + GlobalValue::ExternalLinkage, + "__sqtt_named_marker_data", + module.get() + ); + GlobalVariable* name = makeMarkerString(*module, "one_payload"); + Function* function = makeVoidFunction(*module, "gfx12_named_data", "gfx1200"); + IRBuilder<> builder(function->getEntryBlock().getTerminator()); + builder.CreateCall(data, {name, ConstantInt::get(i32, 17)}); + + SQTTConfig config; + useFullScopeMasks(config); + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + + const std::string funcMap = getFuncMap(*module); + expectContains(funcMap, "M:shader_clock_bits=12;shader_clock_shift=4"); + expectPointEntryWithPayload(funcMap, "one_payload", 1); + EXPECT_EQ(countIntrinsicCalls(*function, Intrinsic::amdgcn_s_getreg), 1u); +} + TEST(MarkerPass, Gfx9BufferAndPermuteAddressTracesUseInlineAsmExecProtocol) { LLVMContext ctx; @@ -615,7 +848,7 @@ TEST(MarkerPass, Gfx9BufferAndPermuteAddressTracesUseInlineAsmExecProtocol) expectContains(ir, "={m0}"); } -TEST(MarkerPass, Gfx10PlusFullM0TracesUseExplicitNopSequence) +TEST(MarkerPass, FullM0TracesUseExplicitNopSequenceOnEveryArchitecture) { LLVMContext ctx; auto module = makeModule(ctx); @@ -640,14 +873,10 @@ TEST(MarkerPass, Gfx10PlusFullM0TracesUseExplicitNopSequence) SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); pass.run(*module, analysisManager); - // gfx9's normal intrinsic lowering already inserts the M0 hazard nop. - // Keep that established path intact; gfx10+ needs the explicit sequence. - EXPECT_EQ(countIntrinsicCalls(*functions[0], Intrinsic::amdgcn_s_ttracedata), 1u); - EXPECT_EQ(findM0NopTrace(*functions[0]), nullptr); - for (size_t index = 1; index < functions.size(); ++index) + for (Function* function : functions) { - const CallInst* trace = findM0NopTrace(*functions[index]); - ASSERT_NE(trace, nullptr) << functions[index]->getName().str(); + const CallInst* trace = findM0NopTrace(*function); + ASSERT_NE(trace, nullptr) << function->getName().str(); auto* inlineAsm = dyn_cast(trace->getCalledOperand()); ASSERT_NE(inlineAsm, nullptr); EXPECT_EQ(inlineAsm->getConstraintString(), "={m0},i"); @@ -702,12 +931,19 @@ TEST(MarkerPass, BarrierInstrumentationHandlesSplitAndStandaloneBarriers) FunctionCallee signal = Intrinsic::getOrInsertDeclaration(module.get(), Intrinsic::amdgcn_s_barrier_signal); FunctionCallee wait = Intrinsic::getOrInsertDeclaration(module.get(), Intrinsic::amdgcn_s_barrier_wait); FunctionCallee full = Intrinsic::getOrInsertDeclaration(module.get(), Intrinsic::amdgcn_s_barrier); + Function* work = Function::Create( + FunctionType::get(Type::getVoidTy(ctx), false), + GlobalValue::ExternalLinkage, + "barrier_work", + module.get() + ); builder.CreateCall(signal, {ConstantInt::get(i32, 0)}); builder.CreateCall(wait, {ConstantInt::get(i16, 0)}); builder.CreateCall(signal, {ConstantInt::get(i32, 0)}); - builder.CreateCall(full); + builder.CreateCall(work); builder.CreateCall(wait, {ConstantInt::get(i16, 0)}); + builder.CreateCall(full); SQTTConfig config; useFullScopeMasks(config); @@ -733,6 +969,67 @@ TEST(MarkerPass, BarrierInstrumentationHandlesSplitAndStandaloneBarriers) EXPECT_EQ(traceCount, 4u); } +TEST(MarkerPass, NamedExitEnterFusionRequiresDirectAdjacency) +{ + LLVMContext ctx; + auto module = makeModule(ctx); + Type* i32 = Type::getInt32Ty(ctx); + Function* exit = makeNamedMarkerSentinel(*module, "__sqtt_named_marker_exit"); + Function* enter = makeNamedMarkerSentinel(*module, "__sqtt_named_marker_enter"); + GlobalVariable* oldName = makeMarkerString(*module, "old"); + GlobalVariable* newName = makeMarkerString(*module, "new"); + + Function* separated = makeFunction( + *module, "separated_named_markers", "gfx1100", FunctionType::get(Type::getVoidTy(ctx), {i32}, false) + ); + BasicBlock* separatedEntry = BasicBlock::Create(ctx, "entry", separated); + IRBuilder<> separatedBuilder(separatedEntry); + separatedBuilder.CreateCall(exit, {oldName}); + separatedBuilder.CreateAdd(separated->getArg(0), ConstantInt::get(i32, 1)); + separatedBuilder.CreateCall(enter, {newName}); + separatedBuilder.CreateRetVoid(); + + Function* adjacent = makeVoidFunction(*module, "adjacent_named_markers", "gfx1100"); + Instruction* adjacentRet = adjacent->getEntryBlock().getTerminator(); + IRBuilder<> adjacentBuilder(adjacentRet); + adjacentBuilder.CreateCall(exit, {oldName}); + adjacentBuilder.CreateCall(enter, {newName}); + + SQTTConfig config; + useFullScopeMasks(config); + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + + std::vector separatedMarkers = traceMarkerValues(*separated); + ASSERT_EQ(separatedMarkers.size(), 2u); + EXPECT_EQ(separatedMarkers[0], FLAG_EXIT_PREV); + EXPECT_EQ(separatedMarkers[1] & FLAG_MASK, FLAG_ENTER); + + std::vector adjacentMarkers = traceMarkerValues(*adjacent); + ASSERT_EQ(adjacentMarkers.size(), 1u); + EXPECT_EQ(adjacentMarkers[0] & FLAG_MASK, FLAG_ENTER | FLAG_EXIT_PREV); +} + +TEST(MarkerPass, NumericMarkersAlwaysReceiveConfiguredMemoryBarriers) +{ + LLVMContext ctx; + auto module = makeModule(ctx); + Function* function = makeVoidFunction(*module, "numeric_marker", "gfx1100"); + insertTraceCallBefore(function->getEntryBlock().getTerminator(), encodeMarker(17, false, false)); + + SQTTConfig config; + useFullScopeMasks(config); + config.MemBarrier = MemBarrierMode::Fence; + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + + EXPECT_EQ(countFences(*function), 2u); +} + TEST(MarkerPass, DirectFunctionInstrumentationHandlesO0Fallback) { LLVMContext ctx; @@ -775,6 +1072,69 @@ TEST(MarkerPass, DirectFunctionInstrumentationHandlesO0Fallback) EXPECT_TRUE(traceMarkerValues(*kernel).empty()); } +TEST(MarkerPass, FunctionThresholdIgnoresPassMarkersBeforeAndAfterOptimization) +{ + LLVMContext ctx; + auto module = makeModule(ctx); + Function* function = makeVoidFunction(*module, "small_function", "gfx1100"); + + SQTTConfig config; + useFullScopeMasks(config); + config.FunctionThreshold = 1; + + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass early(config, SQTTInstrumentPass::Mode::Early); + early.run(*module, analysisManager); + + EXPECT_EQ(earlyFunctionPreOptSize(*module, function->getName()), 1u); + + SQTTInstrumentPass late(config, SQTTInstrumentPass::Mode::Late); + late.run(*module, analysisManager); + + EXPECT_TRUE(traceMarkerValues(*function).empty()); + expectNotContains(getFuncMap(*module), "small_function"); +} + +TEST(MarkerPass, AutomaticFunctionInstrumentationSkipsMustTailFunctions) +{ + auto makeMustTailFunction = [](Module& module, StringRef name) + { + LLVMContext& ctx = module.getContext(); + Type* i32 = Type::getInt32Ty(ctx); + FunctionType* type = FunctionType::get(i32, {i32}, false); + Function* callee = + Function::Create(type, GlobalValue::ExternalLinkage, name.str() + ".callee", &module); + Function* function = makeFunction(module, name, "gfx1100", type); + BasicBlock* entry = BasicBlock::Create(ctx, "entry", function); + IRBuilder<> builder(entry); + CallInst* call = builder.CreateCall(callee, {function->getArg(0)}); + call->setTailCallKind(CallInst::TCK_MustTail); + builder.CreateRet(call); + return function; + }; + + SQTTConfig config; + useFullScopeMasks(config); + config.FunctionThreshold = 1; + ModuleAnalysisManager analysisManager; + + LLVMContext earlyCtx; + auto earlyModule = makeModule(earlyCtx); + Function* earlyFunction = makeMustTailFunction(*earlyModule, "early_musttail"); + SQTTInstrumentPass early(config, SQTTInstrumentPass::Mode::Early); + early.run(*earlyModule, analysisManager); + EXPECT_TRUE(traceMarkerValues(*earlyFunction).empty()); + EXPECT_FALSE(earlyModule->getNamedMetadata("sqtt.funcmap.early")); + + LLVMContext lateCtx; + auto lateModule = makeModule(lateCtx); + Function* lateFunction = makeMustTailFunction(*lateModule, "late_musttail"); + SQTTInstrumentPass late(config, SQTTInstrumentPass::Mode::Late); + late.run(*lateModule, analysisManager); + EXPECT_TRUE(traceMarkerValues(*lateFunction).empty()); + expectNotContains(getFuncMap(*lateModule), "late_musttail"); +} + TEST(MarkerPass, FunctionThresholdPrunesMarkersAndPreservesExistingLlvmUsed) { LLVMContext ctx; @@ -828,5 +1188,9 @@ TEST(MarkerPass, FunctionThresholdPrunesMarkersAndPreservesExistingLlvmUsed) EXPECT_EQ(countIntrinsicCalls(*small, Intrinsic::amdgcn_s_ttracedata), 0u); EXPECT_EQ(countIntrinsicCalls(*small, Intrinsic::amdgcn_s_ttracedata_imm), 0u); - EXPECT_EQ(traceMarkerValues(*numeric), std::vector{encodeMarker(smallId, true, false)}); + const CallInst* numericTrace = findM0NopTrace(*numeric); + ASSERT_NE(numericTrace, nullptr); + auto* numericValue = dyn_cast(numericTrace->getArgOperand(0)); + ASSERT_NE(numericValue, nullptr); + EXPECT_EQ(numericValue->getZExtValue(), encodeMarker(smallId, true, false)); } From eb44a38decf81b9b5efebc2169f5892b52695152 Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 17:43:27 +0200 Subject: [PATCH 04/13] Fix marker layout --- .../docs/markers/SPEC.md | 26 ++++------ .../rocprof-trace-decoder/markers/README.md | 9 ++-- .../python/rocprof_trace_decoder/att.py | 50 ++++++++++++------- .../test/att_tool_test.py | 39 +++++++++++---- 4 files changed, 77 insertions(+), 47 deletions(-) diff --git a/projects/rocprof-trace-decoder/docs/markers/SPEC.md b/projects/rocprof-trace-decoder/docs/markers/SPEC.md index 7537b8101ea7..ecb1a4cf44fb 100644 --- a/projects/rocprof-trace-decoder/docs/markers/SPEC.md +++ b/projects/rocprof-trace-decoder/docs/markers/SPEC.md @@ -130,28 +130,24 @@ exactly one payload (`R:...=1`) and remains eligible for clock packing. The sampled window lets a decoder compensate for the variable delay between the trace instruction issuing and its shaderdata record appearing in SQTT. -For each **marker header** (never an `R:`-declared raw payload), restore the -sampled field to its source positions and calculate a safe lower bound for the -delay modulo the source-clock window: +Shaderdata and shader-clock timestamps have the same rate but an unknown fixed +phase per physical SIMD. For each **marker header** (never an `R:`-declared raw +payload), calculate its circular residue: ```text window_mask = (1ULL << (clock_bits + shader_clock_shift)) - 1 bucket_size = 1ULL << shader_clock_shift -arrival_window = uint32_t(record_time) & window_mask sampled_window = packed_clock_field << shader_clock_shift -delta = (arrival_window - sampled_window) & window_mask -delay_lower_bound = max(0, delta - (bucket_size - 1)) +residue = (uint32_t(record_time) - sampled_window) & window_mask ``` -Only a relative delay is knowable from this truncated clock. The low -`shader_clock_shift` bits were not sampled, so the lower bound avoids moving a -record before any possible issue time; it leaves at most -`(1 << shader_clock_shift) - 1` cycles of residual jitter. Keep the smallest -observed lower bound for each shader-engine clock domain and compatible layout -as the reference, then shift every header earlier by -`delay_lower_bound - minimum_delay`. The actual delivery delay must be shorter -than the sampled clock window (the default 12-bit field at shift 4 has a -65,536-cycle window), otherwise the modulo value is ambiguous. +Within each `(shader engine, CU, SIMD, clock_bits, shader_clock_shift)` domain, +sort residues, find the largest circular gap, and unwrap after it. If the +unwrapped span exceeds half the window, emit one warning for that domain but +still use the deterministic first largest-gap reference. With +`relative = (residue - unwrap_start) & window_mask`, shift a header earlier by +`max(0, relative - (bucket_size - 1))`. This cancels the unknown phase while +never assuming the omitted low clock bits. `att_tool.py` applies this correction while writing JSON by default; `--no-decode-markers` preserves the packed values and arrival timestamps. It diff --git a/projects/rocprof-trace-decoder/markers/README.md b/projects/rocprof-trace-decoder/markers/README.md index 46200ca86729..7ae8e1361e80 100644 --- a/projects/rocprof-trace-decoder/markers/README.md +++ b/projects/rocprof-trace-decoder/markers/README.md @@ -482,10 +482,11 @@ leaving 18 marker ID bits plus the two low flags. Configure this with Numeric point and enter markers keep their legacy values; bare exits use the packed form because their value is indistinguishable from a generated exit. -The packed clock is a truncated sample at instruction issue. Compare it with -the record timestamp modulo its source-clock window and subtract only the -excess over the smallest delay for that shader engine. Apply this only to -headers, never `R:` payloads. `Funcmap::marker_encoding` exposes +The packed clock is a truncated sample at instruction issue. Shaderdata and +shader clocks have an unknown fixed phase per physical SIMD, so `att_tool.py` +circularly unwraps recognized header residues per SIMD/layout before applying a +conservative relative correction. Apply this only to headers, never `R:` +payloads. `Funcmap::marker_encoding` exposes `marker_id_mask()`, `packed_shader_clock_mask()`, and `shader_clock_source_mask()`; without `M:` metadata those describe the legacy full-width ID layout. diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index ee11663c4b30..4d0c15c988c6 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from collections import defaultdict from collections.abc import Iterable from dataclasses import dataclass @@ -172,8 +173,7 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict for record in records.occupancy: occupancy[se, record.cu, record.simd, record.wave_id].append(record) - headers: list[tuple[object, tuple[int, int, int, int, int], int, list[object]]] = [] - minimum_delay: dict[tuple[int, int, int, int, int], int] = {} + headers: dict[tuple[int, int, int, int, int], list[tuple[object, int, list[object]]]] = defaultdict(list) for location, stream in shaderdata.items(): stream.sort(key=lambda record: record.time) intervals = _active_codeobj_intervals(occupancy.get(location, ())) @@ -222,25 +222,39 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict window_mask = (1 << (bits + shift)) - 1 bucket_size = 1 << shift sampled = ((raw >> (32 - bits)) & ((1 << bits) - 1)) << shift - delta = ((int(record.time) & window_mask) - sampled) & window_mask - delay = max(0, delta - (bucket_size - 1)) - # shader_cycles_lo samples are only comparable on one physical - # SIMD. Keep differently encoded clock windows independent too. + residue = (int(record.time) - sampled) & window_mask clock_domain = (*location[:3], bits, shift) - headers.append((record, clock_domain, delay, header_payloads)) - minimum_delay[clock_domain] = min(minimum_delay.get(clock_domain, delay), delay) + headers[clock_domain].append((record, residue, header_payloads)) changed = False - for record, clock_domain, delay, header_payloads in headers: - correction = delay - minimum_delay[clock_domain] - if correction: - record.time -= correction - # The output writer sorts shaderdata after correction. Move raw - # payloads with their header so declared header/payload blocks - # remain ordered for readers that consume R: metadata. - for payload in header_payloads: - payload.time -= correction - changed = True + for clock_domain, domain_headers in headers.items(): + bits, shift = clock_domain[-2:] + window_mask = (1 << (bits + shift)) - 1 + bucket_size = 1 << shift + residues = sorted({header[1] for header in domain_headers}) + if len(residues) < 2: + continue + gaps = [ + (residues[(i + 1) % len(residues)] - residue) & window_mask + for i, residue in enumerate(residues) + ] + largest_gap = max(gaps) + span = window_mask + 1 - largest_gap + if span > (window_mask + 1) // 2: + warnings.warn( + f"SQTT marker correction for {clock_domain}: clock phase span exceeds half the window", + RuntimeWarning, + ) + start = residues[(gaps.index(largest_gap) + 1) % len(residues)] + for record, residue, header_payloads in domain_headers: + correction = max(0, ((residue - start) & window_mask) - (bucket_size - 1)) + if correction: + record.time -= correction + # TODO: sort one-payload blocks atomically; a final per-record + # sort can still interleave another header with this block. + for payload in header_payloads: + payload.time -= correction + changed = True if changed: for _se, records in decoded: records.shaderdata.sort(key=lambda record: record.time) diff --git a/projects/rocprof-trace-decoder/test/att_tool_test.py b/projects/rocprof-trace-decoder/test/att_tool_test.py index a4ebec32d6be..86af152bf967 100644 --- a/projects/rocprof-trace-decoder/test/att_tool_test.py +++ b/projects/rocprof-trace-decoder/test/att_tool_test.py @@ -71,7 +71,7 @@ def test_corrects_headers_and_moves_declared_payloads_with_their_header(self): _correct_marker_timestamps([(0, records)], document) - self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x1010, 0x1020]) + self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x101F, 0x102F]) self.assertIs(records.shaderdata[0], first) self.assertIs(records.shaderdata[1], second) self.assertIs(records.shaderdata[2], payload) @@ -162,7 +162,7 @@ def test_correction_is_default(self): formats="json", ) - self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x1010]) + self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x101F]) self.assertEqual([record.value for record in records.shaderdata], [12, 12]) def test_correction_does_not_share_the_reference_between_simds(self): @@ -186,24 +186,43 @@ def test_correction_does_not_share_the_reference_between_simds(self): self.assertEqual(fast_simd.time, 0x1010) self.assertEqual([record.value for record in records.shaderdata], [12, 12]) - def test_correction_does_not_precede_the_sampled_clock_at_wrap(self): + def test_correction_unwraps_unknown_clock_phase(self): marker_id = 3 << 2 - reference = _shaderdata(0x43FF, (0x10 << 20) | marker_id) - wrapped = _shaderdata(0x400010, (0xFFF << 20) | marker_id) + reference = _shaderdata(0x21F64, (0x200 << 20) | marker_id) + delayed = _shaderdata(0x22384, (0x210 << 20) | marker_id) records = TraceRecords( - occupancy=[_occupancy(1, 0)], shaderdata=[reference, wrapped]) + occupancy=[_occupancy(1, 0)], shaderdata=[reference, delayed]) _correct_marker_timestamps( [(0, records)], { "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], - "sqtt_funcmap_layout": [[7, 12, 10]], + "sqtt_funcmap_layout": [[7, 12, 4]], }, ) - self.assertEqual([record.time for record in records.shaderdata], [0x43FF, 0x3FFFFF]) + self.assertEqual([record.time for record in records.shaderdata], [0x21F64, 0x22073]) self.assertEqual([record.value for record in records.shaderdata], [marker_id, marker_id]) + def test_wide_clock_phase_warns_and_is_corrected(self): + marker_id = 3 << 2 + value = (0x200 << 20) | marker_id + records = TraceRecords( + occupancy=[_occupancy(1, 0)], + shaderdata=[_shaderdata(time, value) for time in (0x12000, 0x16000, 0x1B000)], + ) + + with self.assertWarnsRegex(RuntimeWarning, "span exceeds half"): + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + }, + ) + + self.assertEqual([record.time for record in records.shaderdata], [0x12000, 0x1200F, 0x1200F]) + def test_late_header_after_retirement_is_normalized_and_corrected(self): value = (0x100 << 20) | (3 << 2) before_retirement = _shaderdata(0x1010, value) @@ -222,7 +241,7 @@ def test_late_header_after_retirement_is_normalized_and_corrected(self): ) self.assertEqual(before_retirement.time, 0x1010) - self.assertEqual(after_retirement.time, 0x1010) + self.assertEqual(after_retirement.time, 0x101F) self.assertEqual([record.value for record in records.shaderdata], [12, 12]) def test_later_launch_supersedes_a_retired_wave_slot(self): @@ -246,7 +265,7 @@ def test_later_launch_supersedes_a_retired_wave_slot(self): }, ) - self.assertEqual([record.time for record in records.shaderdata], [0x1020, 0x1020]) + self.assertEqual([record.time for record in records.shaderdata], [0x1020, 0x103F]) self.assertEqual([record.value for record in records.shaderdata], [16, 16]) def test_multi_payload_headers_are_normalized_without_clock_correction(self): From 73b08e9fdcf3e95b63b02f4d85dc594cc5354992 Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 18:23:06 +0200 Subject: [PATCH 05/13] Fix circular logic --- .../docs/markers/SPEC.md | 12 +++++------ .../rocprof-trace-decoder/markers/README.md | 5 +++-- .../python/rocprof_trace_decoder/att.py | 17 ++++------------ .../test/att_tool_test.py | 20 ++++++++++--------- 4 files changed, 24 insertions(+), 30 deletions(-) diff --git a/projects/rocprof-trace-decoder/docs/markers/SPEC.md b/projects/rocprof-trace-decoder/docs/markers/SPEC.md index ecb1a4cf44fb..128f0201ac11 100644 --- a/projects/rocprof-trace-decoder/docs/markers/SPEC.md +++ b/projects/rocprof-trace-decoder/docs/markers/SPEC.md @@ -142,12 +142,12 @@ residue = (uint32_t(record_time) - sampled_window) & window_mask ``` Within each `(shader engine, CU, SIMD, clock_bits, shader_clock_shift)` domain, -sort residues, find the largest circular gap, and unwrap after it. If the -unwrapped span exceeds half the window, emit one warning for that domain but -still use the deterministic first largest-gap reference. With -`relative = (residue - unwrap_start) & window_mask`, shift a header earlier by -`max(0, relative - (bucket_size - 1))`. This cancels the unknown phase while -never assuming the omitted low clock bits. +the implementation assumes the delivery delay does not wrap the sampled +window. Keep the smallest residue as the reference. With +`relative = residue - minimum_residue`, shift a header earlier by +`max(0, relative - (bucket_size - 1))`; the fixed phase cancels in that +difference. If the delay range exceeds half the window, emit one warning for +that domain but still correct it. `att_tool.py` applies this correction while writing JSON by default; `--no-decode-markers` preserves the packed values and arrival timestamps. It diff --git a/projects/rocprof-trace-decoder/markers/README.md b/projects/rocprof-trace-decoder/markers/README.md index 7ae8e1361e80..73a97e8c617d 100644 --- a/projects/rocprof-trace-decoder/markers/README.md +++ b/projects/rocprof-trace-decoder/markers/README.md @@ -484,8 +484,9 @@ packed form because their value is indistinguishable from a generated exit. The packed clock is a truncated sample at instruction issue. Shaderdata and shader clocks have an unknown fixed phase per physical SIMD, so `att_tool.py` -circularly unwraps recognized header residues per SIMD/layout before applying a -conservative relative correction. Apply this only to headers, never `R:` +keeps the smallest residue per SIMD/layout and applies a conservative relative +correction; the fixed phase cancels. A delay range over half the sampled window +emits a warning but is still corrected. Apply this only to headers, never `R:` payloads. `Funcmap::marker_encoding` exposes `marker_id_mask()`, `packed_shader_clock_mask()`, and `shader_clock_source_mask()`; without `M:` metadata those describe the legacy diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index 4d0c15c988c6..1842cff77823 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -231,23 +231,14 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict bits, shift = clock_domain[-2:] window_mask = (1 << (bits + shift)) - 1 bucket_size = 1 << shift - residues = sorted({header[1] for header in domain_headers}) - if len(residues) < 2: - continue - gaps = [ - (residues[(i + 1) % len(residues)] - residue) & window_mask - for i, residue in enumerate(residues) - ] - largest_gap = max(gaps) - span = window_mask + 1 - largest_gap - if span > (window_mask + 1) // 2: + minimum = min(header[1] for header in domain_headers) + if max(header[1] for header in domain_headers) - minimum > (window_mask + 1) // 2: warnings.warn( - f"SQTT marker correction for {clock_domain}: clock phase span exceeds half the window", + f"SQTT marker correction for {clock_domain}: delay range exceeds half the window", RuntimeWarning, ) - start = residues[(gaps.index(largest_gap) + 1) % len(residues)] for record, residue, header_payloads in domain_headers: - correction = max(0, ((residue - start) & window_mask) - (bucket_size - 1)) + correction = max(0, residue - minimum - (bucket_size - 1)) if correction: record.time -= correction # TODO: sort one-payload blocks atomically; a final per-record diff --git a/projects/rocprof-trace-decoder/test/att_tool_test.py b/projects/rocprof-trace-decoder/test/att_tool_test.py index 86af152bf967..59850cb35600 100644 --- a/projects/rocprof-trace-decoder/test/att_tool_test.py +++ b/projects/rocprof-trace-decoder/test/att_tool_test.py @@ -186,10 +186,10 @@ def test_correction_does_not_share_the_reference_between_simds(self): self.assertEqual(fast_simd.time, 0x1010) self.assertEqual([record.value for record in records.shaderdata], [12, 12]) - def test_correction_unwraps_unknown_clock_phase(self): + def test_correction_cancels_unknown_clock_phase(self): marker_id = 3 << 2 - reference = _shaderdata(0x21F64, (0x200 << 20) | marker_id) - delayed = _shaderdata(0x22384, (0x210 << 20) | marker_id) + reference = _shaderdata(0x14080, marker_id) + delayed = _shaderdata(0x144A0, (0x10 << 20) | marker_id) records = TraceRecords( occupancy=[_occupancy(1, 0)], shaderdata=[reference, delayed]) @@ -201,18 +201,19 @@ def test_correction_unwraps_unknown_clock_phase(self): }, ) - self.assertEqual([record.time for record in records.shaderdata], [0x21F64, 0x22073]) + self.assertEqual([record.time for record in records.shaderdata], [0x14080, 0x1418F]) self.assertEqual([record.value for record in records.shaderdata], [marker_id, marker_id]) - def test_wide_clock_phase_warns_and_is_corrected(self): + def test_large_delay_warns_without_moving_the_minimum(self): marker_id = 3 << 2 - value = (0x200 << 20) | marker_id + first = _shaderdata(0x1064, (0x100 << 20) | marker_id) + later = _shaderdata(0x20000, (0x63C << 20) | marker_id) records = TraceRecords( occupancy=[_occupancy(1, 0)], - shaderdata=[_shaderdata(time, value) for time in (0x12000, 0x16000, 0x1B000)], + shaderdata=[first, later], ) - with self.assertWarnsRegex(RuntimeWarning, "span exceeds half"): + with self.assertWarnsRegex(RuntimeWarning, "delay range exceeds half"): _correct_marker_timestamps( [(0, records)], { @@ -221,7 +222,8 @@ def test_wide_clock_phase_warns_and_is_corrected(self): }, ) - self.assertEqual([record.time for record in records.shaderdata], [0x12000, 0x1200F, 0x1200F]) + self.assertEqual(first.time, 0x1064) + self.assertEqual(later.time, 0x16433) def test_late_header_after_retirement_is_normalized_and_corrected(self): value = (0x100 << 20) | (3 << 2) From 717112446c53707d7134043841553793bd8403ed Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 18:32:30 +0200 Subject: [PATCH 06/13] Adding marker test --- .../test/data/navi4_marker.tar.gz | Bin 0 -> 46750 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 projects/rocprof-trace-decoder/test/data/navi4_marker.tar.gz diff --git a/projects/rocprof-trace-decoder/test/data/navi4_marker.tar.gz b/projects/rocprof-trace-decoder/test/data/navi4_marker.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..5b543a69bac434606f234093b4498807ba24bbe0 GIT binary patch literal 46750 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 RV1xe;_z#XE6OaIC0RV-K$9Dh# literal 0 HcmV?d00001 From 7498ad1d8264f0f0fe97a863f5adb8f5f54040b7 Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 19:16:09 +0200 Subject: [PATCH 07/13] Workaround residue --- .../python/rocprof_trace_decoder/att.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index 1842cff77823..67293ff628cf 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -237,8 +237,13 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict f"SQTT marker correction for {clock_domain}: delay range exceeds half the window", RuntimeWarning, ) - for record, residue, header_payloads in domain_headers: - correction = max(0, residue - minimum - (bucket_size - 1)) + # Keep the reference causal; a later minimum cannot revise earlier headers. + reference = window_mask + for record, residue, header_payloads in sorted( + domain_headers, key=lambda header: header[0].time + ): + reference = min(reference, residue) + correction = max(0, residue - reference - (bucket_size - 1)) if correction: record.time -= correction # TODO: sort one-payload blocks atomically; a final per-record From 8d6d7ff91bc7b782d491f831b9b3e1bd544cb53f Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 19:58:21 +0200 Subject: [PATCH 08/13] Fix heavy for gfx12 --- .../test/markers/kernels/heavy.cpp | 120 ++++++++---------- 1 file changed, 52 insertions(+), 68 deletions(-) diff --git a/projects/rocprof-trace-decoder/test/markers/kernels/heavy.cpp b/projects/rocprof-trace-decoder/test/markers/kernels/heavy.cpp index 57d69131b6b3..b6260e877502 100644 --- a/projects/rocprof-trace-decoder/test/markers/kernels/heavy.cpp +++ b/projects/rocprof-trace-decoder/test/markers/kernels/heavy.cpp @@ -98,25 +98,49 @@ using IVec4 = __attribute__((__vector_size__(4 * sizeof(uint32_t)))) uint32_t using Vec16 = __attribute__((__vector_size__(16 * sizeof(float)))) float; using Vec8 = __attribute__((__vector_size__(8 * sizeof(float)))) float; using float8x8 = __attribute__((__vector_size__(8 * sizeof(uint8_t)))) uint8_t; +using IVec2 = __attribute__((__vector_size__(2 * sizeof(int)))) int; static_assert(sizeof(float8) == sizeof(uint8_t)); +#if defined(__gfx1200__) || defined(__gfx1201__) + +inline __device__ IVec2 castfrom8x8(const float8x8& f) +{ + return *reinterpret_cast(&f); +} + +inline __device__ Vec8 mfma(const float8x8& a, const float8x8& b, const Vec8& initial = Vec8{}) +{ + return __builtin_amdgcn_wmma_f32_16x16x16_fp8_fp8_w32_gfx12(castfrom8x8(a), castfrom8x8(b), initial); +} + +inline __device__ Vec4 mfma(const float8x8& a0, const float8x8& a1, const float8x8& b0, const float8x8& b1) +{ + Vec8 ret = mfma(castfrom8x8(a0), castfrom8x8(b0)); + ret = mfma(castfrom8x8(a1), castfrom8x8(b1), ret); + return {ret[0], ret[1], ret[2], ret[3]}; +} + +#else + inline __device__ uint64_t castfrom8x8(const float8x8& f) { return *reinterpret_cast(&f); } -inline __device__ Vec4 mfma(const float8x8& a, const float8x8& b) +inline __device__ Vec4 mfma(const float8x8& a, const float8x8& b, const Vec4& initial = Vec4{}) { - return __builtin_amdgcn_mfma_f32_16x16x32_fp8_fp8(castfrom8x8(a), castfrom8x8(b), Vec4{}, 0, 0, 0); + return __builtin_amdgcn_mfma_f32_16x16x32_fp8_fp8(castfrom8x8(a), castfrom8x8(b), initial, 0, 0, 0); } inline __device__ Vec4 mfma(const float8x8& a0, const float8x8& a1, const float8x8& b0, const float8x8& b1) { - Vec4 ret = __builtin_amdgcn_mfma_f32_16x16x32_fp8_fp8(castfrom8x8(a0), castfrom8x8(b0), Vec4{}, 0, 0, 0); - return __builtin_amdgcn_mfma_f32_16x16x32_fp8_fp8(castfrom8x8(a1), castfrom8x8(b1), ret, 0, 0, 0); + Vec4 ret = mfma(a0, b0); + return mfma(a1, b1, ret); } +#endif + // HEIGHT <= 4 // WIDTH > 4 causes register spill template @@ -309,94 +333,54 @@ void launchHip( int M, int N, int K ) { dim3 block(TBLOCK, 32, 1); + dim3 grid((M + 4*SHMBLOCK - 1) / SHMBLOCK / 4, N / SHMBLOCK); - int mi300_threads = SHMBLOCK * SHMBLOCK * 304; - int bpcu = M * N / mi300_threads; - - if (bpcu >= 8 && M >= 4*SHMBLOCK) - { - dim3 grid((M + 4*SHMBLOCK - 1) / SHMBLOCK / 4, N / SHMBLOCK); - - fp8_gemm_kernel<4><<>>( - reinterpret_cast(a), - reinterpret_cast(b), - reinterpret_cast(c), - scale_a, - scale_b, - M, N, K); - } - else if (bpcu >= 3 && M >= 2*SHMBLOCK) - { - dim3 grid((M + 2*SHMBLOCK - 1) / SHMBLOCK / 2, N / SHMBLOCK); - - fp8_gemm_kernel<2><<>>( - reinterpret_cast(a), - reinterpret_cast(b), - reinterpret_cast(c), - scale_a, - scale_b, - M, N, K); - } - else if (bpcu >= 1 || N > M || 2 * M * N > mi300_threads) - { - dim3 grid((M + SHMBLOCK - 1) / SHMBLOCK, N / SHMBLOCK); - - fp8_gemm_kernel<1><<>>( - reinterpret_cast(a), - reinterpret_cast(b), - reinterpret_cast(c), - scale_a, - scale_b, - M, N, K); - } - else - { - dim3 grid((M + SHMBLOCK - 1) / SHMBLOCK, 2 * N / SHMBLOCK); - - fp8_gemm_kernel<1, 2><<>>( - reinterpret_cast(a), - reinterpret_cast(b), - reinterpret_cast(c), - scale_a, - scale_b, - M, N, K); - } + fp8_gemm_kernel<4><<>>( + reinterpret_cast(a), + reinterpret_cast(b), + reinterpret_cast(c), + scale_a, + scale_b, + M, N, K); HIP_API_CALL(hipGetLastError()); HIP_API_CALL(hipDeviceSynchronize()); } + int main() { const int device = 0; - const int M = 6144; - const int K = 7168; - const int minN = 576; - const int maxN = 4608; + int M = 6144; + int K = 7168; + int N = 4608; hipDeviceProp_t devProp{}; HIP_API_CALL(hipGetDeviceProperties(&devProp, device)); HIP_API_CALL(hipSetDevice(device)); + if (devProp.major > 9 && devProp.minor == 0) + { + M /= 2; + K /= 2; + N /= 2; + } + Matrix a(K, M); - Matrix b(K, maxN); - Matrix c(M, maxN); + Matrix b(K, N); + Matrix c(M, N); Matrix scale_a(K/128, M); - Matrix scale_b(K/128, maxN/128); + Matrix scale_b(K/128, N/128); // warmup - launchHip(a.dev, b.dev, c.dev, scale_a.dev, scale_b.dev, M, minN, K); - launchHip(a.dev, b.dev, c.dev, scale_a.dev, scale_b.dev, M, maxN, K); + launchHip(a.dev, b.dev, c.dev, scale_a.dev, scale_b.dev, M, N, K); HIP_API_CALL(hipDeviceSynchronize()); roctxProfilerResume(0); int runs = 10; for (int i=0; i Date: Wed, 15 Jul 2026 20:01:26 +0200 Subject: [PATCH 09/13] Fix logic --- .../docs/markers/SPEC.md | 21 ++++++----- .../rocprof-trace-decoder/markers/README.md | 6 ++-- .../python/rocprof_trace_decoder/att.py | 35 ++++++++----------- .../test/att_tool_test.py | 31 ++++------------ 4 files changed, 36 insertions(+), 57 deletions(-) diff --git a/projects/rocprof-trace-decoder/docs/markers/SPEC.md b/projects/rocprof-trace-decoder/docs/markers/SPEC.md index 128f0201ac11..7c51777546a6 100644 --- a/projects/rocprof-trace-decoder/docs/markers/SPEC.md +++ b/projects/rocprof-trace-decoder/docs/markers/SPEC.md @@ -131,23 +131,26 @@ exactly one payload (`R:...=1`) and remains eligible for clock packing. The sampled window lets a decoder compensate for the variable delay between the trace instruction issuing and its shaderdata record appearing in SQTT. Shaderdata and shader-clock timestamps have the same rate but an unknown fixed -phase per physical SIMD. For each **marker header** (never an `R:`-declared raw -payload), calculate its circular residue: +phase per physical SIMD. Do not compare them directly. For each **marker +header** (never an `R:`-declared raw payload), choose any header `r` in the +same physical SIMD/layout only as a coordinate origin and calculate: ```text window_mask = (1ULL << (clock_bits + shader_clock_shift)) - 1 +half_window = (window_mask + 1) / 2 bucket_size = 1ULL << shader_clock_shift sampled_window = packed_clock_field << shader_clock_shift -residue = (uint32_t(record_time) - sampled_window) & window_mask +relative = (((record_time - r.record_time) - + (sampled_window - r.sampled_window) + half_window) & window_mask) - half_window ``` Within each `(shader engine, CU, SIMD, clock_bits, shader_clock_shift)` domain, -the implementation assumes the delivery delay does not wrap the sampled -window. Keep the smallest residue as the reference. With -`relative = residue - minimum_residue`, shift a header earlier by -`max(0, relative - (bucket_size - 1))`; the fixed phase cancels in that -difference. If the delay range exceeds half the window, emit one warning for -that domain but still correct it. +the implementation assumes the relative delta stays within the signed sampled +window. Collect the complete domain before selecting `minimum_relative`, then +shift every header earlier by +`max(0, relative - minimum_relative - (bucket_size - 1))`. The coordinate +origin is not a chronological anchor: a later lower value corrects earlier +headers retroactively. `att_tool.py` applies this correction while writing JSON by default; `--no-decode-markers` preserves the packed values and arrival timestamps. It diff --git a/projects/rocprof-trace-decoder/markers/README.md b/projects/rocprof-trace-decoder/markers/README.md index 73a97e8c617d..ceca1ee568fc 100644 --- a/projects/rocprof-trace-decoder/markers/README.md +++ b/projects/rocprof-trace-decoder/markers/README.md @@ -484,9 +484,9 @@ packed form because their value is indistinguishable from a generated exit. The packed clock is a truncated sample at instruction issue. Shaderdata and shader clocks have an unknown fixed phase per physical SIMD, so `att_tool.py` -keeps the smallest residue per SIMD/layout and applies a conservative relative -correction; the fixed phase cancels. A delay range over half the sampled window -emits a warning but is still corrected. Apply this only to headers, never `R:` +cancels it by comparing clock/time deltas to an arbitrary header in the same +SIMD/layout. It takes the minimum from the complete domain and applies the +relative correction retroactively. Apply this only to headers, never `R:` payloads. `Funcmap::marker_encoding` exposes `marker_id_mask()`, `packed_shader_clock_mask()`, and `shader_clock_source_mask()`; without `M:` metadata those describe the legacy diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index 67293ff628cf..ba30f5339ef9 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -1,6 +1,5 @@ from __future__ import annotations -import warnings from collections import defaultdict from collections.abc import Iterable from dataclasses import dataclass @@ -173,7 +172,7 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict for record in records.occupancy: occupancy[se, record.cu, record.simd, record.wave_id].append(record) - headers: dict[tuple[int, int, int, int, int], list[tuple[object, int, list[object]]]] = defaultdict(list) + headers: dict[tuple[int, int, int, int, int], list[tuple[object, int, int, list[object]]]] = defaultdict(list) for location, stream in shaderdata.items(): stream.sort(key=lambda record: record.time) intervals = _active_codeobj_intervals(occupancy.get(location, ())) @@ -219,31 +218,27 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict # can break the declared header/payload record adjacency. if codeobj in multi_payload_codeobjs: continue - window_mask = (1 << (bits + shift)) - 1 - bucket_size = 1 << shift sampled = ((raw >> (32 - bits)) & ((1 << bits) - 1)) << shift - residue = (int(record.time) - sampled) & window_mask clock_domain = (*location[:3], bits, shift) - headers[clock_domain].append((record, residue, header_payloads)) + headers[clock_domain].append((record, int(record.time), sampled, header_payloads)) changed = False for clock_domain, domain_headers in headers.items(): bits, shift = clock_domain[-2:] - window_mask = (1 << (bits + shift)) - 1 + window_size = 1 << (bits + shift) + window_mask = window_size - 1 + half_window = window_size // 2 bucket_size = 1 << shift - minimum = min(header[1] for header in domain_headers) - if max(header[1] for header in domain_headers) - minimum > (window_mask + 1) // 2: - warnings.warn( - f"SQTT marker correction for {clock_domain}: delay range exceeds half the window", - RuntimeWarning, - ) - # Keep the reference causal; a later minimum cannot revise earlier headers. - reference = window_mask - for record, residue, header_payloads in sorted( - domain_headers, key=lambda header: header[0].time - ): - reference = min(reference, residue) - correction = max(0, residue - reference - (bucket_size - 1)) + # This is an arbitrary coordinate origin, not a time/minimum anchor. + # Comparing deltas cancels the fixed phase between the two clocks. + _origin, origin_time, origin_clock, _payloads = domain_headers[0] + delays = [ + (((time - origin_time) - (clock - origin_clock) + half_window) & window_mask) - half_window + for _record, time, clock, _payloads in domain_headers + ] + minimum = min(delays) + for (record, _time, _clock, header_payloads), delay in zip(domain_headers, delays): + correction = max(0, delay - minimum - (bucket_size - 1)) if correction: record.time -= correction # TODO: sort one-payload blocks atomically; a final per-record diff --git a/projects/rocprof-trace-decoder/test/att_tool_test.py b/projects/rocprof-trace-decoder/test/att_tool_test.py index 59850cb35600..acd497513b91 100644 --- a/projects/rocprof-trace-decoder/test/att_tool_test.py +++ b/projects/rocprof-trace-decoder/test/att_tool_test.py @@ -187,11 +187,13 @@ def test_correction_does_not_share_the_reference_between_simds(self): self.assertEqual([record.value for record in records.shaderdata], [12, 12]) def test_correction_cancels_unknown_clock_phase(self): + # The fixed inter-clock phase crosses the sampled window. The + # correction must use clock/time deltas, not absolute values. marker_id = 3 << 2 - reference = _shaderdata(0x14080, marker_id) - delayed = _shaderdata(0x144A0, (0x10 << 20) | marker_id) + delayed = _shaderdata(0x101D0, marker_id) + reference = _shaderdata(0x10DFC, (0x100 << 20) | marker_id) records = TraceRecords( - occupancy=[_occupancy(1, 0)], shaderdata=[reference, delayed]) + occupancy=[_occupancy(1, 0)], shaderdata=[delayed, reference]) _correct_marker_timestamps( [(0, records)], @@ -201,30 +203,9 @@ def test_correction_cancels_unknown_clock_phase(self): }, ) - self.assertEqual([record.time for record in records.shaderdata], [0x14080, 0x1418F]) + self.assertEqual([record.time for record in records.shaderdata], [0xFE0B, 0x10DFC]) self.assertEqual([record.value for record in records.shaderdata], [marker_id, marker_id]) - def test_large_delay_warns_without_moving_the_minimum(self): - marker_id = 3 << 2 - first = _shaderdata(0x1064, (0x100 << 20) | marker_id) - later = _shaderdata(0x20000, (0x63C << 20) | marker_id) - records = TraceRecords( - occupancy=[_occupancy(1, 0)], - shaderdata=[first, later], - ) - - with self.assertWarnsRegex(RuntimeWarning, "delay range exceeds half"): - _correct_marker_timestamps( - [(0, records)], - { - "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], - "sqtt_funcmap_layout": [[7, 12, 4]], - }, - ) - - self.assertEqual(first.time, 0x1064) - self.assertEqual(later.time, 0x16433) - def test_late_header_after_retirement_is_normalized_and_corrected(self): value = (0x100 << 20) | (3 << 2) before_retirement = _shaderdata(0x1010, value) From 8ddd3d09a68ec3ea6abb5b022875d9d4a31f57a2 Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 20:08:12 +0200 Subject: [PATCH 10/13] Add heavy data --- .../test/data/navi4_heavy.tar.gz | Bin 0 -> 366721 bytes .../test/markers/kernels/heavy.cpp | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 projects/rocprof-trace-decoder/test/data/navi4_heavy.tar.gz diff --git a/projects/rocprof-trace-decoder/test/data/navi4_heavy.tar.gz b/projects/rocprof-trace-decoder/test/data/navi4_heavy.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..dc1ea9189d7fafa3e6a9a5678c988fabb5a65d5e GIT binary patch literal 366721 zcmV)lK%c)KiwFP!000001LRq6P+QlPzwb$4L1d6H|M4Fog!m`K(?y7Dgxba)h1rYah$|;-PCcssoR~6oOb_aw)?3~r_-68 zeA(%2KXhhi@*y8~t3CI=_dp2a*p9O^oqdBi_nv#-Irp4<-@U)zdqMYt-|m|Bx)+YN zps(o?;A(Gcqte#iCS*$GLbf)H+uH5zAdz5eZL_r^L)+Iipv-GN5_N|S1~lcLTW|-1 z{*C&5S^J;q70j6b*0whLPS=!g(S`+^YjV!xb&do<3&Ig@fhuwbn z`NyrT4*Tl-Lq{ZFTWc%Kzwl^7z5Dh5eE$D(Pk%Shal6%WU4=eFTt@qDS$-x++5x5` zC=dQup$e1>b#+F+Z21MZR%&EDC*QZxbu)jhM7q`ca%kZ(SzonQezAY8%)F0|^^3!D zdwi`7$=x-DcfW7iE!C&btd+{Ok~8@}{yzP!_?6|*e?&d%2NMcdc_iU&I?lZ(Ae& zZ+bO9v@W;<^Iq2xf6((Isf%l3-skg%AJ($wfh}KqG3<|e@58k2Ya4X=*Jj!9rIue( zy4Fiw)tOzdP8@Zy+Db3Wef1LV)$4sLJB!)`SJ=DYkND?;m%kzW)H)TOn-94n-lN%@-oBDevs8OL_pF z>G#|B(BGbg!M^EXn`_W!ce$qhA=iX2Fz1fi9D{wn;Wn4w@BdqCDC}Qw!}3}m7y6+! zG8GELfjl3K8s3*Gk&r(SkW}bhYvkzc#9ZLFPkyyQSdVyaTjzs*-&}Z>1?pOjl>5Ls zAMr+9o}&=1f6@h~=H!u|!?dl8>|g)weW{i?v>#hvbT4>);W;_whM!tjGZ+fb(Iq+; zcFh9T;C>|U9&jE{d80pr+SFvQ8AP+x(A+FT6J?uGCWzxV-rwl{SftXdJ0Et#Pp`^F zyEUtIdL5^FI9!zdoy)MJ{3@wJWV_+t(|Y)L*?$&%e^+xV{N{fv`zalg>K#q?>*yZY-qK&%_!dYH{3O_rM2j`c3 zknD9vrrjPdtf9c%ryhU2%!>Wa0VC!Tk zN9@w|noChG8$uUjD|*>!B_ly_`aB^f{ss z%Y;5RfJASh&+9>!<;QP-zjEpJ5<)K@=eUOzRbS{4bbZ&;J~8zmD@9nimUCL{92X#L&`5asIV$T}+Fq7{|Z*58Ag=F*I{V zgRZ2uJs-bK_2?Wr!Q@4>jL`plgit(%(9huaPpN_>b?UbEa%$&NbT&*`=I92Zr> zQ)u*4ocBxhS@i9cjz3Ni>iX5@<2=9l_{n4J9>Yr?Y0=v${@9;BeX2^GW8=P^dPALS zhhhIFSIzTp{Fj(g9W(rTQ*JIghI0P+1d{f*m@2khkkF()j&sSGk5#WyTbxS&3YB`p z(np)Y=82PPat8cT+dSB2c4s8J9Oxyx*EnjoQ?@JR25euFY-igo0K4Kn?EW?M!DW82FQXnS{-?Kr+?qg<3WE&?jmCLe+s-p=uC*J@DHdE7Yn|b?9=610TPr z(B#DV!C0J!wmaN18xyp*(s|(3b`E@lYsokG%~4B!VXd;WLh7aNcTw`Sqf>tJ9p`4q5Ytt;7oohj!d7Vk-9n!hnbQu{l~?x=0QxwoHW)L zH^cJR0qZewk|S}x2x7@!6AOYk5yZ*X7UEex)kuno<}!zuV⁡c1mNRT#vs;OnJ1O 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%0A_^_ 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�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

dgw8oI3>Ft9plNDjB@cf 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 c7$G!3u% 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

rLe2ca-@`Bzse_x2*v0&M7DSLx-FxZL>-LS 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%0AiSNd1Qn;n3X_u1N^YTt4LT~b;F&hf*e+gE@{`w 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 9 && devProp.minor == 0) { - M /= 2; - K /= 2; - N /= 2; + M /= 4; + K /= 4; + N /= 4; } Matrix a(K, M); From b9590fc9790198576f0bd4c2517a266323302534 Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Wed, 15 Jul 2026 18:23:06 +0200 Subject: [PATCH 11/13] Fix circular logic --- .../docs/markers/SPEC.md | 21 ++++++----- .../rocprof-trace-decoder/markers/README.md | 5 ++- .../python/rocprof_trace_decoder/att.py | 37 +++++++------------ .../test/att_tool_test.py | 31 ++++------------ 4 files changed, 36 insertions(+), 58 deletions(-) diff --git a/projects/rocprof-trace-decoder/docs/markers/SPEC.md b/projects/rocprof-trace-decoder/docs/markers/SPEC.md index ecb1a4cf44fb..7c51777546a6 100644 --- a/projects/rocprof-trace-decoder/docs/markers/SPEC.md +++ b/projects/rocprof-trace-decoder/docs/markers/SPEC.md @@ -131,23 +131,26 @@ exactly one payload (`R:...=1`) and remains eligible for clock packing. The sampled window lets a decoder compensate for the variable delay between the trace instruction issuing and its shaderdata record appearing in SQTT. Shaderdata and shader-clock timestamps have the same rate but an unknown fixed -phase per physical SIMD. For each **marker header** (never an `R:`-declared raw -payload), calculate its circular residue: +phase per physical SIMD. Do not compare them directly. For each **marker +header** (never an `R:`-declared raw payload), choose any header `r` in the +same physical SIMD/layout only as a coordinate origin and calculate: ```text window_mask = (1ULL << (clock_bits + shader_clock_shift)) - 1 +half_window = (window_mask + 1) / 2 bucket_size = 1ULL << shader_clock_shift sampled_window = packed_clock_field << shader_clock_shift -residue = (uint32_t(record_time) - sampled_window) & window_mask +relative = (((record_time - r.record_time) - + (sampled_window - r.sampled_window) + half_window) & window_mask) - half_window ``` Within each `(shader engine, CU, SIMD, clock_bits, shader_clock_shift)` domain, -sort residues, find the largest circular gap, and unwrap after it. If the -unwrapped span exceeds half the window, emit one warning for that domain but -still use the deterministic first largest-gap reference. With -`relative = (residue - unwrap_start) & window_mask`, shift a header earlier by -`max(0, relative - (bucket_size - 1))`. This cancels the unknown phase while -never assuming the omitted low clock bits. +the implementation assumes the relative delta stays within the signed sampled +window. Collect the complete domain before selecting `minimum_relative`, then +shift every header earlier by +`max(0, relative - minimum_relative - (bucket_size - 1))`. The coordinate +origin is not a chronological anchor: a later lower value corrects earlier +headers retroactively. `att_tool.py` applies this correction while writing JSON by default; `--no-decode-markers` preserves the packed values and arrival timestamps. It diff --git a/projects/rocprof-trace-decoder/markers/README.md b/projects/rocprof-trace-decoder/markers/README.md index 7ae8e1361e80..ceca1ee568fc 100644 --- a/projects/rocprof-trace-decoder/markers/README.md +++ b/projects/rocprof-trace-decoder/markers/README.md @@ -484,8 +484,9 @@ packed form because their value is indistinguishable from a generated exit. The packed clock is a truncated sample at instruction issue. Shaderdata and shader clocks have an unknown fixed phase per physical SIMD, so `att_tool.py` -circularly unwraps recognized header residues per SIMD/layout before applying a -conservative relative correction. Apply this only to headers, never `R:` +cancels it by comparing clock/time deltas to an arbitrary header in the same +SIMD/layout. It takes the minimum from the complete domain and applies the +relative correction retroactively. Apply this only to headers, never `R:` payloads. `Funcmap::marker_encoding` exposes `marker_id_mask()`, `packed_shader_clock_mask()`, and `shader_clock_source_mask()`; without `M:` metadata those describe the legacy diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index 4d0c15c988c6..ba30f5339ef9 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -1,6 +1,5 @@ from __future__ import annotations -import warnings from collections import defaultdict from collections.abc import Iterable from dataclasses import dataclass @@ -173,7 +172,7 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict for record in records.occupancy: occupancy[se, record.cu, record.simd, record.wave_id].append(record) - headers: dict[tuple[int, int, int, int, int], list[tuple[object, int, list[object]]]] = defaultdict(list) + headers: dict[tuple[int, int, int, int, int], list[tuple[object, int, int, list[object]]]] = defaultdict(list) for location, stream in shaderdata.items(): stream.sort(key=lambda record: record.time) intervals = _active_codeobj_intervals(occupancy.get(location, ())) @@ -219,35 +218,27 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict # can break the declared header/payload record adjacency. if codeobj in multi_payload_codeobjs: continue - window_mask = (1 << (bits + shift)) - 1 - bucket_size = 1 << shift sampled = ((raw >> (32 - bits)) & ((1 << bits) - 1)) << shift - residue = (int(record.time) - sampled) & window_mask clock_domain = (*location[:3], bits, shift) - headers[clock_domain].append((record, residue, header_payloads)) + headers[clock_domain].append((record, int(record.time), sampled, header_payloads)) changed = False for clock_domain, domain_headers in headers.items(): bits, shift = clock_domain[-2:] - window_mask = (1 << (bits + shift)) - 1 + window_size = 1 << (bits + shift) + window_mask = window_size - 1 + half_window = window_size // 2 bucket_size = 1 << shift - residues = sorted({header[1] for header in domain_headers}) - if len(residues) < 2: - continue - gaps = [ - (residues[(i + 1) % len(residues)] - residue) & window_mask - for i, residue in enumerate(residues) + # This is an arbitrary coordinate origin, not a time/minimum anchor. + # Comparing deltas cancels the fixed phase between the two clocks. + _origin, origin_time, origin_clock, _payloads = domain_headers[0] + delays = [ + (((time - origin_time) - (clock - origin_clock) + half_window) & window_mask) - half_window + for _record, time, clock, _payloads in domain_headers ] - largest_gap = max(gaps) - span = window_mask + 1 - largest_gap - if span > (window_mask + 1) // 2: - warnings.warn( - f"SQTT marker correction for {clock_domain}: clock phase span exceeds half the window", - RuntimeWarning, - ) - start = residues[(gaps.index(largest_gap) + 1) % len(residues)] - for record, residue, header_payloads in domain_headers: - correction = max(0, ((residue - start) & window_mask) - (bucket_size - 1)) + minimum = min(delays) + for (record, _time, _clock, header_payloads), delay in zip(domain_headers, delays): + correction = max(0, delay - minimum - (bucket_size - 1)) if correction: record.time -= correction # TODO: sort one-payload blocks atomically; a final per-record diff --git a/projects/rocprof-trace-decoder/test/att_tool_test.py b/projects/rocprof-trace-decoder/test/att_tool_test.py index 86af152bf967..acd497513b91 100644 --- a/projects/rocprof-trace-decoder/test/att_tool_test.py +++ b/projects/rocprof-trace-decoder/test/att_tool_test.py @@ -186,12 +186,14 @@ def test_correction_does_not_share_the_reference_between_simds(self): self.assertEqual(fast_simd.time, 0x1010) self.assertEqual([record.value for record in records.shaderdata], [12, 12]) - def test_correction_unwraps_unknown_clock_phase(self): + def test_correction_cancels_unknown_clock_phase(self): + # The fixed inter-clock phase crosses the sampled window. The + # correction must use clock/time deltas, not absolute values. marker_id = 3 << 2 - reference = _shaderdata(0x21F64, (0x200 << 20) | marker_id) - delayed = _shaderdata(0x22384, (0x210 << 20) | marker_id) + delayed = _shaderdata(0x101D0, marker_id) + reference = _shaderdata(0x10DFC, (0x100 << 20) | marker_id) records = TraceRecords( - occupancy=[_occupancy(1, 0)], shaderdata=[reference, delayed]) + occupancy=[_occupancy(1, 0)], shaderdata=[delayed, reference]) _correct_marker_timestamps( [(0, records)], @@ -201,28 +203,9 @@ def test_correction_unwraps_unknown_clock_phase(self): }, ) - self.assertEqual([record.time for record in records.shaderdata], [0x21F64, 0x22073]) + self.assertEqual([record.time for record in records.shaderdata], [0xFE0B, 0x10DFC]) self.assertEqual([record.value for record in records.shaderdata], [marker_id, marker_id]) - def test_wide_clock_phase_warns_and_is_corrected(self): - marker_id = 3 << 2 - value = (0x200 << 20) | marker_id - records = TraceRecords( - occupancy=[_occupancy(1, 0)], - shaderdata=[_shaderdata(time, value) for time in (0x12000, 0x16000, 0x1B000)], - ) - - with self.assertWarnsRegex(RuntimeWarning, "span exceeds half"): - _correct_marker_timestamps( - [(0, records)], - { - "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], - "sqtt_funcmap_layout": [[7, 12, 4]], - }, - ) - - self.assertEqual([record.time for record in records.shaderdata], [0x12000, 0x1200F, 0x1200F]) - def test_late_header_after_retirement_is_normalized_and_corrected(self): value = (0x100 << 20) | (3 << 2) before_retirement = _shaderdata(0x1010, value) From 930dac1285481de9acdce6b9a8f0c751a6d06c87 Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Thu, 16 Jul 2026 19:25:31 +0200 Subject: [PATCH 12/13] Fixes --- projects/rocprof-trace-decoder/README.md | 2 +- .../docs/markers/SPEC.md | 42 ++++++------- .../rocprof_trace_decoder/cxx/funcmap.hpp | 2 +- .../markers/CMakeLists.txt | 3 +- .../rocprof-trace-decoder/markers/README.md | 52 ++++++++-------- .../markers/SQTTConfig.h | 7 +-- .../markers/SQTTMarkers.cpp | 26 +++++++- .../markers/SQTTPass.cpp | 14 ++--- .../markers/SQTTTarget.h | 18 +++++- .../python/rocprof_trace_decoder/att.py | 9 +++ .../python/rocprof_trace_decoder/codegen.py | 9 +-- .../scripts/sqtt_data.py | 6 +- .../rocprof-trace-decoder/test/CMakeLists.txt | 16 +++++ .../test/att_tool_test.py | 61 ++++++++++++++++--- .../test/codegen_test.py | 8 +-- .../test/markers/kernels/heavy.cpp | 52 ++++++++++------ .../test/markers/kernels/marker.cpp | 2 +- .../test/markers/run_tests.py | 36 ++++++++--- .../test/unit/funcmap_test.cpp | 2 +- .../test/unit/markers_test.cpp | 14 ++++- 20 files changed, 260 insertions(+), 121 deletions(-) diff --git a/projects/rocprof-trace-decoder/README.md b/projects/rocprof-trace-decoder/README.md index 62c96080747a..ede13a60f87c 100644 --- a/projects/rocprof-trace-decoder/README.md +++ b/projects/rocprof-trace-decoder/README.md @@ -63,7 +63,7 @@ The chosen `LLVM_DIR` is baked into the generated `rocprof-trace-decoder-config. |---|---| | `rocprof-trace-decoder` | Shared library (`.so`) | | `rocprof-trace-decoder-static` | Static library (`.a`) — built but not installed; consumed via the build-tree CMake export | -| `SQTTInstrumentPass` | LLVM pass plugin for SQTT marker instrumentation (`BUILD_MARKERS=ON`) | +| `SQTTInstrumentPass` | LLVM pass plugin for SQTT marker instrumentation (`libsqttinstrumentpass.so`, `BUILD_MARKERS=ON`) | | `unit_tests` | Unit test executable (requires `BUILD_TESTS=ON`) | | `format` | Run clang-format and cmake-format on all sources | | `docs` | Generate Doxygen API documentation | diff --git a/projects/rocprof-trace-decoder/docs/markers/SPEC.md b/projects/rocprof-trace-decoder/docs/markers/SPEC.md index 7c51777546a6..067f32b36bab 100644 --- a/projects/rocprof-trace-decoder/docs/markers/SPEC.md +++ b/projects/rocprof-trace-decoder/docs/markers/SPEC.md @@ -10,7 +10,7 @@ function entry/exit, barrier synchronization, and user-defined annotations. The system consists of: -- **`SQTTInstrumentPass.so`** -- LLVM pass plugin loaded via `-fpass-plugin=` +- **`libsqttinstrumentpass.so`** -- LLVM pass plugin loaded via `-fpass-plugin=` - **`markers.hpp`** -- Device-side header for user markers - **`sqtt_flamegraph.py`** -- Post-processing tool: reads SQTT traces and `.sqtt_funcmap` sections to generate flamegraphs @@ -33,13 +33,13 @@ it gets symbols from the host compiler process. ```bash # Minimal (barriers + user markers only) SQTT_INSTRUMENT_BARRIERS=1 \ -hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/SQTTInstrumentPass.so \ +hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel # Full (function entry/exit with threshold, barriers, scope filtering) SQTT_INSTRUMENT_FUNCTIONS=10 SQTT_INSTRUMENT_BARRIERS=1 \ SQTT_SCOPE_CU=0x3 \ -hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/SQTTInstrumentPass.so \ +hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -96,9 +96,10 @@ SQTT hardware only captures the low 8 bits from `s_ttracedata_imm`, so the ### GFX12 Shader Clock Packing -On gfx12+, marker headers include a truncated shader clock from -`s_getreg_b32 hwreg(HW_REG_SHADER_CYCLES_LO, shift, bits)`. The default window -is source bits 4-15 (`SQTT_SHADER_CLOCK_SHIFT=4`, `SQTT_SHADER_CLOCK_BITS=12`), +For the supported gfx12 targets, marker headers include a truncated shader clock. +gfx1250 uses `s_get_shader_cycles_u64`; gfx1200 and gfx1201 use +`s_getreg_b32 hwreg(HW_REG_SHADER_CYCLES_LO, shift, bits)`. When enabled, the +default window is source bits 4-15 (`SQTT_SHADER_CLOCK_SHIFT=4`, `SQTT_SHADER_CLOCK_BITS=12`), stored in marker bits 31-20, leaving 18 marker ID bits plus the two low flags: ```python @@ -116,16 +117,16 @@ value is intrinsically indistinguishable from a pass-generated exit. `att_tool.py` restores packed headers to the legacy form by default; `--no-decode-markers` preserves the raw value. -The clock field is the shader clock sampled with `s_getreg`, not the realtime +The clock field is the target-specific shader-clock sample, not the realtime clock associated with `s_sendmsg`. The funcmap records the active layout with `M:shader_clock_bits=N;shader_clock_shift=S`. When `SQTT_SHADER_CLOCK_BITS=0`, gfx12 uses the no-clock full-ID layout and may use `s_ttracedata_imm`. A nonzero `shader_clock_bits` value requires `shader_clock_shift`; a malformed packed row uses the no-clock full-ID layout. -Address trace blocks have `R:extra_payload_count > 1`. If a module emits any -such block, the automatic gfx12 clock width is `0`; an explicit nonzero -`SQTT_SHADER_CLOCK_BITS` is an error. A named `sqtt_marker_data()` record has +Address trace blocks have `R:extra_payload_count > 1`. Clock packing is +disabled by default; an explicit nonzero `SQTT_SHADER_CLOCK_BITS` is an error +when a module emits such a block. A named `sqtt_marker_data()` record has exactly one payload (`R:...=1`) and remains eligible for clock packing. The sampled window lets a decoder compensate for the variable delay between @@ -212,8 +213,8 @@ if (encoding.has_shader_clock() && encoding.is_valid()) ``` `marker_id_mask()` and `packed_shader_clock_mask()` apply to the raw 32-bit -shaderdata value. `shader_clock_source_mask()` describes the matching low-word -window in `HW_REG_SHADER_CYCLES_LO` for timestamp comparison. +shaderdata value. `shader_clock_source_mask()` describes the matching source +low-word window for timestamp comparison (`HW_REG_SHADER_CYCLES_LO` on gfx1200/1201). `decode_shader_clock()` is meaningful only after the record has been identified as a packed header. @@ -337,8 +338,8 @@ All variables are read at **compile time** by the pass plugin. | `SQTT_INSTRUMENT_FUNCTIONS`| `0` | Function entry/exit threshold (0=disabled) | | `SQTT_INSTRUMENT_MEMORY` | off | Memory op markers. Format: `N:M` (N=ops per marker, M=max gap) | | `SQTT_MEM_BARRIER` | `fence` | Reordering boundary around markers (`none`/`asm`/`fence` or `0`/`1`/`2`) | -| `SQTT_SHADER_CLOCK_BITS` | auto | Gfx12 shader clock bits (`12` on gfx12 unless an address block is emitted, then `0`; `0` disables). A nonzero explicit value is invalid with address blocks. | -| `SQTT_SHADER_CLOCK_SHIFT` | `4` | Source bit offset in `HW_REG_SHADER_CYCLES_LO` for shader clock packing | +| `SQTT_SHADER_CLOCK_BITS` | `0` | Gfx12 shader clock bits. Set a nonzero value to enable packing; it is invalid with address blocks. | +| `SQTT_SHADER_CLOCK_SHIFT` | `4` | Source bit offset in the shader-clock low word for packing | ### Marker reorder boundary (`SQTT_MEM_BARRIER`) @@ -578,9 +579,9 @@ The section is added to `llvm.used` to prevent linker stripping. ### `code.json` marker metadata -For a packed code object, `att_tool.py` adds `sqtt_funcmap` data to -`code.json` using the established six-column row shape. Two additive top-level -tables carry metadata that cannot be represented in those rows: +`att_tool.py` adds embedded `sqtt_funcmap` data to `code.json` using the +established six-column row shape. Two additive top-level tables carry metadata +that cannot be represented in those rows: ```json "sqtt_funcmap_layout": [[codeobj_id, shader_clock_bits, shader_clock_shift]], @@ -588,10 +589,9 @@ tables carry metadata that cannot be represented in those rows: ``` `sqtt_funcmap_layout` contains rows only for code objects with shader-clock -packing; `sqtt_funcmap_payloads` contains their nonzero payload counts. Readers -that do not know these optional tables can continue using the unchanged -`sqtt_funcmap` rows. For a packed row, an absent payload count means zero; -legacy `code.json` output is unchanged. +packing; `sqtt_funcmap_payloads` contains nonzero payload counts. Readers that +do not know these optional tables can continue using the unchanged +`sqtt_funcmap` rows. For a packed row, an absent payload count means zero. ### Extracting the funcmap diff --git a/projects/rocprof-trace-decoder/include/rocprof_trace_decoder/cxx/funcmap.hpp b/projects/rocprof-trace-decoder/include/rocprof_trace_decoder/cxx/funcmap.hpp index 28ba770eb29b..2ca0d0eaf9e2 100644 --- a/projects/rocprof-trace-decoder/include/rocprof_trace_decoder/cxx/funcmap.hpp +++ b/projects/rocprof-trace-decoder/include/rocprof_trace_decoder/cxx/funcmap.hpp @@ -159,7 +159,7 @@ struct MarkerEncoding : detail::bit_mask(shader_clock_bits) << (32u - shader_clock_bits); } - /// Corresponding bit window in HW_REG_SHADER_CYCLES_LO. + /// Corresponding bit window in the producer's low shader-clock word. constexpr uint32_t shader_clock_source_mask() const noexcept { return (!has_shader_clock() || !is_valid()) ? 0u diff --git a/projects/rocprof-trace-decoder/markers/CMakeLists.txt b/projects/rocprof-trace-decoder/markers/CMakeLists.txt index d9f3889bca12..e8788eb7112d 100644 --- a/projects/rocprof-trace-decoder/markers/CMakeLists.txt +++ b/projects/rocprof-trace-decoder/markers/CMakeLists.txt @@ -63,7 +63,8 @@ endif() set_target_properties( SQTTInstrumentPass PROPERTIES NO_SONAME ON - PREFIX "" + PREFIX "lib" + OUTPUT_NAME "sqttinstrumentpass" POSITION_INDEPENDENT_CODE ON LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" diff --git a/projects/rocprof-trace-decoder/markers/README.md b/projects/rocprof-trace-decoder/markers/README.md index ceca1ee568fc..629025a0761c 100644 --- a/projects/rocprof-trace-decoder/markers/README.md +++ b/projects/rocprof-trace-decoder/markers/README.md @@ -16,7 +16,7 @@ cmake -B build -DCMAKE_PREFIX_PATH=/opt/rocm cmake --build build ``` -This produces `build/lib/SQTTInstrumentPass.so`. +This produces `build/lib/libsqttinstrumentpass.so`. Use `CMAKE_PREFIX_PATH` to select a specific ROCm installation: @@ -46,7 +46,7 @@ __global__ void my_kernel(float *data, int n) { ``` ```bash -hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/SQTTInstrumentPass.so \ +hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -67,7 +67,7 @@ waves on matching CUs/SIMDs/WGs emit markers: # Only CU 0, SIMD 0, all workgroups SQTT_SCOPE_CU=0x1 SQTT_SCOPE_SIMD=0x1 \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -82,7 +82,7 @@ SQTT already generates wave start/end markers for them. ```bash SQTT_INSTRUMENT_FUNCTIONS=10 \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -91,7 +91,7 @@ Use weighted cost instead of instruction count: ```bash SQTT_INSTRUMENT_FUNCTIONS=cost:50 \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -102,7 +102,7 @@ Insert point markers around `s_barrier_signal` and `s_barrier_wait`: ```bash SQTT_INSTRUMENT_BARRIERS=1 \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -114,7 +114,7 @@ Insert point markers around groups of global/buffer/flat memory operations ```bash SQTT_INSTRUMENT_MEMORY=2:5 \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -134,7 +134,7 @@ cache line utilization analysis, stride detection, and coalescing analysis: ```bash SQTT_TRACE_ADDRESSES=memory \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -143,7 +143,7 @@ Trace LDS addresses only: ```bash SQTT_TRACE_ADDRESSES=lds \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -152,7 +152,7 @@ Trace both global and LDS: ```bash SQTT_TRACE_ADDRESSES=memory,lds \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -177,9 +177,9 @@ to report only active lanes. The parser demultiplexes interleaved records from concurrent waves by grouping on `(cu, simd, wave_id)` before parsing each wave's address block. -Address blocks always have `extra_payload_count > 1`, so automatic gfx12 -shader-clock packing is disabled for the whole module when one is emitted. An -explicit nonzero `SQTT_SHADER_CLOCK_BITS` is an error in that configuration. +Address blocks always have `extra_payload_count > 1`. Shader-clock packing is +disabled by default, and an explicit nonzero `SQTT_SHADER_CLOCK_BITS` is an +error when an address block is emitted. This does not apply to `sqtt_marker_data()`, whose single raw payload remains eligible for clock packing. @@ -195,7 +195,7 @@ SQTT_INSTRUMENT_MEMORY=2:5 \ SQTT_SCOPE_CU=0x1 \ SQTT_SCOPE_SIMD=0x1 \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ my_kernel.hip -o my_kernel ``` @@ -238,7 +238,7 @@ __global__ void my_kernel(float *data, int n) { } ``` -Named markers require the pass plugin (`-fpass-plugin=build/lib/SQTTInstrumentPass.so`). +Named markers require the pass plugin (`-fpass-plugin=build/lib/libsqttinstrumentpass.so`). The pass: 1. Finds all `sqtt_marker_enter("...")`, `sqtt_marker_exit("...")`, and @@ -288,8 +288,8 @@ directly via `getenv()`. | `SQTT_INSTRUMENT_MEMORY` | `N:M` | off | Instrument memory ops. N = ops per marker, M = max gap. `2:5` = 1 marker per 2 ops, sequence breaks at gap > 5. Covers global, buffer, flat (not LDS/scratch). | | `SQTT_TRACE_ADDRESSES` | `memory`, `lds`, or both | off | Trace per-lane virtual addresses. Mutually exclusive with `SQTT_INSTRUMENT_MEMORY`. `memory` = flat/global (AS=0/1) and buffer, `lds` = LDS (AS=3). Expensive. | | `SQTT_MEM_BARRIER` | `none` / `asm` / `fence` (or `0` / `1` / `2`) | `fence` | Reordering boundary planted around every marker. `fence` (default) emits `fence syncscope("workgroup") acq_rel` tagged with AMDGPU local/LDS synchronization metadata, anchoring markers against optimizer and scheduler movement without marker-generated global cache invalidation. `asm` plants an empty `~{memory}` inline asm (IR/MIR-only constraint, no machine code). `none` disables both. Default favors marker accuracy; opt down for tight kernels. | -| `SQTT_SHADER_CLOCK_BITS` | unsigned integer | auto (`12` on gfx12 unless an address block is emitted, then `0`) | Number of gfx12 marker header high bits reserved for shader clock. `0` disables clock packing; a nonzero explicit value is invalid with address blocks. | -| `SQTT_SHADER_CLOCK_SHIFT` | unsigned integer | `4` | Source bit offset in `HW_REG_SHADER_CYCLES_LO` for the packed shader clock field. | +| `SQTT_SHADER_CLOCK_BITS` | unsigned integer | `0` | Number of gfx12 marker header high bits reserved for shader clock. Set a nonzero value to enable packing; it is invalid with address blocks. | +| `SQTT_SHADER_CLOCK_SHIFT` | unsigned integer | `4` | Source bit offset in the shader-clock low word for the packed field. | ## Examples @@ -345,7 +345,7 @@ sqtt_marker_exit("Consumer Thread"); Build with the pass plugin and capture a trace: ```bash -hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/SQTTInstrumentPass.so \ +hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ test/markers/kernels/heavy.cpp -o heavy rocprofv3 --att -d trace -- ./heavy @@ -387,7 +387,7 @@ Build with automatic function instrumentation and capture a trace: ```bash SQTT_INSTRUMENT_FUNCTIONS=10 \ -hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/SQTTInstrumentPass.so \ +hipcc -DSQTT_ENABLED=1 -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ test/markers/kernels/auto.cpp -o auto rocprofv3 --att -d trace -- ./auto @@ -473,11 +473,13 @@ The pass plugin automatically selects `s_ttracedata_imm` when the encoded value fits in 8 bits, the target supports it, and shader-clock packing is inactive. IDs 1-63 use the fast path on RDNA. Exit markers use the same selection rule. -On gfx12+, the default is to pack 12 shader-clock bits from -`s_getreg_b32 hwreg(HW_REG_SHADER_CYCLES_LO, 4, 12)` into marker bits 31-20, -leaving 18 marker ID bits plus the two low flags. Configure this with -`SQTT_SHADER_CLOCK_BITS` and `SQTT_SHADER_CLOCK_SHIFT`; set -`SQTT_SHADER_CLOCK_BITS=0` to use the no-clock full-ID layout on gfx12. The funcmap emits +On gfx1200, gfx1201, and gfx1250, a nonzero `SQTT_SHADER_CLOCK_BITS` enables +clock packing. With the usual 12-bit setting, it occupies marker bits 31-20 +and leaves 18 marker ID bits plus the two low flags. gfx1250 uses +`s_get_shader_cycles_u64`; gfx1200 and gfx1201 use +`s_getreg_b32 hwreg(HW_REG_SHADER_CYCLES_LO, 4, 12)`. Configure this with +`SQTT_SHADER_CLOCK_BITS` and `SQTT_SHADER_CLOCK_SHIFT`; it defaults to `0`, +which uses the no-clock full-ID layout. The funcmap emits `M:shader_clock_bits=N;shader_clock_shift=S` whenever clock packing is active. Numeric point and enter markers keep their legacy values; bare exits use the packed form because their value is indistinguishable from a generated exit. @@ -540,7 +542,7 @@ Check that markers appear in the IR: ```bash SQTT_INSTRUMENT_FUNCTIONS=5 \ hipcc -DSQTT_ENABLED=1 \ - -fpass-plugin=build/lib/SQTTInstrumentPass.so \ + -fpass-plugin=build/lib/libsqttinstrumentpass.so \ -I include/rocprof_trace_decoder/cxx/ -S -emit-llvm my_kernel.hip -o - \ | grep ttracedata ``` diff --git a/projects/rocprof-trace-decoder/markers/SQTTConfig.h b/projects/rocprof-trace-decoder/markers/SQTTConfig.h index 4bf70d06aa5a..8c88428983de 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTConfig.h +++ b/projects/rocprof-trace-decoder/markers/SQTTConfig.h @@ -25,7 +25,6 @@ #include #include #include -#include #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" @@ -104,8 +103,6 @@ enum class MemBarrierMode struct SQTTConfig { - static constexpr unsigned AutoShaderClockBits = std::numeric_limits::max(); - bool InstrumentBarriers = false; CostMode Mode = CostMode::InstructionCount; unsigned FunctionThreshold = 0; // 0 = disabled @@ -119,7 +116,7 @@ struct SQTTConfig MemBarrierMode MemBarrier = MemBarrierMode::Fence; bool TraceMemoryAddrs = false; // trace global/buffer/flat addresses bool TraceLDSAddrs = false; // trace LDS addresses - unsigned ShaderClockBits = AutoShaderClockBits; // auto: gfx12 defaults to clock packing + unsigned ShaderClockBits = 0; // opt in to clock packing explicitly unsigned ShaderClockShift = 4; bool hasAddressTracing() const { return TraceMemoryAddrs || TraceLDSAddrs; } @@ -197,7 +194,7 @@ struct SQTTConfig c.SimdMask = parseEnvMask("SQTT_SCOPE_SIMD", 0xF); c.CuMask = parseEnvMask("SQTT_SCOPE_CU", 0x3); c.WgMask = parseEnvMask("SQTT_SCOPE_WG", 0xFFFFFFFF); - c.ShaderClockBits = parseEnvUnsigned("SQTT_SHADER_CLOCK_BITS", AutoShaderClockBits); + c.ShaderClockBits = parseEnvUnsigned("SQTT_SHADER_CLOCK_BITS", 0); c.ShaderClockShift = parseEnvUnsigned("SQTT_SHADER_CLOCK_SHIFT", 4); const char* funcEnv = std::getenv("SQTT_INSTRUMENT_FUNCTIONS"); diff --git a/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp b/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp index 3de589a26e54..804e3799bc1b 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTMarkers.cpp @@ -511,7 +511,6 @@ bool SQTTInstrumentPass::lowerFullTracesWithM0Nop(Function& F) "s_mov_b32 m0, $1\n" "s_nop 0\n" "s_ttracedata"; - // Constants can be encoded directly in the M0 move. Non-constant values // reaching this helper are required to be scalar. InlineAsm* ImmediateTrace = @@ -586,7 +585,19 @@ bool SQTTInstrumentPass::applyShaderClockPacking(Function& F, GfxGen gen) Module* M = F.getParent(); LLVMContext& Ctx = M->getContext(); Type* I32 = Type::getInt32Ty(Ctx); - Function* SGetReg = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_getreg); + const bool useShaderCyclesU64 = hasShaderCyclesU64(F); + Function* SGetReg = + useShaderCyclesU64 ? nullptr : Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_getreg); + InlineAsm* GetShaderCycles = nullptr; + if (useShaderCyclesU64) + { + GetShaderCycles = InlineAsm::get( + FunctionType::get(Type::getInt64Ty(Ctx), false), + "s_get_shader_cycles_u64 $0", + "=s", + /*hasSideEffects=*/true + ); + } Function* TTD = Intrinsic::getOrInsertDeclaration(M, Intrinsic::amdgcn_s_ttracedata); uint32_t hwreg = GETREG_IMMED(clockBits - 1, Config.ShaderClockShift, GFX12_SHADER_CYCLES_LO); uint32_t clockDestShift = 32 - clockBits; @@ -597,7 +608,16 @@ bool SQTTInstrumentPass::applyShaderClockPacking(Function& F, GfxGen gen) Value* markerAndFlags = encoded; if (markerAndFlags->getType() != I32) markerAndFlags = B.CreateZExtOrTrunc(markerAndFlags, I32); markerAndFlags = B.CreateAnd(markerAndFlags, ConstantInt::get(I32, markerAndFlagMask)); - Value* clock = B.CreateCall(SGetReg, {ConstantInt::get(I32, hwreg)}); + Value* clock; + if (GetShaderCycles) + { + Value* cyclesLo = B.CreateTrunc(B.CreateCall(GetShaderCycles), I32); + // The final shift below discards all but clockBits, matching the + // right-aligned field returned by s_getreg on gfx1200 and gfx1201. + clock = B.CreateLShr(cyclesLo, ConstantInt::get(I32, Config.ShaderClockShift)); + } + else + clock = B.CreateCall(SGetReg, {ConstantInt::get(I32, hwreg)}); Value* shiftedClock = B.CreateShl(clock, ConstantInt::get(I32, clockDestShift)); Value* packed = B.CreateOr(shiftedClock, markerAndFlags); CallInst* packedTrace = B.CreateCall(TTD, {packed}); diff --git a/projects/rocprof-trace-decoder/markers/SQTTPass.cpp b/projects/rocprof-trace-decoder/markers/SQTTPass.cpp index 9d8df7e90bbd..3d7e84de6519 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTPass.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTPass.cpp @@ -25,7 +25,6 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/Analysis/InlineCost.h" #include "llvm/IR/Constants.h" -#include "llvm/IR/InlineAsm.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Support/ErrorHandling.h" #if __has_include("llvm/Plugins/PassPlugin.h") @@ -182,15 +181,10 @@ PreservedAnalyses SQTTInstrumentPass::runLate(Module& M) // Address records have R:extra_payload_count > 1. Do not create a packed // header layout whose JSON timestamp correction could split those blocks. - if (!AddrTraceEntries.empty()) - { - if (Config.ShaderClockBits == SQTTConfig::AutoShaderClockBits) - Config.ShaderClockBits = 0; - else if (Config.ShaderClockBits != 0) - report_fatal_error( - "SQTT_TRACE_ADDRESSES emits multi-payload records; set SQTT_SHADER_CLOCK_BITS=0" - ); - } + if (!AddrTraceEntries.empty() && Config.ShaderClockBits != 0) + report_fatal_error( + "SQTT_TRACE_ADDRESSES emits multi-payload records; set SQTT_SHADER_CLOCK_BITS=0" + ); // Packing must happen after the address decision for the entire module. for (auto& F : M) diff --git a/projects/rocprof-trace-decoder/markers/SQTTTarget.h b/projects/rocprof-trace-decoder/markers/SQTTTarget.h index 446a387c0d4a..5d467cbd0a1f 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTTarget.h +++ b/projects/rocprof-trace-decoder/markers/SQTTTarget.h @@ -55,6 +55,20 @@ inline GfxGen getGfxGen(const llvm::Function& F) return GfxGen::Unknown; } +// gfx1200 and gfx1201 use HW_REG_SHADER_CYCLES_LO through s_getreg; gfx1250 +// and subsequent targets have a dedicated shader-cycle instruction. +inline bool hasShaderCyclesU64(const llvm::Function& F) +{ + llvm::Attribute A = F.getFnAttribute("target-cpu"); + if (!A.isValid()) return false; + llvm::StringRef CPU = A.getValueAsString(); + if (!CPU.consume_front("gfx")) return false; + size_t digits = 0; + while (digits < CPU.size() && CPU[digits] >= '0' && CPU[digits] <= '9') ++digits; + unsigned target = 0; + return digits != 0 && !CPU.take_front(digits).getAsInteger(10, target) && target > 1201; +} + // Does this GfxGen support s_ttracedata_imm? inline bool supportsImmTrace(GfxGen gen) { @@ -82,8 +96,8 @@ inline HwRegEncodings getHwRegEncodings(GfxGen gen) inline unsigned getShaderClockBits(const SQTTConfig& config, GfxGen gen) { - if (config.ShaderClockBits != SQTTConfig::AutoShaderClockBits) return config.ShaderClockBits; - return gen == GfxGen::GFX12 ? 12 : 0; + (void) gen; + return config.ShaderClockBits; } inline bool usesShaderClockPacking(const SQTTConfig& config, GfxGen gen) diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index ba30f5339ef9..fe8a89d6e4f0 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from collections import defaultdict from collections.abc import Iterable from dataclasses import dataclass @@ -229,6 +230,9 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict window_mask = window_size - 1 half_window = window_size // 2 bucket_size = 1 << shift + # A constant field cannot establish a relative issue-time delta. + if len({sampled for _record, _time, sampled, _payloads in domain_headers}) == 1: + continue # This is an arbitrary coordinate origin, not a time/minimum anchor. # Comparing deltas cancels the fixed phase between the two clocks. _origin, origin_time, origin_clock, _payloads = domain_headers[0] @@ -237,6 +241,11 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict for _record, time, clock, _payloads in domain_headers ] minimum = min(delays) + if max(delays) - minimum > half_window: + warnings.warn( + f"SQTT marker correction for {clock_domain}: delay range exceeds half the window", + RuntimeWarning, + ) for (record, _time, _clock, header_payloads), delay in zip(domain_headers, delays): correction = max(0, delay - minimum - (bucket_size - 1)) if correction: diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/codegen.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/codegen.py index 247f65672d68..c27ec0a4f701 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/codegen.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/codegen.py @@ -726,13 +726,14 @@ def generate_code_artifacts(code_objects: Iterable[CodeObject]) -> CodeArtifacts kernels, debug_labels = read_symbol_labels(elf_path) funcmap = _read_sqtt_funcmap(elf_path) - if funcmap is not None and funcmap.shader_clock_bits: + if funcmap is not None: # Keep the existing sqtt_funcmap row shape used by rocprofv3 # consumers. The two new side tables below are additive, so older # readers can ignore them without losing their original schema. - sqtt_funcmap_layouts.append( - [codeobj_id, funcmap.shader_clock_bits, funcmap.shader_clock_shift] - ) + if funcmap.shader_clock_bits: + sqtt_funcmap_layouts.append( + [codeobj_id, funcmap.shader_clock_bits, funcmap.shader_clock_shift] + ) symbol_vaddrs: dict[str, int] = {} for address, (name, _demangled) in sorted(kernels.items()): symbol_vaddrs.setdefault(name, address) diff --git a/projects/rocprof-trace-decoder/scripts/sqtt_data.py b/projects/rocprof-trace-decoder/scripts/sqtt_data.py index ef7b422a1bde..0265cf8f0173 100644 --- a/projects/rocprof-trace-decoder/scripts/sqtt_data.py +++ b/projects/rocprof-trace-decoder/scripts/sqtt_data.py @@ -464,7 +464,9 @@ def find_wave_span_at(spans: list[WaveSpan], time: int) -> Optional[WaveSpan]: Trace data records can arrive after a dispatch retires (the wave is still draining), so we match to the most recent span whose launch_time <= time rather than requiring strict [launch, retire] - containment. + containment. Clock-corrected startup markers can also land just before + the first occupancy launch; with no earlier wave in that slot, they + belong to that first span. """ lo, hi = 0, len(spans) - 1 result = None @@ -476,7 +478,7 @@ def find_wave_span_at(spans: list[WaveSpan], time: int) -> Optional[WaveSpan]: lo = mid + 1 else: hi = mid - 1 - return result + return result if result is not None or not spans else spans[0] def make_funcmap_resolver( diff --git a/projects/rocprof-trace-decoder/test/CMakeLists.txt b/projects/rocprof-trace-decoder/test/CMakeLists.txt index f4d6c0c08038..b0a15072e5ff 100644 --- a/projects/rocprof-trace-decoder/test/CMakeLists.txt +++ b/projects/rocprof-trace-decoder/test/CMakeLists.txt @@ -113,6 +113,7 @@ if(BUILD_INTEGRATION_TESTS) set(PYTHON_API_PY ${CMAKE_CURRENT_SOURCE_DIR}/python_api_test.py) set(CODEGEN_TEST_PY ${CMAKE_CURRENT_SOURCE_DIR}/codegen_test.py) set(ATT_TOOL_TEST_PY ${CMAKE_CURRENT_SOURCE_DIR}/att_tool_test.py) + set(NAVI4_MARKER_TEST_PY ${CMAKE_CURRENT_SOURCE_DIR}/navi4_marker_test.py) set(LIB_PATH ${CMAKE_BINARY_DIR}/lib/librocprof-trace-decoder${CMAKE_SHARED_LIBRARY_SUFFIX}) set(SANITIZED_LIB_PATH ${CMAKE_BINARY_DIR}/lib/librocprof-trace-decoder-sanitized${CMAKE_SHARED_LIBRARY_SUFFIX}) @@ -169,6 +170,21 @@ if(BUILD_INTEGRATION_TESTS) ENVIRONMENT "PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/../python") + add_test( + NAME navi4-marker-flamegraph + COMMAND ${Python3_EXECUTABLE} ${NAVI4_MARKER_TEST_PY} + --lib ${LIB_PATH} + --archive ${CMAKE_CURRENT_SOURCE_DIR}/data/navi4_marker.tar.gz) + set_tests_properties( + navi4-marker-flamegraph + PROPERTIES + TIMEOUT + 60 + LABELS + "integration-tests;markers" + ENVIRONMENT + "${PYTHON_API_TEST_ENV}") + if(DEFINED ROCPROF_TRACE_DECODER_PYTHON_INSTALL_DIR) configure_file(python_api_install_test.cmake.in python_api_install_test.cmake @ONLY) add_test( diff --git a/projects/rocprof-trace-decoder/test/att_tool_test.py b/projects/rocprof-trace-decoder/test/att_tool_test.py index acd497513b91..6286878458c4 100644 --- a/projects/rocprof-trace-decoder/test/att_tool_test.py +++ b/projects/rocprof-trace-decoder/test/att_tool_test.py @@ -55,7 +55,7 @@ def finish(self): class MarkerTimestampCorrectionTest(unittest.TestCase): def test_corrects_headers_and_moves_declared_payloads_with_their_header(self): first_value = (0x100 << 20) | (3 << 2) - second_value = (0x100 << 20) | (4 << 2) + second_value = (0x101 << 20) | (4 << 2) first = _shaderdata(0x1010, first_value) second = _shaderdata(0x1080, second_value) payload = _shaderdata(0x1090, first_value) @@ -71,7 +71,7 @@ def test_corrects_headers_and_moves_declared_payloads_with_their_header(self): _correct_marker_timestamps([(0, records)], document) - self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x101F, 0x102F]) + self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x102F, 0x103F]) self.assertIs(records.shaderdata[0], first) self.assertIs(records.shaderdata[1], second) self.assertIs(records.shaderdata[2], payload) @@ -139,10 +139,11 @@ def test_unknown_packed_values_are_unchanged(self): self.assertEqual(records.shaderdata, original) def test_correction_is_default(self): - value = (0x100 << 20) | (3 << 2) + early_value = (0x100 << 20) | (3 << 2) + late_value = (0x101 << 20) | (3 << 2) records = TraceRecords( occupancy=[_occupancy(1, 0)], - shaderdata=[_shaderdata(0x1080, value), _shaderdata(0x1010, value)], + shaderdata=[_shaderdata(0x1080, late_value), _shaderdata(0x1010, early_value)], ) code_index = CodeIndex.from_document( { @@ -162,9 +163,27 @@ def test_correction_is_default(self): formats="json", ) - self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x101F]) + self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x102F]) self.assertEqual([record.value for record in records.shaderdata], [12, 12]) + def test_constant_clock_samples_are_normalized_without_correction(self): + late = _shaderdata(0x1080, 3 << 2) + early = _shaderdata(0x1010, 4 << 2) + records = TraceRecords( + occupancy=[_occupancy(1, 0)], shaderdata=[late, early] + ) + + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 3, "P", "first", "", 0], [7, 4, "P", "second", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + }, + ) + + self.assertEqual([late.time, early.time], [0x1080, 0x1010]) + self.assertEqual([late.value, early.value], [12, 16]) + def test_correction_does_not_share_the_reference_between_simds(self): value = (0x100 << 20) | (3 << 2) slow_simd = _shaderdata(0x1080, value, simd=2) @@ -206,10 +225,32 @@ def test_correction_cancels_unknown_clock_phase(self): self.assertEqual([record.time for record in records.shaderdata], [0xFE0B, 0x10DFC]) self.assertEqual([record.value for record in records.shaderdata], [marker_id, marker_id]) + def test_large_delay_warns_and_is_corrected(self): + marker_id = 3 << 2 + base = 0x20000 + first = _shaderdata(base, marker_id) + later = _shaderdata(base + 30000, marker_id) + final = _shaderdata(base + 60000, (0x5F9 << 20) | marker_id) + records = TraceRecords( + occupancy=[_occupancy(1, 0)], shaderdata=[first, later, final] + ) + + with self.assertWarnsRegex(RuntimeWarning, "delay range exceeds half"): + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 3, "P", "marker", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + }, + ) + + self.assertEqual(first.time, base - 30000 + 15) + self.assertEqual(later.time, base - 30000 + 15) + self.assertEqual(final.time, base + 60000) + def test_late_header_after_retirement_is_normalized_and_corrected(self): - value = (0x100 << 20) | (3 << 2) - before_retirement = _shaderdata(0x1010, value) - after_retirement = _shaderdata(0x1080, value) + before_retirement = _shaderdata(0x1010, (0x100 << 20) | (3 << 2)) + after_retirement = _shaderdata(0x1080, (0x101 << 20) | (3 << 2)) records = TraceRecords( occupancy=[_occupancy(1, 0), _occupancy(0, 0x1020)], shaderdata=[before_retirement, after_retirement], @@ -224,7 +265,7 @@ def test_late_header_after_retirement_is_normalized_and_corrected(self): ) self.assertEqual(before_retirement.time, 0x1010) - self.assertEqual(after_retirement.time, 0x101F) + self.assertEqual(after_retirement.time, 0x102F) self.assertEqual([record.value for record in records.shaderdata], [12, 12]) def test_later_launch_supersedes_a_retired_wave_slot(self): @@ -248,7 +289,7 @@ def test_later_launch_supersedes_a_retired_wave_slot(self): }, ) - self.assertEqual([record.time for record in records.shaderdata], [0x1020, 0x103F]) + self.assertEqual([record.time for record in records.shaderdata], [0x1020, 0x1080]) self.assertEqual([record.value for record in records.shaderdata], [16, 16]) def test_multi_payload_headers_are_normalized_without_clock_correction(self): diff --git a/projects/rocprof-trace-decoder/test/codegen_test.py b/projects/rocprof-trace-decoder/test/codegen_test.py index 478056204f09..625547ac656e 100644 --- a/projects/rocprof-trace-decoder/test/codegen_test.py +++ b/projects/rocprof-trace-decoder/test/codegen_test.py @@ -48,19 +48,19 @@ def test_adds_packed_metadata_without_changing_funcmap_rows(self): self.assertEqual(doc["sqtt_funcmap_layout"], [[10, 12, 4]]) self.assertEqual(doc["sqtt_funcmap_payloads"], [[10, 8, 1]]) - def test_legacy_funcmap_leaves_code_json_unchanged(self): + def test_legacy_funcmap_exports_rows_without_a_packed_layout(self): doc = self.generate( [codegen._parse_sqtt_funcmap(b"F:7:scope\nR:7:extra_payload_count=1\n")] ) - self.assertNotIn("sqtt_funcmap", doc) + self.assertEqual(doc["sqtt_funcmap"], [[10, 7, "F", "scope", "", 0]]) self.assertNotIn("sqtt_funcmap_layout", doc) - self.assertNotIn("sqtt_funcmap_payloads", doc) + self.assertEqual(doc["sqtt_funcmap_payloads"], [[10, 7, 1]]) def test_incomplete_packed_layout_is_ignored(self): doc = self.generate([codegen._parse_sqtt_funcmap(b"M:shader_clock_bits=12\nF:7:scope\n")]) - self.assertNotIn("sqtt_funcmap", doc) + self.assertEqual(doc["sqtt_funcmap"], [[10, 7, "F", "scope", "", 0]]) self.assertNotIn("sqtt_funcmap_layout", doc) def test_packed_layout_does_not_require_named_entries(self): diff --git a/projects/rocprof-trace-decoder/test/markers/kernels/heavy.cpp b/projects/rocprof-trace-decoder/test/markers/kernels/heavy.cpp index 3de8e2d34b6f..81759dffe04a 100644 --- a/projects/rocprof-trace-decoder/test/markers/kernels/heavy.cpp +++ b/projects/rocprof-trace-decoder/test/markers/kernels/heavy.cpp @@ -99,44 +99,55 @@ using Vec16 = __attribute__((__vector_size__(16 * sizeof(float)))) float; using Vec8 = __attribute__((__vector_size__(8 * sizeof(float)))) float; using float8x8 = __attribute__((__vector_size__(8 * sizeof(uint8_t)))) uint8_t; using IVec2 = __attribute__((__vector_size__(2 * sizeof(int)))) int; +using IVec8 = __attribute__((__vector_size__(8 * sizeof(int)))) int; static_assert(sizeof(float8) == sizeof(uint8_t)); -#if defined(__gfx1200__) || defined(__gfx1201__) +#if defined(__gfx942__) || defined(__gfx950__) -inline __device__ IVec2 castfrom8x8(const float8x8& f) +inline __device__ uint64_t castfrom8x8(const float8x8& f) { - return *reinterpret_cast(&f); + return *reinterpret_cast(&f); } -inline __device__ Vec8 mfma(const float8x8& a, const float8x8& b, const Vec8& initial = Vec8{}) +inline __device__ Vec4 mfma(const float8x8& a, const float8x8& b, const Vec4& initial = Vec4{}) { - return __builtin_amdgcn_wmma_f32_16x16x16_fp8_fp8_w32_gfx12(castfrom8x8(a), castfrom8x8(b), initial); + return __builtin_amdgcn_mfma_f32_16x16x32_fp8_fp8(castfrom8x8(a), castfrom8x8(b), initial, 0, 0, 0); } inline __device__ Vec4 mfma(const float8x8& a0, const float8x8& a1, const float8x8& b0, const float8x8& b1) { - Vec8 ret = mfma(castfrom8x8(a0), castfrom8x8(b0)); - ret = mfma(castfrom8x8(a1), castfrom8x8(b1), ret); - return {ret[0], ret[1], ret[2], ret[3]}; + Vec4 ret = mfma(a0, b0); + return mfma(a1, b1, ret); } #else -inline __device__ uint64_t castfrom8x8(const float8x8& f) +#if defined(__gfx1200__) || defined(__gfx1201__) +inline __device__ IVec2 castfrom8x8(const float8x8& f) { - return *reinterpret_cast(&f); + return *reinterpret_cast(&f); } - -inline __device__ Vec4 mfma(const float8x8& a, const float8x8& b, const Vec4& initial = Vec4{}) +inline __device__ Vec8 mfma(const float8x8& a, const float8x8& b, const Vec8& initial = Vec8{}) { - return __builtin_amdgcn_mfma_f32_16x16x32_fp8_fp8(castfrom8x8(a), castfrom8x8(b), initial, 0, 0, 0); + return __builtin_amdgcn_wmma_f32_16x16x16_fp8_fp8_w32_gfx12(castfrom8x8(a), castfrom8x8(b), initial); +} +#else +inline __device__ IVec8 castfrom8x8(const float8x8& f) +{ + return *reinterpret_cast(&f); +} +inline __device__ Vec8 mfma(const IVec8& a, const IVec8& b, const Vec8& initial = Vec8{}) +{ + return __builtin_amdgcn_wmma_f32_16x16x64_fp8_fp8(a, b, 0, initial, 0, 0); } +#endif inline __device__ Vec4 mfma(const float8x8& a0, const float8x8& a1, const float8x8& b0, const float8x8& b1) { - Vec4 ret = mfma(a0, b0); - return mfma(a1, b1, ret); + Vec8 ret = mfma(castfrom8x8(a0), castfrom8x8(b0)); + ret = mfma(castfrom8x8(a1), castfrom8x8(b1), ret); + return {ret[0], ret[1], ret[2], ret[3]}; } #endif @@ -361,9 +372,14 @@ int main() if (devProp.major > 9 && devProp.minor == 0) { - M /= 4; - K /= 4; - N /= 4; + M /= 3; + K /= 3; + N /= 3; + } + else if (devProp.major > 9 && devProp.minor == 5) + { + M /= 2; + K /= 2; } Matrix a(K, M); diff --git a/projects/rocprof-trace-decoder/test/markers/kernels/marker.cpp b/projects/rocprof-trace-decoder/test/markers/kernels/marker.cpp index 64e64e2c2fdc..1c0803fce55e 100644 --- a/projects/rocprof-trace-decoder/test/markers/kernels/marker.cpp +++ b/projects/rocprof-trace-decoder/test/markers/kernels/marker.cpp @@ -25,7 +25,7 @@ // Named markers require the pass plugin. Numeric markers work standalone. // // Build (enabled + pass plugin for named markers): -// hipcc -DSQTT_ENABLED=1 -fpass-plugin=../../../build/lib/SQTTInstrumentPass.so \ +// hipcc -DSQTT_ENABLED=1 -fpass-plugin=../../../build/lib/libsqttinstrumentpass.so \ // -I ../../include/ marker.cpp -o marker #include diff --git a/projects/rocprof-trace-decoder/test/markers/run_tests.py b/projects/rocprof-trace-decoder/test/markers/run_tests.py index 140fadf1e070..f9f4bf5c4889 100644 --- a/projects/rocprof-trace-decoder/test/markers/run_tests.py +++ b/projects/rocprof-trace-decoder/test/markers/run_tests.py @@ -51,7 +51,7 @@ PROJECT_ROOT = os.path.dirname(os.path.dirname(SCRIPT_DIR)) BUILD_DIR = os.environ.get("SQTT_BUILD_DIR", os.path.join(PROJECT_ROOT, "build")) PASS_PLUGIN = os.environ.get( - "SQTT_PASS_PLUGIN", os.path.join(BUILD_DIR, "lib", "SQTTInstrumentPass.so")) + "SQTT_PASS_PLUGIN", os.path.join(BUILD_DIR, "lib", "libsqttinstrumentpass.so")) INCLUDE_DIR = os.path.join(PROJECT_ROOT, "include", "rocprof_trace_decoder", "cxx") TEST_SOURCE = os.path.join(SCRIPT_DIR, "kernels", "auto.cpp") ADDR_TRACE_SOURCE = os.path.join(SCRIPT_DIR, "kernels", "addr_trace.cpp") @@ -478,7 +478,7 @@ def extract_funcmap(binary: str, tmpdir: str) -> str: { "name": "gfx12_func_instrumentation", "desc": "Function instrumentation on gfx12", - "env": {"SQTT_INSTRUMENT_FUNCTIONS": "10"}, + "env": {"SQTT_INSTRUMENT_FUNCTIONS": "10", "SQTT_SHADER_CLOCK_BITS": "12"}, "mode": "obj", "flags": ["--offload-arch=gfx1200"], "expect_ir": [], @@ -536,7 +536,7 @@ def extract_funcmap(binary: str, tmpdir: str) -> str: { "name": "gfx12_asm_shader_clock", "desc": "gfx12 packs shader clock bits into full s_ttracedata markers", - "env": {"SQTT_INSTRUMENT_FUNCTIONS": "10"}, + "env": {"SQTT_INSTRUMENT_FUNCTIONS": "10", "SQTT_SHADER_CLOCK_BITS": "12"}, "mode": "asm", "flags": ["--offload-arch=gfx1200"], "expect_ir": [ @@ -548,9 +548,27 @@ def extract_funcmap(binary: str, tmpdir: str) -> str: "reject_funcmap": [], }, { - "name": "gfx12_shader_clock_disabled_asm", - "desc": "gfx12: disabled clock packing keeps immediate headers and dynamic payload traces", - "env": {"SQTT_SHADER_CLOCK_BITS": "0"}, + "name": "gfx1250_asm_shader_clock", + "desc": "gfx1250 uses s_get_shader_cycles_u64 for packed shader clocks", + "env": {"SQTT_INSTRUMENT_FUNCTIONS": "10", "SQTT_SHADER_CLOCK_BITS": "12"}, + "mode": "asm", + "flags": ["--offload-arch=gfx1250"], + "expect_ir": [ + r"s_get_shader_cycles_u64\b", + r"s_ttracedata\b", + ], + "reject_ir": [ + r"HW_REG_SHADER_CYCLES_LO", + r"s_ttracedata_imm\b", + ], + "expect_funcmap": [], + "reject_funcmap": [], + "custom_asm_check": "m0_nop_before_ttracedata", + }, + { + "name": "gfx12_default_no_clock_asm", + "desc": "gfx12: default layout keeps immediate headers and dynamic payload traces", + "env": {}, "mode": "asm", "source": "marker", "flags": ["--offload-arch=gfx1200"], @@ -564,9 +582,9 @@ def extract_funcmap(binary: str, tmpdir: str) -> str: "custom_asm_check": "m0_nop_before_ttracedata", }, { - "name": "gfx12_shader_clock_disabled_funcmap", - "desc": "gfx12: disabled clock packing emits no funcmap layout row", - "env": {"SQTT_SHADER_CLOCK_BITS": "0"}, + "name": "gfx12_default_no_clock_funcmap", + "desc": "gfx12: default layout emits no funcmap layout row", + "env": {}, "mode": "obj", "source": "marker", "flags": ["--offload-arch=gfx1200"], diff --git a/projects/rocprof-trace-decoder/test/unit/funcmap_test.cpp b/projects/rocprof-trace-decoder/test/unit/funcmap_test.cpp index 46c39849a6cb..c3746956f0b1 100644 --- a/projects/rocprof-trace-decoder/test/unit/funcmap_test.cpp +++ b/projects/rocprof-trace-decoder/test/unit/funcmap_test.cpp @@ -129,7 +129,7 @@ TEST(MarkerEncoding, ExposesRawAndSourceClockMasks) EXPECT_EQ(enc.marker_id_mask(), 0x000FFFFCu); EXPECT_EQ(enc.packed_shader_clock_mask(), 0xFFF00000u); - // The sampled clock was HW_REG_SHADER_CYCLES_LO bits [15:4]. + // The sampled source-clock low word uses bits [15:4]. EXPECT_EQ(enc.shader_clock_source_mask(), 0x0000FFF0u); uint32_t raw = (0xABCu << 20) | (42u << 2) | 0b10u; diff --git a/projects/rocprof-trace-decoder/test/unit/markers_test.cpp b/projects/rocprof-trace-decoder/test/unit/markers_test.cpp index 9158ba3c8837..b0ebddb48567 100644 --- a/projects/rocprof-trace-decoder/test/unit/markers_test.cpp +++ b/projects/rocprof-trace-decoder/test/unit/markers_test.cpp @@ -491,7 +491,7 @@ TEST(MarkerConfig, ParsesEnvironmentAndRejectsConflictingModes) EXPECT_EQ(config.MemoryMaxGap, 7u); EXPECT_FALSE(config.TraceMemoryAddrs); EXPECT_FALSE(config.TraceLDSAddrs); - EXPECT_EQ(config.ShaderClockBits, SQTTConfig::AutoShaderClockBits); + EXPECT_EQ(config.ShaderClockBits, 0u); EXPECT_EQ(config.ShaderClockShift, 8u); EXPECT_EQ(config.WaveMask, 0xFFFFFFFFu); EXPECT_EQ(config.SimdMask, 0x5u); @@ -518,6 +518,10 @@ TEST(MarkerTarget, ClassifiesArchitecturesAndInstructionCosts) EXPECT_EQ(getGfxGen(*makeVoidFunction(*module, "gfx11_func", "gfx1100")), GfxGen::RDNA); EXPECT_EQ(getGfxGen(*makeVoidFunction(*module, "gfx12_func", "gfx1200")), GfxGen::GFX12); EXPECT_EQ(getGfxGen(*makeVoidFunction(*module, "unknown_func", "notgfx")), GfxGen::Unknown); + EXPECT_FALSE(hasShaderCyclesU64(*makeVoidFunction(*module, "gfx1200_cycles", "gfx1200"))); + EXPECT_FALSE(hasShaderCyclesU64(*makeVoidFunction(*module, "gfx1201_cycles", "gfx1201"))); + EXPECT_TRUE(hasShaderCyclesU64(*makeVoidFunction(*module, "gfx1250_cycles", "gfx1250"))); + EXPECT_TRUE(hasShaderCyclesU64(*makeVoidFunction(*module, "gfx1250_features", "gfx1250:xnack+"))); EXPECT_EQ(getWaveSize(GfxGen::GFX9), 64u); EXPECT_EQ(getWaveSize(GfxGen::RDNA), 32u); @@ -525,10 +529,12 @@ TEST(MarkerTarget, ClassifiesArchitecturesAndInstructionCosts) EXPECT_TRUE(supportsImmTrace(GfxGen::GFX12)); SQTTConfig config; - EXPECT_EQ(getShaderClockBits(config, GfxGen::GFX12), 12u); + EXPECT_EQ(getShaderClockBits(config, GfxGen::GFX12), 0u); EXPECT_EQ(getShaderClockBits(config, GfxGen::RDNA), 0u); + EXPECT_FALSE(usesShaderClockPacking(config, GfxGen::GFX12)); config.ShaderClockBits = 5; EXPECT_EQ(getShaderClockBits(config, GfxGen::RDNA), 5u); + EXPECT_EQ(getShaderClockBits(config, GfxGen::GFX12), 5u); EXPECT_TRUE(usesShaderClockPacking(config, GfxGen::GFX12)); EXPECT_FALSE(usesShaderClockPacking(config, GfxGen::RDNA)); @@ -705,7 +711,7 @@ TEST(MarkerPass, AddressTracingSkipsBufferLoadLds) expectNotContains(funcMap, "W:"); } -TEST(MarkerPass, AddressTraceBlocksDisableAutomaticGfx12ClockPackingAndUseBlockBoundaries) +TEST(MarkerPass, AddressTraceBlocksKeepDefaultGfx12ClockPackingDisabledAndUseBlockBoundaries) { LLVMContext ctx; auto module = makeModule(ctx); @@ -791,6 +797,7 @@ TEST(MarkerPass, OnePayloadNamedDataRemainsEligibleForGfx12ClockPacking) SQTTConfig config; useFullScopeMasks(config); + config.ShaderClockBits = 12; ModuleAnalysisManager analysisManager; SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); @@ -906,6 +913,7 @@ TEST(MarkerPass, Gfx12PacksOnlyPassGeneratedHeaders) SQTTConfig config; useFullScopeMasks(config); config.FunctionThreshold = 1; + config.ShaderClockBits = 12; ModuleAnalysisManager analysisManager; SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); From 9b5efaa15301050ae7a57c8bf96f851b872ac402 Mon Sep 17 00:00:00 2001 From: Giovanni Baraldi Date: Thu, 16 Jul 2026 22:06:28 +0200 Subject: [PATCH 13/13] gfx12 fixes --- .../docs/markers/SPEC.md | 11 +- .../rocprof-trace-decoder/markers/README.md | 15 ++- .../markers/SQTTPass.cpp | 9 +- .../python/rocprof_trace_decoder/att.py | 38 +++--- .../rocprof-trace-decoder/test/CMakeLists.txt | 16 --- .../test/att_tool_test.py | 119 ++++++++++++++---- .../test/unit/markers_test.cpp | 56 ++++----- 7 files changed, 154 insertions(+), 110 deletions(-) diff --git a/projects/rocprof-trace-decoder/docs/markers/SPEC.md b/projects/rocprof-trace-decoder/docs/markers/SPEC.md index 067f32b36bab..5612f82d622f 100644 --- a/projects/rocprof-trace-decoder/docs/markers/SPEC.md +++ b/projects/rocprof-trace-decoder/docs/markers/SPEC.md @@ -124,10 +124,9 @@ gfx12 uses the no-clock full-ID layout and may use `s_ttracedata_imm`. A nonzero `shader_clock_bits` value requires `shader_clock_shift`; a malformed packed row uses the no-clock full-ID layout. -Address trace blocks have `R:extra_payload_count > 1`. Clock packing is -disabled by default; an explicit nonzero `SQTT_SHADER_CLOCK_BITS` is an error -when a module emits such a block. A named `sqtt_marker_data()` record has -exactly one payload (`R:...=1`) and remains eligible for clock packing. +Any marker with `R:extra_payload_count > 0`, including address traces and +named `sqtt_marker_data()` records, requires `SQTT_SHADER_CLOCK_BITS=0`. +Clock packing is disabled by default; an explicit nonzero setting is an error. The sampled window lets a decoder compensate for the variable delay between the trace instruction issuing and its shaderdata record appearing in SQTT. @@ -156,7 +155,7 @@ headers retroactively. `att_tool.py` applies this correction while writing JSON by default; `--no-decode-markers` preserves the packed values and arrival timestamps. It rewrites each corrected header to the legacy `(id << 2) | flags` form. For an -externally produced packed multi-payload block, JSON output may normalize a +externally produced packed payload block, JSON output may normalize a recognized header but does not apply timestamp correction. ### Semantic rules @@ -338,7 +337,7 @@ All variables are read at **compile time** by the pass plugin. | `SQTT_INSTRUMENT_FUNCTIONS`| `0` | Function entry/exit threshold (0=disabled) | | `SQTT_INSTRUMENT_MEMORY` | off | Memory op markers. Format: `N:M` (N=ops per marker, M=max gap) | | `SQTT_MEM_BARRIER` | `fence` | Reordering boundary around markers (`none`/`asm`/`fence` or `0`/`1`/`2`) | -| `SQTT_SHADER_CLOCK_BITS` | `0` | Gfx12 shader clock bits. Set a nonzero value to enable packing; it is invalid with address blocks. | +| `SQTT_SHADER_CLOCK_BITS` | `0` | Gfx12 shader clock bits. Set a nonzero value to enable packing; it is invalid with any payload-bearing marker. | | `SQTT_SHADER_CLOCK_SHIFT` | `4` | Source bit offset in the shader-clock low word for packing | ### Marker reorder boundary (`SQTT_MEM_BARRIER`) diff --git a/projects/rocprof-trace-decoder/markers/README.md b/projects/rocprof-trace-decoder/markers/README.md index 629025a0761c..010817bdd8ca 100644 --- a/projects/rocprof-trace-decoder/markers/README.md +++ b/projects/rocprof-trace-decoder/markers/README.md @@ -177,11 +177,9 @@ to report only active lanes. The parser demultiplexes interleaved records from concurrent waves by grouping on `(cu, simd, wave_id)` before parsing each wave's address block. -Address blocks always have `extra_payload_count > 1`. Shader-clock packing is -disabled by default, and an explicit nonzero `SQTT_SHADER_CLOCK_BITS` is an -error when an address block is emitted. -This does not apply to `sqtt_marker_data()`, whose single raw payload remains -eligible for clock packing. +Any marker with `extra_payload_count > 0`, including address blocks and +`sqtt_marker_data()`, requires `SQTT_SHADER_CLOCK_BITS=0`. Shader-clock +packing is disabled by default; an explicit nonzero setting is an error. ### Everything together @@ -288,7 +286,7 @@ directly via `getenv()`. | `SQTT_INSTRUMENT_MEMORY` | `N:M` | off | Instrument memory ops. N = ops per marker, M = max gap. `2:5` = 1 marker per 2 ops, sequence breaks at gap > 5. Covers global, buffer, flat (not LDS/scratch). | | `SQTT_TRACE_ADDRESSES` | `memory`, `lds`, or both | off | Trace per-lane virtual addresses. Mutually exclusive with `SQTT_INSTRUMENT_MEMORY`. `memory` = flat/global (AS=0/1) and buffer, `lds` = LDS (AS=3). Expensive. | | `SQTT_MEM_BARRIER` | `none` / `asm` / `fence` (or `0` / `1` / `2`) | `fence` | Reordering boundary planted around every marker. `fence` (default) emits `fence syncscope("workgroup") acq_rel` tagged with AMDGPU local/LDS synchronization metadata, anchoring markers against optimizer and scheduler movement without marker-generated global cache invalidation. `asm` plants an empty `~{memory}` inline asm (IR/MIR-only constraint, no machine code). `none` disables both. Default favors marker accuracy; opt down for tight kernels. | -| `SQTT_SHADER_CLOCK_BITS` | unsigned integer | `0` | Number of gfx12 marker header high bits reserved for shader clock. Set a nonzero value to enable packing; it is invalid with address blocks. | +| `SQTT_SHADER_CLOCK_BITS` | unsigned integer | `0` | Number of gfx12 marker header high bits reserved for shader clock. Set a nonzero value to enable packing; it is invalid with any payload-bearing marker. | | `SQTT_SHADER_CLOCK_SHIFT` | unsigned integer | `4` | Source bit offset in the shader-clock low word for the packed field. | ## Examples @@ -481,6 +479,7 @@ and leaves 18 marker ID bits plus the two low flags. gfx1250 uses `SQTT_SHADER_CLOCK_BITS` and `SQTT_SHADER_CLOCK_SHIFT`; it defaults to `0`, which uses the no-clock full-ID layout. The funcmap emits `M:shader_clock_bits=N;shader_clock_shift=S` whenever clock packing is active. +Clock packing cannot be combined with any marker that has an `R:` payload row. Numeric point and enter markers keep their legacy values; bare exits use the packed form because their value is indistinguishable from a generated exit. @@ -497,8 +496,8 @@ full-width ID layout. `att_tool.py` applies that correction by default and masks each recognized header to its ordinary legacy-form marker value for existing viewers. Use `--no-decode-markers` to preserve packed values and arrival timestamps. -For externally packed multi-payload blocks it may normalize a recognized -header, but does not apply timestamp correction. +For externally packed payload blocks it may normalize a recognized header, +but does not apply timestamp correction. For packed code objects, `att_tool.py` adds six-column `sqtt_funcmap` rows and packed-layout/payload tables to `code.json`. Readers that do not know them diff --git a/projects/rocprof-trace-decoder/markers/SQTTPass.cpp b/projects/rocprof-trace-decoder/markers/SQTTPass.cpp index 3d7e84de6519..c75c7800a58d 100644 --- a/projects/rocprof-trace-decoder/markers/SQTTPass.cpp +++ b/projects/rocprof-trace-decoder/markers/SQTTPass.cpp @@ -179,11 +179,12 @@ PreservedAnalyses SQTTInstrumentPass::runLate(Module& M) if (!hadEarlyPass && Config.FunctionThreshold > 0 && !IsKernel) Changed |= instrumentFunctionDirect(F, Gen); } - // Address records have R:extra_payload_count > 1. Do not create a packed - // header layout whose JSON timestamp correction could split those blocks. - if (!AddrTraceEntries.empty() && Config.ShaderClockBits != 0) + bool HasPayloadMarkers = false; + for (const auto& Entry : UserMarkers) HasPayloadMarkers |= Entry.ExtraPayloadCount != 0; + for (const auto& Entry : AddrTraceEntries) HasPayloadMarkers |= Entry.ExtraPayloadCount != 0; + if (HasPayloadMarkers && Config.ShaderClockBits != 0) report_fatal_error( - "SQTT_TRACE_ADDRESSES emits multi-payload records; set SQTT_SHADER_CLOCK_BITS=0" + "SQTT payload markers require SQTT_SHADER_CLOCK_BITS=0" ); // Packing must happen after the address decision for the entire module. diff --git a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py index 41398375035e..47920d5fc3dc 100644 --- a/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py +++ b/projects/rocprof-trace-decoder/python/rocprof_trace_decoder/att.py @@ -149,11 +149,10 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict codeobj, marker_id, kind = int(row[0]), int(row[1]), str(row[2]) except (IndexError, TypeError, ValueError): continue - if codeobj in layouts and kind.lower() not in {"k", "kernel"} and marker_id >= 0: + if kind.lower() not in {"k", "kernel"} and marker_id >= 0: marker_ids[codeobj].add(marker_id) payloads: dict[tuple[int, int], int] = {} - multi_payload_codeobjs: set[int] = set() for row in document.get("sqtt_funcmap_payloads", []): try: codeobj, marker_id, count = map(int, row[:3]) @@ -161,8 +160,7 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict continue if marker_id in marker_ids.get(codeobj, ()) and count > 0: payloads[codeobj, marker_id] = count - if count > 1: - multi_payload_codeobjs.add(codeobj) + payload_codeobjs = {codeobj for codeobj, _marker_id in payloads} shaderdata: dict[tuple[int, int, int, int], list[object]] = defaultdict(list) occupancy: dict[tuple[int, int, int, int], list[object]] = defaultdict(list) @@ -172,17 +170,14 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict for record in records.occupancy: occupancy[se, record.cu, record.simd, record.wave_id].append(record) - headers: dict[tuple[int, int, int, int, int], list[tuple[object, int, int, list[object]]]] = defaultdict(list) + headers: dict[tuple[int, int, int, int, int], list[tuple[object, int, int]]] = defaultdict(list) for location, stream in shaderdata.items(): stream.sort(key=lambda record: record.time) intervals = _active_codeobj_intervals(occupancy.get(location, ())) interval = 0 payload_remaining = 0 - header_payloads: list[object] | None = None for record in stream: if payload_remaining: - if header_payloads is not None: - header_payloads.append(record) payload_remaining -= 1 continue if record.flags & ((1 << ShaderDataFlags.IMM) | (1 << ShaderDataFlags.PRIV)): @@ -209,18 +204,15 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict continue record.value = (marker_id << 2) | (raw & 3) - header_payloads = [] if marker_id in known_ids: payload_remaining = payloads.get((codeobj, marker_id), 0) - # New producers disable clock packing for multi-payload protocols. - # Keep old traces readable by normalizing their headers, but do - # not shift timestamps: sorting corrected headers independently - # can break the declared header/payload record adjacency. - if codeobj in multi_payload_codeobjs: + # New producers forbid clock packing with payload protocols. Keep + # old packed traces readable by normalizing their headers only. + if codeobj in payload_codeobjs: continue sampled = ((raw >> (32 - bits)) & ((1 << bits) - 1)) << shift clock_domain = (*location[:3], bits, shift) - headers[clock_domain].append((record, int(record.time), sampled, header_payloads)) + headers[clock_domain].append((record, int(record.time), sampled)) changed = False for clock_domain, domain_headers in headers.items(): @@ -230,26 +222,24 @@ def _correct_marker_timestamps(decoded: list[tuple[int, object]], document: dict half_window = window_size // 2 bucket_size = 1 << shift # A constant field cannot establish a relative issue-time delta. - if len({sampled for _record, _time, sampled, _payloads in domain_headers}) == 1: + if len({sampled for _record, _time, sampled in domain_headers}) == 1: continue # This is an arbitrary coordinate origin, not a time/minimum anchor. # Comparing deltas cancels the fixed phase between the two clocks. - _origin, origin_time, origin_clock, _payloads = domain_headers[0] + _origin, origin_time, origin_clock = domain_headers[0] delays = [ (((time - origin_time) - (clock - origin_clock) + half_window) & window_mask) - half_window - for _record, time, clock, _payloads in domain_headers + for _record, time, clock in domain_headers ] minimum = min(delays) - for (record, _time, _clock, header_payloads), delay in zip(domain_headers, delays): + for (record, _time, _clock), delay in zip(domain_headers, delays): correction = max(0, delay - minimum - (bucket_size - 1)) if correction: record.time -= correction - # TODO: sort one-payload blocks atomically; a final per-record - # sort can still interleave another header with this block. - for payload in header_payloads: - payload.time -= correction changed = True - if changed: + # Payload order is semantic. Old traces may mix a no-clock payload code + # object with a packed one, so do not split their blocks by re-sorting. + if changed and not payloads: for _se, records in decoded: records.shaderdata.sort(key=lambda record: record.time) diff --git a/projects/rocprof-trace-decoder/test/CMakeLists.txt b/projects/rocprof-trace-decoder/test/CMakeLists.txt index b0a15072e5ff..f4d6c0c08038 100644 --- a/projects/rocprof-trace-decoder/test/CMakeLists.txt +++ b/projects/rocprof-trace-decoder/test/CMakeLists.txt @@ -113,7 +113,6 @@ if(BUILD_INTEGRATION_TESTS) set(PYTHON_API_PY ${CMAKE_CURRENT_SOURCE_DIR}/python_api_test.py) set(CODEGEN_TEST_PY ${CMAKE_CURRENT_SOURCE_DIR}/codegen_test.py) set(ATT_TOOL_TEST_PY ${CMAKE_CURRENT_SOURCE_DIR}/att_tool_test.py) - set(NAVI4_MARKER_TEST_PY ${CMAKE_CURRENT_SOURCE_DIR}/navi4_marker_test.py) set(LIB_PATH ${CMAKE_BINARY_DIR}/lib/librocprof-trace-decoder${CMAKE_SHARED_LIBRARY_SUFFIX}) set(SANITIZED_LIB_PATH ${CMAKE_BINARY_DIR}/lib/librocprof-trace-decoder-sanitized${CMAKE_SHARED_LIBRARY_SUFFIX}) @@ -170,21 +169,6 @@ if(BUILD_INTEGRATION_TESTS) ENVIRONMENT "PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/../python") - add_test( - NAME navi4-marker-flamegraph - COMMAND ${Python3_EXECUTABLE} ${NAVI4_MARKER_TEST_PY} - --lib ${LIB_PATH} - --archive ${CMAKE_CURRENT_SOURCE_DIR}/data/navi4_marker.tar.gz) - set_tests_properties( - navi4-marker-flamegraph - PROPERTIES - TIMEOUT - 60 - LABELS - "integration-tests;markers" - ENVIRONMENT - "${PYTHON_API_TEST_ENV}") - if(DEFINED ROCPROF_TRACE_DECODER_PYTHON_INSTALL_DIR) configure_file(python_api_install_test.cmake.in python_api_install_test.cmake @ONLY) add_test( diff --git a/projects/rocprof-trace-decoder/test/att_tool_test.py b/projects/rocprof-trace-decoder/test/att_tool_test.py index 09a9ba228785..76eb6267c18f 100644 --- a/projects/rocprof-trace-decoder/test/att_tool_test.py +++ b/projects/rocprof-trace-decoder/test/att_tool_test.py @@ -5,16 +5,21 @@ import sys import unittest +import xml.etree.ElementTree as ET from pathlib import Path from tempfile import TemporaryDirectory from unittest.mock import patch -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "python")) +ROOT = Path(__file__).resolve().parents[1] +sys.path[:0] = [str(ROOT / "python"), str(ROOT / "scripts")] from att_tool import build_argparser from rocprof_trace_decoder.att import AttTrace, _correct_marker_timestamps, generate_att_outputs from rocprof_trace_decoder.code_index import CodeIndex from rocprof_trace_decoder.records import Occupancy, Pc, ShaderData, ShaderDataFlags, TraceRecords +from sqtt_data import FuncMap, ShaderRecord, WaveSpan, find_wave_span_at, preprocess_records +from sqtt_flamegraph import build_stacks, render_flamegraph_svg +from sqtt_perfetto import emit_wave_events def _shaderdata(time: int, value: int, flags: int = 0, simd: int = 2) -> ShaderData: @@ -53,29 +58,51 @@ def finish(self): class MarkerTimestampCorrectionTest(unittest.TestCase): - def test_corrects_headers_and_moves_declared_payloads_with_their_header(self): + def test_payload_headers_are_normalized_without_clock_correction(self): first_value = (0x100 << 20) | (3 << 2) second_value = (0x101 << 20) | (4 << 2) - first = _shaderdata(0x1010, first_value) - second = _shaderdata(0x1080, second_value) - payload = _shaderdata(0x1090, first_value) + first = _shaderdata(0x1000, first_value) + payload = _shaderdata(0x1020, 0xDEADBEEF) + second = _shaderdata(0x1040, second_value) records = TraceRecords( - occupancy=[_occupancy(1, 0, 0), _occupancy(0, 0x2000, 0)], - shaderdata=[first, second, payload], + occupancy=[_occupancy(1, 0)], + shaderdata=[first, payload, second], ) document = { - "sqtt_funcmap": [[0, 3, "P", "first", "", 0], [0, 4, "P", "second", "", 0]], - "sqtt_funcmap_layout": [[0, 12, 4]], - "sqtt_funcmap_payloads": [[0, 4, 1]], + "sqtt_funcmap": [[7, 3, "P", "first", "", 0], [7, 4, "P", "second", "", 0]], + "sqtt_funcmap_layout": [[7, 12, 4]], + "sqtt_funcmap_payloads": [[7, 3, 1]], } _correct_marker_timestamps([(0, records)], document) - self.assertEqual([record.time for record in records.shaderdata], [0x1010, 0x102F, 0x103F]) + self.assertEqual([record.time for record in records.shaderdata], [0x1000, 0x1020, 0x1040]) self.assertIs(records.shaderdata[0], first) - self.assertIs(records.shaderdata[1], second) - self.assertIs(records.shaderdata[2], payload) - self.assertEqual([record.value for record in records.shaderdata], [12, 16, first_value]) + self.assertIs(records.shaderdata[1], payload) + self.assertIs(records.shaderdata[2], second) + self.assertEqual([record.value for record in records.shaderdata], [12, 0xDEADBEEF, 16]) + + def test_payload_records_stay_adjacent_when_another_code_object_is_corrected(self): + header = _shaderdata(0x1000, 3 << 2) + payload = _shaderdata(0x1020, 0xDEADBEEF) + first = _shaderdata(0x1040, (0x100 << 20) | (4 << 2)) + second = _shaderdata(0x1080, (0x107 << 20) | (4 << 2)) + records = TraceRecords( + occupancy=[_occupancy(1, 0, codeobj=7), _occupancy(1, 0x1030, codeobj=8)], + shaderdata=[header, payload, first, second], + ) + + _correct_marker_timestamps( + [(0, records)], + { + "sqtt_funcmap": [[7, 3, "P", "payload", "", 0], [8, 4, "P", "marker", "", 0]], + "sqtt_funcmap_layout": [[8, 12, 4]], + "sqtt_funcmap_payloads": [[7, 3, 1]], + }, + ) + + self.assertEqual([record.time for record in records.shaderdata], [0x1000, 0x1020, 0x101F, 0x1080]) + self.assertEqual([record.value for record in records.shaderdata], [12, 0xDEADBEEF, 16, 16]) def test_imm_record_is_not_a_packed_header(self): record = _shaderdata(0x1010, 3 << 2, flags=1 << ShaderDataFlags.IMM) @@ -185,12 +212,13 @@ def test_constant_clock_samples_are_normalized_without_correction(self): self.assertEqual([late.value, early.value], [12, 16]) def test_correction_does_not_share_the_reference_between_simds(self): - value = (0x100 << 20) | (3 << 2) - slow_simd = _shaderdata(0x1080, value, simd=2) - fast_simd = _shaderdata(0x1010, value, simd=3) + slow_first = _shaderdata(0x1000, (0x100 << 20) | (3 << 2), simd=2) + slow_second = _shaderdata(0x1080, (0x101 << 20) | (3 << 2), simd=2) + fast_first = _shaderdata(0x3000, (0x100 << 20) | (3 << 2), simd=3) + fast_second = _shaderdata(0x3080, (0x101 << 20) | (3 << 2), simd=3) records = TraceRecords( occupancy=[_occupancy(1, 0, simd=2), _occupancy(1, 0, simd=3)], - shaderdata=[slow_simd, fast_simd], + shaderdata=[slow_first, slow_second, fast_first, fast_second], ) _correct_marker_timestamps( @@ -201,9 +229,8 @@ def test_correction_does_not_share_the_reference_between_simds(self): }, ) - self.assertEqual(slow_simd.time, 0x1080) - self.assertEqual(fast_simd.time, 0x1010) - self.assertEqual([record.value for record in records.shaderdata], [12, 12]) + self.assertEqual([record.time for record in records.shaderdata], [0x1000, 0x101F, 0x3000, 0x301F]) + self.assertEqual([record.value for record in records.shaderdata], [12, 12, 12, 12]) def test_correction_cancels_unknown_clock_phase(self): # The fixed inter-clock phase crosses the sampled window. The @@ -225,9 +252,9 @@ def test_correction_cancels_unknown_clock_phase(self): self.assertEqual([record.time for record in records.shaderdata], [0xFE0B, 0x10DFC]) self.assertEqual([record.value for record in records.shaderdata], [marker_id, marker_id]) - def test_late_header_after_retirement_is_normalized_and_corrected(self): + def test_packed_bare_exit_after_retirement_is_normalized_and_corrected(self): before_retirement = _shaderdata(0x1010, (0x100 << 20) | (3 << 2)) - after_retirement = _shaderdata(0x1080, (0x101 << 20) | (3 << 2)) + after_retirement = _shaderdata(0x1080, (0x101 << 20) | 1) records = TraceRecords( occupancy=[_occupancy(1, 0), _occupancy(0, 0x1020)], shaderdata=[before_retirement, after_retirement], @@ -243,7 +270,7 @@ def test_late_header_after_retirement_is_normalized_and_corrected(self): self.assertEqual(before_retirement.time, 0x1010) self.assertEqual(after_retirement.time, 0x102F) - self.assertEqual([record.value for record in records.shaderdata], [12, 12]) + self.assertEqual([record.value for record in records.shaderdata], [12, 1]) def test_later_launch_supersedes_a_retired_wave_slot(self): value = (0x80 << 21) | (4 << 2) @@ -329,5 +356,49 @@ def test_no_decode_markers_cli_flag(self): self.assertFalse(parser.parse_args(["--no-decode-markers", "trace.att"]).decode_markers) +class MarkerPipelineTest(unittest.TestCase): + def test_late_records_recover_after_an_orphan_and_render(self): + funcmap = FuncMap(markers={1: ("outer", "function"), 2: ("inner", "function")}) + spans = [WaveSpan(100, 130, 1), WaveSpan(300, 360, 2)] + wave_spans = {(0, 1, 2, 3): spans} + dispatches = {"1": "kernel"} + records = [ + ShaderRecord(160, 1, 1, 2, 3, 0), # orphan exit after retirement + ShaderRecord(191, 6, 1, 2, 3, 0), # enter outer + ShaderRecord(207, 10, 1, 2, 3, 0), # enter inner + ShaderRecord(223, 1, 1, 2, 3, 0), # exit inner + ShaderRecord(239, 1, 1, 2, 3, 0), # exit outer + ] + + self.assertIs(find_wave_span_at(spans, 99), spans[0]) + self.assertIs(find_wave_span_at(spans, 299), spans[0]) + self.assertIs(find_wave_span_at(spans, 300), spans[1]) + + markers, addresses = preprocess_records(records, funcmap) + self.assertFalse(addresses) + folded = build_stacks(markers, {7: funcmap}, dispatches, wave_spans, {"kernel": 7}) + self.assertEqual( + folded, + { + "code_object_7;kernel;outer": (32, 2), + "code_object_7;kernel;outer;inner": (16, 1), + }, + ) + ET.fromstring(render_flamegraph_svg(folded)) + + events, unmatched = emit_wave_events( + markers, + (0, 1, 2, 3, 0), + dispatches, + {"kernel": 7}, + {7: funcmap}, + funcmap, + wave_spans, + 1.0, + ) + self.assertEqual(unmatched, 1) + self.assertEqual([event["ph"] for event in events], ["B", "B", "E", "E"]) + + if __name__ == "__main__": unittest.main() diff --git a/projects/rocprof-trace-decoder/test/unit/markers_test.cpp b/projects/rocprof-trace-decoder/test/unit/markers_test.cpp index b0ebddb48567..7b06e815f729 100644 --- a/projects/rocprof-trace-decoder/test/unit/markers_test.cpp +++ b/projects/rocprof-trace-decoder/test/unit/markers_test.cpp @@ -746,7 +746,7 @@ TEST(MarkerPass, AddressTraceBlocksKeepDefaultGfx12ClockPackingDisabledAndUseBlo EXPECT_EQ(countIntrinsicCalls(*function, Intrinsic::amdgcn_sched_barrier), 2u); } -TEST(MarkerPass, ForcedGfx12ClockPackingFailsForAddressTraceBlocks) +TEST(MarkerPass, ForcedGfx12ClockPackingFailsForPayloadMarkers) { EXPECT_DEATH( { @@ -774,39 +774,39 @@ TEST(MarkerPass, ForcedGfx12ClockPackingFailsForAddressTraceBlocks) SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); pass.run(*module, analysisManager); }, - "SQTT_TRACE_ADDRESSES emits multi-payload records" + "SQTT payload markers require SQTT_SHADER_CLOCK_BITS=0" ); } -TEST(MarkerPass, OnePayloadNamedDataRemainsEligibleForGfx12ClockPacking) +TEST(MarkerPass, ForcedGfx12ClockPackingFailsForNamedData) { - LLVMContext ctx; - auto module = makeModule(ctx); - Type* i32 = Type::getInt32Ty(ctx); - Type* voidTy = Type::getVoidTy(ctx); - Function* data = Function::Create( - FunctionType::get(voidTy, {PointerType::get(ctx, 0), i32}, false), - GlobalValue::ExternalLinkage, - "__sqtt_named_marker_data", - module.get() - ); - GlobalVariable* name = makeMarkerString(*module, "one_payload"); - Function* function = makeVoidFunction(*module, "gfx12_named_data", "gfx1200"); - IRBuilder<> builder(function->getEntryBlock().getTerminator()); - builder.CreateCall(data, {name, ConstantInt::get(i32, 17)}); - - SQTTConfig config; - useFullScopeMasks(config); - config.ShaderClockBits = 12; + EXPECT_DEATH( + { + LLVMContext ctx; + auto module = makeModule(ctx); + Type* i32 = Type::getInt32Ty(ctx); + Type* voidTy = Type::getVoidTy(ctx); + Function* data = Function::Create( + FunctionType::get(voidTy, {PointerType::get(ctx, 0), i32}, false), + GlobalValue::ExternalLinkage, + "__sqtt_named_marker_data", + module.get() + ); + GlobalVariable* name = makeMarkerString(*module, "one_payload"); + Function* function = makeVoidFunction(*module, "gfx12_named_data", "gfx1200"); + IRBuilder<> builder(function->getEntryBlock().getTerminator()); + builder.CreateCall(data, {name, ConstantInt::get(i32, 17)}); - ModuleAnalysisManager analysisManager; - SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); - pass.run(*module, analysisManager); + SQTTConfig config; + useFullScopeMasks(config); + config.ShaderClockBits = 12; - const std::string funcMap = getFuncMap(*module); - expectContains(funcMap, "M:shader_clock_bits=12;shader_clock_shift=4"); - expectPointEntryWithPayload(funcMap, "one_payload", 1); - EXPECT_EQ(countIntrinsicCalls(*function, Intrinsic::amdgcn_s_getreg), 1u); + ModuleAnalysisManager analysisManager; + SQTTInstrumentPass pass(config, SQTTInstrumentPass::Mode::Late); + pass.run(*module, analysisManager); + }, + "SQTT payload markers require SQTT_SHADER_CLOCK_BITS=0" + ); } TEST(MarkerPass, Gfx9BufferAndPermuteAddressTracesUseInlineAsmExecProtocol)