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
336 lines
11 KiB
C++
336 lines
11 KiB
C++
// Copyright Advanced Micro Devices, Inc., or its affiliates.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include <rocRoller/Expression.hpp>
|
|
#include <rocRoller/Graph/Hypergraph.hpp>
|
|
#include <rocRoller/KernelGraph/ControlGraph/ControlFlowRWTracer.hpp>
|
|
#include <rocRoller/KernelGraph/KernelGraph.hpp>
|
|
#include <rocRoller/KernelGraph/Transforms/HoistLoopInvariant.hpp>
|
|
#include <rocRoller/KernelGraph/Transforms/HoistLoopInvariant_detail.hpp>
|
|
#include <rocRoller/KernelGraph/Utils.hpp>
|
|
#include <rocRoller/KernelGraph/Visitors.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
namespace rocRoller::KernelGraph
|
|
{
|
|
using namespace ControlGraph;
|
|
using namespace CoordinateGraph;
|
|
|
|
/**
|
|
* @brief Visitor for extracting DataFlowTags from expressions
|
|
*
|
|
* This visitor traverses an expression tree and collects all DataFlowTag
|
|
* references found within it.
|
|
*/
|
|
struct DataFlowTagExtractorVisitor
|
|
{
|
|
std::set<int> tags;
|
|
|
|
void operator()(Expression::DataFlowTag const& expr)
|
|
{
|
|
tags.insert(expr.tag);
|
|
}
|
|
|
|
void operator()(Expression::ScaledMatrixMultiply const& expr)
|
|
{
|
|
call(expr.matA);
|
|
call(expr.matB);
|
|
call(expr.matC);
|
|
call(expr.scaleA);
|
|
call(expr.scaleB);
|
|
}
|
|
|
|
template <Expression::CNary Expr>
|
|
void operator()(Expr const& expr)
|
|
{
|
|
for(auto const& operand : expr.operands)
|
|
{
|
|
call(operand);
|
|
}
|
|
}
|
|
|
|
template <Expression::CTernary Expr>
|
|
void operator()(Expr const& expr)
|
|
{
|
|
call(expr.lhs);
|
|
call(expr.r1hs);
|
|
call(expr.r2hs);
|
|
}
|
|
|
|
template <Expression::CBinary Expr>
|
|
void operator()(Expr const& expr)
|
|
{
|
|
call(expr.lhs);
|
|
call(expr.rhs);
|
|
}
|
|
|
|
template <Expression::CUnary Expr>
|
|
void operator()(Expr const& expr)
|
|
{
|
|
call(expr.arg);
|
|
}
|
|
|
|
template <Expression::CValue Value>
|
|
void operator()(Value const&)
|
|
{
|
|
// DataFlowTag already matched separately
|
|
}
|
|
|
|
void call(Expression::ExpressionPtr const& expr)
|
|
{
|
|
if(!expr)
|
|
return;
|
|
std::visit(*this, *expr);
|
|
}
|
|
|
|
void call(Expression::Expression const& expr)
|
|
{
|
|
std::visit(*this, expr);
|
|
}
|
|
};
|
|
|
|
std::set<int> extractDataFlowTags(Expression::Expression const& expr)
|
|
{
|
|
DataFlowTagExtractorVisitor visitor;
|
|
visitor.call(expr);
|
|
return visitor.tags;
|
|
}
|
|
|
|
// If the control node is inside a loop, return the loop node, else std::nullopt
|
|
std::optional<int> getParentLoop(KernelGraph const& graph, int control)
|
|
{
|
|
auto stack = controlStack(control, graph);
|
|
for(auto it = stack.rbegin(); it != stack.rend(); ++it)
|
|
{
|
|
int node = *it;
|
|
if(graph.control.get<ForLoopOp>(node).has_value()
|
|
|| graph.control.get<DoWhileOp>(node).has_value())
|
|
{
|
|
return node;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
CoordinateToLoops buildCoordinateLoopMapping(KernelGraph const& graph,
|
|
ControlFlowRWTracer const& tracer)
|
|
{
|
|
CoordinateToLoops result;
|
|
|
|
const auto records = tracer.coordinatesReadWrite();
|
|
|
|
for(const auto& record : records)
|
|
{
|
|
Log::debug("Processing record: coordinate {}, control node {}, rw {}",
|
|
record.coordinate,
|
|
record.control,
|
|
static_cast<int>(record.rw));
|
|
|
|
if(record.rw == ControlFlowRWTracer::READ)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto containingLoop = getParentLoop(graph, record.control);
|
|
if(containingLoop.has_value())
|
|
{
|
|
result[record.coordinate][containingLoop.value()].insert(record.control);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int hoistNodeBeforeLoop(KernelGraph& kgraph, int nodeToHoist, int loopNode)
|
|
{
|
|
int hoistedNode = duplicateControlNode(kgraph, nodeToHoist);
|
|
Log::debug("HoistLoopInvariant: Hoisted node {} to new node {}", nodeToHoist, hoistedNode);
|
|
insertBefore(kgraph, loopNode, hoistedNode, hoistedNode);
|
|
kgraph.control.setElement(nodeToHoist, NOP{});
|
|
kgraph.mapper.purge(nodeToHoist);
|
|
return hoistedNode;
|
|
}
|
|
|
|
KernelGraph HoistLoopInvariant::apply(KernelGraph const& original)
|
|
{
|
|
auto graph = original;
|
|
|
|
ControlFlowRWTracer tracer(graph);
|
|
const auto mapping = buildCoordinateLoopMapping(graph, tracer);
|
|
|
|
for(const auto& [coordinate, loopGroups] : mapping)
|
|
{
|
|
for(const auto& [loopNode, controlNodes] : loopGroups)
|
|
{
|
|
AssertFatal(loopNode >= 0);
|
|
|
|
// Skip if more than one write in the loop body
|
|
// As currently does not handle multiple reaching definitions (e.g. branch within loop body)
|
|
if(controlNodes.size() != 1
|
|
|| countCoordinateWritesInLoop(graph, loopNode, coordinate, tracer) != 1)
|
|
{
|
|
Log::debug(
|
|
"HoistLoopInvariant: skipping {} with {} writers in immediate loop body {}",
|
|
coordinate,
|
|
controlNodes.size(),
|
|
loopNode);
|
|
continue;
|
|
}
|
|
|
|
int controlNode = *controlNodes.begin();
|
|
|
|
// Check for read-before-write pattern in the loop
|
|
// This prevents incorrect hoisting when a read depends on the value from a previous iteration
|
|
if(hasReadBeforeWriteInLoop(graph, loopNode, coordinate, controlNode, tracer))
|
|
{
|
|
Log::debug("HoistLoopInvariant: skipping {} due to read-before-write pattern "
|
|
"in loop {}",
|
|
coordinate,
|
|
loopNode);
|
|
continue;
|
|
}
|
|
|
|
auto maybeAssign = graph.control.get<Assign>(controlNode);
|
|
if(!maybeAssign.has_value())
|
|
{
|
|
// Skip LoadLDSTile, StoreLDSTile, etc.
|
|
// These nodes are more purposefully placed and are unlikely to be beneficial to hoist
|
|
Log::debug("HoistLoopInvariant: skipping {}",
|
|
Graph::variantToString(graph.control.getElement(controlNode)));
|
|
continue;
|
|
}
|
|
|
|
auto assignNode = maybeAssign.value();
|
|
|
|
auto usedTags = extractDataFlowTags(*assignNode.expression);
|
|
|
|
bool allTagsLoopInvariant = true;
|
|
for(auto tag : usedTags)
|
|
{
|
|
if(countCoordinateWritesInLoop(graph, loopNode, tag, tracer) > 0)
|
|
{
|
|
Log::debug(
|
|
"HoistLoopInvariant: DataFlowTag {} is written in loop {}, not "
|
|
"invariant",
|
|
tag,
|
|
loopNode);
|
|
allTagsLoopInvariant = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!allTagsLoopInvariant)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
bool coordinateUsedAfterLoop = false;
|
|
const auto readingControlNodes = [&]() -> Generator<int> {
|
|
for(const auto record : tracer.coordinatesReadWrite(coordinate))
|
|
{
|
|
if(record.rw == ControlFlowRWTracer::READ)
|
|
{
|
|
co_yield record.control;
|
|
}
|
|
}
|
|
}()
|
|
.to<std::set>();
|
|
|
|
// If coordinate is read after loop, hoisting with a zero-iteration loop changes behavior
|
|
for(int nodeAfterLoop : graph.control.nodesAfter(loopNode))
|
|
{
|
|
if(readingControlNodes.find(nodeAfterLoop) != readingControlNodes.end())
|
|
{
|
|
coordinateUsedAfterLoop = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!coordinateUsedAfterLoop)
|
|
{
|
|
Log::debug("HoistLoopInvariant, hoisting {}, {}, to, {}, {}",
|
|
controlNode,
|
|
Graph::variantToString(graph.control.getElement(controlNode)),
|
|
loopNode,
|
|
Graph::variantToString(graph.control.getElement(loopNode)));
|
|
|
|
hoistNodeBeforeLoop(graph, controlNode, loopNode);
|
|
}
|
|
}
|
|
}
|
|
return graph;
|
|
}
|
|
|
|
std::string HoistLoopInvariant::name() const
|
|
{
|
|
return "HoistLoopInvariant";
|
|
}
|
|
|
|
int countCoordinateWritesInLoop(KernelGraph const& kgraph,
|
|
int loopNode,
|
|
int coordinate,
|
|
ControlFlowRWTracer const& tracer)
|
|
{
|
|
auto records = tracer.coordinatesReadWrite(coordinate);
|
|
int writeCount = 0;
|
|
|
|
for(const auto& record : records)
|
|
{
|
|
if(record.rw == ControlFlowRWTracer::WRITE
|
|
|| record.rw == ControlFlowRWTracer::READWRITE)
|
|
{
|
|
auto stack = controlStack(record.control, kgraph);
|
|
|
|
if(std::find(stack.begin(), stack.end(), loopNode) != stack.end())
|
|
{
|
|
writeCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return writeCount;
|
|
}
|
|
|
|
bool hasReadBeforeWriteInLoop(KernelGraph const& graph,
|
|
int loopNode,
|
|
int coordinate,
|
|
int writeControlNode,
|
|
ControlFlowRWTracer const& tracer)
|
|
{
|
|
auto records = tracer.coordinatesReadWrite(coordinate);
|
|
|
|
std::vector<int> readNodes;
|
|
for(const auto& record : records)
|
|
{
|
|
if(record.rw == ControlFlowRWTracer::READ)
|
|
{
|
|
auto stack = controlStack(record.control, graph);
|
|
if(std::find(stack.begin(), stack.end(), loopNode) != stack.end())
|
|
{
|
|
readNodes.push_back(record.control);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if any read node comes before the write node (within loop)
|
|
for(int readNode : readNodes)
|
|
{
|
|
if(readNode == writeControlNode)
|
|
{
|
|
continue;
|
|
}
|
|
auto ordering = graph.control.compareNodes(
|
|
rocRoller::UseCacheIfAvailable, readNode, writeControlNode);
|
|
if(ordering == NodeOrdering::LeftFirst)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|