Files
TheRock/rocm-libraries/shared/rocroller/test/unit/SchedulerTest.cpp
T
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

1100 lines
47 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/CodeGen/CopyGenerator.hpp>
#include <rocRoller/Scheduling/Scheduler.hpp>
#include <rocRoller/Utilities/Error.hpp>
#include <rocRoller/Utilities/Generator.hpp>
#include "GenericContextFixture.hpp"
#include "SourceMatcher.hpp"
#define Inst(opcode) Instruction(opcode, {}, {}, {}, "")
using namespace rocRoller;
namespace rocRollerTest
{
struct SchedulerTest : public GenericContextFixture
{
GPUArchitectureTarget targetArchitecture() override
{
return {GPUArchitectureGFX::GFX90A};
}
void SetUp() override
{
Settings::getInstance()->set(Settings::AllowUnknownInstructions, true);
GenericContextFixture::SetUp();
}
Generator<Instruction> testGeneratorWithComments(bool includeComments = true);
};
Generator<Instruction> SchedulerTest::testGeneratorWithComments(bool includeComments)
{
co_yield_(Instruction("s_sub_u32", {}, {}, {}, "Comment on an instruction"));
if(includeComments)
{
co_yield Instruction::Comment("Pure comment 1");
co_yield Instruction::Comment("Pure comment 2");
}
co_yield_(Instruction("s_add_u32", {}, {}, {}, "Comment on an instruction"));
if(includeComments)
{
co_yield Instruction::Comment("Pure comment 3");
}
co_yield Instruction::Nop("Nop Comment");
co_yield Instruction::Lock(Scheduling::Dependency::SCC, "Lock instruction");
co_yield Instruction::Unlock("Unlock instruction");
if(includeComments)
{
co_yield Instruction::Comment("Pure comment 4");
co_yield Instruction::Comment("Pure comment 5");
}
auto reg = std::make_shared<Register::Value>(
m_context, Register::Type::Vector, DataType::Float, 1);
co_yield reg->allocate();
if(includeComments)
{
co_yield Instruction::Comment("Pure comment 6");
}
co_yield Instruction::Directive(".set .amdgcn.next_free_vgpr, 0");
co_yield Instruction::Label("FooLabel");
if(includeComments)
{
co_yield Instruction::Comment("Pure comment 7");
}
co_yield Instruction::Wait(WaitCount::Zero(m_context->targetArchitecture()));
if(includeComments)
{
co_yield Instruction::Comment("Pure comment 8");
}
}
template <typename Begin, typename End>
Instruction next(Begin& begin, End const& end)
{
AssertFatal(begin != end);
auto inst = *begin;
++begin;
return std::move(inst);
}
TEST_F(SchedulerTest, Enum)
{
using namespace rocRoller::Scheduling;
EXPECT_EQ(toString(SchedulerProcedure::Sequential), "Sequential");
EXPECT_EQ(toString(SchedulerProcedure::RoundRobin), "RoundRobin");
EXPECT_EQ(toString(SchedulerProcedure::Random), "Random");
EXPECT_EQ(toString(SchedulerProcedure::Cooperative), "Cooperative");
EXPECT_EQ(toString(SchedulerProcedure::Priority), "Priority");
EXPECT_EQ(toString(SchedulerProcedure::Count), "Count");
EXPECT_EQ(fromString<SchedulerProcedure>("Sequential"), SchedulerProcedure::Sequential);
EXPECT_EQ(fromString<SchedulerProcedure>("RoundRobin"), SchedulerProcedure::RoundRobin);
EXPECT_EQ(fromString<SchedulerProcedure>("Random"), SchedulerProcedure::Random);
EXPECT_EQ(fromString<SchedulerProcedure>("Cooperative"), SchedulerProcedure::Cooperative);
EXPECT_EQ(fromString<SchedulerProcedure>("Priority"), SchedulerProcedure::Priority);
EXPECT_ANY_THROW(fromString<SchedulerProcedure>("Count"));
EXPECT_ANY_THROW(fromString<SchedulerProcedure>("fjdksl"));
}
TEST_F(SchedulerTest, ConsumeCommentsTest)
{
using namespace rocRoller::Scheduling;
auto gen = testGeneratorWithComments();
auto begin = gen.begin();
auto end = gen.end();
auto inst = next(begin, end);
EXPECT_EQ("s_sub_u32", inst.getOpCode());
EXPECT_TRUE(begin->isCommentOnly());
{
m_context->schedule(consumeComments(begin, end));
std::string expected = R"(// Pure comment 1
// Pure comment 2)";
EXPECT_EQ(NormalizedSource(expected, true), NormalizedSource(output(), true));
auto inst = next(begin, end);
EXPECT_EQ(inst.getOpCode(), "s_add_u32");
}
EXPECT_TRUE(begin != end);
{
clearOutput();
m_context->schedule(consumeComments(begin, end));
std::string expected = "// Pure comment 3\n";
EXPECT_EQ(NormalizedSource(expected, true), NormalizedSource(output(), true));
auto inst = next(begin, end);
EXPECT_EQ(inst.nopCount(), 1);
EXPECT_EQ(inst.toString(LogLevel::Debug), "s_nop 0\n // Nop Comment\n");
}
{
// Next is a lock, so there should be no comments consumed.
clearOutput();
m_context->schedule(consumeComments(begin, end));
std::string expected = "";
EXPECT_EQ(NormalizedSource(expected, true), NormalizedSource(output(), true));
auto inst = next(begin, end);
EXPECT_EQ(inst.toString(LogLevel::Debug), " // Lock instruction\n // Lock SCC\n");
}
{
// Next is an unlock, so there should be no comments consumed.
clearOutput();
m_context->schedule(consumeComments(begin, end));
std::string expected = "";
EXPECT_EQ(NormalizedSource(expected, true), NormalizedSource(output(), true));
auto inst = next(begin, end);
EXPECT_EQ(inst.toString(LogLevel::Debug), " // Unlock instruction\n // Unlock None\n");
}
{
clearOutput();
m_context->schedule(consumeComments(begin, end));
std::string expected = "// Pure comment 4\n// Pure comment 5\n";
EXPECT_EQ(NormalizedSource(expected, true), NormalizedSource(output(), true));
auto inst = next(begin, end);
EXPECT_EQ(inst.toString(LogLevel::Debug), "// Allocated : 1 VGPR (Value: Float)\n");
}
{
clearOutput();
m_context->schedule(consumeComments(begin, end));
std::string expected = "// Pure comment 6\n";
EXPECT_EQ(NormalizedSource(expected, true), NormalizedSource(output(), true));
auto inst = next(begin, end);
EXPECT_EQ(inst.toString(LogLevel::Debug), ".set .amdgcn.next_free_vgpr, 0\n");
}
{
// Next is a label, so there should be no comments consumed.
clearOutput();
m_context->schedule(consumeComments(begin, end));
std::string expected = "";
EXPECT_EQ(NormalizedSource(expected, true), NormalizedSource(output(), true));
auto inst = next(begin, end);
EXPECT_EQ(inst.toString(LogLevel::Debug), "FooLabel:\n");
}
{
clearOutput();
m_context->schedule(consumeComments(begin, end));
std::string expected = "// Pure comment 7\n";
EXPECT_EQ(NormalizedSource(expected, true), NormalizedSource(output(), true));
auto inst = next(begin, end);
auto instString = R"(s_waitcnt vmcnt(0) lgkmcnt(0) expcnt(0))";
EXPECT_EQ(NormalizedSource(inst.toString(LogLevel::Debug)),
NormalizedSource(instString));
}
{
clearOutput();
m_context->schedule(consumeComments(begin, end));
std::string expected = "// Pure comment 8\n";
EXPECT_EQ(NormalizedSource(expected, true), NormalizedSource(output(), true));
EXPECT_EQ(begin, end);
}
}
TEST_F(SchedulerTest, SequentialSchedulerTest)
{
auto generator_one = [&]() -> Generator<Instruction> {
co_yield_(Inst("Instruction 1, Generator 1"));
co_yield_(Inst("Instruction 2, Generator 1"));
co_yield_(Inst("Instruction 3, Generator 1"));
};
auto generator_two = [&]() -> Generator<Instruction> {
co_yield_(Inst("Instruction 1, Generator 2"));
co_yield_(Inst("Instruction 2, Generator 2"));
co_yield_(Inst("Instruction 3, Generator 2"));
co_yield_(Inst("Instruction 4, Generator 2"));
};
std::vector<Generator<Instruction>> generators;
generators.push_back(generator_one());
generators.push_back(generator_two());
std::string expected = R"( Instruction 1, Generator 1
Instruction 2, Generator 1
Instruction 3, Generator 1
Instruction 1, Generator 2
Instruction 2, Generator 2
Instruction 3, Generator 2
Instruction 4, Generator 2
)";
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Sequential, Scheduling::CostFunction::None, m_context);
m_context->schedule((*scheduler)(generators));
EXPECT_EQ(NormalizedSource(output(), true), NormalizedSource(expected, true));
}
TEST_F(SchedulerTest, RoundRobinSchedulerTest)
{
auto generator_one = [&]() -> Generator<Instruction> {
co_yield_(Inst("Instruction 1, Generator 1"));
co_yield_(Inst("Instruction 2, Generator 1"));
co_yield_(Inst("Instruction 3, Generator 1"));
};
auto generator_two = [&]() -> Generator<Instruction> {
co_yield_(Inst("Instruction 1, Generator 2"));
co_yield_(Inst("Instruction 2, Generator 2"));
co_yield_(Inst("Instruction 3, Generator 2"));
co_yield_(Inst("Instruction 4, Generator 2"));
};
std::vector<Generator<Instruction>> generators;
generators.push_back(generator_one());
generators.push_back(generator_two());
std::string expected = R"( Instruction 1, Generator 1
Instruction 1, Generator 2
Instruction 2, Generator 1
Instruction 2, Generator 2
Instruction 3, Generator 1
Instruction 3, Generator 2
Instruction 4, Generator 2
)";
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::RoundRobin, Scheduling::CostFunction::None, m_context);
m_context->schedule((*scheduler)(generators));
EXPECT_EQ(NormalizedSource(output(), true), NormalizedSource(expected, true));
}
// Can be run with the --gtest_also_run_disabled_tests option
TEST_F(SchedulerTest, DISABLED_SchedulerWaitStressTest)
{
auto const& arch = m_context->targetArchitecture();
auto generator = [arch]() -> Generator<Instruction> {
for(size_t i = 0; i < 1000000; i++)
{
co_yield Instruction::Wait(WaitCount::LoadCnt(arch, 1, "Comment"));
}
};
m_context->schedule(generator());
}
// Can be run with the --gtest_also_run_disabled_tests option
TEST_F(SchedulerTest, DISABLED_SchedulerCopyStressTest)
{
auto v_a
= Register::Value::Placeholder(m_context, Register::Type::Vector, DataType::Int32, 1);
auto v_b
= Register::Value::Placeholder(m_context, Register::Type::Vector, DataType::Int32, 1);
auto generator = [&]() -> Generator<Instruction> {
co_yield v_a->allocate();
co_yield v_b->allocate();
for(size_t i = 0; i < 1000000; i++)
{
co_yield m_context->copier()->copy(v_a, v_b, "Comment");
}
};
m_context->schedule(generator());
}
TEST_F(SchedulerTest, DoubleUnlocking)
{
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::RoundRobin, Scheduling::CostFunction::None, m_context);
std::vector<Generator<Instruction>> sequences;
auto noUnlock = [&]() -> Generator<Instruction> {
co_yield(Instruction::Lock(Scheduling::Dependency::SCC));
co_yield(Instruction::Unlock());
co_yield(Inst("Test"));
co_yield(Instruction::Unlock());
};
sequences.push_back(noUnlock());
EXPECT_THROW(m_context->schedule((*scheduler)(sequences));, FatalError);
}
TEST_F(SchedulerTest, noUnlocking)
{
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::RoundRobin, Scheduling::CostFunction::None, m_context);
std::vector<Generator<Instruction>> sequences;
auto noUnlock = [&]() -> Generator<Instruction> {
co_yield(Instruction::Lock(Scheduling::Dependency::SCC));
co_yield(Inst("Test"));
};
sequences.push_back(noUnlock());
EXPECT_THROW(m_context->schedule((*scheduler)(sequences));, FatalError);
}
TEST_F(SchedulerTest, SchedulerDepth)
{
auto schedulerA = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::RoundRobin, Scheduling::CostFunction::None, m_context);
auto schedulerB = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::RoundRobin, Scheduling::CostFunction::None, m_context);
auto schedulerC = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::RoundRobin, Scheduling::CostFunction::None, m_context);
std::vector<Generator<Instruction>> a_sequences;
std::vector<Generator<Instruction>> b_sequences;
std::vector<Generator<Instruction>> c_sequences;
auto opB = [&]() -> Generator<Instruction> {
co_yield(Inst("(C) Op B Begin"));
co_yield(Inst("(C) Op B Instruction"));
co_yield(Inst("(C) Op B End"));
};
auto ifBlock = [&]() -> Generator<Instruction> {
EXPECT_EQ(schedulerA->getLockState().getTopDependency(1), Scheduling::Dependency::VCC);
EXPECT_EQ(schedulerB->getLockState().getTopDependency(0), Scheduling::Dependency::VCC);
EXPECT_EQ(schedulerC->getLockState().getTopDependency(1), Scheduling::Dependency::None);
co_yield(
Inst("(C) If Begin").lock(Scheduling::Dependency::SCC, "(C) Scheduler C Lock"));
EXPECT_EQ(schedulerA->getLockState().getTopDependency(1), Scheduling::Dependency::SCC);
EXPECT_EQ(schedulerB->getLockState().getTopDependency(0), Scheduling::Dependency::SCC);
EXPECT_EQ(schedulerC->getLockState().getTopDependency(1), Scheduling::Dependency::SCC);
co_yield(Inst("+++ Scheduler A Stream 1 Lock Depth: "
+ std::to_string(schedulerA->getLockState().getLockDepth(1))));
co_yield(Inst("+++ Scheduler B Stream 0 Lock Depth: "
+ std::to_string(schedulerB->getLockState().getLockDepth(0))));
co_yield(Inst("+++ Scheduler C Stream 1 Lock Depth: "
+ std::to_string(schedulerC->getLockState().getLockDepth(1))));
co_yield(Inst("(C) If Instruction"));
co_yield(Inst("(C) If End").unlock("(C) Scheduler C Unlock"));
EXPECT_EQ(schedulerA->getLockState().getTopDependency(1), Scheduling::Dependency::VCC);
EXPECT_EQ(schedulerB->getLockState().getTopDependency(0), Scheduling::Dependency::VCC);
EXPECT_EQ(schedulerC->getLockState().getTopDependency(1), Scheduling::Dependency::None);
co_yield(Inst("+++ Scheduler A Stream 1 Lock Depth: "
+ std::to_string(schedulerA->getLockState().getLockDepth(1))));
co_yield(Inst("+++ Scheduler B Stream 0 Lock Depth: "
+ std::to_string(schedulerB->getLockState().getLockDepth(0))));
co_yield(Inst("+++ Scheduler C Stream 1 Lock Depth: "
+ std::to_string(schedulerC->getLockState().getLockDepth(1))));
};
c_sequences.push_back(opB());
c_sequences.push_back(ifBlock());
auto unroll0 = [&]() -> Generator<Instruction> {
EXPECT_EQ(schedulerA->getLockState().getTopDependency(1),
Scheduling::Dependency::Branch);
EXPECT_EQ(schedulerB->getLockState().getTopDependency(0), Scheduling::Dependency::None);
co_yield(Inst("(B) Unroll 0 Begin")
.lock(Scheduling::Dependency::VCC, "(B) Scheduler B Lock"));
EXPECT_EQ(schedulerA->getLockState().getTopDependency(1), Scheduling::Dependency::VCC);
EXPECT_EQ(schedulerB->getLockState().getTopDependency(0), Scheduling::Dependency::VCC);
co_yield(Inst("+++ Scheduler A Stream 1 Lock Depth: "
+ std::to_string(schedulerA->getLockState().getLockDepth(1))));
co_yield(Inst("+++ Scheduler B Stream 0 Lock Depth: "
+ std::to_string(schedulerB->getLockState().getLockDepth(0))));
co_yield((*schedulerC)(c_sequences));
co_yield(Inst("(B) Unroll 0 End")).unlock("(B) Scheduler B Unlock");
EXPECT_EQ(schedulerA->getLockState().getTopDependency(1),
Scheduling::Dependency::Branch);
EXPECT_EQ(schedulerB->getLockState().getTopDependency(0), Scheduling::Dependency::None);
co_yield(Inst("+++ Scheduler A Stream 1 Lock Depth: "
+ std::to_string(schedulerA->getLockState().getLockDepth(1))));
co_yield(Inst("+++ Scheduler B Stream 0 Lock Depth: "
+ std::to_string(schedulerB->getLockState().getLockDepth(0))));
};
auto unroll1 = [&]() -> Generator<Instruction> {
co_yield(Inst("(B) Unroll 1 Begin"));
co_yield(Inst("(B) Unroll 1 Instruction"));
co_yield(Inst("(B) Unroll 1 End"));
};
b_sequences.push_back(unroll0());
b_sequences.push_back(unroll1());
auto opA = [&]() -> Generator<Instruction> {
co_yield(Inst("(A) Op A Begin"));
co_yield(Inst("(A) Op A Instruction"));
co_yield(Inst("(A) Op A End"));
};
auto forloop = [&]() -> Generator<Instruction> {
co_yield(Inst("(A) For Loop Begin")
.lock(Scheduling::Dependency::Branch, "(A) Scheduler A Lock"));
EXPECT_EQ(schedulerA->getLockState().getTopDependency(1),
Scheduling::Dependency::Branch);
co_yield(Inst("+++ Scheduler A Stream 1 Lock Depth: "
+ std::to_string(schedulerA->getLockState().getLockDepth(1))));
co_yield((*schedulerB)(b_sequences));
co_yield(Inst("(A) For Loop End").unlock("(A) Scheduler A Unlock"));
co_yield(Inst("+++ Scheduler A Stream 1 Lock Depth: "
+ std::to_string(schedulerA->getLockState().getLockDepth(1))));
EXPECT_EQ(schedulerA->getLockState().getTopDependency(1), Scheduling::Dependency::None);
};
a_sequences.push_back(opA());
a_sequences.push_back(forloop());
m_context->schedule((*schedulerA)(a_sequences));
std::string expected = R"( (A) Op A Begin
(A) For Loop Begin // (A) Scheduler A Lock
// Lock Branch
+++ Scheduler A Stream 1 Lock Depth: 1
(B) Unroll 0 Begin // (B) Scheduler B Lock
// Lock VCC
(B) Unroll 1 Begin
+++ Scheduler A Stream 1 Lock Depth: 2
(B) Unroll 1 Instruction
+++ Scheduler B Stream 0 Lock Depth: 1
(B) Unroll 1 End
(C) Op B Begin
(C) If Begin // (C) Scheduler C Lock
// Lock SCC
+++ Scheduler A Stream 1 Lock Depth: 3
+++ Scheduler B Stream 0 Lock Depth: 2
+++ Scheduler C Stream 1 Lock Depth: 1
(C) If Instruction
(C) If End // (C) Scheduler C Unlock
// Unlock None
(C) Op B Instruction
+++ Scheduler A Stream 1 Lock Depth: 2
(C) Op B End
+++ Scheduler B Stream 0 Lock Depth: 1
+++ Scheduler C Stream 1 Lock Depth: 0
(B) Unroll 0 End // (B) Scheduler B Unlock
// Unlock None
+++ Scheduler A Stream 1 Lock Depth: 1
+++ Scheduler B Stream 0 Lock Depth: 0
(A) For Loop End // (A) Scheduler A Unlock
// Unlock None
(A) Op A Instruction
+++ Scheduler A Stream 1 Lock Depth: 0
(A) Op A End
)";
EXPECT_EQ(NormalizedSource(output(), true), NormalizedSource(expected, true));
}
struct RandomSchedulerTest : public SchedulerTest, public testing::WithParamInterface<int>
{
void SetUp() override
{
Settings::getInstance()->set(Settings::AllowUnknownInstructions, true);
GenericContextFixture::SetUp();
int seed = GetParam();
RecordProperty("random_seed", seed);
m_context->setRandomSeed(seed);
}
};
Generator<Instruction> noComments()
{
for(int i = 0; i < 100; i++)
co_yield_(Inst(concatenate("s_add_u32 ", i)));
}
TEST_P(RandomSchedulerTest, RandomScheduler)
{
std::string output1, output2, output3, output4, output5;
{
std::vector<Generator<Instruction>> gens;
gens.push_back(testGeneratorWithComments());
gens.push_back(noComments());
gens.push_back(noComments());
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Random, Scheduling::CostFunction::None, m_context);
m_context->schedule((*scheduler)(gens));
output1 = NormalizedSource(output(), true);
}
{
m_context->setRandomSeed(GetParam());
clearOutput();
std::vector<Generator<Instruction>> gens;
gens.push_back(testGeneratorWithComments());
gens.push_back(noComments());
gens.push_back(noComments());
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Random, Scheduling::CostFunction::None, m_context);
m_context->schedule((*scheduler)(gens));
output2 = NormalizedSource(output(), true);
}
{
m_context->setRandomSeed(GetParam());
clearOutput();
std::vector<Generator<Instruction>> gens;
gens.push_back(testGeneratorWithComments(false));
gens.push_back(noComments());
gens.push_back(noComments());
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Random, Scheduling::CostFunction::None, m_context);
m_context->schedule((*scheduler)(gens));
output3 = NormalizedSource(output(), true);
}
{
m_context->setRandomSeed(GetParam() + 1);
clearOutput();
std::vector<Generator<Instruction>> gens;
gens.push_back(testGeneratorWithComments());
gens.push_back(noComments());
gens.push_back(noComments());
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Random, Scheduling::CostFunction::None, m_context);
m_context->schedule((*scheduler)(gens));
output4 = NormalizedSource(output(), true);
}
Settings::getInstance()->set(Settings::RandomSeed, GetParam());
{
m_context->setRandomSeed(GetParam() + 1);
clearOutput();
std::vector<Generator<Instruction>> gens;
gens.push_back(testGeneratorWithComments());
gens.push_back(noComments());
gens.push_back(noComments());
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Random, Scheduling::CostFunction::None, m_context);
m_context->schedule((*scheduler)(gens));
output5 = NormalizedSource(output(), true);
}
// The same generators with the same random seed should produce the same output.
EXPECT_EQ(output1, output2);
// Not including the comments should still produce the same code.
EXPECT_EQ(NormalizedSource(output1), NormalizedSource(output3));
// The same generators with a different random seed should produce different output.
EXPECT_NE(output1, output4);
// The settings override should take precedence.
EXPECT_EQ(output1, output5);
// The scheduler should not break up comments or locked sections of instructions.
auto block1 = NormalizedSource(R"(
// Pure comment 1
// Pure comment 2
)",
true);
auto block2 = NormalizedSource(R"(
// Lock instruction
// Lock SCC
// Unlock instruction
// Unlock None
)",
true);
std::string block3 = NormalizedSource(R"(
// Pure comment 4
// Pure comment 5
)",
true);
EXPECT_THAT(output1, testing::HasSubstr(block1));
EXPECT_THAT(output1, testing::HasSubstr(block2));
EXPECT_THAT(output1, testing::HasSubstr(block3));
EXPECT_THAT(output4, testing::HasSubstr(block1));
EXPECT_THAT(output4, testing::HasSubstr(block2));
EXPECT_THAT(output4, testing::HasSubstr(block3));
}
INSTANTIATE_TEST_SUITE_P(RandomSchedulerTest,
RandomSchedulerTest,
::testing::Values(0, 1, 4, 8, 15, 16, 23, 42));
TEST_F(SchedulerTest, CooperativeSchedulerTest)
{
{
clearOutput();
auto v = createRegisters(Register::Type::Vector, DataType::Float, 6);
auto a = createRegisters(Register::Type::Accumulator, DataType::Float, 1);
auto generator_one = [&]() -> Generator<Instruction> {
co_yield_(Instruction("v_or_b32", {v[2]}, {v[0], v[1]}, {}, ""));
// Since this requires a nop, all the instructions from the other stream will be scheduled here, since none of them require nops.
co_yield_(Instruction("v_mfma_f32_16x16x4f32", {a[0]}, {v[0], v[2], a[0]}, {}, ""));
};
auto generator_two = [&]() -> Generator<Instruction> {
co_yield_(Inst("Instruction 1, Generator 2"));
co_yield_(Inst("Instruction 2, Generator 2"));
co_yield_(Inst("Instruction 3, Generator 2"));
co_yield_(Inst("Instruction 4, Generator 2"));
};
std::vector<Generator<Instruction>> generators;
generators.push_back(generator_one());
generators.push_back(generator_two());
std::string expected = R"( v_or_b32 v2, v0, v1
Instruction 1, Generator 2
Instruction 2, Generator 2
Instruction 3, Generator 2
Instruction 4, Generator 2
v_mfma_f32_16x16x4f32 a0, v0, v2, a0
)";
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Cooperative,
Scheduling::CostFunction::MinNops,
m_context);
m_context->schedule((*scheduler)(generators));
EXPECT_EQ(NormalizedSource(output(), true), NormalizedSource(expected, true));
}
{
clearOutput();
auto v = createRegisters(Register::Type::Vector, DataType::Float, 6);
auto a = createRegisters(Register::Type::Accumulator, DataType::Float, 2);
auto generator_one = [&]() -> Generator<Instruction> {
co_yield_(Instruction("v_or_b32", {v[2]}, {v[0], v[1]}, {}, ""));
};
auto generator_two = [&]() -> Generator<Instruction> {
co_yield_(Instruction("unrelated_op_2", {}, {}, {}, ""));
co_yield_(Instruction("v_mfma_f32_16x16x4f32", {a[0]}, {v[0], v[2], a[0]}, {}, ""));
};
auto generator_three = [&]() -> Generator<Instruction> {
co_yield_(Instruction("unrelated_op_3", {}, {}, {}, ""));
co_yield_(Instruction("v_mfma_f32_16x16x4f32", {a[1]}, {v[0], v[2], a[1]}, {}, ""));
//This instruction will be the first mfma, because of momentum in the coop scheduler.
};
std::vector<Generator<Instruction>> generators;
generators.push_back(generator_one());
generators.push_back(generator_two());
generators.push_back(generator_three());
std::string expected = R"( v_or_b32 v2, v0, v1
unrelated_op_2
unrelated_op_3
v_mfma_f32_16x16x4f32 a1, v0, v2, a1
v_mfma_f32_16x16x4f32 a0, v0, v2, a0
)";
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Cooperative,
Scheduling::CostFunction::MinNops,
m_context);
m_context->schedule((*scheduler)(generators));
EXPECT_EQ(NormalizedSource(output(), true), NormalizedSource(expected, true));
}
{
clearOutput();
auto mfma_v = createRegisters(Register::Type::Vector, DataType::Float, 16);
auto or_v = createRegisters(Register::Type::Vector, DataType::Float, 4);
auto generator_one = [&]() -> Generator<Instruction> {
std::string comment = "stream1";
co_yield_(Instruction("v_mfma_f32_32x32x1f32",
{mfma_v[0]},
{mfma_v[1], mfma_v[2], mfma_v[3]},
{},
comment));
co_yield_(Instruction("v_mfma_f32_32x32x1f32",
{mfma_v[4]},
{mfma_v[5], mfma_v[6], mfma_v[7]},
{},
comment));
co_yield_(Instruction("v_mfma_f32_32x32x1f32",
{mfma_v[8]},
{mfma_v[9], mfma_v[10], mfma_v[11]},
{},
comment));
};
auto generator_two = [&]() -> Generator<Instruction> {
std::string comment = "stream2";
co_yield_(Instruction("unrelated_op_2", {}, {}, {}, comment));
co_yield_(Instruction("v_or_b32", {or_v[0]}, {mfma_v[0], mfma_v[1]}, {}, comment));
co_yield_(Instruction("unrelated_op_3", {}, {}, {}, comment));
co_yield_(Instruction("v_or_b32", {or_v[1]}, {mfma_v[8], mfma_v[9]}, {}, comment));
};
auto generator_three = [&]() -> Generator<Instruction> {
std::string comment = "stream3";
co_yield_(Instruction("unrelated_op_4", {}, {}, {}, comment));
co_yield_(Instruction("v_mfma_f32_32x32x1f32",
{mfma_v[12]},
{mfma_v[13], mfma_v[14], mfma_v[15]},
{},
comment));
co_yield_(Instruction("v_or_b32", {or_v[2]}, {mfma_v[4], mfma_v[5]}, {}, comment));
co_yield_(Instruction("unrelated_op_5", {}, {}, {}, comment));
co_yield_(
Instruction("v_or_b32", {or_v[3]}, {mfma_v[12], mfma_v[13]}, {}, comment));
};
std::vector<Generator<Instruction>> generators;
generators.push_back(generator_one());
generators.push_back(generator_two());
generators.push_back(generator_three());
std::string expected = R"(
v_mfma_f32_32x32x1f32 v0, v1, v2, v3 // stream1
v_mfma_f32_32x32x1f32 v4, v5, v6, v7 // stream1
v_mfma_f32_32x32x1f32 v8, v9, v10, v11 // stream1
unrelated_op_2 // stream2
unrelated_op_4 // stream3
v_mfma_f32_32x32x1f32 v12, v13, v14, v15 // stream3
s_nop 13
v_or_b32 v16, v0, v1 // stream2
// Wait state hazard: XDL Write Hazard
unrelated_op_3 // stream2
v_or_b32 v17, v8, v9 // stream2
v_or_b32 v18, v4, v5 // stream3
unrelated_op_5 // stream3
v_or_b32 v19, v12, v13 // stream3
)";
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Cooperative,
Scheduling::CostFunction::MinNops,
m_context);
m_context->schedule((*scheduler)(generators));
EXPECT_EQ(NormalizedSource(output(), true), NormalizedSource(expected, true));
}
}
TEST_F(SchedulerTest, PriorityCooperativeUniformSchedulerTest)
{
//The result of using the Sequential scheduler and the coop or priority scheduler with uniform cost should be the same.
std::string seqOutput;
std::string coopUniformOutput;
std::string priorityUniformOutput;
auto v = createRegisters(Register::Type::Vector, DataType::Float, 6);
auto a = createRegisters(Register::Type::Accumulator, DataType::Float, 1);
auto gen1 = [&]() -> Generator<Instruction> {
co_yield_(Instruction("v_or_b32", {v[2]}, {v[0], v[1]}, {}, ""));
co_yield_(Instruction("v_mfma_f32_16x16x4f32", {a[0]}, {v[0], v[2], a[0]}, {}, ""));
};
auto gen2 = [&]() -> Generator<Instruction> {
co_yield_(Inst("Instruction 1, Generator 2"));
co_yield_(Inst("Instruction 2, Generator 2"));
co_yield_(Inst("Instruction 3, Generator 2"));
co_yield_(Inst("Instruction 4, Generator 2"));
};
auto gen3 = [&]() -> Generator<Instruction> {
co_yield_(Instruction("v_mfma_f32_32x32x1f32", {v[0]}, {v[1], v[2], v[3]}, {}, ""));
};
auto gen4 = [&]() -> Generator<Instruction> {
co_yield_(Instruction("unrelated_op_2", {}, {}, {}, ""));
co_yield_(Instruction("v_or_b32", {v[4]}, {v[0], v[1]}, {}, ""));
};
auto gen5 = [&]() -> Generator<Instruction> {
co_yield_(Instruction("unrelated_op_3", {}, {}, {}, ""));
co_yield_(Instruction("v_or_b32", {v[4]}, {v[0], v[1]}, {}, ""));
};
{
clearOutput();
std::vector<Generator<Instruction>> generators;
generators.push_back(gen1());
generators.push_back(gen2());
generators.push_back(gen3());
generators.push_back(gen4());
generators.push_back(gen5());
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Cooperative,
Scheduling::CostFunction::Uniform,
m_context);
m_context->schedule((*scheduler)(generators));
coopUniformOutput = output();
}
{
clearOutput();
std::vector<Generator<Instruction>> generators;
generators.push_back(gen1());
generators.push_back(gen2());
generators.push_back(gen3());
generators.push_back(gen4());
generators.push_back(gen5());
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Sequential,
Scheduling::CostFunction::None,
m_context);
m_context->schedule((*scheduler)(generators));
seqOutput = output();
}
{
clearOutput();
std::vector<Generator<Instruction>> generators;
generators.push_back(gen1());
generators.push_back(gen2());
generators.push_back(gen3());
generators.push_back(gen4());
generators.push_back(gen5());
auto scheduler
= Component::GetNew<Scheduling::Scheduler>(Scheduling::SchedulerProcedure::Priority,
Scheduling::CostFunction::Uniform,
m_context);
m_context->schedule((*scheduler)(generators));
priorityUniformOutput = output();
}
EXPECT_EQ(NormalizedSource(seqOutput, true), NormalizedSource(coopUniformOutput, true));
EXPECT_EQ(NormalizedSource(seqOutput, true), NormalizedSource(priorityUniformOutput, true));
}
TEST_F(SchedulerTest, PrioritySchedulerTestTwoGenerators)
{
clearOutput();
auto v = createRegisters(Register::Type::Vector, DataType::Float, 6);
auto a = createRegisters(Register::Type::Accumulator, DataType::Float, 1);
auto generator_one = [&]() -> Generator<Instruction> {
co_yield_(Instruction("v_or_b32", {v[2]}, {v[0], v[1]}, {}, ""));
// Since this requires a nop, instructions will be scheduled from the other stream until nops are no longer needed, then this next instruction will be scheduled.
co_yield_(Instruction("v_mfma_f32_16x16x4f32", {a[0]}, {v[0], v[2], a[0]}, {}, ""));
};
auto generator_two = [&]() -> Generator<Instruction> {
co_yield_(Inst("Instruction 1, Generator 2"));
co_yield_(Inst("Instruction 2, Generator 2"));
co_yield_(Inst("Instruction 3, Generator 2"));
co_yield_(Inst("Instruction 4, Generator 2"));
};
std::vector<Generator<Instruction>> generators;
generators.push_back(generator_one());
generators.push_back(generator_two());
std::string expected = R"( v_or_b32 v2, v0, v1
Instruction 1, Generator 2
Instruction 2, Generator 2
v_mfma_f32_16x16x4f32 a0, v0, v2, a0
Instruction 3, Generator 2
Instruction 4, Generator 2
)";
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Priority, Scheduling::CostFunction::MinNops, m_context);
m_context->schedule((*scheduler)(generators));
EXPECT_EQ(NormalizedSource(output(), true), NormalizedSource(expected, true));
}
TEST_F(SchedulerTest, PrioritySchedulerTestThreeGenerators)
{
clearOutput();
auto mfma_v = createRegisters(Register::Type::Vector, DataType::Float, 16);
auto or_v = createRegisters(Register::Type::Vector, DataType::Float, 4);
auto generator_one = [&]() -> Generator<Instruction> {
std::string comment = "stream1";
co_yield_(Instruction("v_mfma_f32_32x32x1f32",
{mfma_v[0]},
{mfma_v[1], mfma_v[2], mfma_v[3]},
{},
comment));
co_yield_(Instruction("unrelated_op_2", {}, {}, {}, comment));
co_yield_(Instruction("v_or_b32", {or_v[0]}, {mfma_v[0], mfma_v[1]}, {}, comment));
co_yield_(Instruction("v_mfma_f32_32x32x1f32",
{mfma_v[4]},
{mfma_v[5], mfma_v[6], mfma_v[7]},
{},
comment));
co_yield_(Instruction("unrelated_op_3", {}, {}, {}, comment));
co_yield_(Instruction("v_or_b32", {or_v[1]}, {mfma_v[4], mfma_v[5]}, {}, comment));
co_yield_(Instruction("v_mfma_f32_32x32x1f32",
{mfma_v[8]},
{mfma_v[9], mfma_v[10], mfma_v[11]},
{},
comment));
co_yield_(Instruction("unrelated_op_4", {}, {}, {}, comment));
co_yield_(Instruction("v_or_b32", {or_v[2]}, {mfma_v[8], mfma_v[9]}, {}, comment));
};
auto generator_two = [&]() -> Generator<Instruction> {
std::string comment = "stream2";
co_yield_(Instruction("unrelated_op_5", {}, {}, {}, comment));
co_yield_(Instruction("v_mfma_f32_32x32x1f32",
{mfma_v[12]},
{mfma_v[13], mfma_v[14], mfma_v[15]},
{},
comment));
co_yield_(Instruction("v_or_b32", {or_v[2]}, {mfma_v[4], mfma_v[5]}, {}, comment));
co_yield_(Instruction("unrelated_op_6", {}, {}, {}, comment));
co_yield_(Instruction("v_or_b32", {or_v[3]}, {mfma_v[12], mfma_v[13]}, {}, comment));
};
auto generator_three = [&]() -> Generator<Instruction> {
std::string comment = "stream3";
co_yield_(Instruction("unrelated_op_7", {}, {}, {}, comment));
co_yield_(Instruction("unrelated_op_8", {}, {}, {}, comment));
co_yield_(Instruction("unrelated_op_9", {}, {}, {}, comment));
co_yield_(Instruction("unrelated_op_10", {}, {}, {}, comment));
};
std::vector<Generator<Instruction>> generators;
generators.push_back(generator_one());
generators.push_back(generator_two());
generators.push_back(generator_three());
std::string expected = R"(
v_mfma_f32_32x32x1f32 v0, v1, v2, v3 // stream1
unrelated_op_2 // stream1
unrelated_op_5 // stream2
v_mfma_f32_32x32x1f32 v12, v13, v14, v15 // stream2
v_or_b32 v18, v4, v5 // stream2
unrelated_op_6 // stream2
unrelated_op_7 // stream3
unrelated_op_8 // stream3
unrelated_op_9 // stream3
unrelated_op_10 // stream3
s_nop 9
v_or_b32 v16, v0, v1 // stream1
// Wait state hazard: XDL Write Hazard
v_mfma_f32_32x32x1f32 v4, v5, v6, v7 // stream1
unrelated_op_3 // stream1
v_or_b32 v19, v12, v13 // stream2
s_nop 0xf
s_nop 0
v_or_b32 v17, v4, v5 // stream1
// Wait state hazard: XDL Write Hazard
v_mfma_f32_32x32x1f32 v8, v9, v10, v11 // stream1
unrelated_op_4 // stream1
s_nop 0xf
s_nop 1
v_or_b32 v18, v8, v9 // stream1
// Wait state hazard: XDL Write Hazard
)";
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
Scheduling::SchedulerProcedure::Priority, Scheduling::CostFunction::MinNops, m_context);
m_context->schedule((*scheduler)(generators));
EXPECT_EQ(NormalizedSource(output(), true), NormalizedSource(expected, true));
}
struct LockCheckSchedulerTest : public SchedulerTest,
public testing::WithParamInterface<
std::tuple<Scheduling::SchedulerProcedure, std::string>>
{
protected:
GPUArchitectureTarget targetArchitecture() override
{
return {GPUArchitectureGFX::GFX90A};
}
};
TEST_P(LockCheckSchedulerTest, DISABLED_LockCheckTest)
{
#ifdef NDEBUG
GTEST_SKIP() << "Skipping LockCheckTest in release mode.";
#endif
auto gen = [](std::string inst, bool lock) -> Generator<Instruction> {
if(lock)
{
co_yield_(Instruction::Lock(Scheduling::Dependency::SCC));
}
auto label = Register::Value::Label("testLabel");
co_yield_(Instruction(inst, {}, {label}, {}, ""));
if(lock)
{
co_yield_(Instruction::Unlock());
}
};
{
std::vector<Generator<Instruction>> gens;
gens.push_back(testGeneratorWithComments(true));
gens.push_back(gen(std::get<1>(GetParam()), false));
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
std::get<0>(GetParam()), Scheduling::CostFunction::MinNops, m_context);
EXPECT_THROW({ m_context->schedule((*scheduler)(gens)); }, FatalError);
}
{
std::vector<Generator<Instruction>> gens;
gens.push_back(testGeneratorWithComments(true));
gens.push_back(gen(std::get<1>(GetParam()), true));
auto scheduler = Component::GetNew<Scheduling::Scheduler>(
std::get<0>(GetParam()), Scheduling::CostFunction::MinNops, m_context);
EXPECT_NO_THROW({ m_context->schedule((*scheduler)(gens)); });
}
}
INSTANTIATE_TEST_SUITE_P(
LockCheckSchedulerTest,
LockCheckSchedulerTest,
testing::Combine(::testing::Values(Scheduling::SchedulerProcedure::Sequential,
Scheduling::SchedulerProcedure::RoundRobin,
Scheduling::SchedulerProcedure::Random,
Scheduling::SchedulerProcedure::Cooperative,
Scheduling::SchedulerProcedure::Priority),
::testing::Values("s_branch",
"s_cbranch_scc0"))); //, "s_addc_u32", "s_subb_u32")));
}