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
421 lines
16 KiB
C++
421 lines
16 KiB
C++
// Copyright Advanced Micro Devices, Inc., or its affiliates.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#pragma once
|
|
|
|
#include <rocRoller/KernelGraph/ControlGraph/LastRWTracer.hpp>
|
|
#include <rocRoller/KernelGraph/Utils.hpp>
|
|
|
|
namespace rocRoller::KernelGraph
|
|
{
|
|
/**
|
|
* @brief Return DataFlowTag of LHS of binary expression in Assign node.
|
|
*/
|
|
template <Expression::CBinary T>
|
|
std::tuple<int, Expression::ExpressionPtr> getBinaryLHS(KernelGraph const& kgraph, int assign)
|
|
{
|
|
auto op = kgraph.control.get<ControlGraph::Assign>(assign);
|
|
|
|
auto expr = std::get<T>(*op->expression);
|
|
if(!std::holds_alternative<Expression::DataFlowTag>(*expr.lhs))
|
|
return {-1, nullptr};
|
|
auto tag = std::get<Expression::DataFlowTag>(*expr.lhs).tag;
|
|
return {tag, expr.lhs};
|
|
}
|
|
|
|
/**
|
|
* @brief Return DataFlowTag of RHS of binary expression in Assign node.
|
|
*/
|
|
template <Expression::CBinary T>
|
|
std::tuple<int, Expression::ExpressionPtr> getBinaryRHS(KernelGraph const& kgraph, int assign)
|
|
{
|
|
auto op = kgraph.control.get<ControlGraph::Assign>(assign);
|
|
|
|
auto expr = std::get<T>(*op->expression);
|
|
if(!std::holds_alternative<Expression::DataFlowTag>(*expr.rhs))
|
|
return {-1, nullptr};
|
|
auto tag = std::get<Expression::DataFlowTag>(*expr.rhs).tag;
|
|
return {tag, expr.rhs};
|
|
}
|
|
|
|
template <Graph::Direction Direction, typename EdgeType>
|
|
std::optional<int> GetEdgeTag(KernelGraph const& graph, int tag)
|
|
{
|
|
for(auto elem : graph.coordinates.getNeighbours<Direction>(tag))
|
|
{
|
|
auto maybeEdge = graph.coordinates.get<EdgeType>(elem);
|
|
if(maybeEdge)
|
|
return elem;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
template <std::ranges::forward_range Range>
|
|
void purgeNodes(KernelGraph& kgraph, Range nodes)
|
|
{
|
|
for(int tag : nodes)
|
|
{
|
|
for(auto reap : kgraph.control.getNeighbours<Graph::Direction::Upstream>(tag))
|
|
{
|
|
kgraph.control.deleteElement(reap);
|
|
}
|
|
for(auto reap : kgraph.control.getNeighbours<Graph::Direction::Downstream>(tag))
|
|
{
|
|
kgraph.control.deleteElement(reap);
|
|
}
|
|
|
|
kgraph.control.deleteElement(tag);
|
|
kgraph.mapper.purge(tag);
|
|
}
|
|
}
|
|
|
|
template <CForwardRangeOf<int> Range>
|
|
std::optional<int>
|
|
getForLoopCoord(std::optional<int> forLoopOp, KernelGraph const& kgraph, Range within)
|
|
{
|
|
if(!forLoopOp)
|
|
return {};
|
|
|
|
auto forLoopCoord = kgraph.mapper.get<CoordinateGraph::ForLoop>(*forLoopOp);
|
|
|
|
if(std::find(within.cbegin(), within.cend(), forLoopCoord) != within.cend())
|
|
return forLoopCoord;
|
|
else
|
|
return {};
|
|
}
|
|
|
|
template <typename T>
|
|
std::unordered_set<int> filterCoordinates(auto const& candidates, KernelGraph const& kgraph)
|
|
{
|
|
std::unordered_set<int> rv;
|
|
for(auto candidate : candidates)
|
|
if(kgraph.coordinates.get<T>(candidate))
|
|
rv.insert(candidate);
|
|
return rv;
|
|
}
|
|
|
|
template <typename T>
|
|
std::optional<int> findContainingOperation(int candidate, KernelGraph const& kgraph)
|
|
{
|
|
namespace CF = rocRoller::KernelGraph::ControlGraph;
|
|
|
|
int lastTag = -1;
|
|
for(auto parent : kgraph.control.depthFirstVisit(candidate, Graph::Direction::Upstream))
|
|
{
|
|
bool containing = lastTag != -1 && kgraph.control.get<CF::Body>(lastTag);
|
|
lastTag = parent;
|
|
|
|
auto forLoop = kgraph.control.get<T>(parent);
|
|
if(forLoop && containing)
|
|
return parent;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
template <Graph::Direction direction>
|
|
void reconnect(KernelGraph& graph, int newop, int op)
|
|
{
|
|
auto neighbours = graph.control.getNeighbours<direction>(op);
|
|
for(auto const& tag : neighbours)
|
|
{
|
|
auto edge = graph.control.getElement(tag);
|
|
int node = *graph.control.getNeighbours<direction>(tag).begin();
|
|
graph.control.deleteElement(tag);
|
|
if(newop != -1)
|
|
{
|
|
if constexpr(direction == Graph::Direction::Upstream)
|
|
{
|
|
graph.control.addElement(edge, {node}, {newop});
|
|
}
|
|
else
|
|
{
|
|
graph.control.addElement(edge, {newop}, {node});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
std::optional<int> findTopOfContainingOperation(int candidate, KernelGraph const& kgraph)
|
|
{
|
|
namespace CF = rocRoller::KernelGraph::ControlGraph;
|
|
|
|
int lastTag = -1;
|
|
int lastOperation = candidate;
|
|
for(auto parent : kgraph.control.depthFirstVisit(candidate, Graph::Direction::Upstream))
|
|
{
|
|
bool containing = lastTag != -1 && kgraph.control.get<CF::Body>(lastTag);
|
|
lastTag = parent;
|
|
|
|
auto maybeT = kgraph.control.get<T>(parent);
|
|
if(maybeT && containing)
|
|
return lastOperation;
|
|
|
|
if(kgraph.control.getElementType(parent) == Graph::ElementType::Node)
|
|
lastOperation = parent;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
template <std::predicate<int> Predicate>
|
|
std::vector<int> duplicateControlNodes(KernelGraph& graph,
|
|
std::shared_ptr<GraphReindexer> reindexer,
|
|
std::vector<int> const& startNodes,
|
|
Predicate dontDuplicate)
|
|
{
|
|
namespace CT = rocRoller::KernelGraph::CoordinateGraph;
|
|
|
|
std::vector<int> newStartNodes;
|
|
|
|
if(reindexer == nullptr)
|
|
reindexer = std::make_shared<GraphReindexer>();
|
|
|
|
// Create duplicates of all of the nodes downstream of the startNodes
|
|
for(auto const& node :
|
|
graph.control.depthFirstVisit(startNodes, Graph::Direction::Downstream))
|
|
{
|
|
// Only do this step if element is a node
|
|
if(graph.control.getElementType(node) == Graph::ElementType::Node)
|
|
{
|
|
auto op = graph.control.addElement(graph.control.getElement(node));
|
|
reindexer->control[node] = op;
|
|
}
|
|
}
|
|
|
|
for(auto const& reindex : reindexer->control)
|
|
{
|
|
// Create all edges within new sub-graph
|
|
auto location = graph.control.getLocation(reindex.first);
|
|
for(auto const& output : location.outgoing)
|
|
{
|
|
int child
|
|
= *graph.control.getNeighbours<Graph::Direction::Downstream>(output).begin();
|
|
graph.control.addElement(graph.control.getElement(output),
|
|
{reindex.second},
|
|
{reindexer->control[child]});
|
|
}
|
|
|
|
// Use the same coordinate graph mappings
|
|
for(auto const& c : graph.mapper.getConnections(reindex.first))
|
|
{
|
|
auto coord = c.coordinate;
|
|
|
|
if(dontDuplicate(coord))
|
|
{
|
|
graph.mapper.connect(reindex.second, coord, c.connection);
|
|
reindexer->coordinates.insert_or_assign(coord, coord);
|
|
continue;
|
|
}
|
|
|
|
// If one of the mappings represents storage, duplicate it in the CoordinateGraph.
|
|
// This will allow the RegisterTagManager to recognize that the nodes are pointing
|
|
// to different data and to use different registers.
|
|
// Note: A PassThrough edge is added from any of the duplicate nodes to the original
|
|
// node.
|
|
auto maybeMacroTile
|
|
= graph.coordinates.get<CoordinateGraph::MacroTile>(c.coordinate);
|
|
auto maybeLDS = graph.coordinates.get<CoordinateGraph::LDS>(c.coordinate);
|
|
if(maybeMacroTile || maybeLDS)
|
|
{
|
|
if(reindexer->coordinates.count(c.coordinate) == 0)
|
|
{
|
|
auto dim = graph.coordinates.addElement(
|
|
graph.coordinates.getElement(c.coordinate));
|
|
reindexer->coordinates[c.coordinate] = dim;
|
|
auto duplicate = graph.coordinates
|
|
.getOutputNodeIndices(
|
|
coord, CT::isEdge<CoordinateGraph::Duplicate>)
|
|
.to<std::vector>();
|
|
|
|
auto origCoord = duplicate.empty() ? coord : duplicate[0];
|
|
graph.coordinates.addElement(
|
|
CoordinateGraph::Duplicate(), {dim}, {origCoord});
|
|
|
|
// Add View edges for new duplicated coordinates
|
|
auto outgoingViews
|
|
= graph.coordinates
|
|
.getOutputNodeIndices(coord, CT::isEdge<CoordinateGraph::View>)
|
|
.to<std::vector>();
|
|
if(!outgoingViews.empty() && reindexer->coordinates.count(outgoingViews[0]))
|
|
{
|
|
graph.coordinates.addElement(
|
|
CoordinateGraph::View(),
|
|
{dim},
|
|
{reindexer->coordinates[outgoingViews[0]]});
|
|
}
|
|
|
|
auto incomingViews
|
|
= graph.coordinates
|
|
.getInputNodeIndices(origCoord, CT::isEdge<CoordinateGraph::View>)
|
|
.to<std::vector>();
|
|
if(!incomingViews.empty() && reindexer->coordinates.count(incomingViews[0]))
|
|
{
|
|
graph.coordinates.addElement(CoordinateGraph::View(),
|
|
{reindexer->coordinates[incomingViews[0]]},
|
|
{dim});
|
|
}
|
|
}
|
|
coord = reindexer->coordinates[c.coordinate];
|
|
}
|
|
graph.mapper.connect(reindex.second, coord, c.connection);
|
|
}
|
|
}
|
|
|
|
// Change coordinate values in Expressions
|
|
for(auto const& pair : reindexer->control)
|
|
{
|
|
reindexExpressions(graph, pair.second, *reindexer);
|
|
}
|
|
|
|
// Return the new start nodes
|
|
for(auto const& startNode : startNodes)
|
|
{
|
|
newStartNodes.push_back(reindexer->control[startNode]);
|
|
}
|
|
|
|
return newStartNodes;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the first and last nodes from a set of nodes that are totally ordered
|
|
*/
|
|
template <typename T>
|
|
std::pair<int, int> getFirstAndLastNodes(KernelGraph const& graph, T const& nodes)
|
|
{
|
|
AssertFatal(not nodes.empty());
|
|
|
|
auto firstNode = *nodes.begin();
|
|
auto lastNode = *nodes.begin();
|
|
|
|
for(auto const& n : nodes)
|
|
{
|
|
using namespace rocRoller::KernelGraph::ControlGraph;
|
|
|
|
if(firstNode != n)
|
|
{
|
|
auto order = graph.control.compareNodes(rocRoller::IgnoreCache, firstNode, n);
|
|
// If this assertion fails, that means the nodes are not totally ordered
|
|
AssertFatal(order != NodeOrdering::Undefined, "nodes are not totally ordered");
|
|
if(order != NodeOrdering::LeftFirst and order != NodeOrdering::LeftInBodyOfRight)
|
|
{
|
|
firstNode = n;
|
|
}
|
|
}
|
|
|
|
if(lastNode != n)
|
|
{
|
|
auto order = graph.control.compareNodes(rocRoller::IgnoreCache, lastNode, n);
|
|
// If this assertion fails, that means the nodes are not totally ordered
|
|
AssertFatal(order != NodeOrdering::Undefined, "nodes are not totally ordered");
|
|
if(order == NodeOrdering::LeftFirst or order == NodeOrdering::LeftInBodyOfRight)
|
|
{
|
|
lastNode = n;
|
|
}
|
|
}
|
|
}
|
|
return {firstNode, lastNode};
|
|
}
|
|
|
|
template <typename EdgeType>
|
|
void connectAllPairs(std::vector<int> const& A, std::vector<int> const& B, KernelGraph& kg)
|
|
{
|
|
if(A.empty() or B.empty())
|
|
return;
|
|
|
|
for(auto const a : A)
|
|
for(auto const b : B)
|
|
kg.control.addElement(EdgeType(), {a}, {b});
|
|
}
|
|
|
|
template <ControlGraph::COperation SrcOpType, ControlGraph::COperation DstOpType>
|
|
requires(
|
|
(std::is_same_v<
|
|
SrcOpType,
|
|
ControlGraph::LoadTiled> && std::is_same_v<DstOpType, ControlGraph::StoreLDSTile>)
|
|
|| (std::is_same_v<
|
|
SrcOpType,
|
|
ControlGraph::StoreLDSTile> && std::is_same_v<DstOpType, ControlGraph::LoadTiled>))
|
|
std::vector<int> getAssociatedOps(KernelGraph const& kgraph, int srcOpTag)
|
|
{
|
|
using namespace ControlGraph;
|
|
using namespace CoordinateGraph;
|
|
|
|
const auto element = kgraph.control.getElement(srcOpTag);
|
|
AssertFatal(std::holds_alternative<Operation>(element),
|
|
concatenate("Expected Operation but got Edge", ShowValue(srcOpTag)));
|
|
|
|
const auto op = std::get<Operation>(element);
|
|
AssertFatal(std::holds_alternative<SrcOpType>(op),
|
|
fmt::format("Expected {} but got {}", typeName<SrcOpType>(), toString(op)));
|
|
|
|
auto macroTileTag = kgraph.mapper.get<MacroTile>(srcOpTag);
|
|
|
|
std::vector<int> rv{};
|
|
|
|
for(auto conn : kgraph.mapper.getCoordinateConnections(macroTileTag))
|
|
{
|
|
const auto dstOpTag = conn.control;
|
|
const auto element = kgraph.control.getElement(dstOpTag);
|
|
AssertFatal(std::holds_alternative<Operation>(element),
|
|
concatenate("Expected Operation but got Edge", ShowValue(dstOpTag)));
|
|
|
|
const auto op = std::get<Operation>(element);
|
|
if(std::holds_alternative<DstOpType>(op))
|
|
{
|
|
rv.push_back(dstOpTag);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
template <std::predicate<int> Predicate>
|
|
std::vector<std::pair<int, int>> getLoadTiledStoreLDSTilePairs(KernelGraph const& kgraph,
|
|
Predicate predicate)
|
|
{
|
|
using namespace ControlGraph;
|
|
using namespace CoordinateGraph;
|
|
|
|
std::vector<std::pair<int, int>> rv;
|
|
|
|
for(auto loadTiledTag : kgraph.control.findElements(predicate))
|
|
{
|
|
const auto storeLDSTags{
|
|
getAssociatedOps<LoadTiled, StoreLDSTile>(kgraph, loadTiledTag)};
|
|
|
|
if(storeLDSTags.size() == 1)
|
|
{
|
|
rv.push_back({loadTiledTag, storeLDSTags[0]});
|
|
}
|
|
else
|
|
{
|
|
AssertFatal(storeLDSTags.size() <= 2,
|
|
"getLoadTiledStoreLDSTilePairs: More than 2 ComputeIndex operation "
|
|
"required for StoreLDSTile.",
|
|
ShowValue(loadTiledTag),
|
|
ShowValue(storeLDSTags.size()));
|
|
for(const auto& storeLDS : storeLDSTags)
|
|
{
|
|
auto maybeForLoopOfLoad
|
|
= findContainingOperation<ForLoopOp>(loadTiledTag, kgraph);
|
|
auto maybeForLoopOfStore = findContainingOperation<ForLoopOp>(storeLDS, kgraph);
|
|
|
|
const auto isLoadInLoop = maybeForLoopOfLoad.has_value();
|
|
const auto isStoreInLoop = maybeForLoopOfStore.has_value();
|
|
|
|
const auto bothInSameLoop
|
|
= isLoadInLoop && isStoreInLoop
|
|
&& maybeForLoopOfLoad.value() == maybeForLoopOfStore.value();
|
|
|
|
const auto bothNotInLoop = not isLoadInLoop && not isStoreInLoop;
|
|
|
|
if(bothInSameLoop || bothNotInLoop)
|
|
{
|
|
rv.push_back({loadTiledTag, storeLDS});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
}
|