Files
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

414 lines
18 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/GPUArchitecture/GPUInstructionInfo.hpp>
#include <rocRoller/Scheduling/Observers/WaitcntObserver.hpp>
#include <rocRoller/KernelOptions_detail.hpp>
#include <rocRoller/Utilities/Settings.hpp>
namespace rocRoller
{
namespace Scheduling
{
WaitcntState::WaitcntState() = default;
WaitcntState::WaitcntState(WaitQueueMap<bool> const& needsWaitZero,
WaitQueueMap<GPUWaitQueueType> const& typeInQueue,
WaitCntQueues const& instruction_queues_with_alloc)
: m_needsWaitZero(needsWaitZero)
, m_typeInQueue(typeInQueue)
{
// Here we're iterating through all of the Register::ValuePtrs and
// converting them to RegisterIDs
for(auto& queue : instruction_queues_with_alloc)
{
if(m_instructionQueues.find(queue.first) == m_instructionQueues.end())
{
m_instructionQueues[queue.first] = {};
}
for(auto& dsts : queue.second)
{
m_instructionQueues[queue.first].emplace_back(
std::vector<Register::RegisterId>{});
for(auto& dst : dsts)
{
if(dst)
{
for(auto& regid : dst->getRegisterIds())
{
m_instructionQueues[queue.first]
[m_instructionQueues[queue.first].size() - 1]
.emplace_back(regid);
}
}
}
}
}
}
void WaitcntState::assertSafeToBranchTo(const WaitcntState& labelState,
std::string const& label) const
{
if(*this == labelState)
return;
bool fail = false;
// In Debug mode, defer throwing the exception until we have
// captured a more complete error message.
bool longErrMsg = Settings::Get(Settings::LogLvl) >= LogLevel::Debug;
std::string msg
= "Branching to label '" + label + "' with a different waitcnt state.\n";
// If queues do not have needsWaitZero set, and none of the instructions
// contain a destination, it is still safe to branch, even if the
// queues do not match exactly.
for(auto const& [queue, instructions] : m_instructionQueues)
{
if(m_needsWaitZero.at(queue) || labelState.m_needsWaitZero.at(queue))
{
fail = true;
msg += concatenate(" Wait zero: ",
ShowValue(m_needsWaitZero.at(queue)),
ShowValue(labelState.m_needsWaitZero.at(queue)),
ShowValue(queue),
"\n");
if(!longErrMsg)
AssertFatal(!fail, msg);
}
for(auto const& instruction : instructions)
{
if(!instruction.empty())
{
fail = true;
msg += concatenate(" Extra register at label: ",
ShowValue(instruction),
ShowValue(queue),
"\n");
if(!longErrMsg)
AssertFatal(!fail, msg);
}
}
for(auto const& instruction : labelState.m_instructionQueues.at(queue))
{
if(!instruction.empty())
{
fail = true;
msg += concatenate(" Extra register at branch: ",
ShowValue(instruction),
ShowValue(queue),
"\n");
if(!longErrMsg)
AssertFatal(!fail, msg);
}
}
}
AssertFatal(!fail, msg);
}
WaitcntObserver::WaitcntObserver() = default;
WaitcntObserver::WaitcntObserver(ContextPtr context)
: m_context(context)
{
m_includeExplanation
= Settings::getInstance()->get(Settings::LogLvl) >= LogLevel::Verbose;
m_displayState = Settings::getInstance()->get(Settings::LogLvl) >= LogLevel::Debug;
{
auto const& architecture = context->targetArchitecture();
auto hasBarrier = architecture.HasCapability(GPUCapability::s_barrier);
auto hasBarrierSignal = architecture.HasCapability(GPUCapability::s_barrier_signal);
AssertFatal(hasBarrier || hasBarrierSignal,
"Either s_barrier or s_barrier_signal must be supported.",
ShowValue(architecture.target()));
m_barrierOpcode = hasBarrierSignal ? "s_barrier_signal" : "s_barrier";
}
for(int i = 0; i < static_cast<int>(GPUWaitQueue::Count); i++)
{
GPUWaitQueue waitQueue = static_cast<GPUWaitQueue>(i);
m_instructionQueues[waitQueue] = {};
m_needsWaitZero[waitQueue] = false;
m_typeInQueue[waitQueue] = GPUWaitQueueType::None;
}
AssertFatal(m_instructionQueues.size() == static_cast<uint8_t>(GPUWaitQueue::Count),
ShowValue(m_instructionQueues.size()),
ShowValue(static_cast<uint32_t>(GPUWaitQueue::Count)));
AssertFatal(m_needsWaitZero.size() == static_cast<uint8_t>(GPUWaitQueue::Count),
ShowValue(m_needsWaitZero.size()),
ShowValue(static_cast<uint32_t>(GPUWaitQueue::Count)));
AssertFatal(m_typeInQueue.size() == static_cast<uint8_t>(GPUWaitQueue::Count),
ShowValue(m_typeInQueue.size()),
ShowValue(static_cast<uint32_t>(GPUWaitQueue::Count)));
};
void WaitcntObserver::observe(Instruction const& inst)
{
auto context = m_context.lock();
auto const& architecture = context->targetArchitecture();
GPUInstructionInfo info = architecture.GetInstructionInfo(inst.getOpCode());
if(context->kernelOptions()->assertWaitCntState)
{
if(info.isBranch())
{
AssertFatal(inst.getSrcs()[0],
"Branch without a label\n",
ShowValue(inst.toString(LogLevel::Debug)));
addBranchState(inst.getSrcs()[0]->toString());
}
else if(inst.isLabel())
{
addLabelState(inst.getLabel());
}
}
auto instWaitQueues = info.getWaitQueues();
WaitCount waiting = inst.getWaitCount();
if(std::find(
instWaitQueues.begin(), instWaitQueues.end(), GPUWaitQueueType::FinalInstruction)
!= instWaitQueues.end())
{
waiting = WaitCount::Zero(context->targetArchitecture());
if(context->kernelOptions()->assertWaitCntState)
{
assertLabelConsistency();
}
}
for(int i = 0; i < static_cast<int>(GPUWaitQueue::Count); i++)
{
applyWaitToQueue(waiting.getCount(static_cast<GPUWaitQueue>(i)),
static_cast<GPUWaitQueue>(i));
}
for(GPUWaitQueueType queueType : instWaitQueues)
{
GPUWaitQueue waitQueue = fromWaitQueueType(queueType);
if(queueType != GPUWaitQueueType::None
&& m_instructionQueues.find(waitQueue) != m_instructionQueues.end())
{
int instWaitCnt = info.getWaitCount();
if(instWaitCnt >= 0)
{
if(instWaitCnt == 0)
{
m_needsWaitZero[waitQueue] = true;
instWaitCnt = 1;
}
else if(m_typeInQueue[waitQueue] != GPUWaitQueueType::None
&& m_typeInQueue[waitQueue] != queueType)
{
m_needsWaitZero[waitQueue] = true;
}
for(int i = 0; i < instWaitCnt; i++)
{
WaitQueueRegisters queueRegisters;
append(queueRegisters, inst.getAllDsts());
// track LDS access to avoid write-after-read races.
auto isLDSReg = [](Register::ValuePtr const reg) -> bool {
return reg->regType() == Register::Type::LocalData;
};
append(queueRegisters, filter(isLDSReg, inst.getAllSrcs()));
m_instructionQueues[waitQueue].push_back(std::move(queueRegisters));
}
m_typeInQueue[waitQueue] = queueType;
}
}
}
}
std::string WaitcntObserver::getWaitQueueState() const
{
std::stringstream retval;
for(int i = 0; i < static_cast<int>(GPUWaitQueue::Count); i++)
{
GPUWaitQueue waitQueue = static_cast<GPUWaitQueue>(i);
// Only include state information for wait queues in a non-default state.
if(m_needsWaitZero.at(waitQueue)
|| m_typeInQueue.at(waitQueue) != GPUWaitQueueType::None
|| m_instructionQueues.at(waitQueue).size() > 0)
{
if(retval.rdbuf()->in_avail() == 0)
{
retval << "\nWait Queue State:";
}
retval << "\n--Queue: " << waitQueue;
retval << "\n----Needs Wait Zero: "
<< (m_needsWaitZero.at(waitQueue) ? "True" : "False");
retval << "\n----Type In Queue : " << m_typeInQueue.at(waitQueue);
retval << "\n----Registers : ";
for(int queue_i = 0; queue_i < m_instructionQueues.at(waitQueue).size();
queue_i++)
{
retval << "\n------Dst: {";
for(auto& reg : m_instructionQueues.at(waitQueue)[queue_i])
{
if(reg)
{
retval << reg->toString() << ", ";
}
}
retval << "}";
}
}
}
return retval.str();
}
WaitCount WaitcntObserver::computeZeroBarrierWaitCount(Instruction const& inst,
std::string* explanation) const
{
auto context = m_context.lock();
WaitCount rv;
if(context->kernelOptions()->alwaysWaitZeroBeforeBarrier)
{
const auto& architecture = context->targetArchitecture();
if(inst.getOpCode() == m_barrierOpcode)
{
if(explanation != nullptr)
{
*explanation += "WaitCnt Needed: alwaysWaitZeroBeforeBarrier is set.\n";
}
rv.combine(WaitCount::Zero(architecture));
}
}
return rv;
}
WaitCount WaitcntObserver::computeSyncQueueWaitCount(Instruction const& inst,
std::string* explanation) const
{
auto context = m_context.lock();
WaitCount rv;
auto queuesToSync = inst.getWaitCount().queuesToSync();
if(queuesToSync.any())
{
const auto& architecture = context->targetArchitecture();
for(int i = 0; i < static_cast<int>(GPUWaitQueueType::Count); i++)
{
GPUWaitQueueType queueType = static_cast<GPUWaitQueueType>(i);
GPUWaitQueue queue = fromWaitQueueType(queueType);
if(queuesToSync[queueType])
{
if(!m_instructionQueues.at(queue).empty()
&& (m_typeInQueue.at(queue) == queueType || m_needsWaitZero.at(queue)))
{
rv.combine(WaitCount(architecture, queue, 0));
if(explanation)
{
*explanation += fmt::format("Wait for Queue {} {}: empty: {}, "
"needsWaitZero: {}, typeInQueue: {}",
toString(queueType),
queuesToSync[queueType],
m_instructionQueues.at(queue).empty(),
m_needsWaitZero.at(queue),
toString(m_typeInQueue.at(queue)));
}
}
}
}
}
return rv;
}
WaitCount WaitcntObserver::computeRegisterWaitCount(Instruction const& inst,
std::string* explanation) const
{
auto context = m_context.lock();
const auto& architecture = context->targetArchitecture();
WaitCount retval;
// No wait required before LDS reads as the wait happens before LDS barriers
if(GPUInstructionInfo::isLDSRead(inst.getOpCode()))
return retval;
if(inst.getOpCode().size() > 0 && inst.hasRegisters())
{
for(int i = 0; i < static_cast<int>(GPUWaitQueue::Count); i++)
{
GPUWaitQueue waitQueue = static_cast<GPUWaitQueue>(i);
for(int queue_i = m_instructionQueues.at(waitQueue).size() - 1; queue_i >= 0;
queue_i--)
{
if(inst.isAfterWriteDependency(m_instructionQueues.at(waitQueue)[queue_i]))
{
if(m_needsWaitZero.at(waitQueue))
{
retval.combine(WaitCount(architecture, waitQueue, 0));
if(explanation != nullptr)
{
*explanation += "WaitCnt Needed: Intersects with registers in '"
+ toString(waitQueue)
+ "', which needs a wait zero.\n";
}
}
else
{
int waitval
= m_instructionQueues.at(waitQueue).size() - (queue_i + 1);
retval.combine(WaitCount(architecture, waitQueue, waitval));
if(explanation != nullptr)
{
*explanation += "WaitCnt Needed: Intersects with registers in '"
+ toString(waitQueue) + "', at "
+ std::to_string(queue_i)
+ " and the queue size is "
+ std::to_string(
m_instructionQueues.at(waitQueue).size())
+ ", so a waitcnt of " + std::to_string(waitval)
+ " is required.\n";
}
}
break;
}
}
}
}
return retval;
}
WaitCount WaitcntObserver::computeWaitCount(Instruction const& inst,
std::string* explanation) const
{
auto context = m_context.lock();
const auto& architecture = context->targetArchitecture();
WaitCount retval = computeZeroBarrierWaitCount(inst, explanation);
retval.combine(computeSyncQueueWaitCount(inst, explanation));
retval.combine(computeRegisterWaitCount(inst, explanation));
return retval.getAsSaturatedWaitCount(architecture);
}
}
}