// MIT License // // Copyright (c) 2024-2026 Advanced Micro Devices, Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #include "trace_parser.hpp" #include #include #include #include #include #include "gfx10/gfx10token.h" #include "gfx9/gfx9token.h" #include "rocprof_trace_decoder/rocprof_trace_decoder.h" #include "stitch/stitch.hpp" // Forward declarations for internal functions std::unique_ptr AnalyseBinary_GFX9_internal( CppReturnInfo& info, const uint8_t* tokendata, uint64_t buffersize, int target_cu, class Stitcher& stitch, bool double_buffer, bool is_mi350 ); // Note: ToPcV2 is defined in segment_test.cpp // Tests for CppReturnInfo TEST(CppReturnInfoTest, DefaultConstruction) { CppReturnInfo info; EXPECT_TRUE(info.perfevents.empty()); EXPECT_EQ(info.realtime_frequency, 0); EXPECT_EQ(info.counter_frequency, 0); EXPECT_FALSE(info.bPacketLost); EXPECT_EQ(info.globaltime, 0); EXPECT_EQ(info.basetime, 0); } // Tests for occupancy_info_t // Note: 'start' is a 1-bit field: 1 = wave_start, 0 = wave_end TEST(OccupancyInfoTest, ConstructorWithUint64WaveStart) { pcinfo_t pc{100, 5}; occupancy_info_t occ( pc, 1000, uint64_t(1), uint64_t(2), uint64_t(3), uint64_t(1), uint64_t(0), uint64_t(0), uint64_t(0), uint64_t(0) ); EXPECT_EQ(occ.pc.address, 100); EXPECT_EQ(occ.pc.code_object_id, 5); EXPECT_EQ(occ.time, 1000); EXPECT_EQ(occ.cu, 1); EXPECT_EQ(occ.simd, 2); EXPECT_EQ(occ.wave_id, 3); EXPECT_EQ(occ.start, 1); // wave_start } TEST(OccupancyInfoTest, ConstructorWithUint64WaveEnd) { pcinfo_t pc{100, 5}; occupancy_info_t occ( pc, 1000, uint64_t(1), uint64_t(2), uint64_t(3), uint64_t(0), uint64_t(0), uint64_t(0), uint64_t(0), uint64_t(0) ); EXPECT_EQ(occ.start, 0); // wave_end } TEST(OccupancyInfoTest, ConstructorWithInt8) { pcinfo_t pc{200, 10}; occupancy_info_t occ( pc, 2000, int8_t(4), int8_t(1), int8_t(7), uint64_t(1), uint64_t(0), uint64_t(0), uint64_t(0), uint64_t(0) ); EXPECT_EQ(occ.pc.address, 200); EXPECT_EQ(occ.pc.code_object_id, 10); EXPECT_EQ(occ.time, 2000); EXPECT_EQ(occ.cu, 4); EXPECT_EQ(occ.simd, 1); EXPECT_EQ(occ.wave_id, 7); EXPECT_EQ(occ.start, 1); // wave_start } // Note: bValid tests are in stitch_utils_test.cpp // Tests for PipeArray64 TEST(PipeArray64Test, SetLoSetsLowBits) { PipeArray64 arr{}; struct MockToken { int me; int pipe; }; MockToken t{0, 0}; arr.setlo(t, 0xDEADBEEF); EXPECT_EQ(arr.at_reg(t) & 0xFFFFFFFF, 0xDEADBEEF); } TEST(PipeArray64Test, SetHiSetsHighBits) { PipeArray64 arr{}; struct MockToken { int me; int pipe; }; MockToken t{0, 0}; arr.sethi(t, 0xCAFEBABE); EXPECT_EQ(arr.at_reg(t) >> 32, 0xCAFEBABE); } TEST(PipeArray64Test, SetLoPreservesHighBits) { PipeArray64 arr{}; struct MockToken { int me; int pipe; }; MockToken t{0, 0}; arr.sethi(t, 0xAAAAAAAA); arr.setlo(t, 0xBBBBBBBB); EXPECT_EQ(arr.at_reg(t), 0xAAAAAAAABBBBBBBBull); } TEST(PipeArray64Test, SetHiPreservesLowBits) { PipeArray64 arr{}; struct MockToken { int me; int pipe; }; MockToken t{0, 0}; arr.setlo(t, 0xCCCCCCCC); arr.sethi(t, 0xDDDDDDDD); EXPECT_EQ(arr.at_reg(t), 0xDDDDDDDDCCCCCCCCull); } // Tests for TokenGenerator base class behavior class TestTokenGenerator : public TokenGenerator { public: TestTokenGenerator(const uint8_t* buf, size_t size, int64_t gt, int64_t bt) : TokenGenerator(buf, size, gt, bt) {} }; TEST(TokenGeneratorTest, ConstructorSetsFields) { uint8_t buffer[100]; TestTokenGenerator gen(buffer, 100, 5000, 1000); EXPECT_EQ(gen.BUFFER_SIZE, 100); EXPECT_EQ(gen.get_time(), 5000); EXPECT_EQ(gen.get_base_time(), 1000); } // Edge case: minimum buffer size TEST(TokenGeneratorEdgeCaseTest, MinimumBufferSize) { uint8_t buffer[16]; // Minimum practical size TestTokenGenerator gen(buffer, 16, 0, 0); EXPECT_EQ(gen.BUFFER_SIZE, 16); } // Edge case: large time values TEST(TokenGeneratorEdgeCaseTest, LargeTimeValues) { uint8_t buffer[100]; int64_t large_time = 0x7FFFFFFFFFFFF; // Large but not max TestTokenGenerator gen(buffer, 100, large_time, large_time - 1000); EXPECT_EQ(gen.get_time(), large_time); EXPECT_EQ(gen.get_base_time(), large_time - 1000); } // Edge case: PipeArray64 combines hi and lo correctly TEST(PipeArray64EdgeCaseTest, HiLoComposition) { PipeArray64 arr{}; struct MockToken { int me; int pipe; }; MockToken t{0, 0}; arr.sethi(t, 0x12345678); arr.setlo(t, 0xABCDEF00); EXPECT_EQ(arr.at_reg(t), 0x12345678ABCDEF00ull); } // Tests for CSRegisterHandler struct FakeRegToken { int me = 0; int pipe = 0; uint32_t regaddr = 0; uint64_t regdata = 0; }; TEST(CSRegisterHandlerTest, AddressClassification) { CSRegisterHandler h; EXPECT_TRUE(h.IsUserdata(0xC340)); EXPECT_TRUE(h.IsUserdata(0xC343)); EXPECT_FALSE(h.IsUserdata(0xC344)); EXPECT_TRUE(h.IsUserdata0(0xC340)); EXPECT_TRUE(h.IsUserdata1(0xC341)); EXPECT_TRUE(h.IsUserdata2(0xC342)); EXPECT_TRUE(h.IsUserdata3(0xC343)); } TEST(CSRegisterHandlerTest, UpdateRegNoCSNonUserdata2Ignored) { CSRegisterHandler h; FakeRegToken tok{0, 0, 0xC340, 0}; EXPECT_EQ(h.UpdateRegNoCS(tok).kind, CSRegisterHandler::RegUpdateEvent::NONE); } TEST(CSRegisterHandlerTest, ROCHeaderAndCodeObjLoadUnload) { CSRegisterHandler h; FakeRegToken tok{0, 0, 0xC342, 0}; // Activate ROC format rocprof_trace_decoder_instrument_enable_t en{}; en.char1 = '\0'; en.char2 = 'R'; en.char3 = 'O'; en.char4 = 'C'; tok.regdata = en.u32All; h.UpdateRegNoCS(tok); ASSERT_TRUE(h.bIsROCMFormat); // CODEOBJ ADDR_LO header + payload rocprof_trace_decoder_packet_header_t pkt{}; pkt.opcode = ROCPROF_TRACE_DECODER_PACKET_OPCODE_CODEOBJ; pkt.type = ROCPROF_TRACE_DECODER_CODEOBJ_MARKER_TYPE_ADDR_LO; tok.regdata = pkt.u32All; h.UpdateRegNoCS(tok); tok.regdata = 0x1000; h.UpdateRegNoCS(tok); // ADDR_HI pkt.type = ROCPROF_TRACE_DECODER_CODEOBJ_MARKER_TYPE_ADDR_HI; tok.regdata = pkt.u32All; h.UpdateRegNoCS(tok); tok.regdata = 0; h.UpdateRegNoCS(tok); // SIZE_LO pkt.type = ROCPROF_TRACE_DECODER_CODEOBJ_MARKER_TYPE_SIZE_LO; tok.regdata = pkt.u32All; h.UpdateRegNoCS(tok); tok.regdata = 0x100; h.UpdateRegNoCS(tok); // TAIL (load) pkt.type = ROCPROF_TRACE_DECODER_CODEOBJ_MARKER_TYPE_TAIL; tok.regdata = pkt.u32All; h.UpdateRegNoCS(tok); rocprof_trace_decoder_codeobj_marker_tail_t tail{}; tail.isUnload = 0; tail.bFromStart = 1; tail.legacy_id = 42; tok.regdata = tail.raw; h.UpdateRegNoCS(tok); EXPECT_FALSE(h.active_codeobjs.read().empty()); // TAIL (unload) tok.regdata = pkt.u32All; h.UpdateRegNoCS(tok); tail.isUnload = 1; tok.regdata = tail.raw; h.UpdateRegNoCS(tok); EXPECT_TRUE(h.active_codeobjs.read().empty()); } TEST(CSRegisterHandlerTest, AgentInfoCounterAndRtFrequency) { CSRegisterHandler h; h.bIsROCMFormat = true; h.userdata_state = ROCPROF_TRACE_DECODER_CODEOBJ_MARKER_TYPE_LAST; FakeRegToken tok{0, 0, 0xC342, 0}; rocprof_trace_decoder_packet_header_t pkt{}; pkt.opcode = ROCPROF_TRACE_DECODER_PACKET_OPCODE_AGENT_INFO; // Counter interval pkt.type = ROCPROF_TRACE_DECODER_AGENT_INFO_TYPE_COUNTER_INTERVAL; pkt.data20 = 256; tok.regdata = pkt.u32All; EXPECT_EQ(h.UpdateRegNoCS(tok).kind, CSRegisterHandler::RegUpdateEvent::COUNTER_FREQUENCY_CHANGED); EXPECT_EQ(h.counter_frequency, 256u); // RT frequency pkt.type = ROCPROF_TRACE_DECODER_AGENT_INFO_TYPE_RT_FREQUENCY_KHZ; pkt.data20 = 100; tok.regdata = pkt.u32All; EXPECT_EQ(h.UpdateRegNoCS(tok).kind, CSRegisterHandler::RegUpdateEvent::NONE); EXPECT_EQ(h.realtime_frequency, 100u * 1000); } // Tests for AnalyseBinary dispatch namespace { rocprofiler_thread_trace_decoder_status_t noop_cb(rocprofiler_thread_trace_decoder_record_type_t, void*, uint64_t, void*) { return ROCPROFILER_THREAD_TRACE_DECODER_STATUS_SUCCESS; } class AnalyseBinaryMock : public ICodeServicer { public: MOCK_METHOD(assemblyLine, GetInstruction, (pcinfo_t addr, int gfxip), (override)); }; struct Gfx9ParserSink { std::vector events{}; std::vector occupancy{}; }; rocprofiler_thread_trace_decoder_status_t collect_gfx9_records( rocprofiler_thread_trace_decoder_record_type_t type, void* data, uint64_t count, void* userdata ) { auto& sink = *static_cast(userdata); if (type == ROCPROFILER_THREAD_TRACE_DECODER_RECORD_EVENT) { auto* records = static_cast(data); sink.events.insert(sink.events.end(), records, records + count); } else if (type == ROCPROFILER_THREAD_TRACE_DECODER_RECORD_OCCUPANCY) { auto* records = static_cast(data); sink.occupancy.insert(sink.occupancy.end(), records, records + count); } return ROCPROFILER_THREAD_TRACE_DECODER_STATUS_SUCCESS; } void append_gfx9_word(std::vector& data, uint64_t word, size_t bytes) { for (size_t i = 0; i < bytes; ++i) data.push_back(static_cast((word >> (8 * i)) & 0xFF)); } uint64_t encode_gfx9_reg(uint8_t me, uint8_t pipe, uint16_t regaddr, uint32_t regdata) { const uint64_t enc_me = (1u - (me & 1u)) & 1u; return static_cast(gfx9::TOKEN_REG) | (static_cast(pipe & 0x3) << 5) | (enc_me << 7) | (uint64_t{gfx9::Reg::REG_TYPE_USERDATA} << 10) | (uint64_t{1} << 15) | (static_cast(regaddr) << 16) | (static_cast(regdata) << 32); } void append_gfx9_userdata2(std::vector& data, uint32_t regdata) { append_gfx9_word(data, encode_gfx9_reg(0, 0, CSRegisterHandler::USERDATA_ADDR_2, regdata), 8); } void append_gfx9_codeobj_field( std::vector& data, rocprof_trace_decoder_codeobj_marker_type_t type, uint32_t payload ) { rocprof_trace_decoder_packet_header_t header{}; header.opcode = ROCPROF_TRACE_DECODER_PACKET_OPCODE_CODEOBJ; header.type = type; append_gfx9_userdata2(data, header.u32All); append_gfx9_userdata2(data, payload); } void append_gfx9_codeobj_load_unload(std::vector& data, uint32_t code_object_id) { rocprof_trace_decoder_instrument_enable_t enable{}; enable.char1 = '\0'; enable.char2 = 'R'; enable.char3 = 'O'; enable.char4 = 'C'; append_gfx9_userdata2(data, enable.u32All); append_gfx9_codeobj_field(data, ROCPROF_TRACE_DECODER_CODEOBJ_MARKER_TYPE_ADDR_LO, 0x1000); append_gfx9_codeobj_field(data, ROCPROF_TRACE_DECODER_CODEOBJ_MARKER_TYPE_SIZE_LO, 0x200); rocprof_trace_decoder_codeobj_marker_tail_t tail{}; tail.bFromStart = 1; tail.legacy_id = code_object_id; append_gfx9_codeobj_field(data, ROCPROF_TRACE_DECODER_CODEOBJ_MARKER_TYPE_TAIL, tail.raw); tail.isUnload = 1; append_gfx9_codeobj_field(data, ROCPROF_TRACE_DECODER_CODEOBJ_MARKER_TYPE_TAIL, tail.raw); } void append_gfx9_counter_interval(std::vector& data, uint32_t interval) { rocprof_trace_decoder_packet_header_t header{}; header.opcode = ROCPROF_TRACE_DECODER_PACKET_OPCODE_AGENT_INFO; header.type = ROCPROF_TRACE_DECODER_AGENT_INFO_TYPE_COUNTER_INTERVAL; header.data20 = interval; append_gfx9_userdata2(data, header.u32All); } void append_gfx9_perf( std::vector& data, uint8_t cu, uint8_t bank, std::array counters, bool advance_time = true ) { uint64_t word = static_cast(gfx9::TOKEN_PERF); if (advance_time) word |= uint64_t{1} << 4; word |= static_cast(cu & 0xF) << 6; word |= static_cast(bank & 0x3) << 10; word |= static_cast(counters[0] & 0x1FFF) << 12; word |= static_cast(counters[1] & 0x1FFF) << 25; word |= static_cast(counters[2] & 0x1FFF) << 38; word |= static_cast(counters[3] & 0x1FFF) << 51; append_gfx9_word(data, word, 8); } void append_gfx9_misc_time_delta(std::vector& data, uint8_t delta) { const uint16_t word = static_cast(gfx9::TOKEN_MISC) | (static_cast(delta) << 4); append_gfx9_word(data, word, 2); } void append_gfx9_wave_end(std::vector& data, uint8_t cu, uint8_t simd, uint8_t wave) { uint16_t word = static_cast(gfx9::TOKEN_WAVE_END); word |= static_cast(cu & 0xF) << 6; word |= static_cast(wave & 0xF) << 10; word |= static_cast(simd & 0x3) << 14; append_gfx9_word(data, word, 2); } } // namespace TEST(AnalyseBinaryTest, InvalidBufferReturnsNull) { auto mock = std::make_shared(); Stitcher stitch(mock, noop_cb, nullptr); uint8_t buf[8] = {0}; CppReturnInfo info; EXPECT_EQ(AnalyseBinary_internal(info, buf, sizeof(buf), -1, stitch), nullptr); } TEST(AnalyseBinaryTest, Gfx9PositiveTargetCu) { auto mock = std::make_shared(); Stitcher stitch(mock, noop_cb, nullptr); uint8_t buf[64] = {0}; CppReturnInfo info; EXPECT_NE(AnalyseBinary_GFX9_internal(info, buf, sizeof(buf), 0, stitch, false, false), nullptr); } TEST(AnalyseBinaryGfx9Test, EmitsCodeObjectPerfAndRecoveredOccupancyRecords) { constexpr uint32_t code_object_id = 42; std::vector buffer; append_gfx9_codeobj_load_unload(buffer, code_object_id); append_gfx9_wave_end(buffer, 1, 2, 3); append_gfx9_counter_interval(buffer, 4); append_gfx9_perf(buffer, 0, 1, {11, 22, 33, 44}); append_gfx9_misc_time_delta(buffer, 5); append_gfx9_perf(buffer, 0, 1, {55, 66, 77, 88}); auto mock = std::make_shared(); Gfx9ParserSink sink; Stitcher stitch(mock, collect_gfx9_records, &sink); CppReturnInfo info; EXPECT_NE(AnalyseBinary_GFX9_internal(info, buffer.data(), buffer.size(), 0, stitch, false, false), nullptr); ASSERT_EQ(sink.occupancy.size(), 2u); EXPECT_EQ(sink.occupancy[0].start, 1u); EXPECT_EQ(sink.occupancy[1].start, 0u); for (const auto& occ : sink.occupancy) { EXPECT_EQ(occ.time, 0u); EXPECT_EQ(occ.cu, 1u); EXPECT_EQ(occ.simd, 2u); EXPECT_EQ(occ.wave_id, 3u); } ASSERT_EQ(sink.events.size(), 2u); EXPECT_EQ(sink.events[0].type, ROCPROF_TRACE_DECODER_EVENT_CODE_OBJECT_LOAD); EXPECT_EQ(sink.events[0].payload.raw, code_object_id); EXPECT_EQ(sink.events[1].type, ROCPROF_TRACE_DECODER_EVENT_CODE_OBJECT_UNLOAD); EXPECT_EQ(sink.events[1].payload.raw, code_object_id); EXPECT_EQ(info.counter_frequency, 4u); ASSERT_EQ(info.perfevents.size(), 4u); EXPECT_EQ(info.perfevents[0].time, 4); EXPECT_EQ(info.perfevents[0].bank, 1u); EXPECT_EQ(info.perfevents[0].events0, 11u); EXPECT_EQ(info.perfevents[0].events3, 44u); EXPECT_EQ(info.perfevents[1].time, 28); EXPECT_EQ(info.perfevents[1].events0, 0u); EXPECT_EQ(info.perfevents[2].time, 28); EXPECT_EQ(info.perfevents[2].events0, 55u); EXPECT_EQ(info.perfevents[2].events3, 88u); EXPECT_EQ(info.perfevents[3].time, 32); EXPECT_EQ(info.perfevents[3].events0, 0u); } namespace { std::array bytes_from_word(uint64_t word) { std::array bytes{}; std::memcpy(bytes.data(), &word, sizeof(word)); return bytes; } } // namespace TEST(DetectArchInternalTest, SniffsSupportedHeadersAndRejectsInvalidBuffers) { EXPECT_EQ(DetectArch_internal(nullptr, sizeof(uint64_t)), TraceArch::UNKNOWN); std::array zeros{}; EXPECT_EQ(DetectArch_internal(zeros.data(), sizeof(uint64_t) - 1), TraceArch::UNKNOWN); EXPECT_EQ(DetectArch_internal(zeros.data(), zeros.size()), TraceArch::UNKNOWN); rocprof_trace_decoder_gfx9_header_t gfx9{}; gfx9.legacy_version = 0; gfx9.gfx9_version2 = 5; auto bytes = bytes_from_word(gfx9.raw); EXPECT_EQ(DetectArch_internal(bytes.data(), bytes.size()), TraceArch::GFX9); gfx9.legacy_version = 0x11; gfx9.gfx9_version2 = 6; bytes = bytes_from_word(gfx9.raw); EXPECT_EQ(DetectArch_internal(bytes.data(), bytes.size()), TraceArch::GFX9); gfx9.legacy_version = 0; gfx9.gfx9_version2 = 7; bytes = bytes_from_word(gfx9.raw); EXPECT_EQ(DetectArch_internal(bytes.data(), bytes.size()), TraceArch::UNKNOWN); struct rdna_case_t { uint8_t version; TraceArch expected; }; const std::array cases{ rdna_case_t{1, TraceArch::GFX10 }, rdna_case_t{2, TraceArch::GFX10 }, rdna_case_t{3, TraceArch::GFX11 }, rdna_case_t{4, TraceArch::GFX12 }, rdna_case_t{5, TraceArch::MI400 }, rdna_case_t{6, TraceArch::UNKNOWN}, }; for (const auto& c : cases) { header_type header{}; header.header = 0b0010001; header.version = c.version; bytes = bytes_from_word(header.raw); EXPECT_EQ(DetectArch_internal(bytes.data(), bytes.size()), c.expected) << "version=" << int(c.version); } }