diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/vm/amdgpu/compute_unit.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/vm/amdgpu/compute_unit.cpp index bc9b25314799..65f1bec12ae4 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/vm/amdgpu/compute_unit.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/vm/amdgpu/compute_unit.cpp @@ -19,6 +19,7 @@ #include "rocjitsu/isa/arch/amdgpu/shared/alu_exceptions.h" #include "rocjitsu/isa/instruction.h" #include "rocjitsu/vm/amdgpu/mem_state.h" +#include "rocjitsu/vm/amdgpu/register_access.h" #include "util/except.h" #include "util/log.h" @@ -812,10 +813,9 @@ void ComputeUnitCore::issue_instruction(Wavefront *active) { auto mn = std::string_view(inst->mnemonic()); if (mn.find("s_setpc") != std::string_view::npos || mn.find("s_swappc") != std::string_view::npos) { - uint32_t ssrc0_idx = words[0] & 0x7F; - uint32_t sb = active->sgpr_alloc().base; - uint64_t target = static_cast(read_sgpr(sb + ssrc0_idx)) | - (static_cast(read_sgpr(sb + ssrc0_idx + 1)) << 32); + const Operand *target_operand = inst->src_operand(0); + assert(target_operand && "indirect PC instruction must have a target operand"); + uint64_t target = RegisterAccess(*active).read_scalar64(*target_operand); if (target == 0) { active->halt(); delete inst; diff --git a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/vm/plugins/race_detector/plugin.cpp b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/vm/plugins/race_detector/plugin.cpp index 20779d6ae5cc..7a353b4fe765 100644 --- a/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/vm/plugins/race_detector/plugin.cpp +++ b/emulation/rocjitsu/lib/rocjitsu/src/rocjitsu/vm/plugins/race_detector/plugin.cpp @@ -3,6 +3,7 @@ #include "rocjitsu/vm/plugins/race_detector/plugin.h" +#include "rocjitsu/isa/arch/amdgpu/shared/scalar_operand_read.h" #include "rocjitsu/isa/instruction.h" #include "rocjitsu/vm/amdgpu/compute_unit.h" #include "rocjitsu/vm/amdgpu/lds.h" @@ -337,11 +338,19 @@ void RaceDetectorPlugin::onAmdgpuRouteMemoryInstruction(const Instruction &inst, if (inst.data()->tag() == amdgpu::SCALAR_MEM) { auto &d = *inst.data_as(); if (d.is_load) { - uint32_t logicalBase = d.dst_reg_base - wf.sgpr_alloc().base; - std::vector registers(d.num_dwords); - for (uint32_t i = 0; i < d.num_dwords; ++i) - registers[i] = logicalBase + i; - rs->registerEvent(wf.pc, MemoryEventType::GLOBAL_TO_SGPR, std::move(registers), wf.exec()); + // TTMP destinations use the wave-private trap file. They do not produce + // ordinary-SGPR read callbacks, so registering a GLOBAL_TO_SGPR event for + // them would leave an event that no matching consumer can diagnose. + // Modeling pending scalar loads into TTMP is separate race-detector work. + std::vector registers; + registers.reserve(d.num_dwords); + for (uint32_t i = 0; i < d.num_dwords; ++i) { + const uint32_t selector = d.dst_selector + i; + if (selector < amdgpu::kTtmpSelectorFirst || selector > amdgpu::kTtmpSelectorLast) + registers.push_back(selector); + } + if (!registers.empty()) + rs->registerEvent(wf.pc, MemoryEventType::GLOBAL_TO_SGPR, std::move(registers), wf.exec()); } } } diff --git a/emulation/rocjitsu/tests/amdgpu_vm_test.cpp b/emulation/rocjitsu/tests/amdgpu_vm_test.cpp index 878bd39bb834..566e573b7905 100644 --- a/emulation/rocjitsu/tests/amdgpu_vm_test.cpp +++ b/emulation/rocjitsu/tests/amdgpu_vm_test.cpp @@ -6,6 +6,7 @@ #include "embedded_schema.h" #include "rocjitsu/code/amdgpu_elf.h" +#include "rocjitsu/code/builders/instruction_builder.h" #include "rocjitsu/code/kernel_descriptor_scan.h" #include "rocjitsu/code/kernel_symbol.h" #include "rocjitsu/config/config_loader.h" @@ -2120,6 +2121,46 @@ TEST_P(IsaTest, DispatchWfReturnsNullWhenSlotsExhausted) { EXPECT_EQ(cu->dispatch_wf(0, 0x1040, 104, 256), nullptr); } +TEST(TrapRegisterPcTest, SetpcPrecheckReadsDecodedTtmpPair) { + amdgpu::GpuMemory mem("cdna4_setpc_ttmp_mem"); + amdgpu::L2Cache l2("cdna4_setpc_ttmp_l2"); + amdgpu::ComputeUnitCore::Config cfg{}; + cfg.arch = ROCJITSU_CODE_ARCH_CDNA4; + cfg.num_wf_slots = 2; + cfg.sgprs_per_wf = 104; + cfg.vgprs_per_wf = 16; + cfg.lds_size_kb = 64; + auto cu = amdgpu::ComputeUnitCore::create("cdna4_setpc_ttmp_cu", cfg, &mem, &l2); + ASSERT_NE(cu, nullptr); + + test::HaltSnapshotPlugin *snapshot = nullptr; + cu->set_plugin_group(test::make_halt_snapshot_group(&snapshot)); + ASSERT_NE(snapshot, nullptr); + + constexpr uint64_t kStartPc = 0x1000; + constexpr uint64_t kTargetPc = kStartPc + 2 * sizeof(uint32_t); + constexpr uint32_t kTtmp0Selector = 108; + const std::array code = { + build_s_setpc_b64(kTtmp0Selector, ROCJITSU_CODE_ARCH_CDNA4), + build_s_endpgm(ROCJITSU_CODE_ARCH_CDNA4), + build_s_mov_b32(/*sdst=*/0, scalar_positive_inline_u32(1), ROCJITSU_CODE_ARCH_CDNA4), + build_s_endpgm(ROCJITSU_CODE_ARCH_CDNA4), + }; + mem.load_image(reinterpret_cast(code.data()), sizeof(code), kStartPc); + + auto *wavefront = cu->dispatch_wf(/*wg_id=*/0, kStartPc, cfg.sgprs_per_wf, cfg.vgprs_per_wf); + ASSERT_NE(wavefront, nullptr); + wavefront->set_ttmp(/*TTMP0=*/0, static_cast(kTargetPc)); + wavefront->set_ttmp(/*TTMP1=*/1, static_cast(kTargetPc >> 32)); + + for (uint32_t step = 0; step < code.size() && cu->has_active_wfs(); ++step) + static_cast(cu->step()); + + ASSERT_FALSE(cu->has_active_wfs()); + ASSERT_EQ(snapshot->snapshots().size(), 1u); + EXPECT_EQ(snapshot->snapshots()[0].sgpr(0), 1u); +} + TEST(CommandProcessorTest, DispatchToQuiescedDebugHaltedCuReactivatesEventLoop) { VmFixture f("cdna4", 2, 2); test::AqlQueue queue(f.mem(), f.cp()); diff --git a/emulation/rocjitsu/tests/execution_plugin_test.cpp b/emulation/rocjitsu/tests/execution_plugin_test.cpp index 3f1636d79cdb..4ae1a54ab8fb 100644 --- a/emulation/rocjitsu/tests/execution_plugin_test.cpp +++ b/emulation/rocjitsu/tests/execution_plugin_test.cpp @@ -34,6 +34,7 @@ #include "rocjitsu/isa/arch/amdgpu/shared/dpp_sdwa_ops.h" #include "rocjitsu/isa/arch/amdgpu/shared/instruction_encoding.h" #include "rocjitsu/isa/arch/amdgpu/shared/mma_exec.h" +#include "rocjitsu/isa/arch/amdgpu/shared/scalar_operand_read.h" #include "rocjitsu/isa/decoder.h" #include "rocjitsu/isa/instruction.h" #include "rocjitsu/kmd/linux/kfd_process.h" @@ -2640,6 +2641,46 @@ TEST(RaceDetectorPluginTest, D16LoadTracksFullDwordWhenSramEccEnabled) { EXPECT_FALSE(opposite_half_read_reports_race("rdna4", /*wavefront_size=*/32)); } +TEST(RaceDetectorPluginTest, ScalarLoadToTtmpDoesNotRegisterOrdinarySgprHazard) { + PluginFixture f(/*num_wf_slots=*/2); + PluginSinkConfig sink_config; + sink_config.emplace(); + f.plugin_group_ = std::make_shared(std::move(sink_config)); + auto detector_plugin = std::make_unique(); + auto *detector_plugin_ptr = detector_plugin.get(); + ASSERT_TRUE(f.plugin_group_->add(std::move(detector_plugin))); + f.soc->set_plugin_group(f.plugin_group_); + f.plugin_group_->onInit(); + + auto *cu = f.cu(); + auto *wf = cu->dispatch_wf(/*wg_id=*/0, /*pc=*/0, /*sgprs=*/104, /*vgprs=*/256); + ASSERT_NE(wf, nullptr); + wf->set_exec(1u); + std::array waves{wf}; + f.plugin_group_->onAmdgpuWorkgroupDispatched( + /*dispatch_id=*/1, /*wg_id=*/0, /*physical_vgpr_count=*/256, + /*physical_sgpr_count=*/104, waves); + + constexpr uint32_t kTtmp0Selector = amdgpu::kTtmpSelectorFirst; + auto state = std::make_unique(); + state->is_load = true; + state->num_dwords = 1; + state->dst_selector = kTtmp0Selector; + state->dst_reg_base = wf->sgpr_alloc().base + state->dst_selector; + TestMemoryInstruction load(std::move(state)); + f.plugin_group_->onAmdgpuRouteMemoryInstruction(load, *wf); + + auto *plugin_state = + static_cast(wf->plugin_state(detector_plugin_ptr->slot_index())); + ASSERT_NE(plugin_state, nullptr); + ASSERT_NE(plugin_state->race_state, nullptr); + const auto &events = plugin_state->race_state->getWaveMemoryEvents(); + const auto ®istry = plugin_state->race_state->getDetector()->events(); + EXPECT_TRUE(std::none_of(events.begin(), events.end(), [&](EventId event) { + return registry.type(event) == MemoryEventType::GLOBAL_TO_SGPR; + })); +} + TEST(ExecutionPluginTest, F64SourceReadObservationReportsBothHalves) { if constexpr (!util::has_stdx_simd) { GTEST_SKIP() << " unavailable";