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

1030 lines
41 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <rocRoller/Expression.hpp>
#include <rocRoller/KernelGraph/Constraints.hpp>
#include <rocRoller/KernelGraph/KernelGraph.hpp>
#include <rocRoller/KernelGraph/Transforms/AddLDSBarriers.hpp>
using namespace rocRoller;
using namespace rocRoller::Expression;
using namespace rocRoller::KernelGraph::ControlGraph;
using namespace rocRoller::KernelGraph::CoordinateGraph;
using namespace Catch::Matchers;
namespace KG = rocRoller::KernelGraph;
namespace
{
/**
* @brief Helper to get the VerifyLDSBarriers constraint from AddLDSBarriers transform.
*/
std::vector<KG::GraphConstraint> getVerifyLDSBarriersConstraints()
{
KG::AddLDSBarriers transform;
return transform.postConstraints();
}
}
// =============================================================================
// Basic LDS barrier tests (no loops)
// =============================================================================
TEST_CASE("VerifyLDSBarriers - No LDS operations passes", "[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto assign = graph.control.addElement(
Assign{Register::Type::Vector, dataFlowTag(tile, Register::Type::Vector, DataType::Float)});
graph.control.addElement(Body(), {kernel}, {assign});
graph.mapper.connect(assign, tile, NaryArgument::DEST);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - LDS write and read without barrier fails",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK_FALSE(result.satisfied);
CHECK_THAT(result.explanation, ContainsSubstring("Missing LDS barrier"));
}
TEST_CASE("VerifyLDSBarriers - LDS write and read with LDS-connected barrier passes",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier});
graph.control.addElement(Sequence(), {barrier}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<LDS>(barrier, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - Barrier not connected to LDS is not considered",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto otherTile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier});
graph.control.addElement(Sequence(), {barrier}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
// Barrier connected to MacroTile, NOT LDS
graph.mapper.connect<MacroTile>(barrier, otherTile);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK_FALSE(result.satisfied);
CHECK_THAT(result.explanation, ContainsSubstring("Missing LDS barrier"));
}
TEST_CASE("VerifyLDSBarriers - Barrier connected to different LDS coordinate fails",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds1 = graph.coordinates.addElement(LDS());
auto lds2 = graph.coordinates.addElement(LDS()); // Different LDS coordinate
auto kernel = graph.control.addElement(Kernel());
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier});
graph.control.addElement(Sequence(), {barrier}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds1);
// Barrier connected to lds2, NOT lds1
graph.mapper.connect<LDS>(barrier, lds2);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds1);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
// The barrier is connected to LDS but to a DIFFERENT LDS coordinate,
// so it should fail.
CHECK_FALSE(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - Multiple LDS coordinates require barriers for each",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile1 = graph.coordinates.addElement(MacroTile());
auto tile2 = graph.coordinates.addElement(MacroTile());
auto lds1 = graph.coordinates.addElement(LDS());
auto lds2 = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto storeLDS1 = graph.control.addElement(StoreLDSTile());
auto storeLDS2 = graph.control.addElement(StoreLDSTile());
auto barrier1 = graph.control.addElement(Barrier());
auto barrier2 = graph.control.addElement(Barrier());
auto loadLDS1 = graph.control.addElement(LoadLDSTile());
auto loadLDS2 = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {storeLDS1});
graph.control.addElement(Sequence(), {storeLDS1}, {storeLDS2});
graph.control.addElement(Sequence(), {storeLDS2}, {barrier1});
graph.control.addElement(Sequence(), {barrier1}, {loadLDS1});
graph.control.addElement(Sequence(), {loadLDS1}, {barrier2});
graph.control.addElement(Sequence(), {barrier2}, {loadLDS2});
graph.mapper.connect<MacroTile>(storeLDS1, tile1);
graph.mapper.connect<LDS>(storeLDS1, lds1);
graph.mapper.connect<MacroTile>(storeLDS2, tile2);
graph.mapper.connect<LDS>(storeLDS2, lds2);
graph.mapper.connect<LDS>(barrier1, lds1);
graph.mapper.connect<MacroTile>(loadLDS1, tile1);
graph.mapper.connect<LDS>(loadLDS1, lds1);
graph.mapper.connect<LDS>(barrier2, lds2);
graph.mapper.connect<MacroTile>(loadLDS2, tile2);
graph.mapper.connect<LDS>(loadLDS2, lds2);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
// Both LDS coordinates need barriers, but barrier only connected to lds1.
// The implementation checks if barrier is connected to any LDS,
// so this should pass because there's an LDS-connected barrier between all ops.
CHECK(result.satisfied);
}
// =============================================================================
// ForLoop tests
// =============================================================================
TEST_CASE("VerifyLDSBarriers - ForLoop without barrier fails", "[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto forCoord = graph.coordinates.addElement(ForLoop());
auto kernel = graph.control.addElement(Kernel());
auto counter = dataFlowTag(forCoord, Register::Type::Scalar, DataType::Int32);
auto forLoop = graph.control.addElement(ForLoopOp{counter < literal(10), "test_loop"});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {forLoop});
graph.control.addElement(Body(), {forLoop}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK_FALSE(result.satisfied);
CHECK_THAT(result.explanation, ContainsSubstring("Missing LDS barrier"));
}
TEST_CASE("VerifyLDSBarriers - ForLoop with single barrier between ops fails for loop-carried",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto forCoord = graph.coordinates.addElement(ForLoop());
auto kernel = graph.control.addElement(Kernel());
auto counter = dataFlowTag(forCoord, Register::Type::Scalar, DataType::Int32);
auto forLoop = graph.control.addElement(ForLoopOp{counter < literal(10), "test_loop"});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {forLoop});
graph.control.addElement(Body(), {forLoop}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier});
graph.control.addElement(Sequence(), {barrier}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<LDS>(barrier, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
// Only one barrier between store and load.
// This handles forward dependency, but not loop-carried dependency
// (from iteration N's load to iteration N+1's store).
CHECK_FALSE(result.satisfied);
CHECK_THAT(result.explanation, ContainsSubstring("loop-carried"));
}
TEST_CASE("VerifyLDSBarriers - ForLoop with barrier after load handles loop-carried",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto forCoord = graph.coordinates.addElement(ForLoop());
auto kernel = graph.control.addElement(Kernel());
auto counter = dataFlowTag(forCoord, Register::Type::Scalar, DataType::Int32);
auto forLoop = graph.control.addElement(ForLoopOp{counter < literal(10), "test_loop"});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier1 = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
auto barrier2 = graph.control.addElement(Barrier());
graph.control.addElement(Body(), {kernel}, {forLoop});
graph.control.addElement(Body(), {forLoop}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier1});
graph.control.addElement(Sequence(), {barrier1}, {loadLDS});
graph.control.addElement(Sequence(), {loadLDS}, {barrier2});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<LDS>(barrier1, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
graph.mapper.connect<LDS>(barrier2, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
// barrier1: between store and load (forward dependency)
// barrier2: after load (handles loop-carried dependency)
CHECK(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - ForLoop with barrier before store handles loop-carried",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto forCoord = graph.coordinates.addElement(ForLoop());
auto kernel = graph.control.addElement(Kernel());
auto counter = dataFlowTag(forCoord, Register::Type::Scalar, DataType::Int32);
auto forLoop = graph.control.addElement(ForLoopOp{counter < literal(10), "test_loop"});
auto barrier1 = graph.control.addElement(Barrier());
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier2 = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {forLoop});
graph.control.addElement(Body(), {forLoop}, {barrier1});
graph.control.addElement(Sequence(), {barrier1}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier2});
graph.control.addElement(Sequence(), {barrier2}, {loadLDS});
graph.mapper.connect<LDS>(barrier1, lds);
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<LDS>(barrier2, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
// barrier1: before store (handles loop-carried dependency)
// barrier2: between store and load (forward dependency)
CHECK(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - ForLoop barrier not connected to LDS fails",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto forCoord = graph.coordinates.addElement(ForLoop());
auto otherTile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto counter = dataFlowTag(forCoord, Register::Type::Scalar, DataType::Int32);
auto forLoop = graph.control.addElement(ForLoopOp{counter < literal(10), "test_loop"});
auto barrier1 = graph.control.addElement(Barrier());
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier2 = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {forLoop});
graph.control.addElement(Body(), {forLoop}, {barrier1});
graph.control.addElement(Sequence(), {barrier1}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier2});
graph.control.addElement(Sequence(), {barrier2}, {loadLDS});
// barrier1 connected to MacroTile instead of LDS
graph.mapper.connect<MacroTile>(barrier1, otherTile);
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
// barrier2 connected to MacroTile instead of LDS
graph.mapper.connect<MacroTile>(barrier2, otherTile);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK_FALSE(result.satisfied);
CHECK_THAT(result.explanation, ContainsSubstring("Missing LDS barrier"));
}
// =============================================================================
// DoWhile tests
// =============================================================================
TEST_CASE("VerifyLDSBarriers - DoWhile without barrier fails", "[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto condTile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto condExpr = dataFlowTag(condTile, Register::Type::Vector, DataType::Float) > literal(0.0f);
auto doWhile = graph.control.addElement(DoWhileOp{condExpr});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {doWhile});
graph.control.addElement(Body(), {doWhile}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK_FALSE(result.satisfied);
CHECK_THAT(result.explanation, ContainsSubstring("Missing LDS barrier"));
}
TEST_CASE("VerifyLDSBarriers - DoWhile with single barrier between ops fails for loop-carried",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto condTile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto condExpr = dataFlowTag(condTile, Register::Type::Vector, DataType::Float) > literal(0.0f);
auto doWhile = graph.control.addElement(DoWhileOp{condExpr});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {doWhile});
graph.control.addElement(Body(), {doWhile}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier});
graph.control.addElement(Sequence(), {barrier}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<LDS>(barrier, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK_FALSE(result.satisfied);
CHECK_THAT(result.explanation, ContainsSubstring("loop-carried"));
}
TEST_CASE("VerifyLDSBarriers - DoWhile with two barriers passes", "[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto condTile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto condExpr = dataFlowTag(condTile, Register::Type::Vector, DataType::Float) > literal(0.0f);
auto doWhile = graph.control.addElement(DoWhileOp{condExpr});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier1 = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
auto barrier2 = graph.control.addElement(Barrier());
graph.control.addElement(Body(), {kernel}, {doWhile});
graph.control.addElement(Body(), {doWhile}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier1});
graph.control.addElement(Sequence(), {barrier1}, {loadLDS});
graph.control.addElement(Sequence(), {loadLDS}, {barrier2});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<LDS>(barrier1, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
graph.mapper.connect<LDS>(barrier2, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - DoWhile only LDS-connected barriers are valid",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto condTile = graph.coordinates.addElement(MacroTile());
auto otherTile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto condExpr = dataFlowTag(condTile, Register::Type::Vector, DataType::Float) > literal(0.0f);
auto doWhile = graph.control.addElement(DoWhileOp{condExpr});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier1 = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
auto barrier2 = graph.control.addElement(Barrier());
graph.control.addElement(Body(), {kernel}, {doWhile});
graph.control.addElement(Body(), {doWhile}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier1});
graph.control.addElement(Sequence(), {barrier1}, {loadLDS});
graph.control.addElement(Sequence(), {loadLDS}, {barrier2});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<LDS>(barrier1, lds); // LDS-connected
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
// barrier2 NOT connected to LDS, connected to MacroTile instead
graph.mapper.connect<MacroTile>(barrier2, otherTile);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
// barrier2 is not LDS-connected, so loop-carried dependency is not satisfied
CHECK_FALSE(result.satisfied);
CHECK_THAT(result.explanation, ContainsSubstring("loop-carried"));
}
// =============================================================================
// LoadTileDirect2LDS tests
// =============================================================================
TEST_CASE("VerifyLDSBarriers - LoadTileDirect2LDS write and LoadLDSTile read needs barrier",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto loadDirect = graph.control.addElement(LoadTileDirect2LDS());
auto loadLDSTile = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {loadDirect});
graph.control.addElement(Sequence(), {loadDirect}, {loadLDSTile});
graph.mapper.connect<MacroTile>(loadDirect, tile);
graph.mapper.connect<LDS>(loadDirect, lds);
graph.mapper.connect<MacroTile>(loadLDSTile, tile);
graph.mapper.connect<LDS>(loadLDSTile, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK_FALSE(result.satisfied);
CHECK_THAT(result.explanation, ContainsSubstring("Missing LDS barrier"));
}
TEST_CASE("VerifyLDSBarriers - LoadTileDirect2LDS with LDS barrier passes",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto loadDirect = graph.control.addElement(LoadTileDirect2LDS());
auto barrier = graph.control.addElement(Barrier());
auto loadLDSTile = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {loadDirect});
graph.control.addElement(Sequence(), {loadDirect}, {barrier});
graph.control.addElement(Sequence(), {barrier}, {loadLDSTile});
graph.mapper.connect<MacroTile>(loadDirect, tile);
graph.mapper.connect<LDS>(loadDirect, lds);
graph.mapper.connect<LDS>(barrier, lds);
graph.mapper.connect<MacroTile>(loadLDSTile, tile);
graph.mapper.connect<LDS>(loadLDSTile, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK(result.satisfied);
}
// =============================================================================
// Nested loops tests
// =============================================================================
TEST_CASE("VerifyLDSBarriers - Nested ForLoops need barriers in common ancestor",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto forCoord1 = graph.coordinates.addElement(ForLoop());
auto forCoord2 = graph.coordinates.addElement(ForLoop());
auto kernel = graph.control.addElement(Kernel());
auto counter1 = dataFlowTag(forCoord1, Register::Type::Scalar, DataType::Int32);
auto outerLoop = graph.control.addElement(ForLoopOp{counter1 < literal(10), "outer_loop"});
auto counter2 = dataFlowTag(forCoord2, Register::Type::Scalar, DataType::Int32);
auto innerLoop = graph.control.addElement(ForLoopOp{counter2 < literal(10), "inner_loop"});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier1 = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
auto barrier2 = graph.control.addElement(Barrier());
graph.control.addElement(Body(), {kernel}, {outerLoop});
graph.control.addElement(Body(), {outerLoop}, {innerLoop});
graph.control.addElement(Body(), {innerLoop}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier1});
graph.control.addElement(Sequence(), {barrier1}, {loadLDS});
graph.control.addElement(Sequence(), {loadLDS}, {barrier2});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<LDS>(barrier1, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
graph.mapper.connect<LDS>(barrier2, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
// Both barriers are in innerLoop, so loop-carried is handled for innerLoop.
// But the common ancestor is outerLoop, so loop-carried for outer loop
// is also handled since barriers are inside it.
CHECK(result.satisfied);
}
// =============================================================================
// Edge cases
// =============================================================================
TEST_CASE("VerifyLDSBarriers - Empty kernel passes", "[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto kernel = graph.control.addElement(Kernel());
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - Only LDS write without read passes",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto storeLDS = graph.control.addElement(StoreLDSTile());
graph.control.addElement(Body(), {kernel}, {storeLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - Only LDS read without write passes",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {loadLDS});
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - Read before write in sequence (needs barrier)",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto loadLDS = graph.control.addElement(LoadLDSTile());
auto storeLDS = graph.control.addElement(StoreLDSTile());
graph.control.addElement(Body(), {kernel}, {loadLDS});
graph.control.addElement(Sequence(), {loadLDS}, {storeLDS});
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
// Read first, then write - still needs barrier between them
CHECK_FALSE(result.satisfied);
}
TEST_CASE("VerifyLDSBarriers - Read before write in sequence with barrier passes",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto loadLDS = graph.control.addElement(LoadLDSTile());
auto barrier = graph.control.addElement(Barrier());
auto storeLDS = graph.control.addElement(StoreLDSTile());
graph.control.addElement(Body(), {kernel}, {loadLDS});
graph.control.addElement(Sequence(), {loadLDS}, {barrier});
graph.control.addElement(Sequence(), {barrier}, {storeLDS});
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
graph.mapper.connect<LDS>(barrier, lds);
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
auto constraints = getVerifyLDSBarriersConstraints();
auto result = graph.checkConstraints(constraints);
CHECK(result.satisfied);
}
// =============================================================================
// AddLDSBarriers::apply tests
// =============================================================================
TEST_CASE("AddLDSBarriers::apply - No LDS operations returns unchanged graph",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto assign = graph.control.addElement(
Assign{Register::Type::Vector, dataFlowTag(tile, Register::Type::Vector, DataType::Float)});
graph.control.addElement(Body(), {kernel}, {assign});
graph.mapper.connect(assign, tile, NaryArgument::DEST);
KG::AddLDSBarriers transform;
auto result = transform.apply(graph);
// Should return graph unchanged (no barriers added)
auto constraints = transform.postConstraints();
auto status = result.checkConstraints(constraints);
CHECK(status.satisfied);
}
TEST_CASE("AddLDSBarriers::apply - Inserts barrier between LDS write and read",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
// Verify constraint fails before applying transform
auto constraints = KG::AddLDSBarriers().postConstraints();
auto statusBefore = graph.checkConstraints(constraints);
CHECK_FALSE(statusBefore.satisfied);
// Apply the transform
KG::AddLDSBarriers transform;
auto result = transform.apply(graph);
// Verify constraint passes after applying transform
auto statusAfter = result.checkConstraints(constraints);
CHECK(statusAfter.satisfied);
}
TEST_CASE("AddLDSBarriers::apply - Do not reuse existing non-LDS barrier",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto otherTile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier});
graph.control.addElement(Sequence(), {barrier}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
// Barrier connected to a non-LDS coordinate
graph.mapper.connect<MacroTile>(barrier, otherTile);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
// Verify constraint fails before applying transform
auto constraints = KG::AddLDSBarriers().postConstraints();
auto statusBefore = graph.checkConstraints(constraints);
CHECK_FALSE(statusBefore.satisfied);
// Count barriers before
int barrierCountBefore = 0;
for(auto tag : graph.control.getNodes<Barrier>())
{
(void)tag;
barrierCountBefore++;
}
// Apply the transform
KG::AddLDSBarriers transform;
auto result = transform.apply(graph);
// Count barriers after
int barrierCountAfter = 0;
for(auto tag : result.control.getNodes<Barrier>())
{
(void)tag;
barrierCountAfter++;
}
// Should reuse the existing barrier (no new barriers added)
CHECK(barrierCountAfter > barrierCountBefore);
// Verify constraint passes after applying transform
auto statusAfter = result.checkConstraints(constraints);
CHECK(statusAfter.satisfied);
}
TEST_CASE("AddLDSBarriers::apply - Inserts two barriers for loop",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto forCoord = graph.coordinates.addElement(ForLoop());
auto kernel = graph.control.addElement(Kernel());
auto counter = dataFlowTag(forCoord, Register::Type::Scalar, DataType::Int32);
auto forLoop = graph.control.addElement(ForLoopOp{counter < literal(10), "test_loop"});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto loadLDS = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {forLoop});
graph.control.addElement(Body(), {forLoop}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {loadLDS});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
// Verify constraint fails before applying transform
auto constraints = KG::AddLDSBarriers().postConstraints();
auto statusBefore = graph.checkConstraints(constraints);
CHECK_FALSE(statusBefore.satisfied);
// Apply the transform
KG::AddLDSBarriers transform;
auto result = transform.apply(graph);
// Verify constraint passes after applying transform
auto statusAfter = result.checkConstraints(constraints);
CHECK(statusAfter.satisfied);
}
TEST_CASE("AddLDSBarriers::apply - Do not reuse existing barriers in loop",
"[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto forCoord = graph.coordinates.addElement(ForLoop());
auto otherTile = graph.coordinates.addElement(MacroTile());
auto kernel = graph.control.addElement(Kernel());
auto counter = dataFlowTag(forCoord, Register::Type::Scalar, DataType::Int32);
auto forLoop = graph.control.addElement(ForLoopOp{counter < literal(10), "test_loop"});
auto storeLDS = graph.control.addElement(StoreLDSTile());
auto barrier1 = graph.control.addElement(Barrier());
auto loadLDS = graph.control.addElement(LoadLDSTile());
auto barrier2 = graph.control.addElement(Barrier());
graph.control.addElement(Body(), {kernel}, {forLoop});
graph.control.addElement(Body(), {forLoop}, {storeLDS});
graph.control.addElement(Sequence(), {storeLDS}, {barrier1});
graph.control.addElement(Sequence(), {barrier1}, {loadLDS});
graph.control.addElement(Sequence(), {loadLDS}, {barrier2});
graph.mapper.connect<MacroTile>(storeLDS, tile);
graph.mapper.connect<LDS>(storeLDS, lds);
// Barriers connected to non-LDS
graph.mapper.connect<MacroTile>(barrier1, otherTile);
graph.mapper.connect<MacroTile>(barrier2, otherTile);
graph.mapper.connect<MacroTile>(loadLDS, tile);
graph.mapper.connect<LDS>(loadLDS, lds);
// Verify constraint fails before applying transform
auto constraints = KG::AddLDSBarriers().postConstraints();
auto statusBefore = graph.checkConstraints(constraints);
CHECK_FALSE(statusBefore.satisfied);
// Count barriers before
int barrierCountBefore = 0;
for(auto tag : graph.control.getNodes<Barrier>())
{
(void)tag;
barrierCountBefore++;
}
// Apply the transform
KG::AddLDSBarriers transform;
auto result = transform.apply(graph);
// Count barriers after
int barrierCountAfter = 0;
for(auto tag : result.control.getNodes<Barrier>())
{
(void)tag;
barrierCountAfter++;
}
// Should reuse the existing barriers (no new barriers added)
CHECK(barrierCountAfter > barrierCountBefore);
// Verify constraint passes after applying transform
auto statusAfter = result.checkConstraints(constraints);
CHECK(statusAfter.satisfied);
}
TEST_CASE("AddLDSBarriers::apply - Handles LoadTileDirect2LDS", "[kernel-graph][graph-transform]")
{
KG::KernelGraph graph;
auto tile = graph.coordinates.addElement(MacroTile());
auto lds = graph.coordinates.addElement(LDS());
auto kernel = graph.control.addElement(Kernel());
auto loadDirect = graph.control.addElement(LoadTileDirect2LDS());
auto loadLDSTile = graph.control.addElement(LoadLDSTile());
graph.control.addElement(Body(), {kernel}, {loadDirect});
graph.control.addElement(Sequence(), {loadDirect}, {loadLDSTile});
graph.mapper.connect<MacroTile>(loadDirect, tile);
graph.mapper.connect<LDS>(loadDirect, lds);
graph.mapper.connect<MacroTile>(loadLDSTile, tile);
graph.mapper.connect<LDS>(loadLDSTile, lds);
// Verify constraint fails before applying transform
auto constraints = KG::AddLDSBarriers().postConstraints();
auto statusBefore = graph.checkConstraints(constraints);
CHECK_FALSE(statusBefore.satisfied);
// Apply the transform
KG::AddLDSBarriers transform;
auto result = transform.apply(graph);
// Verify constraint passes after applying transform
auto statusAfter = result.checkConstraints(constraints);
CHECK(statusAfter.satisfied);
}