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

699 lines
23 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/KernelGraph/ControlGraph/ControlFlowRWTracer.hpp>
#include <rocRoller/KernelGraph/Utils.hpp>
namespace rocRoller::KernelGraph
{
using namespace CoordinateGraph;
using namespace ControlGraph;
namespace CT = rocRoller::KernelGraph::CoordinateGraph;
std::string toString(ControlFlowRWTracer::ReadWriteRecord const& record)
{
return fmt::format(
"ctrl {} {} coord {}", record.control, toString(record.rw), record.coordinate);
}
/**
* @brief Collect all coordinate tags referenced in an Expression.
*/
struct CollectDataFlowExpressionVisitor
{
std::set<int> tags;
template <Expression::CUnary Expr>
void operator()(Expr const& expr)
{
call(expr.arg);
}
template <Expression::CBinary Expr>
void operator()(Expr const& expr)
{
call(expr.lhs);
call(expr.rhs);
}
void operator()(Expression::ScaledMatrixMultiply const& expr)
{
call(expr.matA);
call(expr.matB);
call(expr.matC);
call(expr.scaleA);
call(expr.scaleB);
}
template <Expression::CTernary Expr>
void operator()(Expr const& expr)
{
call(expr.lhs);
call(expr.r1hs);
call(expr.r2hs);
}
template <Expression::CNary Expr>
void operator()(Expr const& expr)
{
std::ranges::for_each(expr.operands, [this](auto const& op) { call(op); });
}
void operator()(Expression::DataFlowTag const& expr)
{
tags.insert(expr.tag);
}
template <Expression::CValue Value>
void operator()(Value const& expr)
{
}
void call(Expression::ExpressionPtr expr)
{
if(expr)
{
std::visit(*this, *expr);
}
}
};
ControlFlowRWTracer::ControlFlowRWTracer(KernelGraph const& graph,
int start,
bool trackConnections)
: m_graph(graph)
, m_trackConnections(trackConnections)
{
TIMER(t, "ControlFlowRWTracer")
if(start == -1)
trace();
else
trace(start);
}
void ControlFlowRWTracer::trace()
{
auto candidates = m_graph.control.roots().to<std::set>();
generate(candidates);
}
void ControlFlowRWTracer::trace(int start)
{
auto body = m_graph.control.getOutputNodeIndices<Body>(start).to<std::set>();
generate(body);
}
std::vector<ControlFlowRWTracer::ReadWriteRecord>
ControlFlowRWTracer::coordinatesReadWrite() const
{
return m_trace;
}
std::vector<ControlFlowRWTracer::ReadWriteRecord>
ControlFlowRWTracer::coordinatesReadWrite(int coordinate) const
{
std::vector<ControlFlowRWTracer::ReadWriteRecord> rv;
std::copy_if(m_trace.begin(),
m_trace.end(),
std::back_inserter(rv),
[coordinate](ControlFlowRWTracer::ReadWriteRecord x) {
return coordinate == x.coordinate;
});
return rv;
}
std::set<int> ControlFlowRWTracer::getCoordinateDependencies(int coordinate) const
{
if(!m_dependenciesBuilt)
{
Throw<FatalError>("Dependencies not built. Call buildDependencies() first.");
}
auto it = m_dependencies.find(coordinate);
if(it != m_dependencies.end())
{
return it->second;
}
return {};
}
//
// This assumes that: the trace is ordered and records for the
// same control operation are consecutive.
//
void ControlFlowRWTracer::buildDependencies()
{
if(m_dependenciesBuilt)
return;
m_dependencies.clear();
// Single pass through the trace to build backward dependencies
auto it = m_trace.begin();
while(it != m_trace.end())
{
int currentControl = it->control;
std::unordered_set<int> writtenCoords;
std::unordered_set<int> readCoords;
// Collect all reads and writes for the current operation
while(it != m_trace.end() && it->control == currentControl)
{
if(it->rw == WRITE || it->rw == READWRITE)
{
writtenCoords.insert(it->coordinate);
}
if(it->rw == READ || it->rw == READWRITE)
{
readCoords.insert(it->coordinate);
}
++it;
}
// For each coordinate being written
for(int written : writtenCoords)
{
// Collect all coordinates this write depends on (including transitive)
std::unordered_set<int> allDeps;
for(int read : readCoords)
{
if(written == read)
continue;
allDeps.insert(read);
// Add transitive dependencies
if(m_dependencies.contains(read))
allDeps.insert(m_dependencies[read].begin(), m_dependencies[read].end());
}
// Update backward dependencies for the written coordinate
m_dependencies[written].insert(allDeps.begin(), allDeps.end());
}
}
m_dependenciesBuilt = true;
}
std::vector<ControlFlowRWTracer::ReadWriteRecord> ControlFlowRWTracer::opReadWrite(int op) const
{
std::vector<ControlFlowRWTracer::ReadWriteRecord> rv;
std::copy_if(m_trace.begin(),
m_trace.end(),
std::back_inserter(rv),
[op](ControlFlowRWTracer::ReadWriteRecord x) { return op == x.control; });
return rv;
}
void ControlFlowRWTracer::trackRegister(int control, int coordinate, ReadWrite rw)
{
AssertFatal(control > 0 && coordinate > 0,
ShowValue(control),
ShowValue(coordinate),
ShowValue(rw));
m_trace.push_back({control, coordinate, rw});
if(m_graph.coordinates.getElementType(coordinate) == Graph::ElementType::Node)
{
for(auto indexCoord :
m_graph.coordinates.getOutputNodeIndices(coordinate, CT::isEdge<CT::Index>))
trackRegister(control, indexCoord, rw);
for(auto segmentCoord :
m_graph.coordinates.getOutputNodeIndices(coordinate, CT::isEdge<CT::Segment>))
trackRegister(control, segmentCoord, rw);
}
}
void ControlFlowRWTracer::trackConnections(int control,
std::unordered_set<int> const& except,
ReadWrite rw)
{
if(!m_trackConnections)
return;
if(control < 0)
return;
for(auto const& c : m_graph.mapper.getConnections(control))
{
if(except.contains(c.coordinate))
continue;
if(m_graph.coordinates.exists(c.coordinate))
m_trace.push_back({control, c.coordinate, rw});
}
}
// It can be removed once trackConnections can be used
void ControlFlowRWTracer::trackOffsetAndStride(int control, ReadWrite rw)
{
AssertFatal(control > 0);
for(auto const& c : m_graph.mapper.getConnections(control))
{
auto maybeStride = m_graph.coordinates.get<Stride>(c.coordinate).has_value();
auto maybeOffset = m_graph.coordinates.get<Offset>(c.coordinate).has_value();
if(maybeStride || maybeOffset)
{
trackRegister(control, c.coordinate, rw);
}
}
}
void ControlFlowRWTracer::trackBuffer(int control, ReadWrite rw)
{
AssertFatal(control > 0);
for(auto const& c : m_graph.mapper.getConnections(control))
{
if(m_graph.coordinates.get<Buffer>(c.coordinate).has_value())
trackRegister(control, c.coordinate, rw);
}
}
void ControlFlowRWTracer::trackTDM(int control, ReadWrite rw)
{
AssertFatal(control > 0);
for(auto const& c : m_graph.mapper.getConnections(control))
{
if(m_graph.coordinates.get<TDM>(c.coordinate).has_value())
trackRegister(control, c.coordinate, rw);
}
}
bool ControlFlowRWTracer::hasGeneratedInputs(int const& tag)
{
auto inputs = m_graph.control.getInputNodeIndices<Sequence>(tag);
for(auto const& input : inputs)
{
if(m_completedControlNodes.find(input) == m_completedControlNodes.end())
return false;
}
return true;
}
void ControlFlowRWTracer::generate(std::set<int> candidates)
{
while(!candidates.empty())
{
std::set<int> nodes;
// Find all candidate nodes whose inputs have been satisfied
for(auto const& tag : candidates)
if(hasGeneratedInputs(tag))
nodes.insert(tag);
// If there are none, we have a problem.
AssertRecoverable(!nodes.empty(),
"ControlFlowRWTracer:Invalid control graph!",
ShowValue(m_graph.control),
ShowValue(candidates),
ShowValue(m_completedControlNodes));
// Visit all the nodes we found.
for(auto const& tag : nodes)
{
auto op = m_graph.control.getNode(tag);
call(op, tag);
}
// Add output nodes to candidates.
for(auto const& tag : nodes)
{
auto outTags = m_graph.control.getOutputNodeIndices<Sequence>(tag);
candidates.insert(outTags.begin(), outTags.end());
}
// Delete generated nodes from candidates.
for(auto const& node : nodes)
candidates.erase(node);
}
}
void ControlFlowRWTracer::call(Operation const& op, int tag)
{
std::visit(*this, op, std::variant<int>(tag));
m_completedControlNodes.insert(tag);
}
void ControlFlowRWTracer::operator()(Assign const& op, int tag)
{
CollectDataFlowExpressionVisitor visitor;
visitor.call(op.expression);
for(auto src : visitor.tags)
{
trackRegister(tag, src, ReadWrite::READ);
}
auto dst = m_graph.mapper.getConnections(tag)[0].coordinate;
trackRegister(tag, dst, ReadWrite::WRITE);
}
void ControlFlowRWTracer::operator()(Barrier const& op, int tag)
{
for(auto const& c : m_graph.mapper.getConnections(tag))
{
m_trace.push_back({tag, c.coordinate, ReadWrite::READ});
}
}
void ControlFlowRWTracer::operator()(ConditionalOp const& op, int tag)
{
CollectDataFlowExpressionVisitor visitor;
visitor.call(op.condition);
for(auto src : visitor.tags)
{
trackRegister(tag, src, ReadWrite::READ);
}
auto trueBody = m_graph.control.getOutputNodeIndices<Body>(tag).to<std::set>();
generate(trueBody);
auto falseBody = m_graph.control.getOutputNodeIndices<Else>(tag).to<std::set>();
generate(falseBody);
}
void ControlFlowRWTracer::operator()(AssertOp const& op, int tag)
{
CollectDataFlowExpressionVisitor visitor;
visitor.call(op.condition);
for(auto src : visitor.tags)
{
trackRegister(tag, src, ReadWrite::READ);
}
}
void ControlFlowRWTracer::operator()(Deallocate const& op, int tag) {}
void ControlFlowRWTracer::operator()(DoWhileOp const& op, int tag)
{
CollectDataFlowExpressionVisitor visitor;
visitor.call(op.condition);
for(auto src : visitor.tags)
{
trackRegister(tag, src, ReadWrite::READ);
}
auto body = m_graph.control.getOutputNodeIndices<Body>(tag).to<std::set>();
generate(body);
}
void ControlFlowRWTracer::operator()(ForLoopOp const& op, int tag)
{
//
// Don't examine for loop intialize or increment operations.
//
// Assign operations within loop initialisation operations
// are scoped already.
//
// Assign operations within loop increment operations
// typically involve: incrementing loop counters and
// offsets. Loop counters are scoped already.
//
// Offsets are created by AssignIndexExpressions and are
// used in other nodes like LoadTiled. These
// references do not explicitly appear in the graph.
//
// If we examine loop increment operations and "track" an
// offset increment, but don't track it during loads, then
// a Deallocate node would be mis-placed.
//
// A few solutions:
//
// 1. Don't examine loop increment operations. They
// already appear in Scopes so are deallocated regardless.
// Fairly easy but perhaps we miss an opporunity to free
// up registers early.
//
// 2. Teach the tracker how to dig into all nodes. Very
// tedious and not future-proof.
//
// 3. Expose all references in the graph. Ideal but we
// aren't there yet.
//
// auto init = m_graph.control.getOutputNodeIndices<Initialize>(tag).to<std::set>();
// generate(init);
auto incr = m_graph.control.getOutputNodeIndices<ForLoopIncrement>(tag).to<std::set>();
generate(incr);
CollectDataFlowExpressionVisitor visitor;
visitor.call(op.condition);
for(auto src : visitor.tags)
{
trackRegister(tag, src, ReadWrite::READ);
}
auto body = m_graph.control.getOutputNodeIndices<Body>(tag).to<std::set>();
generate(body);
}
void ControlFlowRWTracer::operator()(Kernel const& op, int tag)
{
auto body = m_graph.control.getOutputNodeIndices<Body>(tag).to<std::set>();
generate(body);
}
void ControlFlowRWTracer::operator()(LoadLDSTile const& op, int tag)
{
auto dst = m_graph.mapper.get<MacroTile>(tag);
auto lds = m_graph.mapper.get<LDS>(tag);
dst = only(m_graph.coordinates.getInputNodeIndices(dst, CT::isEdge<CT::View>))
.value_or(dst);
lds = only(m_graph.coordinates.getOutputNodeIndices(lds, CT::isEdge<CT::View>))
.value_or(lds);
trackRegister(tag, lds, ReadWrite::READ);
trackConnections(tag, {dst, lds}, ReadWrite::READ);
trackRegister(tag, dst, ReadWrite::WRITE);
trackOffsetAndStride(tag, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(LoadLinear const& op, int tag)
{
auto dst = m_graph.mapper.get<Linear>(tag);
trackConnections(tag, {dst}, ReadWrite::READ);
trackRegister(tag, dst, ReadWrite::WRITE);
}
void ControlFlowRWTracer::operator()(SeedPRNG const& op, int tag)
{
// Tracking read/write from/to the VGPR that stores the seed of random number generator
// This VGPR should be deallocated by tracer when it is no longer being used.
auto seedVGPR = m_graph.mapper.get(tag, NaryArgument::DEST);
trackRegister(tag, seedVGPR, ReadWrite::WRITE);
trackConnections(tag, {seedVGPR}, ReadWrite::READ);
auto rhs = m_graph.mapper.get(tag, NaryArgument::RHS);
trackRegister(tag, rhs, ReadWrite::READ);
trackConnections(tag, {rhs}, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(LoadTiled const& op, int tag)
{
auto user = m_graph.mapper.get<User>(tag);
auto dst = m_graph.mapper.get<MacroTile>(tag);
dst = only(m_graph.coordinates.getInputNodeIndices(dst, CT::isEdge<CT::View>))
.value_or(dst);
trackRegister(tag, user, ReadWrite::READ);
trackRegister(tag, dst, ReadWrite::WRITE);
trackConnections(tag, {user, dst}, ReadWrite::READ);
trackOffsetAndStride(tag, ReadWrite::READ);
trackBuffer(tag, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(LoadVGPR const& op, int tag)
{
auto dst = m_graph.mapper.get<VGPR>(tag);
trackConnections(tag, {dst}, ReadWrite::READ);
trackRegister(tag, dst, ReadWrite::WRITE);
}
void ControlFlowRWTracer::operator()(LoadSGPR const& op, int tag)
{
auto dst = m_graph.mapper.get<VGPR>(tag);
trackConnections(tag, {dst}, ReadWrite::READ);
trackRegister(tag, dst, ReadWrite::WRITE);
}
void ControlFlowRWTracer::operator()(Multiply const& op, int tag)
{
auto a = m_graph.mapper.get(tag, Connections::typeArgument<MacroTile>(NaryArgument::LHS));
trackRegister(tag, a, ReadWrite::READ);
if(op.scaleA == Operations::ScaleMode::Separate)
{
auto aScale = m_graph.mapper.get(
tag, Connections::typeArgument<MacroTile>(NaryArgument::LHS_SCALE));
trackRegister(tag, aScale, ReadWrite::READ);
}
else if(op.scaleA == Operations::ScaleMode::SingleScale)
{
auto aScale = m_graph.mapper.get(tag, NaryArgument::LHS_SCALE);
AssertFatal(aScale != -1);
trackRegister(tag, aScale, ReadWrite::READ);
}
auto b = m_graph.mapper.get(tag, Connections::typeArgument<MacroTile>(NaryArgument::RHS));
trackRegister(tag, b, ReadWrite::READ);
if(op.scaleB == Operations::ScaleMode::Separate)
{
auto bScale = m_graph.mapper.get(
tag, Connections::typeArgument<MacroTile>(NaryArgument::RHS_SCALE));
trackRegister(tag, bScale, ReadWrite::READ);
}
else if(op.scaleB == Operations::ScaleMode::SingleScale)
{
auto bScale = m_graph.mapper.get(tag, NaryArgument::RHS_SCALE);
AssertFatal(bScale != -1);
trackRegister(tag, bScale, ReadWrite::READ);
}
auto dst
= m_graph.mapper.get(tag, Connections::typeArgument<MacroTile>(NaryArgument::DEST));
trackRegister(tag, dst, ReadWrite::READWRITE);
}
void ControlFlowRWTracer::operator()(NOP const& op, int tag)
{
auto body = m_graph.control.getOutputNodeIndices<Body>(tag).to<std::set>();
generate(body);
}
void ControlFlowRWTracer::operator()(Block const& op, int tag)
{
auto body = m_graph.control.getOutputNodeIndices<Body>(tag).to<std::set>();
generate(body);
}
void ControlFlowRWTracer::operator()(Scope const& op, int tag)
{
auto body = m_graph.control.getOutputNodeIndices<Body>(tag).to<std::set>();
generate(body);
}
void ControlFlowRWTracer::operator()(SetCoordinate const& op, int tag)
{
auto body = m_graph.control.getOutputNodeIndices<Body>(tag).to<std::set>();
generate(body);
}
void ControlFlowRWTracer::operator()(StoreLDSTile const& op, int tag)
{
auto dst = m_graph.mapper.get<MacroTile>(tag);
auto lds = m_graph.mapper.get<LDS>(tag);
trackRegister(tag, dst, ReadWrite::READ);
trackConnections(tag, {dst, lds}, ReadWrite::READ);
trackRegister(tag, lds, ReadWrite::WRITE);
trackOffsetAndStride(tag, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(LoadTileDirect2LDS const& op, int tag)
{
auto source = m_graph.mapper.get<MacroTile>(tag);
auto dst = m_graph.mapper.get<LDS>(tag);
trackRegister(tag, source, ReadWrite::READ);
trackRegister(tag, dst, ReadWrite::WRITE);
trackConnections(tag, {source, dst}, ReadWrite::READ);
trackOffsetAndStride(tag, ReadWrite::READ);
trackBuffer(tag, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(LoadTiledTDMToLDS const& op, int tag)
{
auto source = m_graph.mapper.get<MacroTile>(tag);
auto dst = m_graph.mapper.get<LDS>(tag);
trackRegister(tag, source, ReadWrite::READ);
trackRegister(tag, dst, ReadWrite::WRITE);
trackConnections(tag, {source, dst}, ReadWrite::READ);
trackOffsetAndStride(tag, ReadWrite::READ);
trackTDM(tag, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(StoreLinear const& op, int tag)
{
auto src = m_graph.mapper.get<Linear>(tag);
trackRegister(tag, src, ReadWrite::READ);
trackConnections(tag, {src}, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(StoreTiled const& op, int tag)
{
auto src = m_graph.mapper.get<MacroTile>(tag);
src = only(m_graph.coordinates.getOutputNodeIndices(src, CT::isEdge<CT::View>))
.value_or(src);
trackRegister(tag, src, ReadWrite::READ);
trackConnections(tag, {src}, ReadWrite::READ);
trackOffsetAndStride(tag, ReadWrite::READ);
trackBuffer(tag, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(StoreVGPR const& op, int tag)
{
auto src = m_graph.mapper.get<VGPR>(tag);
trackRegister(tag, src, ReadWrite::READ);
trackConnections(tag, {src}, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(StoreSGPR const& op, int tag)
{
auto src = m_graph.mapper.get<VGPR>(tag);
trackRegister(tag, src, ReadWrite::READ);
trackConnections(tag, {src}, ReadWrite::READ);
}
void ControlFlowRWTracer::operator()(TensorContraction const& op, int tag) {}
void ControlFlowRWTracer::operator()(UnrollOp const& op, int tag)
{
Throw<FatalError>("ControlFlowRWTracer UnrollOp not implemented yet.");
}
void ControlFlowRWTracer::operator()(WaitZero const& op, int tag) {}
void ControlFlowRWTracer::operator()(Exchange const& op, int tag)
{
auto src = m_graph.mapper.get<MacroTile>(tag);
trackRegister(tag, src, ReadWrite::READ);
auto dst = m_graph.mapper.get(tag, NaryArgument::DEST);
trackRegister(tag, dst, ReadWrite::READWRITE);
}
std::string toString(ControlFlowRWTracer::ReadWrite rw)
{
switch(rw)
{
case ControlFlowRWTracer::READ:
return "READ";
case ControlFlowRWTracer::WRITE:
return "WRITE";
case ControlFlowRWTracer::READWRITE:
return "READWRITE";
case ControlFlowRWTracer::Count:
break;
}
Throw<FatalError>("Invalid ReadWrite.");
}
std::ostream& operator<<(std::ostream& stream, ControlFlowRWTracer::ReadWrite rw)
{
return stream << toString(rw);
}
}