Repository navigation
feat(rocprofiler-sdk): Add SPM aqlprofile support for gfx12/gfx1250 - #9646
Conversation
✅ All Policy Checks Passed
📖 Need help? See the Policy FAQ for details on every check and how to fix failures. |
|
🚫 Please fix the failed policies before requesting reviews. The following policy checks failed:
The |
|
This pull request has been inactive for 25 days and will be marked as stale. If you would like to keep this PR open, please:
This PR will be automatically closed in 5 days if no further activity occurs. |
ApoKalipse-V
left a comment
There was a problem hiding this comment.
A few notes:
- This touches a lot of code that gfx9 uses, so I think a MI300 needs to be part of the validation before merging.
- I don't see explicit handling of SP and accum(level) counters. Is this covered by the logic? If not, maybe we need to return an error when we see them for now as to not silently give users wrong results.
- There are a lot of possible states/combinations of programming when mixing 32 and 16 bit counters that doesn't seem possible to cover in integration testing. Maybe this needs unit tests for asserting the registers are set to the right place in these combinations.
- I'm just trusting the register programming is correct :D I did not verify that.
- This is missing formatting, but I'll argue we SHOULDN'T run formatting during review, otherwise the diff will be larger. Just a note it needs formatting eventually.
- This review doesn't cover all of spm_builder.h as I'm still looking at the logic there.
1b47982 to
1a6a70a
Compare
sauverma93
left a comment
There was a problem hiding this comment.
Some points from AI review-
Paths below are relative to projects/rocprofiler-sdk/source/lib/aqlprofile/ unless noted otherwise.
Supported-event query
core/spm_v2.cpp→aqlprofile_spm_is_event_supported(): theGetGpuId() < MI200_GPU_ID || GetGpuId() > MI350_GPU_IDgate is unchanged, so gfx12/gfx1250 still return false from this query even though packet creation now supports them.core/spm_v2.cpp→aqlprofile_spm_is_event_supported()vsGetCounter()/resolve_spm_depth(): the depth resolver forces gfx12 SQG to 32-bit, but the query returns true forAQLPROFILE_SPM_DEPTH_32_BITSonly whenblock_name == HSA_VEN_AMD_AQLPROFILE_BLOCK_NAME_SQ.AQLPROFILE_BLOCK_NAME_SQGis a distinct enum value, so a caller that queries first gets false for the exact configuration the runtime will produce.core/spm_v2.cpp→resolve_spm_depth():is_gfx9_squsesGetGpuId() <= GFX10_GPU_IDandis_gfx12_sqgusesGetGpuId() > GFX10_GPU_ID. Per thegpu_id_tordering incore/pm4_factory.h, gfx10 falls into the first and gfx11 into the second, so the "gfx12 SQG" rule also applies to gfx11 SQG. If that is intended, the naming and comment could reflect it; if not, the guards need narrowing.core/spm_v2.cpp→AllocateCounter():is_gfx12_sqccomparesblock_des.id(a block table id) againstHSA_VEN_AMD_AQLPROFILE_BLOCK_NAME_SQ(an HSA enum). On gfx12 these coincide becauseSqcCounterBlockInfoingfxip/gfx12/gfx12_block_table.huses__BLOCK_ID_HSA(SQ); worth confirming the two domains are interchangeable on every target that reaches this path.
Block metadata
def/gpu_block_info.h(GpuBlockInfo::spm_counter_count) +core/spm_v2.cpp→AllocateCounter(): the bound check moves fromcounter_countto the newspm_counter_count.gfxip/gfx9/gfx9_block_table.handgfxip/gfx12/gfx12_block_table.hare updated; the gfx10 and gfx11 tables are not, so the field is 0 there. The query API already excluded those GPUs, but anycreate_packetspath that bypasses the query would now throw"Event is out of block counter registers number limit".def/gpu_block_info.h(GpuBlockInfo,counter_des_t): both gain trailing fields that every block table initializes positionally, so a table that is missed gets a silent zero rather than a build error. Designated initializers, an explicit default, or astatic_asserton table shape would make future additions safer.
WGP topology
core/spm_v2.cpp→CountersVec()vspm4/spm_builder.h→GpuSpmBuilderconstructor:CountersVec()sizes the SA/WGP expansion frompm4_factory->GetNumWGPs()(which resolves toGpuPmcBuilder::wgp_per_sa_inpm4/pmc_builder.h), while the SPM builder recomputes the samecu_num / 2formula into its ownwgp_per_sa_and writes that intospm_buffer_desc->num_wgp. They agree today but can drift independently, and a mismatch corrupts decode rather than failing. One source of truth would avoid that.core/spm_decode.cpp→aqlprofile_spm_decode_stream_v1(): ifdesc->num_wgpis 0,wgp_countis 0 andsize_t(sa_count) * size_t(wgp_count) - 1underflows, advancingi_expby ~2^64. Clampingwgp_per_sa_to at least 1 in the builder and guarding here would contain it.
End() and delay programming
pm4/spm_builder.h→GpuSpmBuilder::End()+gfxip/gfx12/gfx12_primitives.h→rlc_spm_perfmon_cntl_value():End()unconditionally writesrlc_spm_perfmon_cntl_value(0, 0)on every generation. The gfx12 implementation computessampling_rate - 1, so withsampling_rate == 0thePERFMON_SAMPLE_INTERVALfield wraps to its maximum rather than zero. If the intent is a single flush pulse, the value probably needs to be explicit.pm4/spm_builder.h→GpuSpmBuilder::Begin(), guarded byPrimitives::SPM_DELAY_PROGRAMMING_REQUIRED(false ingfxip/gfx10/gfx10_primitives.handgfxip/gfx11/gfx11_primitives.h): this skips delay programming that previously ran on those targets. If those writes were inert because the blocks useBLOCK_DELAY_NONE, that is worth stating in the description; otherwise it is a behavior change.pm4/spm_builder.h→GpuSpmBuilder::Begin(): theRLC_SPM_PERFMON_SAMPLE_DELAY_MAXwrite is no longer preceded by aGRBM_GFX_INDEXbroadcast write, so it now executes under whatever GRBM index is current at that point.
Decode path and descriptor contract
core/spm_decode.cpp→encode_spm_shader_engine(), called fromaqlprofile_spm_decode_stream_v1(): this packswgp << 24 | sa << 16 | seinto theshader_engineargument ofaqlprofile_spm_decode_callback_v1_t(declared inaql_profile_v2.h), which previously carried-1for global or a plain SE index. The in-tree consumer atsource/lib/rocprofiler-sdk/spm/decode.cpp→decode_cb()still treats< 0as global and anything else as an SE id, so packed values will be misread. This needs either a documented v1 encoding with consumers updated, or a v2 callback.pm4/spm_builder.h→GpuSpmBuilder::Begin()(spm_buffer_desc->num_events = original_event_count) vscore/spm_decode.cpp: the descriptor now reports the original event count whilecounter_mapholds the full expanded vector, soSpmBufferDesc(incore/spm_common.hpp) no longer describes the length of its own map and the decoder walks pastnum_eventsusing out-of-band knowledge. Noteaqlprofile_spm_decode_query()returns this same value forAQLPROFILE_SPM_DECODE_QUERY_EVENT_COUNT. Carrying both counts explicitly would make the descriptor self-describing.core/spm_decode.cpp→aqlprofile_spm_decode_stream_v1(): the 32-bit high word is read asdatain[index + 16]with a literal stride and no bounds check. The outerdatain + seg_elem > data_endguard does not coverindex + 16, and the global odd line is only reserved whenhas_global_32bit_spmis set inpm4/spm_builder.h.core/spm_decode.cpp→decode_bufvalue(): all-ones maps touint64_t(-1), which is indistinguishable from a legitimate saturated value — particularly in 16-bit clamp mode, where0xFFFFis a reachable counter value. A separate overflow indicator would avoid the ambiguity for consumers.core/spm_common.hpp(SPM_COUNTER_MAP_INDEX_MASK): the usable index narrows from 15 bits to 12. Nothing inpm4/spm_builder.happears to check thateven_idx/odd_idxstay under 4096 for large segment sizes.
gfx12 primitives
gfxip/gfx12/gfx12_primitives.h:mux_info_tchanges bitfield widths and ordering (counter6→5,instance5→6, fields reordered) andSQ_BLOCK_IDmoves from__BLOCK_ID_HSA(SQ)to__BLOCK_ID(SQG). Both feed mux RAM programming and the block dispatch inpm4/spm_builder.h→Begin().
Build hooks
CMakeLists.txt:DEBUG_TRACEis driven byENV{CMAKE_DEBUG_TRACE}at configure time. Environment variables are not cached and do not trigger reconfiguration, so a normal cache option would be more predictable.core/spm_v2.cpp→_internal_aqlprofile_spm_create_packets()+core/memorymanager.hpp→SPMMemoryManager::CreateSampleCountBuf(): underDEBUG_TRACEthis adds a device allocation that can returnHSA_STATUS_ERROR_OUT_OF_RESOURCES, so the debug build can fail where release succeeds.pm4/spm_builder.h: now includescore/logger.hppforWARN_LOGGING. Worth checking that standalone consumers of the pm4 headers still build and link cleanly, includingpm4/tests/trace_config_test.cpp.
Scope
- The change combines gfx12 SPM support, a cross-generation primitive signature refactor (
sq_spm_select_value(),rlc_spm_perfmon_cntl_value(),rlc_spm_perfmon_segment_size_value(),spm_timestamp_muxsel()across all fourgfxip/*/*_primitives.h), theEnd()stop-path fix, and the DEBUG_TRACE tooling in one commit. Splitting these would make the non-gfx12 risk easier to reason about and bisect.
vlaindic
left a comment
There was a problem hiding this comment.
Not an expert in the field, so adding couple comments and mostly agreeing with Gio's comments. I'm also adding the full AI review in case it will help. Thanks!
Review — PR #9646
[rocprofiler-sdk][aqlprofile][gfx12][spm] Add gfx12/gfx1250 SPM core support
JIRA: AIPROFSDK-109 · 24 files, ~3019 diff lines, all under projects/rocprofiler-sdk/source/lib/aqlprofile/
Verdict: Request changes.
This is a substantial and largely well-constructed change — the SE/SA/WGP expansion model is
coherent, several long-standing hardcoded assumptions are correctly removed, and the hardware
comments in gfx12_block_table.h and spm_builder.h are genuinely good. The blockers below are
mostly about the edges of the new model: uninitialized table fields, an unguarded arithmetic
underflow, and an encoder/decoder contract that is computed independently in two places.
Note: the PR is currently labeled "Not ready to Review". If this is still WIP, treat the
CRITICAL section as the priority list and ignore the rest for now.
CRITICAL
1. spm_counter_count is uninitialized for gfx10, gfx11, and 8 gfx9 blocks — SPM will throw on the first counter
GpuBlockInfo::spm_counter_count is the last struct member and has no default initializer
(def/gpu_block_info.h:137). Every block table uses positional aggregate initialization, so any
table entry that doesn't explicitly supply the field gets 0.
Its only consumer is the allocation guard in core/spm_v2.cpp:286:
if(reg_index >= block_info->spm_counter_count)
throw std::runtime_error("Event is out of block counter registers number limit");reg_index starts at 0, so 0 >= 0 is true and this throws on the first SPM counter for any
block with an unset count.
Scope:
- gfx10 — 16
GpuBlockInfoentries, zerospm::references.gfxip/gfx10/gfx10_block_info.h
has nonamespace spmat all. - gfx11 — 19
GpuBlockInfoentries, zerospm::references. Same. - gfx9 — 29
CounterBlockInfoaggregates, 21 updated → 8 missed. Confirmed by inspection:
SqGsCounterBlockInfo(carriesCounterBlockSqAttr),WdCounterBlockInfo,McVmL2CounterBlockInfo
all still end atBLOCK_DELAY_NONE};.
This is not dormant code on gfx10/gfx11 — both instantiate GpuSpmBuilder
(core/gfx10_factory.cpp:80, core/gfx11_factory.cpp:53), both define RLC_SPM_PERFMON_CNTL__ADDR
(gfx10_primitives.h:125, gfx11_primitives.h:130), and both have blocks carrying
CounterBlockSpmGlobalAttr (gfx10_block_table.h:436,447,458, gfx11_block_table.h:494,505,516).
SPM is live on those parts today and this PR breaks it.
Fix:
- Give the field a default so omission is safe rather than silently fatal:
uint32_t spm_counter_count = 0;
- Populate
spm_counter_countfor the gfx10 and gfx11 tables, and for the 8 remaining gfx9 blocks. - Replace the bare
>=guard with an explicit unsupported-block diagnostic so a future missed
entry produces a clear message instead of "out of block counter registers number limit":if(block_info->spm_counter_count == 0) throw std::runtime_error(std::string("SPM not supported for block ") + block_info->name); if(reg_index >= block_info->spm_counter_count) throw std::runtime_error("Event is out of block counter registers number limit");
2. Unsigned underflow programs the maximum sample interval on every SPM stop
pm4/spm_builder.h:781-783 (End()):
// Force one last RLC sample pulse before SPM stop so the current partial interval is
// materialized into the stream instead of being dropped when CP_PERFMON_CNTL stops/reset SPM.
builder.BuildWriteUConfigRegPacket(cmd_buffer,
Primitives::RLC_SPM_PERFMON_CNTL__ADDR,
Primitives::rlc_spm_perfmon_cntl_value(0, 0));gfx12_primitives.h:545 now computes sampling_rate - 1:
SET_REG_FIELD_BITS(RLC_SPM_PERFMON_CNTL, PERFMON_SAMPLE_INTERVAL, sampling_rate - 1)With sampling_rate == 0 this underflows to 0xFFFFFFFF and the field is programmed to all-ones —
the largest possible interval, which is the exact opposite of the comment's stated intent of forcing
a pulse. This fires on every SPM stop, not an edge case.
Fix — two parts:
- Guard the arithmetic so no caller can underflow it:
const uint32_t encoded_interval = (sampling_rate > 0) ? (sampling_rate - 1) : 0u;
- Confirm with hardware whether
PERFMON_SAMPLE_INTERVAL = 0actually forces a sample pulse. If it
does not, theEnd()write doesn't do what the comment claims and should be removed rather than
fixed — the comment is currently the only evidence the mechanism works.
3. num_sa is hardcoded to 2 while the encoder expands using the real SA count
Three places disagree about the same value:
| Location | Value used |
|---|---|
core/spm_v2.cpp:480 |
trace_config.sa_number = pm4_factory->GetGpuId() >= GFX10_GPU_ID ? 2 : 0; |
core/spm_v2.cpp:303 (CountersVec()) |
pm4_factory->GetShaderArraysNumber() |
core/spm_decode.cpp:100 |
sa_count = is_sa ? desc->num_sa : 1; |
spm_builder.h:709 copies sa_number into SpmBufferDesc::num_sa, which the decoder then uses as
its stride. The encoder expands counters using the real shader_arrays_per_se; the decoder
walks them assuming 2. On any gfx10+ part where shader_arrays_per_se != 2 the two disagree and
every counter is attributed to the wrong SA/WGP — silently, with no error and no obviously wrong
values.
Fix: drive trace_config.sa_number from pm4_factory->GetShaderArraysNumber(), the same source
CountersVec() uses. If gfx9 genuinely needs 0 as a "not applicable" sentinel, keep that branch
but derive the gfx10+ branch from the agent info.
4. wgp_per_sa is computed independently in two places
pm4/spm_builder.h:92-105 (constructor):
wgp_per_sa_ = (agent_info->cu_num / 2 + sa_number * se_number - 1) / (se_number * sa_number);
wgp_per_sa_ /= xcc_number;pm4/pmc_builder.h:372-374 (GetNumWGPs()) computes the same quantity separately, and
CountersVec() in spm_v2.cpp uses pm4_factory->GetNumWGPs() — i.e. the other one.
So the encode side (spm_builder.h) and the expansion side (spm_v2.cpp) are driven by two
independent computations of a value that must agree exactly for decode to work. Any future edit to
one is a silent correctness bug.
Two secondary problems in the spm_builder.h copy:
- No zero-guard on
xcc_numberbeforewgp_per_sa_ /= xcc_number. cu_num / 2 + sa_number * se_number - 1underflows ifcu_num == 0andsa_number * se_number == 0
— theif(se_number && sa_number)guard covers the divisor but the numerator is evaluated inside it.
Fix: compute this once and thread it through TraceConfig alongside the existing
se_number / sa_number / xcc_number fields, e.g. add wgp_number. Delete the constructor copy
and have GpuSpmBuilder read config->wgp_number.
5. counter_map writes are unbounded — user-controllable overflow into the SPM data region
SPM_DESC_SIZE is a fixed 4 KB. SpmBufferDesc is followed by a raw uint16_t* counter_map with
roughly 2030 usable slots, and the SPM data region begins immediately after at
output_buffer_ptr + SPM_DESC_SIZE (spm_v2.cpp:561).
Before this PR the number of counter_map writes equalled the user's event count. After it, the
expansion in CountersVec() makes it num_events × sa_count × wgp_count — on gfx1250 that is
2 SA × ~8 WGP = 16×. There is no bound check anywhere on the write path. Roughly 127
user-specified WGP events is enough to run past the descriptor and corrupt the beginning of the SPM
data stream.
profile.event_count is user-controllable, so this is reachable from the public API without any
privileged input.
Fix: define the capacity explicitly and reject over-subscription with a real error rather than
overflowing:
static constexpr size_t kMaxCounterMapEntries =
(SPM_DESC_SIZE - sizeof(SpmBufferDesc)) / sizeof(uint16_t);
if(counters_vec.size() > kMaxCounterMapEntries)
return HSA_STATUS_ERROR_OUT_OF_RESOURCES;Place the check after expansion in CountersVec() so it accounts for the SA/WGP multiplier.
6. Three different bit layouts for the same SE/SA/WGP index, plus a 6-bit overflow
The encoded block index has three inconsistent representations:
| Location | Layout |
|---|---|
gfx12_factory.cpp:64-73, gfx1250_factory.cpp:42-51 |
(sa_index << 5) | (wgp_index << 1) | inst_index |
gfx12_primitives.h:491-496 (decode_spm_instance_index) |
& 0x1 for WGP blocks, & 0x1F for SA blocks |
gfx12_primitives.h:294 (grbm_inst_se_sh_wgp_index_value) |
(wgp_side << 6) | (wgp_index << 2) | instance_index |
Concretely:
- The SA-block mask
0x1Fcovers bits 0-4, which includes the entirewgp_indexfield (bits
1-4). It does not isolate the instance index. - The encoder puts WGP at bit 1; the GRBM helper puts it at bit 2. These describe the same logical
quantity with different shifts. - The encoded value is stored into
mux_info_t.gfx.instance, which this PR narrows to 6 bits
(gfx12_primitives.h:172). Withsa_index = 1,sa << 5alone is0x20;wgp_index >= 16
pushes the value to0x40+and truncates into the adjacentblockfield, corrupting the muxsel
word.
This also makes the validation in core/pm4_factory.h:199,215 check the wrong quantity, since it
now bounds-checks DecodeSpmInstanceIndex(info, event->block_index) against instance_count.
Fix: define the layout once, next to mux_info_t, and derive everything from it:
static constexpr uint32_t SPM_INST_BITS = 1;
static constexpr uint32_t SPM_WGP_BITS = 4;
static constexpr uint32_t SPM_WGP_SHIFT = SPM_INST_BITS;
static constexpr uint32_t SPM_SA_SHIFT = SPM_INST_BITS + SPM_WGP_BITS;
static_assert(SPM_SA_SHIFT + 1 <= 6, "encoded index must fit mux_info_t.gfx.instance");Then express the encoder, both decode masks, and the GRBM helper in terms of those constants, and
add a runtime check that wgp_index < (1u << SPM_WGP_BITS).
IMPORTANT
7. Dead code: #if PER_WGP_INST_SELECT
pm4/spm_builder.h:629-647 is guarded by PER_WGP_INST_SELECT, which is never defined anywhere in
the repository — a grep across projects/rocprofiler-sdk/ returns exactly one hit, the #if
itself. The block never compiles, so it is never type-checked.
It also uses (wgp << 2), contradicting EncodeSpmBlockIndex's (wgp << 1) — so if it were ever
enabled it would disagree with finding #6's encoder. And #if on an undefined macro warns under
-Wundef.
Fix: delete the block. If it's a genuine alternative that may be needed, move it to a design note
or a tracked ticket rather than leaving uncompiled code in the header.
8. DEBUG_TRACE plumbing has five separate problems
a. Env-var-driven build configuration (CMakeLists.txt):
if(DEFINED ENV{CMAKE_DEBUG_TRACE})
if("$ENV{CMAKE_DEBUG_TRACE}" STREQUAL "1")
target_compile_definitions(rocprofiler-sdk-aqlprofile PRIVATE DEBUG_TRACE=1)
endif()
...This is invisible to cmake -D, doesn't appear in CMakeCache.txt, doesn't show up in
cmake -LAH, and makes builds non-reproducible from the command line alone. It also uses an
un-namespaced CMAKE_-prefixed name, which is reserved-looking.
b. Values other than 1 and 2 are silently ignored — CMAKE_DEBUG_TRACE=3 produces a normal
build with no diagnostic.
c. Production headers carry debug-only members. pm4/trace_config.h:63 and
core/memorymanager.hpp:391-402 (CreateSampleCountBuf / GetSampleCountBuf /
sample_count_buf_) exist solely to serve the DEBUG_TRACE path but are unconditionally compiled
into every build.
d. core/spm_v2.cpp:462-471 uses catch(...), discarding the reason the operation failed.
e. pm4/spm_builder.h:715-737 writes diagnostics with std::cout and pulls
<iomanip>, <iostream>, and <mutex> into a header included widely across the library.
Fix:
option(ROCPROFILER_SDK_AQLPROFILE_SPM_DEBUG_TRACE
"Enable verbose SPM muxsel tracing (0=off, 1=summary, 2=full)" 0)
target_compile_definitions(rocprofiler-sdk-aqlprofile
PRIVATE ROCPROFILER_SDK_AQLPROFILE_SPM_DEBUG_TRACE=${ROCPROFILER_SDK_AQLPROFILE_SPM_DEBUG_TRACE})Route the output through the existing logger rather than std::cout, wrap the debug-only members in
#if, and replace catch(...) with catch(const std::exception& e) plus a log of e.what().
9. EncodeSpmBlockIndex / DecodeSpmInstanceIndex are byte-identical in two factories
core/gfx12_factory.cpp:64-73 and core/gfx1250_factory.cpp:42-51 contain the same two overrides
character-for-character. Both delegate to gfx12_cntx_prim::decode_spm_instance_index, so the shared
logic already lives in a shared place — only the override boilerplate is duplicated.
Fix: hoist both overrides into the common gfx12 factory base and let Mi450Factory inherit them.
If Mi450Factory doesn't derive from Gfx12Factory, add a small shared mixin rather than copying.
10. Unrelated PMC and SQTT behavior changes are bundled into an SPM PR
Four changes in this diff affect code paths outside SPM:
gfx12_primitives.h:80—SQ_PERFCOUNTER_MASK_ADDRchanges fromRegister(0xD9E1)toRegister{}.pm4/sqtt_builder.h:227-231— the write ofconfig->perfMASKis now conditional on that register
being non-null. Combined with the line above, gfx12 thread trace silently stops programming
perfMASK.pm4/pmc_builder.h:661— the guard changes fromPrimitives::GFXIP_LEVEL == 9to a non-null
register check, altering which generations writeSQ_PERFCOUNTER_MASK_ADDRon the PMC path.core/aql_profile.cpp—trace_config.spm_sq_32bit_mode = true;removed from the v1 start path.
None of these are SPM. The SQTT one is a user-visible behavior change to thread trace with no test
and no mention in the description. A future PMC or ATT bisect will land on a commit titled "gfx12
SPM support".
Separately, 0xD9E1 is a bare offset with no HWIP/base-index, unlike every other register in these
files — worth a comment explaining why, if the value is retained anywhere.
Fix: split the PMC and SQTT changes into their own PR with their own justification and test
coverage. If they must ship together, state explicitly in the description that gfx12 thread trace no
longer programs perfMASK and why that is correct.
11. Runtime if where if constexpr is used elsewhere in the same file
pm4/spm_builder.h:510-513 and 552-555 use if(Primitives::GFXIP_LEVEL >= 11) on a
compile-time constant, while lines 96, 415, and 494 in the same file correctly use if constexpr.
Fix: make these if constexpr for consistency. Better still, key off the capability rather than
the level — if constexpr(!(Primitives::SQG_PERFCOUNTER_CTRL2_ADDR == Register())) expresses the
actual dependency and won't need editing when gfx13 lands.
12. aqlprofile_spm_is_event_supported() skips the bounds check on the 32-bit path
core/spm_v2.cpp:788-793:
const bool is_sq_path = event.block_name == HSA_VEN_AMD_AQLPROFILE_BLOCK_NAME_SQ;
if(depth == AQLPROFILE_SPM_DEPTH_32_BITS) return is_sq_path;
if(depth == AQLPROFILE_SPM_DEPTH_16_BITS)
return !is_sq_path && event.block_name < blocks.size() && blocks.at(event.block_name);
return false;Two issues:
- The 32-bit branch returns without the
event.block_name < blocks.size()check the 16-bit branch
performs. - It disagrees with
resolve_spm_depth()(spm_v2.cpp:225-255), which permits explicit 32-bit depth
on blocks other than SQ. Sois_event_supported()reportsfalsefor configurations the
allocator will happily accept. AQLPROFILE_SPM_DEPTH_64_BITSfalls through toreturn falsewith no diagnostic.
Fix: rewrite as a switch over the depth enum with no default: label, so adding a depth
produces a -Wswitch warning rather than a silent false. Apply the same block-name bounds check on
every branch, and make it consult resolve_spm_depth() so the two agree by construction.
13. get_original_event_count() conflates "unset" with "zero"
pm4/cmd_config.h:53-56:
void set_original_event_count(size_t count) { original_event_count_ = count; }
size_t get_original_event_count() const
{
return original_event_count_ ? original_event_count_ : this->size();
}The this->size() fallback is exactly the pre-PR behavior this field exists to correct. If any code
path constructs the config without calling the setter, the bug returns silently with no way to
detect it. original_event_count_ is also not initialized in the member list.
Fix: use std::optional<size_t> and let the accessor assert or throw when unset, so a missed
setter call fails loudly at the point of the mistake rather than producing subtly wrong counts.
14. No workflow comment block for the SPM subsystem
SPM is a subsystem with a clear lifecycle — Begin() → sampling → End(), with SPMMemoryManager
holding resources across the whole span, plus descriptor setup, muxsel RAM programming, and buffer
teardown. This PR substantially rewrites all three phases (spm_builder.h alone is +414/-151), and
there is no comment anywhere describing the sequence.
For a reader arriving at spm_builder.h cold, the relationship between Begin(), the muxsel
programming loop, the counter-map writes, and End() has to be reconstructed from the code.
Fix: add a block at the top of pm4/spm_builder.h:
// SPM workflow:
// 1. Initialization — TraceConfig is populated (se/sa/wgp counts, depth policy); the SPM
// descriptor is allocated at output_buffer_ptr; muxsel RAM is programmed from the
// expanded counter vector; RLC_SPM_* segment sizes and sample interval are set.
// 2. Usage — RLC samples autonomously into the ring at PERFMON_SAMPLE_INTERVAL;
// no host interaction is expected between Begin() and End(). Not reentrant; one active
// SPM session per queue.
// 3. Destruction — End() issues a final sample pulse, stops CP_PERFMON_CNTL, and the
// buffer is handed to the decoder. counter_map in the descriptor must outlive decode.
Fill in the reentrancy and ownership lines with whatever is actually true — those are the parts a
reviewer cannot infer.
15. Description doesn't call out the workarounds
Three non-obvious things in this PR are documented in the code but invisible to someone reading
the description first:
CounterRegInfo::select1_addris repurposed for SQC/SP to hold the odd
SQ_PERFCOUNTERn_SELECTrather than its nominal meaning (gfx12_block_table.h:217-222).- The gfx1250 SP block reuses SQC's SPM identity —
SPM_SE_BLOCK_NAME_SQCand
spm::SqcCounterBlockNumCounters— with no explanation of why SP shares SQC's counter block. - GFX12 requires GRBM broadcast rather than explicit SA/WGP topology addressing in the muxsel
path (spm_builder.h:616-623).
The first and third have good in-code comments. The second has none. All three are the kind of thing
a reviewer should be told about up front rather than discovering mid-diff.
Fix: add a "Workarounds and non-obvious decisions" paragraph to the PR description naming all
three, and add an inline comment above the SP block entry explaining the SQC identity reuse.
SUGGESTIONS
16. Missing docstrings and inline comments on the new logic
Functions without docstrings:
EncodeSpmBlockIndex/DecodeSpmInstanceIndex(both factories) — these define the encode/decode
contract that findings #3, #4, and #6 all concern. This is the single most valuable docstring in
the PR: state the bit layout, which side produces it, which side consumes it, and the valid range
of each field.CreateSampleCountBuf/GetSampleCountBuf(memorymanager.hpp:391-402) — ownership and
lifetime of the returned pointer are not obvious.counter_map_valuelambda (spm_builder.h) — the flag encoding it produces.set_original_event_count/get_original_event_count(cmd_config.h) — why the distinction from
size()exists at all.
Changes without inline comments:
mux_info_tfield reorder and resize (gfx12_primitives.h:172) —counter6→5 bits,instance
5→6 bits, and the field order changes. This is a wire-format change to the muxsel word with no
comment explaining what hardware requires it.SPM_COUNTER_MAP_INDEX_MASKnarrowing from0x7FFFto0x0FFF(spm_common.hpp:22) — the index
space shrinks from 15 to 12 bits to make room for the new flags. Worth stating the resulting
maximum index.- TCP SPM clamp switched from
PERFMON_SPM_MODE_32BIT_CLAMPtoPERFMON_SPM_MODE_16BIT_CLAMPin
both TCP select functions — a counter-semantics change with no rationale. SQ_BLOCK_IDchanged from__BLOCK_ID_HSA(SQ)to__BLOCK_ID(SQG).spm_timestamp_muxsel()changed from a constant0xF0F0to a computed per-index value.counter_des.index / 2andindex = counter_des.index >> 2/select = (counter_des.index % 4) >> 1
in the select-register addressing — the div/shift relationship to the register layout is not
self-evident.- The asymmetric
counter_map[index]assignment in the odd-lane loops (both global and SE): the
32-bit branch writesmux_rambut omitscounter_map[index], while the 16-bit branch assigns it.
If intentional, say so; it reads as an omission.
17. Test coverage is effectively zero
The only test change in 3019 lines is two swapped assertions in
pm4/tests/trace_config_test.cpp — asserting a struct default, not any new behavior. No exemption
applies: this is new hardware functionality, not a build, docs, test-only, or rename change.
pm4/tests/pmc_builder_harvest_gfx12_tests.cpp already constructs synthetic AgentInfo values and
tests GetNumWGPs() — it is the natural place to extend rather than starting a new file.
Four concrete tests, each covering one of the CRITICAL findings:
- Encode/decode round-trip over the full
(sa, wgp, inst)domain for gfx12 and gfx1250 — assert
DecodeSpmInstanceIndex(EncodeSpmBlockIndex(sa, wgp, inst)) == instand that no encoded value
exceeds 6 bits. Catches #6. - Expansion count and contiguity — for a known
AgentInfo, assertCountersVec()produces
exactlynum_events × sa × wgpentries with contiguous indices, and that thesa/wgpused
matchTraceConfig::sa_number. Catches #3 and #4. rlc_spm_perfmon_cntl_value(0, 0)— assert thePERFMON_SAMPLE_INTERVALfield is not
all-ones. Catches #2.- Counter-map capacity — assert an event count that would exceed
kMaxCounterMapEntriesafter
expansion returns an error rather than writing past the descriptor. Catches #5.
A fifth, cheap and high-value: iterate every GpuBlockInfo in every generation's table and assert
that any block with an SPM attribute has spm_counter_count > 0. That single test catches #1 across
all four generations and prevents the next table from regressing.
18. decode_cb's se argument silently becomes a packed triple
core/spm_decode.cpp:49-53:
static inline int
encode_spm_shader_engine(uint32_t se_index, uint32_t sa_index = 0, uint32_t wgp_index = 0)
{
return int((wgp_index << 24) | (sa_index << 16) | se_index);
}This value is passed to aqlprofile_spm_decode_callback_v1_t as the se_index parameter. Existing
callers that treat it as a shader-engine index will now receive a packed
(wgp << 24) | (sa << 16) | se value and misbehave — with no compile error, since the type is
unchanged.
Fix: either document the new encoding in the public header alongside the callback typedef and
bump the interface version, or add a v2 callback and keep v1 emitting a bare SE index. Silently
changing the meaning of an existing public parameter is the one option that shouldn't ship.
19. Minor items
pm4/spm_builder.h:410-413— missing braces on a multi-lineifbody.spm_v2.cpp:333andspm_builder.h:340—static aqlprofile_spm_depth_t depth[]should be
static constexpr std::array<aqlprofile_spm_depth_t, N>.spm_decode.cpp:90,114,124—i_exp/event_exp_start/event_i_expare hard to keep straight
in a nested loop;expanded_idx/event_base/event_expanded_idxwould read better. Same for
mxinf/mxinf_filler→mux_info/mux_info_filler.spm_builder.h:553,597,610—int je, jo;compared against.size()triggers sign-compare
warnings; usesize_t.spm_decode.cpp:77,81—return uint64_t(-1);should be a namedconstexpr
(kInvalidCounterValue) and documented, since it reaches user callbacks as a sentinel.spm_v2.cpp:267-294—is_gfx12_sqctestsHSA_VEN_AMD_AQLPROFILE_BLOCK_NAME_SQ, not SQC. Rename
to match what it checks.spm_v2.cpp:446-454— the REFCLK gfx12+ restriction returns a bare
HSA_STATUS_ERROR_INVALID_ARGUMENTwith no log line. Add aWARN_LOGGINGnaming the unsupported
combination, and document the restriction wherespm_sample_interval_typeis declared.- Conflicting SPM select programming is warned about but not rejected (
spm_builder.h:697-704) —
consider whether a conflicting select should be an error rather than a warning followed by
whichever write lands last.
What's good here
Worth saying explicitly, since the list above is long:
SPM_COUNTER_MAP_*_FLAGnamed constants (spm_common.hpp:18-22) replace bare literals in the
counter-map encoding — a clear readability win.(4 * se_nlines)→(se_number * se_nlines)inrlc_spm_perfmon_segment_size_valueremoves a
hardcoded 4-SE assumption that would have been a real bug on non-4-SE parts.- The
decode_bufvaluelambda (spm_decode.cpp:74-83) de-duplicates four copies of the
16/32-bit value reconstruction and centralizes the sentinel handling. - Splitting
GetCounterfromAllocateCountercleanly separates depth policy from register
assignment. if constexpr(SPM_DELAY_PROGRAMMING_REQUIRED)is the right tool for a compile-time capability
check (see #11 — just apply it consistently).WARN_LOGGINGon conflicting SPM select is a good instinct even if it should arguably be an
error.- Two genuinely excellent hardware comments: the SQC
select1_addrrepurposing explanation
(gfx12_block_table.h:217-222) and the GFX12 SA/WGP GRBM broadcast constraint block
(spm_builder.h:616-623). Both explain why, name the constraint, and would have taken a reviewer
a long time to derive. More of these, please — particularly on themux_info_treorder and the TCP
clamp change.
Hygiene summary
| Check | Result |
|---|---|
| A — JIRA ID | Pass — AIPROFSDK-109, correct format |
| A — Description completeness | Fail — no test-status section |
| A2 — Workflow comment block | Fail — see #14 |
| B — Test coverage | Fail — one default-value assertion; no exemption applies |
| C — Test adequacy | Fail — no new behavior is exercised |
| D — Feature flag | Fail — new GPU path + PMC/SQTT behavior changes ship unconditionally |
| E — Adjacent test reuse | Extend pm4/tests/pmc_builder_harvest_gfx12_tests.cpp |
| F — Risk | High — new GPU code path and changes to default runtime behavior on three shipping generations, without tests and without a flag |
| G — Unicode / injection scan | Pass — no non-ASCII, zero-width, bidi, or homoglyph characters |
License header year (2026): no newly added files in this diff — all 24 are pre-existing. Exemption
applies and no header is flagged. pm4/spm_builder.h (+414/-151) is close to the "substantial
rewrite" threshold if you want to bump it.
Suggested priority
1a6a70a to
13383e7
Compare
|
@ApoKalipse-V, this is the reply to your general notes:
1. MI300 validation breadth 2. SP / accum(level) handling I agree that we should not silently imply the same validation coverage for those paths as for the exercised set. I have now added a warning in the SPM path when the These paths are not part of the core SPM functionality exercised by this PR, so this does not affect the main gfx12/gfx1250 SPM support being added here. 3. Mixed 16/32 coverage breadth That said, I did not intentionally narrow the integration coverage here. I exercised the mixed-width combinations I could identify, including same-block and cross-block cases, and repeated the representative reruns to make sure the behavior stayed stable. So the current integration coverage is meant to be broad and representative, not minimal. I also agree that corner cases may still exist. Those remaining corner cases, if any, are more likely to come from real usage patterns than from a fully enumerable combination matrix in unit tests. The integration-test work itself will also come in a separate PR. |
|
Thanks for the follow-up commit. Re-reviewed against Fixed ✅
Reported fixed, but not
Still open — correctness
Testing. Still one test change in the PR — a default-value swap in Debug hygiene. Risk: High — new GPU code path, no test coverage, no feature flag, and unrelated behavior changes to PMC/SQTT bundled in. The PR also still carries the |
Thanks @vlaindic for the careful re-review. I went through the remaining items one by one. 1. Reported fixed, but not 1.1 Bit layout / encode-decode round-trip The internal pair here is For the current internal encoding, WGP blocks recover the physical instance with I added a short comment above the helper pair to make that explicit. 1.2 Decode callback documentation Agreed. I added the missing typedef-level documentation for 2. Still open - correctness 2.1 For this PR, gfx10/gfx11 SPM enablement is not part of the intended support scope. The shared gfx10/gfx11 changes here are only the minimum compatibility work needed so the newer builder/decode interfaces still compile cleanly. I did update the diagnostics part, though: when 2.2 Agreed. I fixed this in two parts: the stop-path write no longer uses When that path is enabled, it now uses 2.3 Agreed. I changed 2.4 Agreed. I added an explicit counter-map capacity check after expansion. 2.5 Unrelated bundled changes I do not think these are unrelated changes. The gfx12 SPM work touches shared primitive paths that are also used by PMC/SQTT, so some corresponding updates there are required to keep the code coherent and compiling. This is a common practice for builders when the same primitives are used across them. Also, this does not introduce a new behavior change there. For the TCP For the 2.6 SP / SQ-ACCUM / SQG-ACCUM are warnings only This is intentional here. Changing the SDK-facing gating behavior is outside the scope of this PR. 2.7 Requested comments were not added Partially agreed. I added the missing For the 2.8 Agreed. 3. Testing Testing here is split by layer.
4. Debug hygiene 4.1 This is legacy 4.2 Other This is also legacy 4.3 Debug-only includes / This part was specific to the new debug dump added in this PR. I switched that dump from 5. Risk I agree there is still real risk here, especially because the SDK-side integration is still TBD and this PR is a prerequisite for that later work. At the same time, the risk should be scoped accurately. The gfx1200/gfx1201 and MI455X validation work we planned on the More broadly, SPM is still a beta feature, so the tolerance for some remaining integration risk is higher than it would be for a fully mature path. |
|
Here is the second round of AI review- Re-reviewed against Addressed
Still open
|
34b8541 to
722da49
Compare
Thanks for the second-round AI review. Since it was re-reviewed against What is already addressed in the newer code:
For the items that remain by design:
|
4d8f418 to
c34a515
Compare
ApoKalipse-V
left a comment
There was a problem hiding this comment.
LGTM, just one simplification NIT
c34a515 to
19d96ef
Compare
6a8b67b to
41941cb
Compare
…support Add gfx12/gfx1250 SPM core support to aqlprofile. This updates the runtime, factory, builder, block metadata, and primitive layers for the current gfx12 SPM model, including: - SA and WGP-aware SPM topology support - explicit SPM slot-count metadata - per-counter SPM depth propagation - mixed-depth SE/global builder support - current SQ/SQG handling required by the gfx12 SPM path - normalized primitive helper call shapes used by the builder - overflow-aware SPM decode behavior - DEBUG_TRACE build hook support via CMAKE_DEBUG_TRACE=1/2 - End() stop-path immediate-sample fix via RLC_SPM_PERFMON_CNTL A few gfx9/gfx10/gfx11 helper signatures are adjusted where required so the newer builder interfaces compile cleanly, without changing the intended support scope of this PR. Validation: - validated on gfx1201 (Radeon RX 9070 XT) - validated on gfx1250 (MI455X)
… feedback Address the review follow-up changes in the aqlprofile SPM path. This updates the implementation to: - add a public `aqlprofile_spm_decode_shader_engine()` helper for the caller-facing packed `shader_engine` callback value - move gfx12/gfx1250 internal SPM `block_index` encode/decode ownership into the primitive layer and keep the factory methods as thin wrappers - warn when `SP` or SQ/SQG level-counter requests enter the SPM path, since those paths are still outside the exercised support set - switch the per-block SPM ordering to `stable_sort()` so equal-key ties preserve original request order for ACCUM-sensitive cases - replace repeated GRBM index write suppression logic with a local `SetGrbmGfxIndex()` helper - remove the dormant `PER_WGP_INST_SELECT` branch from the live code path - add small clarifying comments where the GRBM/index behavior was not obvious These changes are intended to tighten the active SPM path and address review concerns without broadening the supported feature scope claimed by this PR.
…dback Address the latest vlaindic review follow-up changes in the aqlprofile SPM path. This updates the implementation to: - add targeted unit coverage for `aqlprofile_spm_decode_shader_engine()`, the new `spm_force_sample_before_stop` flag, and the gfx12 internal SPM `block_index` encode/decode helpers - fix the gfx12 SPM stop-path write so the forced pre-stop sample uses the minimum valid interval instead of relying on an underflowing helper input - gate that forced pre-stop sample path explicitly in `SpmConfig` - use the real shader-array count when populating `trace_config.sa_number` - add an explicit counter-map capacity check after SA/WGP expansion - tighten the `spm_counter_count == 0` failure path so it reports the block name explicitly - add the missing decode comment for the `(sa == 0 && wgp == 0)` skip case - document the internal gfx12 SPM `block_index` helper pair more clearly - switch the new DEBUG_TRACE mux dump from `std::cout` to `printf` and remove the unneeded stream-formatting headers These changes address the latest review items without changing the intended feature scope of the PR.
…eedback Address the current sauverma93 review follow-up changes in the aqlprofile SPM path. This updates the implementation to: - make `aqlprofile_spm_is_event_supported()` use generic block support via `spm_counter_count > 0` - share SPM depth policy between the query path and runtime path through a common `resolve_spm_depth()` helper - reject explicit wrong depths on the affected SQ-family forced-32 paths instead of silently coercing them, while still keeping `NONE` on the established backend default path - split SPM sample-interval handling into the old gfx9/gfx10-style aligned path and the newer gfx11+/gfx12-style raw-interval path - make `aqlprofile_spm_query_agent_configurations()` report GPU-family-appropriate interval ranges and modes, including `REFCLK` for the newer path - replace the old env-var-driven `DEBUG_TRACE` build hook with a cached `AQLPROFILE_DEBUG_TRACE=0/1/2` CMake setting - extend unit coverage for the updated SPM query APIs in `aqlprofile-core-tests` These changes tighten the backend SPM query/configuration path and address the current review concerns without changing the intended top-level rollout gate of this PR. That gate is expected to be removed together with the later SDK PR that enables gfx12/gfx1250 SPM support end-to-end.
… feedback Address the current SrirakshaNag review follow-up changes in the aqlprofile SPM path. This updates the implementation to: - add a minimal representative non-SQ depth-options unit test covering `NONE`, `16_BITS`, and `32_BITS` - add a clarifying comment for the DEBUG_TRACE-only sample-count buffer used during SPM integration-test debugging - remove the unused `AQLPROFILE_SPM_DEPTH_64_BITS` public enum value, since the current SPM path has no 64-bit depth support and neither the backend nor the SDK uses it These changes address the current review items without broadening the supported feature scope of the PR.
… feedback - second round
Address the second-round ApoKalipse-V review follow-up changes in the aqlprofile path.
This updates the implementation to:
- add explicit default initializers to the local `spm_counter_des_t` helper in `spm_v2.cpp` to address the style/legibility concern around member initialization, while keeping the current behavior unchanged because the live path still inserts both fields explicitly
- restore the unconditional `SQ_PERFCOUNTER_MASK_ADDR` write in the SQTT `StartPerfMon()` path, because that path is only reached from the gfx9 branch and the added silent `Register(){}` skip could hide a regression instead of surfacing it
These changes address the latest ApoKalipse-V follow-up without changing the intended support scope of the PR.
Apply the CI-requested formatting fixes in the aqlprofile SPM files. This updates the affected source and CMake files using the same formatter versions reported by CI: - clang-format 11.1.0 - cmake-format 0.6.13 The changes are formatting-only and do not alter the intended behavior of the current gfx12/gfx1250 SPM review follow-up work. The local `rocprofiler-sdk-aqlprofile` target was rebuilt successfully after applying the formatting updates.
41941cb to
c0e33d0
Compare
…9646) ## Motivation Add gfx12/gfx1250 SPM support to aqlprofile. ## Technical Details This PR adds gfx12/gfx1250 SPM support across the aqlprofile runtime, factory, builder, metadata, and decode layers. The feature set includes: - SA/WGP-aware SPM topology handling for gfx12/gfx1250 - explicit SPM slot-count metadata - per-counter SPM depth propagation and validation - mixed-depth SE/global builder support - SPM capability/configuration query updates, including supported sample-interval reporting - overflow-aware SPM decode behavior - a public `aqlprofile_spm_decode_shader_engine(...)` helper for decoding the packed caller-facing `shader_engine` value returned by the SPM decode callback Some shared-path helper/primitive behavior for gfx9/gfx10/gfx11 is adjusted where needed so the newer gfx12 path builds and behaves consistently, without changing the intended support scope of this PR. Current validation/support-scope note: - this PR validates the core gfx12/gfx1250 SPM path - `SP` block and `SQ` / `SQG` level-counter support are outside the exercised support set for this PR and now warn instead of implying equivalent validation coverage ## Issue Tracking JIRA ID : AIPROFSDK-109 ## Test Plan - gfx1201 (Radeon RX 9070 XT), parity and new features: validated using testv2 - gfx1250 (MI455X), parity and new features: validated using testv2 - gfx942 (MI300X), regression: validated using `spm-counter-test` ctests and `rocprofv3` ## Test Result Pass for the tests listed in test plan. ## Submission Checklist - [x] Look over the contributing guidelines at https://github.com/ROCm/rocm-systems/blob/develop/CONTRIBUTING.md.
TheRock Submodule Bump ActivityNewest first
|
Motivation
Add gfx12/gfx1250 SPM support to aqlprofile.
Technical Details
This PR adds gfx12/gfx1250 SPM support across the aqlprofile runtime, factory, builder, metadata, and decode layers. The feature set includes:
aqlprofile_spm_decode_shader_engine(...)helper for decoding the packed caller-facingshader_enginevalue returned by the SPM decode callbackSome shared-path helper/primitive behavior for gfx9/gfx10/gfx11 is adjusted where needed so the newer gfx12 path builds and behaves consistently, without changing the intended support scope of this PR.
Current validation/support-scope note:
SPblock andSQ/SQGlevel-counter support are outside the exercised support set for this PR and now warn instead of implying equivalent validation coverageIssue Tracking
JIRA ID : AIPROFSDK-109
Test Plan
spm-counter-testctests androcprofv3Test Result
Pass for the tests listed in test plan.
Submission Checklist