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

3291 lines
136 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <cmath>
#include <memory>
#include <rocRoller/Expression.hpp>
#include <rocRoller/ExpressionTransformations.hpp>
#include <rocRoller/Expression_evaluate_detail.hpp>
#include <rocRoller/GPUArchitecture/GPUArchitectureLibrary.hpp>
#include <rocRoller/KernelGraph/RegisterTagManager.hpp>
#include <rocRoller/Operations/Command.hpp>
#include <rocRoller/Utilities/Generator.hpp>
#include "CustomMatchers.hpp"
#include "CustomSections.hpp"
#include "TestContext.hpp"
#include "TestKernels.hpp"
#include <catch2/catch_test_macros.hpp>
#include <catch2/benchmark/catch_benchmark_all.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/generators/catch_generators_adapters.hpp>
#include <catch2/generators/catch_generators_range.hpp>
#include <catch2/matchers/catch_matchers.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <common/SourceMatcher.hpp>
#include <common/TestValues.hpp>
using namespace rocRoller;
namespace ExpressionTest
{
TEST_CASE("Basic expression code generation", "[expression][codegen]")
{
auto context = TestContext::ForDefaultTarget();
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
ra->setName("ra");
ra->allocateNow();
auto rb = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
rb->setName("rb");
rb->allocateNow();
auto a = ra->expression();
auto b = rb->expression();
auto expr1 = a + b;
auto expr2 = b * expr1;
Register::ValuePtr dest;
context.get()->schedule(Expression::generate(dest, expr2, context.get()));
// Explicitly copy the result into another register.
auto dest2 = dest->placeholder();
dest2->allocateNow();
auto regIndexBefore = Generated(dest2->registerIndices())[0];
context.get()->schedule(Expression::generate(dest2, dest->expression(), context.get()));
auto regIndexAfter = Generated(dest2->registerIndices())[0];
CHECK(regIndexBefore == regIndexAfter);
context.get()->schedule(Expression::generate(dest2, expr2, context.get()));
regIndexAfter = Generated(dest2->registerIndices())[0];
CHECK(regIndexBefore == regIndexAfter);
std::string expected = R"(
v_add_i32 v2, v0, v1
v_mul_lo_u32 v3, v1, v2
// Note that v2 is reused
v_mov_b32 v2, v3
// Still storing into v2
v_add_i32 v4, v0, v1
v_mul_lo_u32 v2, v1, v4
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(expected));
}
TEST_CASE("FMA Expressions", "[expression][codegen]")
{
auto context = TestContext::ForDefaultTarget();
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
ra->setName("ra");
ra->allocateNow();
auto rb = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
rb->setName("rb");
rb->allocateNow();
auto rc = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
rc->setName("rc");
rc->allocateNow();
auto a = ra->expression();
auto b = rb->expression();
auto c = rc->expression();
auto expr1 = multiplyAdd(a, b, c);
auto raf = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Float, 1);
raf->allocateNow();
auto rbf = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Float, 1);
rbf->allocateNow();
auto rcf = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Float, 1);
rcf->allocateNow();
auto af = raf->expression();
auto bf = rbf->expression();
auto cf = rcf->expression();
auto expr2 = multiplyAdd(af, bf, cf);
Register::ValuePtr dest1, dest2;
context.get()->schedule(Expression::generate(dest1, expr1, context.get()));
context.get()->schedule(Expression::generate(dest2, expr2, context.get()));
std::string expected = R"(
// Int32: a * x + y doesn't have FMA, so should see multiply then add
v_mul_lo_u32 v6, v0, v1
v_add_i32 v6, v6, v2
// Float: a * x + y has FMA
v_fma_f32 v7, v3, v4, v5
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(expected));
}
TEST_CASE("BFE Expressions", "[expression][codegen][bfe]")
{
auto context = TestContext::ForDefaultTarget();
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt32, 1);
ra->setName("ra");
ra->allocateNow();
auto a = ra->expression();
// Unsigned (no sign extension)
auto expr1 = bfe(DataType::UInt8, a, 0, 8); // full width
auto expr2 = bfe(DataType::UInt8, a, 1, 5); // partial
// Signed (maybe sign extension)
auto expr3
= bfe(DataType::Int8, a, 2, 8); // full width, bfe as unsigned with no sign extension
auto expr4 = bfe(DataType::Int8, a, 3, 7); // partial, sign extension logic
auto expr5
= bfe(DataType::Int8, a, 4, 7); // second partial, hopefully avoid additional load
auto expr6 = bfe(DataType::Int32, a, 5, 17); // partial but full register,
auto expr7 = bfe(DataType::Int64, a, 6, 23); // extract to wider output
#define create_output_reg(width, sign_char) \
std::make_shared<Register::Value>( \
context.get(), Register::Type::Vector, DataType::sign_char##Int##width, 1)
auto dest1 = create_output_reg(8, U);
auto dest2 = create_output_reg(8, U);
auto dest3 = create_output_reg(8, );
auto dest4 = create_output_reg(8, );
auto dest5 = create_output_reg(8, );
auto dest6 = create_output_reg(32, );
auto dest7 = create_output_reg(64, );
#undef create_output_reg
context.get()->schedule(Expression::generate(dest1, expr1, context.get()));
context.get()->schedule(Expression::generate(dest2, expr2, context.get()));
context.get()->schedule(Expression::generate(dest3, expr3, context.get()));
context.get()->schedule(Expression::generate(dest4, expr4, context.get()));
context.get()->schedule(Expression::generate(dest5, expr5, context.get()));
context.get()->schedule(Expression::generate(dest6, expr6, context.get()));
context.get()->schedule(Expression::generate(dest7, expr7, context.get()));
std::string expected = R"(
// Unsigned (no sign extension)
// expr1
v_bfe_u32 v1, v0, 0, 8
// expr2
v_bfe_u32 v2, v0, 1, 5
// Signed (maybe sign extension)
// expr3
v_bfe_u32 v3, v0, 2, 8
// expr4
v_bfe_i32 v4, v0, 3, 7
v_and_b32 v4, 255, v4
// expr5
v_bfe_i32 v5, v0, 4, 7
v_and_b32 v5, 255, v5
// expr6
v_bfe_i32 v6, v0, 5, 17
// expr7
v_bfe_i32 v9, v0, 6, 23
v_ashrrev_i64 v[8:9], 32, v[8:9]
)";
// Test extraction of packed data types
for(auto const& packedDT : {DataType::Halfx2,
DataType::BFloat16x2,
DataType::FP8x4,
DataType::BF8x4,
DataType::FP4x8})
{
auto packedDTInfo = rocRoller::DataTypeInfo::Get(packedDT);
auto unpackedDT = packedDTInfo.segmentVariableType.dataType;
auto unpackedDTInfo = rocRoller::DataTypeInfo::Get(unpackedDT);
;
auto rArg = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, packedDT, 1);
rArg->setName("rArg");
rArg->allocateNow();
auto argExpr = rArg->expression();
auto expr8 = bfe(unpackedDT, argExpr, 0, unpackedDTInfo.elementBits);
auto dest8 = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, unpackedDT, packedDTInfo.packing);
context.get()->schedule(Expression::generate(dest8, expr8, context.get()));
}
expected += R"(
// BitFieldExtract<0,16>(rb: VGPR Value: Halfx2 x 1: v1)
// Allocated : 2 VGPRs (Value: Half): v3, v2
v_bfe_u32 v10, v7, 0, 16
v_bfe_u32 v11, v7, 16, 16
// BitFieldExtract<0,16>(rb: VGPR Value: BFloat16x2 x 1: v1)
// Allocated : 2 VGPRs (Value: BFloat16): v3, v2
v_bfe_u32 v10, v7, 0, 16
v_bfe_u32 v11, v7, 16, 16
// BitFieldExtract<0,8>(rb: VGPR Value: FP8x4 x 1: v1)
// Allocated : 4 VGPRs (Value: FP8): v5, v4, v3, v2
v_bfe_u32 v10, v7, 0, 8
v_bfe_u32 v11, v7, 8, 8
v_bfe_u32 v12, v7, 16, 8
v_bfe_u32 v13, v7, 24, 8
// BitFieldExtract<0,8>(rb: VGPR Value: BF8x4 x 1: v1)
// Allocated : 4 VGPRs (Value: BF8): v5, v4, v3, v2
v_bfe_u32 v10, v7, 0, 8
v_bfe_u32 v11, v7, 8, 8
v_bfe_u32 v12, v7, 16, 8
v_bfe_u32 v13, v7, 24, 8
// BitFieldExtract<0,4>(rb: VGPR Value: FP4x8 x 1: v1)
// Allocated : 8 VGPRs (Value: FP4): v9, v8, v7, v6, v5, v4, v3, v2
v_bfe_u32 v10, v7, 0, 4
v_bfe_u32 v11, v7, 4, 4
v_bfe_u32 v12, v7, 8, 4
v_bfe_u32 v13, v7, 12, 4
v_bfe_u32 v14, v7, 16, 4
v_bfe_u32 v15, v7, 20, 4
v_bfe_u32 v16, v7, 24, 4
v_bfe_u32 v17, v7, 28, 4
)";
{
// Extract to a SGPR
auto rArg = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Halfx2, 1);
rArg->setName("rArg");
rArg->allocateNow();
auto argExpr = rArg->expression();
auto expr9 = bfe(DataType::Half, argExpr, 0, 16);
auto dest9 = std::make_shared<Register::Value>(
context.get(), Register::Type::Scalar, DataType::Half, 1);
context.get()->schedule(Expression::generate(dest9, expr9, context.get()));
}
expected += R"(
// BitFieldExtract<0,16>(rArg: VGPR Value: Halfx2 x 1: v7)
// Allocated : 1 SGPR (Value: Half): s0
s_bfe_u32 s0, v7, 1048576 // expr.offset = 0
)";
{
auto rb = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt64, 1);
rb->setName("rb");
rb->allocateNow();
auto b = rb->expression();
auto expr10 = bfe(DataType::UInt32, a, 0, 32); // redundant full register
auto expr11
= bfe(DataType::UInt32, b, 0, 32); // full register from Uint64 (2 registers)
auto expr12
= bfe(DataType::UInt32, b, 32, 32); // full register from Uint64 (2 registers)
auto expr13 = bfe(DataType::UInt64, b, 0, 64); // redundant full register from UInt64
auto expr14 = bfe(DataType::Int32, b, 0, 32); // full register from Uint64 (2 registers)
auto dest10 = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt32, 1);
auto dest11 = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt32, 1);
auto dest12 = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt32, 1);
auto dest13 = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt64, 1);
auto dest14 = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
context.get()->schedule(Expression::generate(dest10, expr10, context.get()));
context.get()->schedule(Expression::generate(dest11, expr11, context.get()));
context.get()->schedule(Expression::generate(dest12, expr12, context.get()));
context.get()->schedule(Expression::generate(dest13, expr13, context.get()));
context.get()->schedule(Expression::generate(dest14, expr14, context.get()));
}
expected += R"(
// expr10 - full register extraction should optimize away
v_mov_b32 v7, v0
// expr11 - extracting first register from UInt64 to UInt32
v_mov_b32 v12, v10
// expr12 - extracting second register from UInt64 to UInt32
v_mov_b32 v13, v11
// expr13 - full register extraction from UInt64 to UInt64
v_mov_b32 v14, v10
v_mov_b32 v15, v11
// expr14 - extracting first register from UInt64 to Int32
v_mov_b32 v16, v10
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(expected));
auto bfe1 = Expression::BitFieldExtract{{.arg{a}}, DataType::UInt8, 300u, 8u};
CHECK(bfe1.offset == 300u);
}
TEST_CASE("Expression comments", "[expression][comments][codegen]")
{
auto context = TestContext::ForDefaultTarget();
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
ra->allocateNow();
auto rb = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
rb->allocateNow();
auto a = ra->expression();
auto b = rb->expression();
auto expr1 = a + b;
auto expr2 = b * expr1;
setComment(expr1, "The Addition");
appendComment(expr1, " extra comment");
setComment(expr2, "The Multiplication");
CHECK(getComment(expr1) == "The Addition extra comment");
auto expr3 = simplify(expr2);
CHECK(getComment(expr3) == "The Multiplication");
Register::ValuePtr dest;
context.get()->schedule(Expression::generate(dest, expr2, context.get()));
auto normalized = NormalizedSource(context.output(), true);
using namespace Catch::Matchers;
CHECK_THAT(normalized,
ContainsSubstring("// Generate {The Multiplication: Multiply(v1:I, {The "
"Addition extra comment: Add(v0:I, v1:I)I})I} into nullptr"));
CHECK_THAT(normalized, ContainsSubstring("// BEGIN: The Addition extra comment"));
CHECK_THAT(normalized,
ContainsSubstring("// {The Addition extra comment: Add(v0:I, v1:I)I}"));
CHECK_THAT(normalized, ContainsSubstring("// Allocated : 1 VGPR (Value: Int32): v2"));
CHECK_THAT(normalized, ContainsSubstring("v_add_i32 v2, v0, v1"));
CHECK_THAT(normalized, ContainsSubstring("// END: The Addition extra comment"));
CHECK_THAT(normalized, ContainsSubstring("// BEGIN: The Multiplication"));
CHECK_THAT(
normalized,
ContainsSubstring(
"// {The Multiplication: Multiply(v1:I, {The Addition extra comment: v2:I})I}"));
CHECK_THAT(normalized, ContainsSubstring("// Allocated : 1 VGPR (Value: Int32): v3"));
CHECK_THAT(normalized, ContainsSubstring("v_mul_lo_u32 v3, v1, v2"));
CHECK_THAT(normalized, ContainsSubstring("// END: The Multiplication"));
CHECK_THAT(
normalized,
ContainsSubstring("// Freeing The Addition extra comment: 1 VGPR (Value: Int32): v2"));
BENCHMARK("Generate expression")
{
context.get()->schedule(Expression::generate(dest, expr2, context.get()));
};
}
TEST_CASE("Expression comment exceptions", "[expression][comments][codegen]")
{
auto context = TestContext::ForDefaultTarget();
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
ra->setName("ra");
ra->allocateNow();
auto a = ra->expression();
CHECK_THROWS_AS(setComment(a, "The a input"), FatalError);
CHECK_THROWS_AS(appendComment(a, "extra comment"), FatalError);
CHECK(getComment(a) == "ra");
Expression::ExpressionPtr expr1;
CHECK_THROWS_AS(setComment(expr1, "The first expression"), FatalError);
CHECK_THROWS_AS(appendComment(expr1, "extra"), FatalError);
CHECK(getComment(expr1) == "");
}
TEST_CASE("Expression generation exceptions", "[expression][comments][codegen]")
{
auto context = TestContext::ForDefaultTarget();
Register::ValuePtr result;
context.get()->schedule(context.get()->kernel()->preamble());
context.get()->schedule(context.get()->kernel()->prolog());
SECTION("Magic Numbers need generate time operands")
{
auto reg = context.get()->kernel()->workitemIndex()[0]->expression();
auto exp = Expression::magicMultiple(reg);
CHECK_THROWS(context.get()->schedule(Expression::generate(result, exp, context.get())));
CHECK_THROWS(context.get()->schedule(
Expression::generate(result, Expression::magicShifts(reg), context.get())));
CHECK_THROWS(context.get()->schedule(
Expression::generate(result, Expression::magicShiftAndSign(reg), context.get())));
}
SECTION("CommandArgument needs the user args")
{
CommandArgumentPtr arg;
auto argExp = std::make_shared<Expression::Expression>(arg);
CHECK_THROWS(
context.get()->schedule(Expression::generate(result, argExp, context.get())));
}
SECTION("More complex expressions")
{
Register::ValuePtr nullResult;
auto unallocResult = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Int32, 1);
auto allocResult = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Int32, 1);
allocResult->allocateNow();
auto result = GENERATE_COPY(nullResult, unallocResult, allocResult);
CAPTURE(result);
auto unallocated = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Int32, 1);
CHECK_THROWS(context.get()->schedule(
Expression::generate(result, unallocated->expression(), context.get())));
REQUIRE(unallocated->allocationState() == Register::AllocationState::Unallocated);
CHECK_THROWS(context.get()->schedule(Expression::generate(
result, unallocated->expression() + Expression::literal(5), context.get())));
REQUIRE(unallocated->allocationState() == Register::AllocationState::Unallocated);
CHECK_THROWS(context.get()->schedule(Expression::generate(
result,
Expression::multiplyHigh(unallocated->expression(), Expression::literal(5)),
context.get())));
REQUIRE(unallocated->allocationState() == Register::AllocationState::Unallocated);
CHECK_THROWS(context.get()->schedule(
Expression::generate(unallocated, unallocated->expression(), context.get())));
REQUIRE(unallocated->allocationState() == Register::AllocationState::Unallocated);
}
}
TEST_CASE("Matrix Multiply Expressions", "[expression][codegen][mfma]")
{
auto context = TestContext::ForDefaultTarget();
int M = 32;
int N = 32;
int K = 2;
int batches = 1;
auto A_tile = std::make_shared<KernelGraph::CoordinateGraph::WaveTile>();
auto B_tile = std::make_shared<KernelGraph::CoordinateGraph::WaveTile>();
A_tile->sizes = {M, K};
A_tile->vgpr
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Float,
M * K / 64,
Register::AllocationOptions::FullyContiguous());
A_tile->vgpr->allocateNow();
B_tile->sizes = {K, N};
B_tile->vgpr
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Float,
K * N / 64,
Register::AllocationOptions::FullyContiguous());
B_tile->vgpr->allocateNow();
auto ic = std::make_shared<Register::Value>(context.get(),
Register::Type::Accumulator,
DataType::Float,
M * N * batches / 64,
Register::AllocationOptions::FullyContiguous());
ic->allocateNow();
auto A = std::make_shared<Expression::Expression>(A_tile);
auto B = std::make_shared<Expression::Expression>(B_tile);
auto C = ic->expression();
auto expr = std::make_shared<Expression::Expression>(Expression::MatrixMultiply(A, B, C));
context.get()->schedule(
Expression::generate(ic, expr, context.get())); //Test using input C as dest.
Register::ValuePtr rc;
context.get()->schedule(
Expression::generate(rc, expr, context.get())); //Test using a nullptr as dest.
CHECK(ic->regType() == Register::Type::Accumulator);
CHECK(ic->valueCount() == 16);
CHECK(rc->regType() == Register::Type::Accumulator);
CHECK(rc->valueCount() == 16);
auto result = R"(
v_mfma_f32_32x32x2f32 a[0:15], v0, v1, a[0:15] //is matmul
v_mfma_f32_32x32x2f32 a[16:31], v0, v1, a[0:15] //rc matmul
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
TEST_CASE("Expressions reuse input vgprs as output vgprs in arithmetic",
"[expression][codegen][optimization][fp32]")
{
auto context = TestContext::ForDefaultTarget();
int M = 16;
int N = 16;
int K = 4;
int batches = 1;
auto A_tile = std::make_shared<KernelGraph::CoordinateGraph::WaveTile>();
auto B_tile = std::make_shared<KernelGraph::CoordinateGraph::WaveTile>();
A_tile->sizes = {M, K};
A_tile->vgpr
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Float,
M * K / 64,
Register::AllocationOptions::FullyContiguous());
A_tile->vgpr->allocateNow();
B_tile->sizes = {K, N};
B_tile->vgpr
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Float,
K * N / 64,
Register::AllocationOptions::FullyContiguous());
B_tile->vgpr->allocateNow();
auto accumD
= std::make_shared<Register::Value>(context.get(),
Register::Type::Accumulator,
DataType::Float,
M * N * batches / 64,
Register::AllocationOptions::FullyContiguous());
accumD->allocateNow();
auto A = std::make_shared<Expression::Expression>(A_tile);
auto B = std::make_shared<Expression::Expression>(B_tile);
auto D = accumD->expression();
auto mulABExpr
= std::make_shared<Expression::Expression>(Expression::MatrixMultiply(A, B, D));
context.get()->schedule(
Expression::generate(accumD, mulABExpr, context.get())); //Test using input D as dest.
CHECK(accumD->regType() == Register::Type::Accumulator);
CHECK(accumD->valueCount() == 4);
auto vecD
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Float,
M * N * batches / 64,
Register::AllocationOptions::FullyContiguous());
context.get()->schedule(Expression::generate(vecD, D, context.get()));
auto scaleDExpr = Expression::literal(2.0f) * vecD->expression();
context.get()->schedule(Expression::generate(vecD, scaleDExpr, context.get()));
auto vecC
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Float,
M * N * batches / 64,
Register::AllocationOptions::FullyContiguous());
vecC->allocateNow();
auto addCDExpr = vecC->expression() + vecD->expression();
context.get()->schedule(Expression::generate(vecD, addCDExpr, context.get()));
auto result = R"(
v_mfma_f32_16x16x4f32 a[0:3], v0, v1, a[0:3]
s_nop 10
v_accvgpr_read v2, a0
v_accvgpr_read v3, a1
v_accvgpr_read v4, a2
v_accvgpr_read v5, a3
v_mul_f32 v2, 2.00000, v2
v_mul_f32 v3, 2.00000, v3
v_mul_f32 v4, 2.00000, v4
v_mul_f32 v5, 2.00000, v5
v_add_f32 v2, v6, v2
v_add_f32 v3, v7, v3
v_add_f32 v4, v8, v4
v_add_f32 v5, v9, v5
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
TEST_CASE("Expressions reuse input vgprs as output vgprs in arithmetic f16",
"[expression][codegen][optimization][fp16]")
{
auto context = TestContext::ForDefaultTarget();
int M = 32;
int N = 32;
int K = 8;
int batches = 1;
auto A_tile = std::make_shared<KernelGraph::CoordinateGraph::WaveTile>();
auto B_tile = std::make_shared<KernelGraph::CoordinateGraph::WaveTile>();
A_tile->sizes = {M, K};
A_tile->vgpr
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Halfx2,
M * K / 64 / 2,
Register::AllocationOptions::FullyContiguous());
A_tile->vgpr->allocateNow();
B_tile->sizes = {K, N};
B_tile->vgpr
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Halfx2,
K * N / 64 / 2,
Register::AllocationOptions::FullyContiguous());
B_tile->vgpr->allocateNow();
auto accumD
= std::make_shared<Register::Value>(context.get(),
Register::Type::Accumulator,
DataType::Float,
M * N * batches / 64,
Register::AllocationOptions::FullyContiguous());
accumD->allocateNow();
auto A = std::make_shared<Expression::Expression>(A_tile);
auto B = std::make_shared<Expression::Expression>(B_tile);
auto D = accumD->expression();
auto mulABExpr
= std::make_shared<Expression::Expression>(Expression::MatrixMultiply(A, B, D));
context.get()->schedule(
Expression::generate(accumD, mulABExpr, context.get())); //Test using input D as dest.
CHECK(accumD->regType() == Register::Type::Accumulator);
CHECK(accumD->valueCount() == 16);
auto vecD
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Float,
M * N * batches / 64,
Register::AllocationOptions::FullyContiguous());
auto vecC
= std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Half,
M * N * batches / 64,
Register::AllocationOptions::FullyContiguous());
vecC->allocateNow();
auto scaleDExpr = Expression::literal(2.0, DataType::Half) * vecD->expression();
auto addCDExpr = vecC->expression() + vecD->expression();
context.get()->schedule(Expression::generate(vecD, D, context.get()));
context.get()->schedule(Expression::generate(vecD, scaleDExpr, context.get()));
context.get()->schedule(Expression::generate(vecD, addCDExpr, context.get()));
auto X = std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Halfx2,
M * K / 64 / 2,
Register::AllocationOptions::FullyContiguous());
X->allocateNow();
auto Y = std::make_shared<Register::Value>(context.get(),
Register::Type::Vector,
DataType::Half,
M * K / 64,
Register::AllocationOptions::FullyContiguous());
Y->allocateNow();
auto addXYExpr = X->expression() + Y->expression();
context.get()->schedule(Expression::generate(Y, addXYExpr, context.get()));
// TODO If operand being converted is a literal, do one conversion only.
auto result = R"(
// A is in v[0:1], B is in v[2:3], C is in v[4:19], D is in a[0:15]
// Result R will end up in v[20:35]. Steps are:
// R <- D
// R <- alpha * R
// R <- R + C
v_mfma_f32_32x32x8f16 a[0:15], v[0:1], v[2:3], a[0:15]
s_nop 0xf
s_nop 2
v_accvgpr_read v20, a0
v_accvgpr_read v21, a1
v_accvgpr_read v22, a2
v_accvgpr_read v23, a3
v_accvgpr_read v24, a4
v_accvgpr_read v25, a5
v_accvgpr_read v26, a6
v_accvgpr_read v27, a7
v_accvgpr_read v28, a8
v_accvgpr_read v29, a9
v_accvgpr_read v30, a10
v_accvgpr_read v31, a11
v_accvgpr_read v32, a12
v_accvgpr_read v33, a13
v_accvgpr_read v34, a14
v_accvgpr_read v35, a15
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v20, v36, v20
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v21, v36, v21
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v22, v36, v22
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v23, v36, v23
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v24, v36, v24
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v25, v36, v25
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v26, v36, v26
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v27, v36, v27
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v28, v36, v28
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v29, v36, v29
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v30, v36, v30
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v31, v36, v31
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v32, v36, v32
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v33, v36, v33
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v34, v36, v34
v_cvt_f32_f16 v36, 2.00000
v_mul_f32 v35, v36, v35
v_cvt_f32_f16 v36, v4
v_add_f32 v20, v36, v20
v_cvt_f32_f16 v36, v5
v_add_f32 v21, v36, v21
v_cvt_f32_f16 v36, v6
v_add_f32 v22, v36, v22
v_cvt_f32_f16 v36, v7
v_add_f32 v23, v36, v23
v_cvt_f32_f16 v36, v8
v_add_f32 v24, v36, v24
v_cvt_f32_f16 v36, v9
v_add_f32 v25, v36, v25
v_cvt_f32_f16 v36, v10
v_add_f32 v26, v36, v26
v_cvt_f32_f16 v36, v11
v_add_f32 v27, v36, v27
v_cvt_f32_f16 v36, v12
v_add_f32 v28, v36, v28
v_cvt_f32_f16 v36, v13
v_add_f32 v29, v36, v29
v_cvt_f32_f16 v36, v14
v_add_f32 v30, v36, v30
v_cvt_f32_f16 v36, v15
v_add_f32 v31, v36, v31
v_cvt_f32_f16 v36, v16
v_add_f32 v32, v36, v32
v_cvt_f32_f16 v36, v17
v_add_f32 v33, v36, v33
v_cvt_f32_f16 v36, v18
v_add_f32 v34, v36, v34
v_cvt_f32_f16 v36, v19
v_add_f32 v35, v36, v35
// X is v[36:37]:2xH and Y is v[38:41]:H (and Z is same as Y)
// Then Y <- X + Y will be: Add(v[36:37]:2xH, v[38:41]:H)
v_and_b32 v42, 65535, v36
v_lshrrev_b32 v43, 16, v36
v_add_f16 v38, v42, v38
v_add_f16 v39, v43, v39
v_and_b32 v42, 65535, v37
v_lshrrev_b32 v43, 16, v37
v_add_f16 v40, v42, v40
v_add_f16 v41, v43, v41
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
TEST_CASE(
"Expressions reuse input vgprs as output vgprs in arithmetic f16 with smaller packing",
"[expression][codegen][optimization][future][fp16]")
{
auto context = TestContext::ForDefaultTarget();
int M = 32;
int K = 8;
auto X = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Halfx2, M * K / 64 / 2);
X->allocateNow();
auto Y = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Half, M * K / 64);
Y->allocateNow();
// Since we are asking the result to be stored into X, we
// currently get a failure.
// TODO See the "Destination/result packing mismatch" assertion
// in Expression_generate.cpp.
auto addXYExpr = X->expression() + Y->expression();
CHECK_THROWS_AS(context.get()->schedule(Expression::generate(X, addXYExpr, context.get())),
FatalError);
// The above should be possible: Y should be packed, and then
// the v_pk_add_f16 instructions called.
}
TEST_CASE("Expression result types", "[expression]")
{
auto context = TestContext::ForDefaultTarget();
auto vgprFloat = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Float, 1)
->expression();
auto vgprDouble = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Double, 1)
->expression();
auto vgprInt32 = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Int32, 1)
->expression();
auto vgprInt64 = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Int64, 1)
->expression();
auto vgprUInt32 = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::UInt32, 1)
->expression();
auto vgprUInt64 = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::UInt64, 1)
->expression();
auto vgprHalf
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Half, 1)
->expression();
auto vgprHalfx2 = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Halfx2, 1)
->expression();
auto vgprBool32 = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Bool32, 1)
->expression();
auto vgprBool
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Bool, 1)
->expression();
auto sgprFloat = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Float, 1)
->expression();
auto sgprDouble = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Double, 1)
->expression();
auto sgprInt32 = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Int32, 1)
->expression();
auto sgprInt64 = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Int64, 1)
->expression();
auto sgprUInt32 = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::UInt32, 1)
->expression();
auto sgprUInt64 = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::UInt64, 1)
->expression();
auto sgprHalf
= Register::Value::Placeholder(context.get(), Register::Type::Scalar, DataType::Half, 1)
->expression();
auto sgprHalfx2 = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Halfx2, 1)
->expression();
auto sgprBool64 = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Bool64, 1)
->expression();
auto sgprBool32 = Register::Value::Placeholder(
context.get(), Register::Type::Scalar, DataType::Bool32, 1)
->expression();
auto sgprBool
= Register::Value::Placeholder(context.get(), Register::Type::Scalar, DataType::Bool, 1)
->expression();
auto sgprWavefrontSized
= Register::Value::Placeholder(context.get(),
Register::Type::Scalar,
context.get()->kernel()->wavefront_size() == 64
? DataType::Bool64
: DataType::Bool32,
1)
->expression();
auto agprFloat = Register::Value::Placeholder(
context.get(), Register::Type::Accumulator, DataType::Float, 1)
->expression();
auto agprDouble = Register::Value::Placeholder(
context.get(), Register::Type::Accumulator, DataType::Double, 1)
->expression();
auto litInt32 = Expression::literal<int32_t>(5);
auto litInt64 = Expression::literal<int64_t>(5);
auto litFloat = Expression::literal(5.0f);
auto litDouble = Expression::literal(5.0);
Expression::ResultType rVgprFloat{Register::Type::Vector, DataType::Float, 1};
Expression::ResultType rVgprDouble{Register::Type::Vector, DataType::Double, 1};
Expression::ResultType rVgprInt32{Register::Type::Vector, DataType::Int32, 1};
Expression::ResultType rVgprInt64{Register::Type::Vector, DataType::Int64, 1};
Expression::ResultType rVgprUInt32{Register::Type::Vector, DataType::UInt32, 1};
Expression::ResultType rVgprUInt64{Register::Type::Vector, DataType::UInt64, 1};
Expression::ResultType rVgprHalf{Register::Type::Vector, DataType::Half, 1};
Expression::ResultType rVgprHalfx2{Register::Type::Vector, DataType::Halfx2, 2};
Expression::ResultType rVgprBool32{Register::Type::Vector, DataType::Bool32, 1};
Expression::ResultType rSgprFloat{Register::Type::Scalar, DataType::Float, 1};
Expression::ResultType rSgprDouble{Register::Type::Scalar, DataType::Double, 1};
Expression::ResultType rSgprInt32{Register::Type::Scalar, DataType::Int32, 1};
Expression::ResultType rSgprInt64{Register::Type::Scalar, DataType::Int64, 1};
Expression::ResultType rSgprUInt32{Register::Type::Scalar, DataType::UInt32, 1};
Expression::ResultType rSgprUInt64{Register::Type::Scalar, DataType::UInt64, 1};
Expression::ResultType rSgprHalf{Register::Type::Scalar, DataType::Half, 1};
Expression::ResultType rSgprHalfx2{Register::Type::Scalar, DataType::Halfx2, 2};
Expression::ResultType rSgprBool32{Register::Type::Scalar, DataType::Bool32, 1};
Expression::ResultType rSgprBool64{Register::Type::Scalar, DataType::Bool64, 1};
Expression::ResultType rSgprBool{Register::Type::Scalar, DataType::Bool, 1};
Expression::ResultType rSgprWavefrontSized{
Register::Type::Scalar,
context.get()->kernel()->wavefront_size() == 64 ? DataType::Bool64 : DataType::Bool32,
1};
Expression::ResultType rVCC{Register::Type::VCC, DataType::Bool32, 1};
Expression::ResultType rSCC{Register::Type::SCC, DataType::Bool, 1};
Expression::ResultType rAgprFloat{Register::Type::Accumulator, DataType::Float, 1};
Expression::ResultType rAgprDouble{Register::Type::Accumulator, DataType::Double, 1};
SECTION("Value expressions")
{
CHECK(rSgprInt64 == resultType(sgprInt64));
CHECK(rVgprInt32 == resultType(vgprInt32));
CHECK(rVgprInt64 == resultType(vgprInt64));
CHECK(rVgprFloat == resultType(vgprFloat));
CHECK(rSgprFloat == resultType(sgprFloat));
CHECK(rVgprDouble == resultType(vgprDouble));
CHECK(rSgprDouble == resultType(sgprDouble));
CHECK(rAgprDouble == resultType(agprDouble));
CHECK(rAgprDouble == resultType(agprDouble));
}
SECTION("Binary expressions")
{
CHECK(rVgprInt32 == resultType(vgprInt32 + vgprInt32));
CHECK(rVgprInt32 == resultType(vgprInt32 + sgprInt32));
CHECK(rVgprInt32 == resultType(sgprInt32 - vgprInt32));
CHECK(rSgprInt32 == resultType(sgprInt32 * sgprInt32));
CHECK(rVgprInt64 == resultType(vgprInt64 + vgprInt32));
CHECK(rVgprInt64 == resultType(vgprInt32 + vgprInt64));
CHECK(rVgprInt64 == resultType(vgprInt64 + vgprInt64));
CHECK(rVgprFloat == resultType(vgprFloat + vgprFloat));
CHECK(rVgprFloat == resultType(vgprFloat - sgprFloat));
CHECK(rVgprFloat == resultType(litFloat * vgprFloat));
CHECK(rVgprFloat == resultType(vgprFloat * litFloat));
CHECK(rSgprInt32 == resultType(sgprInt32 + sgprInt32));
CHECK(rSgprInt32 == resultType(sgprInt32 + litInt32));
CHECK(rSgprInt32 == resultType(litInt32 + sgprInt32));
CHECK(rSgprInt64 == resultType(litInt32 + sgprInt64));
CHECK(rSgprInt64 == resultType(sgprInt64 + litInt32));
CHECK(rSgprInt64 == resultType(sgprInt64 + sgprInt32));
CHECK(rSgprWavefrontSized == resultType(vgprFloat > vgprFloat));
CHECK_THROWS_AS(resultType((vgprFloat > vgprFloat) && vgprBool), FatalError);
CHECK(rSgprWavefrontSized == resultType(sgprFloat < vgprFloat));
CHECK(rSgprWavefrontSized == resultType(sgprFloat < vgprFloat && sgprBool));
CHECK(rSgprWavefrontSized == resultType(sgprDouble <= vgprDouble));
CHECK(rSgprWavefrontSized == resultType(sgprInt32 <= vgprInt32));
CHECK(rSgprWavefrontSized == resultType(sgprInt32 <= vgprInt32 || sgprBool));
CHECK(rSgprWavefrontSized == resultType(litInt32 > vgprInt64));
CHECK(rSgprWavefrontSized == resultType(litInt32 > vgprInt64 || sgprBool));
CHECK(rSgprWavefrontSized
== resultType(litInt32 > vgprInt64 || Expression::literal(false)));
CHECK(rSgprBool == resultType(litInt32 <= sgprInt64));
CHECK(rSgprBool == resultType(sgprInt32 >= litInt32));
}
CHECK_THROWS(resultType(sgprDouble <= vgprFloat));
CHECK_THROWS(resultType(vgprInt32 > vgprFloat));
SECTION("Arithmetic unary ops")
{
// auto ops = ;
auto op = GENERATE_COPY(
from_range(std::to_array({Expression::operator-, // cppcheck-suppress syntaxError
Expression::operator~,
Expression::magicMultiple})));
CAPTURE(op(vgprFloat));
CHECK(rVgprFloat == resultType(op(vgprFloat)));
CHECK(rVgprDouble == resultType(op(vgprDouble)));
CHECK(rVgprInt32 == resultType(op(vgprInt32)));
CHECK(rVgprInt64 == resultType(op(vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64)));
CHECK(rVgprHalf == resultType(op(vgprHalf)));
CHECK(rVgprHalfx2 == resultType(op(vgprHalfx2)));
CHECK(rVgprBool32 == resultType(op(vgprBool32)));
CHECK(rSgprFloat == resultType(op(sgprFloat)));
CHECK(rSgprDouble == resultType(op(sgprDouble)));
CHECK(rSgprInt32 == resultType(op(sgprInt32)));
CHECK(rSgprInt64 == resultType(op(sgprInt64)));
CHECK(rSgprUInt32 == resultType(op(sgprUInt32)));
CHECK(rSgprUInt64 == resultType(op(sgprUInt64)));
CHECK(rSgprHalf == resultType(op(sgprHalf)));
CHECK(rSgprHalfx2 == resultType(op(sgprHalfx2)));
CHECK(rSgprBool32 == resultType(op(sgprBool32)));
}
SECTION("Magic shifts")
{
auto op = Expression::magicShifts;
CHECK(rVgprInt32 == resultType(op(vgprFloat)));
CHECK(rVgprInt32 == resultType(op(vgprDouble)));
CHECK(rVgprInt32 == resultType(op(vgprInt32)));
CHECK(rVgprInt32 == resultType(op(vgprInt64)));
CHECK(rVgprInt32 == resultType(op(vgprUInt32)));
CHECK(rVgprInt32 == resultType(op(vgprUInt64)));
CHECK(rVgprInt32 == resultType(op(vgprHalf)));
CHECK(Expression::ResultType(
rVgprInt32.regType, rVgprInt32.varType, rVgprInt32.valueCount * 2)
== resultType(op(vgprHalfx2)));
CHECK(rVgprInt32 == resultType(op(vgprBool32)));
CHECK(rSgprInt32 == resultType(op(sgprFloat)));
CHECK(rSgprInt32 == resultType(op(sgprDouble)));
CHECK(rSgprInt32 == resultType(op(sgprInt32)));
CHECK(rSgprInt32 == resultType(op(sgprInt64)));
CHECK(rSgprInt32 == resultType(op(sgprUInt32)));
CHECK(rSgprInt32 == resultType(op(sgprUInt64)));
CHECK(rSgprInt32 == resultType(op(sgprHalf)));
CHECK(Expression::ResultType(
rSgprInt32.regType, rSgprInt32.varType, rSgprInt32.valueCount * 2)
== resultType(op(sgprHalfx2)));
CHECK(rSgprInt32 == resultType(op(sgprBool32)));
}
SECTION("Magic shiftAndSign")
{
auto op = Expression::magicShiftAndSign;
CHECK(rVgprUInt32 == resultType(op(vgprFloat)));
CHECK(rVgprUInt32 == resultType(op(vgprDouble)));
CHECK(rVgprUInt32 == resultType(op(vgprInt32)));
CHECK(rVgprUInt32 == resultType(op(vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprHalf)));
CHECK(Expression::ResultType(
rVgprUInt32.regType, rVgprUInt32.varType, rVgprUInt32.valueCount * 2)
== resultType(op(vgprHalfx2)));
CHECK(rVgprUInt32 == resultType(op(vgprBool32)));
CHECK(rSgprUInt32 == resultType(op(sgprFloat)));
CHECK(rSgprUInt32 == resultType(op(sgprDouble)));
CHECK(rSgprUInt32 == resultType(op(sgprInt32)));
CHECK(rSgprUInt32 == resultType(op(sgprInt64)));
CHECK(rSgprUInt32 == resultType(op(sgprUInt32)));
CHECK(rSgprUInt32 == resultType(op(sgprUInt64)));
CHECK(rSgprUInt32 == resultType(op(sgprHalf)));
CHECK(Expression::ResultType(
rSgprUInt32.regType, rSgprUInt32.varType, rSgprUInt32.valueCount * 2)
== resultType(op(sgprHalfx2)));
CHECK(rSgprUInt32 == resultType(op(sgprBool32)));
}
SECTION("Comparisons")
{
auto op = GENERATE(Expression::operator>,
Expression::operator>=,
Expression::operator<,
Expression::operator<=,
Expression::operator==);
CAPTURE(op(vgprFloat, vgprFloat));
CHECK(rSgprWavefrontSized == resultType(op(vgprFloat, vgprFloat)));
CHECK(rSgprWavefrontSized == resultType(op(vgprDouble, vgprDouble)));
CHECK(rSgprWavefrontSized == resultType(op(vgprInt32, vgprInt32)));
CHECK(rSgprWavefrontSized == resultType(op(vgprInt64, vgprInt64)));
CHECK(rSgprWavefrontSized == resultType(op(vgprUInt32, vgprUInt32)));
CHECK(rSgprWavefrontSized == resultType(op(vgprUInt64, vgprUInt64)));
CHECK(rSgprWavefrontSized == resultType(op(vgprHalf, vgprHalf)));
CHECK(Expression::ResultType(rSgprWavefrontSized.regType,
rSgprWavefrontSized.varType,
rSgprWavefrontSized.valueCount * 2)
== resultType(op(vgprHalfx2, vgprHalfx2)));
CHECK(rSgprWavefrontSized == resultType(op(vgprBool32, vgprBool32)));
CHECK(rSgprWavefrontSized == resultType(op(vgprBool, vgprBool)));
CHECK(rSgprBool == resultType(op(sgprFloat, sgprFloat)));
CHECK(rSgprBool == resultType(op(sgprDouble, sgprDouble)));
CHECK(rSgprBool == resultType(op(sgprInt32, sgprInt32)));
CHECK(rSgprBool == resultType(op(sgprInt64, sgprInt64)));
CHECK(rSgprBool == resultType(op(sgprUInt32, sgprUInt32)));
CHECK(rSgprBool == resultType(op(sgprUInt64, sgprUInt64)));
CHECK(rSgprBool == resultType(op(sgprHalf, sgprHalf)));
CHECK(Expression::ResultType(
rSgprBool.regType, rSgprBool.varType, rSgprBool.valueCount * 2)
== resultType(op(sgprHalfx2, sgprHalfx2)));
CHECK(rSgprBool == resultType(op(sgprBool32, sgprBool32)));
CHECK(rSgprBool == resultType(op(sgprBool, sgprBool)));
}
SECTION("Arithmetic binary")
{
auto op = GENERATE(from_range(std::to_array({Expression::operator+,
Expression::operator-,
Expression::operator*,
Expression::operator/,
Expression::operator%,
Expression::operator<<,
Expression::operator>>,
Expression::operator&,
Expression::arithmeticShiftR})));
CAPTURE(op(vgprFloat, vgprFloat));
CHECK(rVgprFloat == resultType(op(vgprFloat, vgprFloat)));
CHECK(rVgprDouble == resultType(op(vgprDouble, vgprDouble)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32)));
CHECK(rVgprInt64 == resultType(op(vgprInt64, vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64)));
CHECK(rVgprHalf == resultType(op(vgprHalf, vgprHalf)));
CHECK(rVgprHalfx2 == resultType(op(vgprHalfx2, vgprHalfx2)));
CHECK(rVgprBool32 == resultType(op(vgprBool32, vgprBool32)));
CHECK(rSgprFloat == resultType(op(sgprFloat, sgprFloat)));
CHECK(rSgprDouble == resultType(op(sgprDouble, sgprDouble)));
CHECK(rSgprInt32 == resultType(op(sgprInt32, sgprInt32)));
CHECK(rSgprInt64 == resultType(op(sgprInt64, sgprInt64)));
CHECK(rSgprUInt32 == resultType(op(sgprUInt32, sgprUInt32)));
CHECK(rSgprUInt64 == resultType(op(sgprUInt64, sgprUInt64)));
CHECK(rSgprHalf == resultType(op(sgprHalf, sgprHalf)));
CHECK(rSgprHalfx2 == resultType(op(sgprHalfx2, sgprHalfx2)));
CHECK(rSgprBool32 == resultType(op(sgprBool32, sgprBool32)));
}
SECTION("Logical")
{
auto op = GENERATE(Expression::operator&&, Expression::operator||);
CAPTURE(op(sgprBool64, sgprBool64));
CHECK_THROWS(resultType(op(vgprFloat, vgprFloat)));
CHECK_THROWS(resultType(op(vgprDouble, vgprDouble)));
CHECK_THROWS(resultType(op(vgprInt32, vgprInt32)));
CHECK_THROWS(resultType(op(vgprInt64, vgprInt64)));
CHECK_THROWS(resultType(op(vgprUInt32, vgprUInt32)));
CHECK_THROWS(resultType(op(vgprUInt64, vgprUInt64)));
CHECK_THROWS(resultType(op(vgprHalf, vgprHalf)));
CHECK_THROWS(resultType(op(vgprHalfx2, vgprHalfx2)));
CHECK_THROWS(resultType(op(vgprBool32, vgprBool32)));
CHECK_THROWS(resultType(op(vgprBool, vgprBool)));
CHECK_THROWS(resultType(op(sgprFloat, sgprFloat)));
CHECK_THROWS(resultType(op(sgprDouble, sgprDouble)));
CHECK_THROWS(resultType(op(sgprInt32, sgprInt32)));
CHECK_THROWS(resultType(op(sgprInt64, sgprInt64)));
CHECK_THROWS(resultType(op(sgprUInt32, sgprUInt32)));
CHECK(rSgprBool64 == resultType(op(sgprBool64, sgprBool64)));
CHECK(rSgprBool64 == resultType(op(sgprBool64, sgprBool)));
CHECK_THROWS(resultType(op(sgprHalf, sgprHalf)));
CHECK_THROWS(resultType(op(sgprHalfx2, sgprHalfx2)));
CHECK(rSgprBool32 == resultType(op(sgprBool32, sgprBool32)));
CHECK(rSgprBool32 == resultType(op(sgprBool32, sgprBool)));
CHECK(rSgprBool32 == resultType(op(sgprBool, sgprBool32)));
CHECK(rSgprBool == resultType(op(sgprBool, sgprBool)));
}
SECTION("Bitwise binary")
{
auto op = GENERATE(from_range(std::to_array({Expression::operator<<,
Expression::logicalShiftR,
Expression::operator&,
Expression::operator^,
Expression::operator|})));
CAPTURE(op(vgprFloat, vgprFloat));
CHECK(rVgprFloat == resultType(op(vgprFloat, vgprFloat)));
CHECK(rVgprDouble == resultType(op(vgprDouble, vgprDouble)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32)));
CHECK(rVgprInt64 == resultType(op(vgprInt64, vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32)));
CHECK(rVgprHalf == resultType(op(vgprHalf, vgprHalf)));
CHECK(rVgprHalfx2 == resultType(op(vgprHalfx2, vgprHalfx2)));
CHECK(rVgprBool32 == resultType(op(vgprBool32, vgprBool32)));
CHECK(rSgprFloat == resultType(op(sgprFloat, sgprFloat)));
CHECK(rSgprDouble == resultType(op(sgprDouble, sgprDouble)));
CHECK(rSgprInt32 == resultType(op(sgprInt32, sgprInt32)));
CHECK(rSgprInt64 == resultType(op(sgprInt64, sgprInt64)));
CHECK(rSgprUInt32 == resultType(op(sgprUInt32, sgprUInt32)));
CHECK(rSgprHalf == resultType(op(sgprHalf, sgprHalf)));
CHECK(rSgprHalfx2 == resultType(op(sgprHalfx2, sgprHalfx2)));
CHECK(rSgprBool32 == resultType(op(sgprBool32, sgprBool32)));
}
SECTION("MultiplyAdd")
{
auto op = Expression::multiplyAdd;
CAPTURE(op(vgprFloat, vgprFloat, vgprFloat));
CHECK(rVgprFloat == resultType(op(vgprFloat, vgprFloat, vgprFloat)));
CHECK(rVgprDouble == resultType(op(vgprDouble, vgprDouble, vgprDouble)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32, vgprInt32)));
CHECK(rVgprInt64 == resultType(op(vgprInt64, vgprInt64, vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32, vgprUInt32)));
CHECK(rVgprHalf == resultType(op(vgprHalf, vgprHalf, vgprHalf)));
CHECK(rVgprHalfx2 == resultType(op(vgprHalfx2, vgprHalfx2, vgprHalfx2)));
CHECK(rVgprBool32 == resultType(op(vgprBool32, vgprBool32, vgprBool32)));
CHECK(rSgprFloat == resultType(op(sgprFloat, sgprFloat, sgprFloat)));
CHECK(rSgprDouble == resultType(op(sgprDouble, sgprDouble, sgprDouble)));
CHECK(rSgprInt32 == resultType(op(sgprInt32, sgprInt32, sgprInt32)));
CHECK(rSgprInt64 == resultType(op(sgprInt64, sgprInt64, sgprInt64)));
CHECK(rSgprUInt32 == resultType(op(sgprUInt32, sgprUInt32, sgprUInt32)));
CHECK(rSgprHalf == resultType(op(sgprHalf, sgprHalf, sgprHalf)));
CHECK(rSgprHalfx2 == resultType(op(sgprHalfx2, sgprHalfx2, sgprHalfx2)));
CHECK(rSgprBool32 == resultType(op(sgprBool32, sgprBool32, sgprBool32)));
}
SECTION("AddShiftL")
{
auto op = Expression::addShiftL;
CAPTURE(op(vgprFloat, vgprFloat, vgprFloat));
CHECK(rVgprFloat == resultType(op(vgprFloat, vgprFloat, vgprFloat)));
CHECK(rVgprDouble == resultType(op(vgprDouble, vgprDouble, vgprDouble)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32, vgprInt32)));
CHECK(rVgprInt64 == resultType(op(vgprInt64, vgprInt64, vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32, vgprUInt32)));
CHECK(rVgprHalf == resultType(op(vgprHalf, vgprHalf, vgprHalf)));
CHECK(rVgprHalfx2 == resultType(op(vgprHalfx2, vgprHalfx2, vgprHalfx2)));
CHECK(rVgprBool32 == resultType(op(vgprBool32, vgprBool32, vgprBool32)));
CHECK(rSgprFloat == resultType(op(sgprFloat, sgprFloat, sgprFloat)));
CHECK(rSgprDouble == resultType(op(sgprDouble, sgprDouble, sgprDouble)));
CHECK(rSgprInt32 == resultType(op(sgprInt32, sgprInt32, sgprInt32)));
CHECK(rSgprInt64 == resultType(op(sgprInt64, sgprInt64, sgprInt64)));
CHECK(rSgprUInt32 == resultType(op(sgprUInt32, sgprUInt32, sgprUInt32)));
CHECK(rSgprHalf == resultType(op(sgprHalf, sgprHalf, sgprHalf)));
CHECK(rSgprHalfx2 == resultType(op(sgprHalfx2, sgprHalfx2, sgprHalfx2)));
CHECK(rSgprBool32 == resultType(op(sgprBool32, sgprBool32, sgprBool32)));
// Shift not considered in promotion
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32, vgprUInt32)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32, vgprInt32)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32, vgprUInt64)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32, vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32, vgprUInt32)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32, vgprInt32)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32, vgprUInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32, vgprInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprUInt32)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprInt32)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprUInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprUInt32)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprInt32)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprUInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprInt64)));
}
SECTION("ShiftLAdd")
{
auto op = Expression::shiftLAdd;
CAPTURE(op(vgprFloat, vgprFloat, vgprFloat));
CHECK(rVgprFloat == resultType(op(vgprFloat, vgprFloat, vgprFloat)));
CHECK(rVgprDouble == resultType(op(vgprDouble, vgprDouble, vgprDouble)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32, vgprInt32)));
CHECK(rVgprInt64 == resultType(op(vgprInt64, vgprInt64, vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32, vgprUInt32)));
CHECK(rVgprHalf == resultType(op(vgprHalf, vgprHalf, vgprHalf)));
CHECK(rVgprHalfx2 == resultType(op(vgprHalfx2, vgprHalfx2, vgprHalfx2)));
CHECK(rVgprBool32 == resultType(op(vgprBool32, vgprBool32, vgprBool32)));
CHECK(rSgprFloat == resultType(op(sgprFloat, sgprFloat, sgprFloat)));
CHECK(rSgprDouble == resultType(op(sgprDouble, sgprDouble, sgprDouble)));
CHECK(rSgprInt32 == resultType(op(sgprInt32, sgprInt32, sgprInt32)));
CHECK(rSgprInt64 == resultType(op(sgprInt64, sgprInt64, sgprInt64)));
CHECK(rSgprUInt32 == resultType(op(sgprUInt32, sgprUInt32, sgprUInt32)));
CHECK(rSgprHalf == resultType(op(sgprHalf, sgprHalf, sgprHalf)));
CHECK(rSgprHalfx2 == resultType(op(sgprHalfx2, sgprHalfx2, sgprHalfx2)));
CHECK(rSgprBool32 == resultType(op(sgprBool32, sgprBool32, sgprBool32)));
// Shift not considered in promotion
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprUInt32, vgprInt32)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt32, vgprInt32)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprUInt64, vgprInt32)));
CHECK(rVgprInt32 == resultType(op(vgprInt32, vgprInt64, vgprInt32)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt32, vgprUInt32)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprInt32, vgprUInt32)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprUInt64, vgprUInt32)));
CHECK(rVgprUInt32 == resultType(op(vgprUInt32, vgprInt64, vgprUInt32)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt32, vgprUInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprInt32, vgprUInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprUInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprInt64, vgprUInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt32, vgprUInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprInt32, vgprUInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprUInt64, vgprUInt64)));
CHECK(rVgprUInt64 == resultType(op(vgprUInt64, vgprInt64, vgprUInt64)));
}
SECTION("Conditional")
{
auto op = Expression::conditional;
CHECK(rVgprFloat == resultType(op(sgprBool, vgprFloat, vgprFloat)));
CHECK(rVgprDouble == resultType(op(sgprBool, vgprDouble, vgprDouble)));
CHECK(rVgprInt32 == resultType(op(sgprBool, vgprInt32, vgprInt32)));
CHECK(rVgprInt64 == resultType(op(sgprBool, vgprInt64, vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(sgprBool, vgprUInt32, vgprUInt32)));
CHECK(rVgprHalf == resultType(op(sgprBool, vgprHalf, vgprHalf)));
CHECK(rVgprHalfx2 == resultType(op(sgprBool, vgprHalfx2, vgprHalfx2)));
CHECK(rVgprBool32 == resultType(op(sgprBool, vgprBool32, vgprBool32)));
CHECK(rVgprFloat == resultType(op(vgprBool, vgprFloat, vgprFloat)));
CHECK(rVgprDouble == resultType(op(vgprBool, vgprDouble, vgprDouble)));
CHECK(rVgprInt32 == resultType(op(vgprBool, vgprInt32, vgprInt32)));
CHECK(rVgprInt64 == resultType(op(vgprBool, vgprInt64, vgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprBool, vgprUInt32, vgprUInt32)));
CHECK(rVgprHalf == resultType(op(vgprBool, vgprHalf, vgprHalf)));
CHECK(rVgprHalfx2 == resultType(op(vgprBool, vgprHalfx2, vgprHalfx2)));
CHECK(rVgprBool32 == resultType(op(vgprBool, vgprBool32, vgprBool32)));
CHECK(rSgprFloat == resultType(op(sgprBool, sgprFloat, sgprFloat)));
CHECK(rSgprDouble == resultType(op(sgprBool, sgprDouble, sgprDouble)));
CHECK(rSgprInt32 == resultType(op(sgprBool, sgprInt32, sgprInt32)));
CHECK(rSgprInt64 == resultType(op(sgprBool, sgprInt64, sgprInt64)));
CHECK(rSgprUInt32 == resultType(op(sgprBool, sgprUInt32, sgprUInt32)));
CHECK(rSgprHalf == resultType(op(sgprBool, sgprHalf, sgprHalf)));
CHECK(rSgprHalfx2 == resultType(op(sgprBool, sgprHalfx2, sgprHalfx2)));
CHECK(rSgprBool32 == resultType(op(sgprBool, sgprBool32, sgprBool32)));
CHECK(rVgprFloat == resultType(op(vgprBool, sgprFloat, sgprFloat)));
CHECK(rVgprDouble == resultType(op(vgprBool, sgprDouble, sgprDouble)));
CHECK(rVgprInt32 == resultType(op(vgprBool, sgprInt32, sgprInt32)));
CHECK(rVgprInt64 == resultType(op(vgprBool, sgprInt64, sgprInt64)));
CHECK(rVgprUInt32 == resultType(op(vgprBool, sgprUInt32, sgprUInt32)));
CHECK(rVgprHalf == resultType(op(vgprBool, sgprHalf, sgprHalf)));
CHECK(rVgprHalfx2 == resultType(op(vgprBool, sgprHalfx2, sgprHalfx2)));
CHECK(rVgprBool32 == resultType(op(vgprBool, sgprBool32, sgprBool32)));
}
}
TEST_CASE("Expression evaluate", "[expression]")
{
SECTION("No arguments")
{
auto a = std::make_shared<Expression::Expression>(1.0);
auto b = std::make_shared<Expression::Expression>(2.0);
auto expr1 = a + b;
auto expr2 = b * expr1;
auto expectedTimes = Expression::EvaluationTimes::All();
CHECK(expectedTimes == Expression::evaluationTimes(expr2));
CHECK(Expression::canEvaluateTo(3.0, expr1));
CHECK(Expression::canEvaluateTo(6.0, expr2));
CHECK(3.0 == std::get<double>(evaluate(expr1)));
CHECK(6.0 == std::get<double>(evaluate(expr2)));
}
SECTION("Arguments")
{
size_t valueCount = 1;
VariableType doubleVal{DataType::Double, PointerType::Value};
auto ca = std::make_shared<CommandArgument>(nullptr, doubleVal, 0);
auto cb = std::make_shared<CommandArgument>(nullptr, doubleVal, 8);
auto a = std::make_shared<Expression::Expression>(ca);
auto b = std::make_shared<Expression::Expression>(cb);
auto expr1 = a + b;
auto expr2 = b * expr1;
auto expr3 = -expr2;
struct
{
double a = 1.0;
double b = 2.0;
} args;
RuntimeArguments runtimeArgs((uint8_t*)&args, sizeof(args));
Expression::ResultType expected{Register::Type::Literal, DataType::Double, valueCount};
CHECK(expected == resultType(expr2));
CHECK(6.0 == std::get<double>(evaluate(expr2, runtimeArgs)));
args.a = 2.0;
CHECK(8.0 == std::get<double>(evaluate(expr2, runtimeArgs)));
CHECK(-8.0 == std::get<double>(evaluate(expr3, runtimeArgs)));
args.b = 1.5;
CHECK(5.25 == std::get<double>(evaluate(expr2, runtimeArgs)));
CHECK_FALSE(Expression::canEvaluateTo(5.25, expr2));
// Don't send in the runtimeArgs, can't evaluate the arguments.
CHECK_THROWS_AS(evaluate(expr2), std::runtime_error);
Expression::EvaluationTimes expectedTimes{Expression::EvaluationTime::KernelLaunch};
CHECK(expectedTimes == Expression::evaluationTimes(expr2));
}
}
TEST_CASE("Expression test evaluate mixed types", "[expression]")
{
size_t valueCount = 1;
using Expression::literal;
auto one = literal(1.0);
auto two = literal(2.0f);
auto twoPoint5 = literal(2.5f);
auto five = literal(5);
auto seven = literal(7.0);
auto eightPoint75 = literal(8.75);
auto ptrNull = std::make_shared<Expression::Expression>((float*)nullptr);
float x = 3.0f;
auto ptrValid = std::make_shared<Expression::Expression>(&x);
double y = 9.0;
auto ptrDoubleValid = std::make_shared<Expression::Expression>(&y);
// double + float -> double
auto expr1 = one + two;
// float * double -> double
auto exprSix = two * expr1;
// double - int -> double
auto exprOneBad = exprSix - five;
auto exprOne = exprSix - convert(DataType::Double, five);
// float + int -> float
auto exprSeven = two + convert(DataType::Float, five);
CHECK(6.0 == std::get<double>(evaluate(exprSix)));
CHECK_THROWS_AS(evaluate(exprOneBad), FatalError);
CHECK(1.0 == std::get<double>(evaluate(exprOne)));
CHECK(7.0f == std::get<float>(evaluate(exprSeven)));
auto twoDouble = convert(DataType::Double, two);
CHECK(2.0 == std::get<double>(evaluate(twoDouble)));
auto twoInt = convert(DataType::Int32, twoPoint5);
CHECK(2 == std::get<int>(evaluate(twoInt)));
auto fiveDoubleBad = seven - twoInt;
auto fiveDouble = seven - convert(DataType::Double, twoInt);
CHECK_THROWS_AS(std::get<double>(evaluate(fiveDoubleBad)), FatalError);
CHECK(5.0 == std::get<double>(evaluate(fiveDouble)));
auto minusThree64 = convert(DataType::Int64, twoInt - five);
CHECK(-3l == std::get<int64_t>(evaluate(minusThree64)));
auto minusThreeU64 = convert(DataType::UInt64, twoInt - five);
CHECK(18446744073709551613ul == std::get<uint64_t>(evaluate(minusThreeU64)));
auto eight75Half = convert(DataType::Half, eightPoint75);
CHECK(Half(8.75) == std::get<Half>(evaluate(eight75Half)));
Expression::ResultType litDouble{Register::Type::Literal, DataType::Double, valueCount};
Expression::ResultType litFloat{Register::Type::Literal, DataType::Float, valueCount};
Expression::ResultType litBool{Register::Type::Literal, DataType::Bool, valueCount};
CHECK(litDouble == resultType(exprSix));
// Result type not (yet?) defined for mixed integral/floating point types.
CHECK(true == std::get<bool>(evaluate(exprSix > exprOne)));
CHECK(true == std::get<bool>(evaluate(exprSix >= exprOne)));
CHECK(false == std::get<bool>(evaluate(exprSix < exprOne)));
CHECK(false == std::get<bool>(evaluate(exprSix <= exprOne)));
CHECK(true == std::get<bool>(evaluate(exprSix != exprOne)));
CHECK(litBool == resultType(one > seven));
CHECK(true == std::get<bool>(evaluate(exprSix < exprSeven)));
CHECK(true == std::get<bool>(evaluate(exprSix <= exprSeven)));
CHECK(false == std::get<bool>(evaluate(exprSix > exprSeven)));
CHECK(false == std::get<bool>(evaluate(exprSix >= exprSeven)));
CHECK(true == std::get<bool>(evaluate(one <= exprOne)));
CHECK(true == std::get<bool>(evaluate(one == exprOne)));
CHECK(true == std::get<bool>(evaluate(one >= exprOne)));
CHECK(false == std::get<bool>(evaluate(one != exprOne)));
auto trueExp = std::make_shared<Expression::Expression>(true);
CHECK(true == std::get<bool>(evaluate(trueExp == (one >= exprOne))));
CHECK(false == std::get<bool>(evaluate(trueExp == (one < exprOne))));
// Pointer + double -> error.
{
auto exprThrow = ptrValid + exprOne;
CHECK_THROWS_AS(evaluate(exprThrow), std::runtime_error);
CHECK_THROWS(resultType(exprThrow));
}
// Pointer * int -> error.
{
auto exprThrow = ptrValid * five;
CHECK_THROWS_AS(evaluate(exprThrow), std::runtime_error);
}
// Pointer + pointer -> error
{
auto exprThrow = ptrValid + ptrDoubleValid;
CHECK_THROWS_AS(evaluate(exprThrow), std::runtime_error);
CHECK_THROWS(resultType(exprThrow));
}
// (float *) - (double *) -> error
{
auto exprThrow = ptrValid - ptrDoubleValid;
CHECK_THROWS_AS(evaluate(exprThrow), std::runtime_error);
CHECK_THROWS(resultType(exprThrow));
}
{
auto exprThrow = ptrNull + five;
// nullptr + int -> error;
CHECK_THROWS_AS(evaluate(exprThrow), std::runtime_error);
}
{
auto exprThrow = -ptrNull;
// -pointer -> error;
CHECK_THROWS_AS(evaluate(exprThrow), std::runtime_error);
}
{
auto exprThrow = five + ptrNull;
// Nullptr + int -> error;
CHECK_THROWS_AS(evaluate(exprThrow), std::runtime_error);
}
auto exprXPlus5 = ptrValid + five;
float* dontDereferenceThis = std::get<float*>(evaluate(exprXPlus5));
auto ptrDifference = dontDereferenceThis - (&x);
CHECK(5 == ptrDifference);
auto expr10PlusX = five + exprXPlus5;
dontDereferenceThis = std::get<float*>(evaluate(expr10PlusX));
ptrDifference = dontDereferenceThis - (&x);
CHECK(10 == ptrDifference);
auto expr5PtrDiff = expr10PlusX - exprXPlus5;
CHECK(5 == std::get<int64_t>(evaluate(expr5PtrDiff)));
CHECK(true == std::get<bool>(evaluate(expr10PlusX > ptrValid)));
CHECK(false == std::get<bool>(evaluate(expr10PlusX < ptrValid)));
}
TEST_CASE("Expression equality", "[expression][codegen]")
{
auto context = TestContext::ForDefaultTarget();
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
ra->setName("ra");
ra->allocateNow();
auto rb = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
rb->setName("rb");
rb->allocateNow();
auto a = ra->expression();
auto b = rb->expression();
auto expr1 = a + b;
auto expr2 = b * a;
auto expr3 = expr1 == expr2;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr3, context.get()));
auto result = R"(
v_add_i32 v2, v0, v1
v_mul_lo_u32 v3, v1, v0
v_cmp_eq_i32 s[0:1], v2, v3
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
TEST_CASE("64bit Addition should use SGPR carry when possible", "[expression][codegen]")
{
auto context = TestContext::ForDefaultTarget();
SECTION("64bit Addition can use SGPR carry for literal 64 bit input that has "
"inline-constant lsb and msb",
"[expression][codegen]")
{
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt64, 1);
ra->setName("a");
ra->allocateNow();
auto a = ra->expression();
auto b = Expression::literal(4294967297UL); // UInt32 max + 2
auto expr1 = a + b;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr1, context.get()));
auto result = R"(
v_add_co_u32 v2, s[0:1], 1, v0
v_addc_co_u32 v3, s[0:1], 1, v1, s[0:1]
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("64bit Addition should use SGPR carry for literal 64 bit input that has large msb",
"[expression][codegen]")
{
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt64, 1);
ra->setName("a");
ra->allocateNow();
auto a = ra->expression();
auto b = Expression::literal(549755813888UL); // 2^(32+7)
auto expr1 = a + b;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr1, context.get()));
auto result = R"(
v_mov_b32 v2, 128
v_add_co_u32 v4, s[0:1], 0, v0
v_addc_co_u32 v5, s[0:1], v2, v1, s[0:1]
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("64bit Addition should use VCC carry for literal 64 bit input that is not an "
"inline-constant",
"[expression][codegen]")
{
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt64, 1);
ra->setName("a");
ra->allocateNow();
auto a = ra->expression();
auto b = Expression::literal(100); // > 64
auto expr1 = a + b;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr1, context.get()));
auto result = R"(
v_add_co_u32 v2, vcc, 100, v0
v_addc_co_u32 v3, vcc, 0, v1, vcc
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("64bit Addition should not add two literals for 32bit register input",
"[expression][codegen]")
{
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt32, 1);
ra->setName("a");
ra->allocateNow();
auto a = ra->expression();
auto b = Expression::literal(137438953473UL); // 2^(32+5) + 1
auto expr1 = a + b;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr1, context.get()));
auto result = R"(
v_mov_b32 v1, 0
v_add_co_u32 v2, s[0:1], 1, v0
v_addc_co_u32 v3, s[0:1], 32, v1, s[0:1]
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("64bit Addition should use VCC carry for literal 32 bit input that is an "
"inline-constant",
"[expression][codegen]")
{
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt64, 1);
ra->setName("a");
ra->allocateNow();
auto a = ra->expression();
auto b = Expression::literal(5);
auto expr1 = a + b;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr1, context.get()));
auto result = R"(
v_add_co_u32 v2, s[0:1], 5, v0
v_addc_co_u32 v3, s[0:1], 0, v1, s[0:1]
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("64bit Addition should use SGPR carry for non-literal input",
"[expression][codegen]")
{
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt64, 1);
ra->setName("a");
ra->allocateNow();
auto a = ra->expression();
auto rb = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt64, 1);
rb->setName("b");
rb->allocateNow();
auto b = rb->expression();
auto expr1 = a + b;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr1, context.get()));
auto result = R"(
v_add_co_u32 v4, s[0:1], v0, v2
v_addc_co_u32 v5, s[0:1], v1, v3, s[0:1]
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("64bit Addition should use SGPR carry for non-literal 32bit input",
"[expression][codegen]")
{
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt64, 1);
ra->setName("a");
ra->allocateNow();
auto a = ra->expression();
auto rb = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt32, 1);
rb->setName("b");
rb->allocateNow();
auto b = rb->expression();
auto expr1 = a + b;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr1, context.get()));
auto result = R"(
v_add_co_u32 v4, s[0:1], v0, v2
v_addc_co_u32 v5, s[0:1], 0, v1, s[0:1]
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
}
TEST_CASE("Ignore converts between same-width integral types", "[expression][codegen]")
{
auto context = TestContext::ForDefaultTarget();
const auto make_expression = [&](auto type) {
auto r
= std::make_shared<Register::Value>(context.get(), Register::Type::Vector, type, 1);
r->allocateNow();
return r->expression();
};
SECTION("Int32")
{
auto a = make_expression(DataType::Int32);
auto expr = convert(DataType::UInt32, a) + convert(DataType::UInt32, a);
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr, context.get()));
auto result = R"(
v_add_u32 v1, v0, v0
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("Int32 Mixed")
{
auto a = make_expression(DataType::Int32);
auto b = make_expression(DataType::UInt32);
auto expr = convert(DataType::UInt32, a) + convert(DataType::Int32, b);
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr, context.get()));
auto result = R"(
v_add_u32 v2, v0, v1
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("Int64 Mixed")
{
auto a = make_expression(DataType::Int64);
auto b = make_expression(DataType::UInt64);
auto expr = convert(DataType::UInt64, a) + convert(DataType::Int64, b);
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr, context.get()));
CHECK(context.output().find("mov") == std::string::npos);
}
}
TEST_CASE("Reinterpret produces no instructions and changes register type",
"[expression][codegen]")
{
auto context = TestContext::ForDefaultTarget();
const auto make_expression = [&](auto type) {
auto r
= std::make_shared<Register::Value>(context.get(), Register::Type::Vector, type, 1);
r->allocateNow();
return r->expression();
};
SECTION("Int32 to UInt32")
{
auto a = make_expression(DataType::Int32);
auto expr = Expression::reinterpret(DataType::UInt32, a);
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr, context.get()));
auto result = R"()";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
CHECK(destReg->variableType().dataType == DataType::UInt32);
}
SECTION("Float to UInt32")
{
auto a = make_expression(DataType::Float);
auto expr = Expression::reinterpret(DataType::UInt32, a);
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr, context.get()));
auto result = R"()";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
CHECK(destReg->variableType().dataType == DataType::UInt32);
}
SECTION("UInt64 to Int64")
{
auto a = make_expression(DataType::UInt64);
auto expr = Expression::reinterpret(DataType::Int64, a);
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr, context.get()));
auto result = R"()";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
CHECK(destReg->variableType().dataType == DataType::Int64);
}
SECTION("Reinterpret in arithmetic expression, uint32 -> int32")
{
auto a = make_expression(DataType::UInt32);
auto b = make_expression(DataType::UInt32);
auto c = make_expression(DataType::Int32);
auto expr = Expression::reinterpret(DataType::Int32, a + b) + c;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr, context.get()));
auto result = R"(
v_add_u32 v3, v0, v1
v_add_i32 v4, v3, v2
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("Reinterpret in arithmetic expression, float -> uint32")
{
auto a = make_expression(DataType::Float);
auto b = make_expression(DataType::UInt32);
auto expr = Expression::reinterpret(DataType::UInt32, a) + b;
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr, context.get()));
auto result = R"(
v_add_u32 v2, v0, v1
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
SECTION("Size mismatch throws")
{
auto a = make_expression(DataType::Float);
CHECK_THROWS_AS(Expression::reinterpret(DataType::Int64, a), FatalError);
}
SECTION("Invalid reinterpret - unsupported type")
{
auto a = make_expression(DataType::UInt32);
CHECK_THROWS(Expression::reinterpret(DataType::None, a));
}
}
TEST_CASE("Expression evaluate comparisons", "[expression]")
{
auto command = std::make_shared<Command>();
auto aTag = command->allocateTag();
auto ca = command->allocateArgument(
{DataType::Double, PointerType::Value}, aTag, ArgumentType::Value);
auto bTag = command->allocateTag();
auto cb = command->allocateArgument(
{DataType::Double, PointerType::Value}, bTag, ArgumentType::Value);
auto a = std::make_shared<Expression::Expression>(ca);
auto b = std::make_shared<Expression::Expression>(cb);
auto vals_gt = a > b;
auto vals_lt = a < b;
auto vals_ge = a >= b;
auto vals_le = a <= b;
auto vals_eq = a == b;
auto expr_gt = a > (a + b);
auto expr_lt = a < (a + b);
auto expr_ge = a >= (a + b);
auto expr_le = a <= (a + b);
auto expr_eq = a == (a + b);
auto aVal = GENERATE(from_range(TestValues::doubleValues));
auto bVal = GENERATE(from_range(TestValues::doubleValues));
CAPTURE(aVal, bVal);
KernelArguments runtimeArgs;
runtimeArgs.append("a", aVal);
runtimeArgs.append("b", bVal);
auto args = runtimeArgs.runtimeArguments();
CHECK(std::get<bool>(evaluate(vals_gt, args)) == (aVal > bVal));
CHECK(std::get<bool>(evaluate(vals_lt, args)) == (aVal < bVal));
CHECK(std::get<bool>(evaluate(vals_ge, args)) == (aVal >= bVal));
CHECK(std::get<bool>(evaluate(vals_le, args)) == (aVal <= bVal));
CHECK(std::get<bool>(evaluate(vals_eq, args)) == (aVal == bVal));
CHECK(std::get<bool>(evaluate(expr_gt, args)) == (aVal > (aVal + bVal)));
CHECK(std::get<bool>(evaluate(expr_lt, args)) == (aVal < (aVal + bVal)));
CHECK(std::get<bool>(evaluate(expr_ge, args)) == (aVal >= (aVal + bVal)));
CHECK(std::get<bool>(evaluate(expr_le, args)) == (aVal <= (aVal + bVal)));
CHECK(std::get<bool>(evaluate(expr_eq, args)) == (aVal == (aVal + bVal)));
}
TEST_CASE("Expression evaluate logical", "[expression]")
{
using Expression::literal;
auto command = std::make_shared<Command>();
auto aTag = command->allocateTag();
auto ca = command->allocateArgument(
{DataType::Int32, PointerType::Value}, aTag, ArgumentType::Value);
auto bTag = command->allocateTag();
auto cb = command->allocateArgument(
{DataType::Int32, PointerType::Value}, bTag, ArgumentType::Value);
auto a = ca->expression();
auto b = cb->expression();
auto zero = literal(0);
auto vals_negate = logicalNot(a != zero);
auto vals_double_negate = logicalNot(logicalNot(a != zero));
auto vals_and = (a != zero) && (b != zero);
auto vals_or = (a != zero) || (b != zero);
auto aVal = GENERATE(from_range(TestValues::int32Values));
auto bVal = GENERATE(from_range(TestValues::int32Values));
DYNAMIC_SECTION(concatenate(aVal, ", ", bVal))
{
CAPTURE(aVal, bVal);
KernelArguments runtimeArgs;
runtimeArgs.append("a", aVal);
runtimeArgs.append("b", bVal);
auto args = runtimeArgs.runtimeArguments();
CHECK(std::get<bool>(evaluate(vals_negate, args)) == (!aVal));
CHECK(std::get<bool>(evaluate(vals_double_negate, args)) == (!!aVal));
CHECK(std::get<bool>(evaluate(vals_and, args)) == (aVal && bVal));
CHECK(std::get<bool>(evaluate(vals_or, args)) == (aVal || bVal));
}
}
TEST_CASE("Expression evaluate shifts", "[expression]")
{
auto command = std::make_shared<Command>();
auto aTag = command->allocateTag();
auto ca = command->allocateArgument(
{DataType::Int32, PointerType::Value}, aTag, ArgumentType::Value);
auto bTag = command->allocateTag();
auto cb = command->allocateArgument(
{DataType::Int32, PointerType::Value}, bTag, ArgumentType::Value);
auto a = std::make_shared<Expression::Expression>(ca);
auto b = std::make_shared<Expression::Expression>(cb);
auto vals_shiftL = a << b;
auto vals_shiftR = logicalShiftR(a, b);
auto vals_signedShiftR = a >> b;
auto expr_shiftL = (a + b) << b;
auto expr_shiftR = logicalShiftR(a + b, b);
auto expr_signedShiftR = (a + b) >> b;
auto aVal = GENERATE(from_range(TestValues::int32Values));
auto bVal = GENERATE(from_range(TestValues::shiftValues));
CAPTURE(aVal, bVal);
KernelArguments runtimeArgs;
runtimeArgs.append("a", aVal);
runtimeArgs.append("b", bVal);
auto args = runtimeArgs.runtimeArguments();
CHECK(std::get<int>(evaluate(vals_shiftL, args)) == (aVal << bVal));
CHECK(std::get<int>(evaluate(vals_shiftR, args))
== (static_cast<unsigned int>(aVal) >> bVal));
CHECK(std::get<int>(evaluate(vals_signedShiftR, args)) == (aVal >> bVal));
CHECK(std::get<int>(evaluate(expr_shiftL, args)) == ((aVal + bVal) << bVal));
CHECK(std::get<int>(evaluate(expr_shiftR, args))
== (static_cast<unsigned int>(aVal + bVal) >> bVal));
CHECK(std::get<int>(evaluate(expr_signedShiftR, args)) == ((aVal + bVal) >> bVal));
}
TEST_CASE("Expression evaluate conditional operator", "[expression]")
{
auto command = std::make_shared<Command>();
auto aTag = command->allocateTag();
auto ca = command->allocateArgument(
{DataType::Int32, PointerType::Value}, aTag, ArgumentType::Value);
auto bTag = command->allocateTag();
auto cb = command->allocateArgument(
{DataType::Int32, PointerType::Value}, bTag, ArgumentType::Value);
auto a = std::make_shared<Expression::Expression>(ca);
auto b = std::make_shared<Expression::Expression>(cb);
auto vals_shiftL = conditional(a >= b, a, b);
auto aVal = GENERATE(from_range(TestValues::int32Values));
auto bVal = GENERATE(from_range(TestValues::int32Values));
CAPTURE(aVal, bVal);
KernelArguments runtimeArgs;
runtimeArgs.append("a", aVal);
runtimeArgs.append("b", bVal);
auto args = runtimeArgs.runtimeArguments();
// At kernel launch time
CHECK(std::get<int>(evaluate(vals_shiftL, args)) == (aVal >= bVal ? aVal : bVal));
// At translate time
auto a_static = std::make_shared<Expression::Expression>(aVal);
auto b_static = std::make_shared<Expression::Expression>(bVal);
CHECK(std::get<int>(evaluate(conditional(a_static >= b_static, a_static, b_static)))
== (aVal >= bVal ? aVal : bVal));
}
TEST_CASE("Expression evaluate bitwise ops", "[expression]")
{
auto command = std::make_shared<Command>();
auto aTag = command->allocateTag();
auto ca = command->allocateArgument(
{DataType::Int32, PointerType::Value}, aTag, ArgumentType::Value);
auto bTag = command->allocateTag();
auto cb = command->allocateArgument(
{DataType::Int32, PointerType::Value}, bTag, ArgumentType::Value);
auto a = std::make_shared<Expression::Expression>(ca);
auto b = std::make_shared<Expression::Expression>(cb);
auto vals_and = a & b;
auto vals_or = a | b;
auto vals_negate = ~a;
auto expr_and = (a + b) & b;
auto expr_or = (a + b) | b;
auto aVal = GENERATE(from_range(TestValues::int32Values));
auto bVal = GENERATE(from_range(TestValues::int32Values));
CAPTURE(aVal, bVal);
KernelArguments runtimeArgs;
runtimeArgs.append("a", aVal);
runtimeArgs.append("b", bVal);
auto args = runtimeArgs.runtimeArguments();
CHECK(std::get<int>(evaluate(vals_and, args)) == (aVal & bVal));
CHECK(std::get<int>(evaluate(vals_or, args)) == (aVal | bVal));
CHECK(std::get<int>(evaluate(vals_negate, args)) == (~aVal));
CHECK(std::get<int>(evaluate(expr_and, args)) == ((aVal + bVal) & bVal));
CHECK(std::get<int>(evaluate(expr_or, args)) == ((aVal + bVal) | bVal));
}
TEST_CASE("Expression evaluate multiplyHigh signed", "[expression]")
{
auto command = std::make_shared<Command>();
auto aTag = command->allocateTag();
auto ca = command->allocateArgument(
{DataType::Int32, PointerType::Value}, aTag, ArgumentType::Value);
auto bTag = command->allocateTag();
auto cb = command->allocateArgument(
{DataType::Int32, PointerType::Value}, bTag, ArgumentType::Value);
auto a = std::make_shared<Expression::Expression>(ca);
auto b = std::make_shared<Expression::Expression>(cb);
auto expr1 = multiplyHigh(a, b);
auto expr2 = multiplyHigh(a + b, b);
std::vector<int> a_values = {-21474836,
-146000,
-1,
0,
1,
2,
4,
5,
7,
12,
19,
33,
63,
906,
3017123,
800000,
1234456,
4022112};
auto aVal = GENERATE_COPY(from_range(a_values));
auto bVal = GENERATE_COPY(from_range(a_values));
CAPTURE(aVal, bVal);
KernelArguments runtimeArgs;
runtimeArgs.append("a", aVal);
runtimeArgs.append("b", bVal);
auto args = runtimeArgs.runtimeArguments();
CHECK(std::get<int>(evaluate(expr1, args)) == ((aVal * (int64_t)bVal) >> 32));
CHECK(std::get<int>(evaluate(expr2, args)) == (((aVal + bVal) * (int64_t)bVal) >> 32));
}
TEST_CASE("Expression evaluate multiplyHigh unsigned", "[expression]")
{
auto command = std::make_shared<Command>();
auto aTag = command->allocateTag();
auto ca = command->allocateArgument(
{DataType::UInt32, PointerType::Value}, aTag, ArgumentType::Value);
auto bTag = command->allocateTag();
auto cb = command->allocateArgument(
{DataType::UInt32, PointerType::Value}, bTag, ArgumentType::Value);
auto a = std::make_shared<Expression::Expression>(ca);
auto b = std::make_shared<Expression::Expression>(cb);
auto expr1 = multiplyHigh(a, b);
auto expr2 = multiplyHigh(a + b, b);
std::vector<unsigned int> a_values = {
0, 1, 2, 4, 5, 7, 12, 19, 33, 63, 906, 3017123, 800000, 1234456, 4022112,
//2863311531u // Can cause overflow
};
auto aVal = GENERATE_COPY(from_range(a_values));
auto bVal = GENERATE_COPY(from_range(a_values));
CAPTURE(aVal, bVal);
KernelArguments runtimeArgs;
runtimeArgs.append("a", aVal);
runtimeArgs.append("b", bVal);
auto args = runtimeArgs.runtimeArguments();
CHECK(std::get<unsigned int>(evaluate(expr1, args)) == ((aVal * (uint64_t)bVal) >> 32));
CHECK(std::get<unsigned int>(evaluate(expr2, args))
== (((aVal + (uint64_t)bVal) * (uint64_t)bVal) >> 32));
}
TEST_CASE("Expression evaluate exp2", "[expression]")
{
auto command = std::make_shared<Command>();
auto aTag = command->allocateTag();
auto ca = command->allocateArgument(
{DataType::Float, PointerType::Value}, aTag, ArgumentType::Value);
auto a = std::make_shared<Expression::Expression>(ca);
auto expr = exp2(a);
auto aVal = GENERATE(from_range(TestValues::floatValues));
CAPTURE(aVal);
KernelArguments runtimeArgs;
runtimeArgs.append("a", aVal);
auto args = runtimeArgs.runtimeArguments();
CHECK(std::exp2(aVal) == std::get<float>(evaluate(expr, args)));
}
TEST_CASE("Expression evaluate convert expressions", "[expression]")
{
using namespace Expression;
float a = 1.25f;
Half b = 1.1111;
double c = 5.2619;
BFloat16 d(1.0f);
auto a_exp = literal(a);
auto b_exp = literal(b);
auto c_exp = literal(c);
auto d_exp = literal(d);
auto exp1 = convert<DataType::Half>(a_exp);
auto exp2 = convert<DataType::Half>(b_exp);
auto exp3 = convert<DataType::Half>(c_exp);
CHECK(resultVariableType(exp1).dataType == DataType::Half);
CHECK(resultVariableType(exp2).dataType == DataType::Half);
CHECK(resultVariableType(exp3).dataType == DataType::Half);
CHECK(std::get<Half>(evaluate(exp1)) == static_cast<Half>(a));
CHECK(std::get<Half>(evaluate(exp2)) == b);
CHECK(std::get<Half>(evaluate(exp3)) == static_cast<Half>(c));
auto exp4 = convert<DataType::Float>(a_exp);
auto exp5 = convert<DataType::Float>(b_exp);
auto exp6 = convert<DataType::Float>(c_exp);
auto exp7 = convert<DataType::Float>(d_exp);
CHECK(resultVariableType(exp4).dataType == DataType::Float);
CHECK(resultVariableType(exp5).dataType == DataType::Float);
CHECK(resultVariableType(exp6).dataType == DataType::Float);
CHECK(resultVariableType(exp7).dataType == DataType::Float);
CHECK(std::get<float>(evaluate(exp4)) == a);
CHECK(std::get<float>(evaluate(exp5)) == static_cast<float>(b));
CHECK(std::get<float>(evaluate(exp6)) == static_cast<float>(c));
CHECK(std::get<float>(evaluate(exp7)) == static_cast<float>(d));
}
TEST_CASE("Expression evaluate BitfieldCombine", "[expression]")
{
using namespace Expression;
SECTION("Evaluation 1")
{
auto const srcOffset = 0u;
auto const dstOffset = 0u;
auto const width = 32u;
auto src = literal(1222u);
auto dst = literal(0u);
auto expr = bfc(src, dst, srcOffset, dstOffset, width);
CHECK(std::get<uint32_t>(evaluate(expr)) == 1222u);
}
SECTION("Evaluation 2")
{
auto const srcOffset = 0u;
auto const dstOffset = 16u;
auto const width = 8u;
auto src = literal(0u);
auto dst = literal(0xffffffffu);
auto expr = bfc(src, dst, srcOffset, dstOffset, width);
CHECK(std::get<uint32_t>(evaluate(expr)) == 0xff00ffffu);
}
SECTION("Evaluation 3")
{
auto const srcOffset = 16u;
auto const dstOffset = 0;
auto const width = 16u;
auto src = literal(0xffff0000u);
auto dst = literal(0u);
auto expr = bfc(src, dst, srcOffset, dstOffset, width);
CHECK(std::get<uint32_t>(evaluate(expr)) == 0x0000ffffu);
}
SECTION("Evaluation 4")
{
auto const srcOffset = 8u;
auto const dstOffset = 16u;
auto const width = 8u;
auto src = literal(0x0000ff00u);
auto dst = literal(0u);
auto expr = bfc(src, dst, srcOffset, dstOffset, width);
CHECK(std::get<uint32_t>(evaluate(expr)) == 0x00ff0000u);
}
SECTION("Evaluation 5 - UInt32 source to Raw32 destination")
{
auto const srcOffset = 8u;
auto const dstOffset = 4u;
auto const width = 12u;
auto src = literal(0x00abcd00u);
auto dst = literal(Raw32(0xffff0000u));
auto expr = bfc(src, dst, srcOffset, dstOffset, width);
CHECK(std::get<Raw32>(evaluate(expr)).value == 0xffffbcd0u);
}
SECTION("Evaluation 6 - UInt64 source to Raw32 destination")
{
auto const srcOffset = 16u;
auto const dstOffset = 8u;
auto const width = 16u;
auto src = literal(0x123456789abcdef0ull, DataType::UInt64);
auto dst = literal(Raw32(0x000000ffu));
auto expr = bfc(src, dst, srcOffset, dstOffset, width);
CHECK(std::get<Raw32>(evaluate(splitBitfieldCombine(expr))).value == 0x009abcffu);
}
SECTION("Evaluation 7 - UInt32 to Raw32 full width")
{
auto const srcOffset = 0u;
auto const dstOffset = 0u;
auto const width = 32u;
auto src = literal(0xdeadbeefu);
auto dst = literal(Raw32(0u));
auto expr = bfc(src, dst, srcOffset, dstOffset, width);
CHECK(std::get<Raw32>(evaluate(expr)).value == 0xdeadbeefu);
}
}
TEST_CASE("Expression evaluate BitfieldExtract", "[expression]")
{
using namespace Expression;
SECTION("Evaluation 1")
{
auto const offset = 0u;
auto const width = 32u;
auto src = literal(1222u);
auto expr = bfe(DataType::UInt32, src, offset, width);
CHECK(std::get<uint32_t>(evaluate(expr)) == 1222u);
}
SECTION("Evaluation 2")
{
auto const offset = 0u;
auto const width = 8u;
auto src = literal(0x000000ffu);
auto expr = bfe(DataType::UInt32, src, offset, width);
CHECK(std::get<uint32_t>(evaluate(expr)) == 0x000000ffu);
}
SECTION("Evaluation 3")
{
auto const offset = 16u;
auto const width = 8u;
auto src = literal(0x00ff0000u);
auto expr = bfe(DataType::UInt32, src, offset, width);
CHECK(std::get<uint32_t>(evaluate(expr)) == 0x000000ffu);
}
SECTION("Evaluation 4")
{
auto const offset = 48u;
auto const width = 8u;
auto src = literal(0x00ff000000000000ul);
auto expr = bfe(DataType::UInt32, src, offset, width);
CHECK(std::get<uint32_t>(evaluate(expr)) == 0x000000ffu);
}
SECTION("Evaluation 5")
{
auto const offset = 2u;
auto const width = 30u;
auto src1 = literal(4u);
auto src2 = literal(-4);
auto expr1 = bfe(DataType::UInt32, src1, offset, width);
auto expr2 = bfe(DataType::Int32, src2, offset, width);
CHECK(std::get<uint32_t>(evaluate(expr1)) == 1u);
CHECK(std::get<int32_t>(evaluate(expr2)) == -1);
}
SECTION("Evaluation 6 - UInt64 to UInt32 truncation")
{
auto const offset = 0u;
auto const width = 32u;
auto src = literal(0x123456789abcdef0ull, DataType::UInt64);
auto expr = bfe(DataType::UInt32, src, offset, width);
CHECK(std::get<uint32_t>(evaluate(expr)) == 0x9abcdef0u);
}
SECTION("Evaluation 7 - UInt64 to Raw32 truncation")
{
auto const offset = 0u;
auto const width = 32u;
auto src = literal(0xfedcba9876543210ull, DataType::UInt64);
auto expr = bfe(DataType::Raw32, src, offset, width);
CHECK(std::get<Raw32>(evaluate(expr)).value == 0x76543210u);
}
SECTION("Evaluation 8 - Int64 to Int32 truncation with sign extension")
{
auto const offset = 0u;
auto const width = 32u;
auto src = literal(-1ll, DataType::Int64);
auto expr = bfe(DataType::Int32, src, offset, width);
CHECK(std::get<int32_t>(evaluate(expr)) == -1);
}
}
TEST_CASE("Expression evaluate reinterpret", "[expression]")
{
using namespace Expression;
SECTION("Int32 to UInt32")
{
auto value = literal(int32_t(-1));
auto expr = reinterpret(DataType::UInt32, value);
CHECK(resultVariableType(expr).dataType == DataType::UInt32);
CHECK(std::get<uint32_t>(evaluate(expr)) == 0xFFFFFFFFu);
}
SECTION("UInt32 to Int32")
{
auto value = literal(uint32_t(0xFFFFFFFFu));
auto expr = reinterpret(DataType::Int32, value);
CHECK(resultVariableType(expr).dataType == DataType::Int32);
CHECK(std::get<int32_t>(evaluate(expr)) == -1);
}
SECTION("Int32 to Raw32")
{
auto value = literal(int32_t(-1));
auto expr = reinterpret(DataType::Raw32, value);
CHECK(resultVariableType(expr).dataType == DataType::Raw32);
CHECK(std::get<Raw32>(evaluate(expr)).value == 0xFFFFFFFFu);
}
SECTION("Raw32 to UInt32")
{
auto value = literal(Raw32(0xDEADBEEFu));
auto expr = reinterpret(DataType::UInt32, value);
CHECK(resultVariableType(expr).dataType == DataType::UInt32);
CHECK(std::get<uint32_t>(evaluate(expr)) == 0xDEADBEEFu);
}
SECTION("Raw32 to Int32")
{
auto value = literal(Raw32(0x80000000u));
auto expr = reinterpret(DataType::Int32, value);
CHECK(resultVariableType(expr).dataType == DataType::Int32);
CHECK(std::get<int32_t>(evaluate(expr)) == static_cast<int32_t>(0x80000000));
}
SECTION("UInt32 to Raw32")
{
auto value = literal(uint32_t(0x12345678u));
auto expr = reinterpret(DataType::Raw32, value);
CHECK(resultVariableType(expr).dataType == DataType::Raw32);
CHECK(std::get<Raw32>(evaluate(expr)).value == 0x12345678u);
}
SECTION("Int64 to UInt64")
{
auto value = literal(int64_t(-1LL));
auto expr = reinterpret(DataType::UInt64, value);
CHECK(resultVariableType(expr).dataType == DataType::UInt64);
CHECK(std::get<uint64_t>(evaluate(expr)) == 0xFFFFFFFFFFFFFFFFull);
}
SECTION("UInt64 to Int64")
{
auto value = literal(uint64_t(0x8000000000000000ull));
auto expr = reinterpret(DataType::Int64, value);
CHECK(resultVariableType(expr).dataType == DataType::Int64);
CHECK(std::get<int64_t>(evaluate(expr)) == static_cast<int64_t>(0x8000000000000000ull));
}
SECTION("Float to UInt32")
{
auto value = literal(1.0f);
auto expr = reinterpret(DataType::UInt32, value);
CHECK(resultVariableType(expr).dataType == DataType::UInt32);
// 1.0f in IEEE 754
CHECK(std::get<uint32_t>(evaluate(expr)) == 0x3F800000u);
}
SECTION("UInt32 to Float")
{
auto value = literal(uint32_t(0x40000000u)); // 2.0f in IEEE 754
auto expr = reinterpret(DataType::Float, value);
CHECK(resultVariableType(expr).dataType == DataType::Float);
CHECK(std::get<float>(evaluate(expr)) == 2.0f);
}
SECTION("Double to UInt64")
{
auto value = literal(1.0);
auto expr = reinterpret(DataType::UInt64, value);
CHECK(resultVariableType(expr).dataType == DataType::UInt64);
// 1.0 in IEEE 754 double
CHECK(std::get<uint64_t>(evaluate(expr)) == 0x3FF0000000000000ull);
}
SECTION("Invalid reinterpret - different sizes")
{
auto value = literal(int32_t(42));
CHECK_THROWS_AS(reinterpret(DataType::Int64, value), FatalError);
}
SECTION("Invalid reinterpret - unsupported type")
{
auto value = literal(int32_t(42));
CHECK_THROWS(reinterpret(DataType::None, value));
}
}
TEST_CASE("Expression evaluate reinterpretTruncateValue with endianness", "[expression]")
{
auto swapEndian = [](auto value) -> decltype(value) {
using T = decltype(value);
constexpr size_t N = sizeof(T);
auto bytes = std::bit_cast<std::array<std::byte, N>>(value);
std::reverse(bytes.begin(), bytes.end());
return std::bit_cast<T>(bytes);
};
constexpr bool isLittleEndian = std::endian::native == std::endian::little;
constexpr bool isBigEndian = std::endian::native == std::endian::big;
SECTION("Round-trip: Native to opposite endianness and back - UInt64 to UInt32")
{
uint64_t originalValue = 15ull;
if constexpr(isLittleEndian)
{
// System is LE: convert to BE, truncate, convert back
uint64_t valueAsBE = swapEndian(originalValue);
auto resultBE = Expression::EvaluateDetail::reinterpretTruncateValue(
valueAsBE, DataType::UInt32, std::endian::big);
uint32_t truncatedBE = std::get<uint32_t>(resultBE);
uint32_t truncatedLE = swapEndian(truncatedBE);
CHECK(truncatedLE == 15u);
}
else if constexpr(isBigEndian)
{
// System is BE: convert to LE, truncate, convert back
uint64_t valueAsLE = swapEndian(originalValue);
auto resultLE = Expression::EvaluateDetail::reinterpretTruncateValue(
valueAsLE, DataType::UInt32, std::endian::little);
uint32_t truncatedLE = std::get<uint32_t>(resultLE);
uint32_t truncatedBE = swapEndian(truncatedLE);
CHECK(truncatedBE == 15u);
}
}
SECTION("Native endianness - UInt64 to UInt32")
{
uint64_t originalValue = 15ull;
auto result = Expression::EvaluateDetail::reinterpretTruncateValue(
originalValue, DataType::UInt32, std::endian::native);
CHECK(std::get<uint32_t>(result) == 15u);
}
SECTION("Same-size reinterpret - endianness independent (no truncation)")
{
int value = 0x00000FFF;
auto resultLE = Expression::EvaluateDetail::reinterpretTruncateValue(
value, DataType::UInt32, std::endian::little);
auto resultBE = Expression::EvaluateDetail::reinterpretTruncateValue(
value, DataType::UInt32, std::endian::big);
CHECK(std::get<uint32_t>(resultLE) == 0x00000FFFu);
CHECK(std::get<uint32_t>(resultBE) == 0x00000FFFu);
}
}
TEST_CASE("Expression generate dataflow tags", "[expression][codegen]")
{
auto context = TestContext::ForDefaultTarget();
Register::AllocationOptions allocOptions{.contiguousChunkWidth
= Register::FULLY_CONTIGUOUS};
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Float, 4, allocOptions);
ra->allocateNow();
auto dfa = std::make_shared<Expression::Expression>(
Expression::DataFlowTag{1, Register::Type::Vector, DataType::None});
context.get()->registerTagManager()->addRegister(1, ra);
auto rb = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Float, 4, allocOptions);
rb->allocateNow();
auto dfb = std::make_shared<Expression::Expression>(
Expression::DataFlowTag{2, Register::Type::Vector, DataType::None});
context.get()->registerTagManager()->addRegister(2, rb);
auto rc = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Float, 4, allocOptions);
rc->allocateNow();
auto dfc = std::make_shared<Expression::Expression>(
Expression::DataFlowTag{3, Register::Type::Vector, DataType::None});
context.get()->registerTagManager()->addRegister(3, rc);
Register::ValuePtr rr1;
context.get()->schedule(Expression::generate(rr1, dfa * dfb, context.get()));
Register::ValuePtr rr2;
context.get()->schedule(
Expression::generate(rr2, Expression::fuseTernary(dfa * dfb + dfc), context.get()));
auto result = R"(
v_mul_f32 v12, v0, v4
v_mul_f32 v13, v1, v5
v_mul_f32 v14, v2, v6
v_mul_f32 v15, v3, v7
v_fma_f32 v16, v0, v4, v8
v_fma_f32 v17, v1, v5, v9
v_fma_f32 v18, v2, v6, v10
v_fma_f32 v19, v3, v7, v11
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
TEST_CASE("Expression literal datatypes", "[expression]")
{
std::vector<VariableType> dataTypes = {{DataType::Int32},
{DataType::UInt32},
{DataType::Int64},
{DataType::UInt64},
{DataType::Float},
{DataType::Half},
{DataType::Double},
{DataType::Bool}};
auto dataType = GENERATE_COPY(from_range(dataTypes));
CAPTURE(dataType);
CHECK(dataType == Expression::resultVariableType(Expression::literal(1, dataType)));
}
TEST_CASE("Expression codegen literal int swap", "[expression][codegen][optimization]")
{
auto context = TestContext::ForDefaultTarget();
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
ra->setName("ra");
ra->allocateNow();
auto expr1 = ra->expression();
auto expr2 = Expression::literal(-5);
Register::ValuePtr destReg;
context.get()->schedule(Expression::generate(destReg, expr1 + expr2, context.get()));
context.get()->schedule(Expression::generate(destReg, expr1 & expr2, context.get()));
context.get()->schedule(Expression::generate(destReg, expr1 | expr2, context.get()));
context.get()->schedule(Expression::generate(destReg, expr1 ^ expr2, context.get()));
auto result = R"(
v_add_i32 v1, v0, -5
v_and_b32 v1, -5, v0
v_or_b32 v1, -5, v0
v_xor_b32 v1, -5, v0
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(result));
}
TEST_CASE("Expression variant test", "[expression]")
{
using Expression::literal;
auto context = TestContext::ForDefaultTarget();
int32_t x1 = 3;
auto intPtr = Expression::literal(&x1);
int64_t x2 = 3L;
auto intLongPtr = Expression::literal(&x2);
uint32_t x3 = 3u;
auto uintPtr = Expression::literal(&x3);
uint64_t x4 = 3UL;
auto uintLongPtr = Expression::literal(&x4);
float x5 = 3.0f;
auto floatPtr = Expression::literal(&x5);
double x6 = 3.0;
auto doublePtr = Expression::literal(&x6);
auto intExpr = Expression::literal(1);
auto uintExpr = Expression::literal(1u);
auto floatExpr = Expression::literal(1.0f);
auto doubleExpr = Expression::literal(1.0);
auto boolExpr = Expression::literal(true);
auto v_a = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Double, 1);
v_a->allocateNow();
Expression::Expression value = Register::Value::Literal(1);
Expression::ExpressionPtr valuePtr = std::make_shared<Expression::Expression>(value);
Expression::Expression tag = Expression::DataFlowTag();
Expression::ExpressionPtr tagPtr = std::make_shared<Expression::Expression>(tag);
Expression::Expression waveTile
= std::make_shared<KernelGraph::CoordinateGraph::WaveTile>();
Expression::ExpressionPtr waveTilePtr = std::make_shared<Expression::Expression>(waveTile);
SECTION("evaluate, fastDivision, toString, evaluationTimes throw")
{
std::vector<Expression::ExpressionPtr> exprs = {
intExpr,
uintExpr,
floatExpr,
doubleExpr,
boolExpr,
intExpr + intExpr,
intExpr - intExpr,
intExpr * intExpr,
intExpr / intExpr,
intExpr % intExpr,
intExpr << intExpr,
intExpr >> intExpr,
logicalShiftR(intExpr, intExpr),
intExpr & intExpr,
intExpr ^ intExpr,
intExpr > intExpr,
intExpr < intExpr,
intExpr >= intExpr,
intExpr <= intExpr,
intExpr == intExpr,
-intExpr,
intPtr,
intLongPtr,
uintPtr,
uintLongPtr,
floatPtr,
doublePtr,
valuePtr,
};
auto testFunc = [](auto const& expr) {
CAPTURE(expr);
CHECK_NOTHROW(Expression::toString(expr));
CHECK_NOTHROW(Expression::evaluationTimes(expr));
};
for(auto const& expr : exprs)
{
testFunc(expr);
CHECK_NOTHROW(evaluate(expr));
CHECK_NOTHROW(Expression::fastDivision(expr, context.get()));
}
testFunc(v_a);
CHECK_THROWS_AS(Expression::evaluate(v_a), FatalError);
testFunc(tag);
CHECK_THROWS_AS(evaluate(tag), FatalError);
testFunc(tagPtr);
CHECK_THROWS_AS(evaluate(tagPtr), FatalError);
CHECK_NOTHROW(Expression::fastDivision(tagPtr, context.get()));
testFunc(value);
CHECK_NOTHROW(evaluate(value));
testFunc(waveTile);
CHECK_THROWS_AS(evaluate(waveTile), FatalError);
testFunc(waveTilePtr);
CHECK_THROWS_AS(evaluate(waveTilePtr), FatalError);
CHECK_NOTHROW(Expression::fastDivision(waveTilePtr, context.get()));
}
SECTION("convert")
{
// This just checks that the runtime version of `convert()` will throw an exception
// for a conversion that doesn't exist instead of returning something incorrect.
CHECK_NOTHROW(convert(DataType::Float, intExpr));
CHECK_NOTHROW(convert(DataType::Double, intExpr));
CHECK_THROWS_AS(convert(DataType::ComplexFloat, intExpr), FatalError);
CHECK_THROWS_AS(convert(DataType::ComplexDouble, intExpr), FatalError);
CHECK_NOTHROW(convert(DataType::Half, intExpr));
CHECK_NOTHROW(convert(DataType::Halfx2, intExpr));
CHECK_THROWS_AS(convert(DataType::Int8x4, intExpr), FatalError);
CHECK_NOTHROW(convert(DataType::Int32, intExpr));
CHECK_NOTHROW(convert(DataType::Int64, intExpr));
CHECK_NOTHROW(convert(DataType::BFloat16, intExpr));
CHECK_NOTHROW(convert(DataType::BFloat16x2, intExpr));
CHECK_THROWS_AS(convert(DataType::Int8, intExpr), FatalError);
CHECK_THROWS_AS(convert(DataType::Raw32, intExpr), FatalError);
CHECK_NOTHROW(convert(DataType::UInt32, intExpr));
CHECK_NOTHROW(convert(DataType::UInt64, intExpr));
CHECK_NOTHROW(convert(DataType::Bool, intExpr));
CHECK_NOTHROW(convert(DataType::Bool32, intExpr));
CHECK_THROWS_AS(convert(DataType::Count, intExpr), FatalError);
CHECK_THROWS_AS(convert(static_cast<DataType>(200), intExpr), FatalError);
}
}
TEST_CASE("Expression complexity values", "[expression][optimization]")
{
auto intExpr = Expression::literal(1);
CHECK(Expression::complexity(intExpr) == 0);
CHECK(Expression::complexity(intExpr + intExpr) > Expression::complexity(intExpr));
CHECK(Expression::complexity(intExpr + intExpr + intExpr)
> Expression::complexity(intExpr + intExpr));
CHECK(Expression::complexity(intExpr / intExpr)
> Expression::complexity(intExpr + intExpr));
}
TEST_CASE("Expression kernel arguments", "[expression][utility]")
{
namespace XP = Expression;
using strings = std::unordered_set<std::string>;
auto karg0 = std::make_shared<AssemblyKernelArgument>("KernelArg0", DataType::Int32);
auto karg1 = std::make_shared<AssemblyKernelArgument>("KernelArg1", DataType::Int32);
auto karg2 = std::make_shared<AssemblyKernelArgument>("KernelArg2", DataType::Int32);
auto kargExp0 = std::make_shared<XP::Expression>(karg0);
auto kargExp1 = std::make_shared<XP::Expression>(karg1);
auto kargExp2 = std::make_shared<XP::Expression>(karg2);
CHECK(referencedKernelArguments(kargExp0) == strings({"KernelArg0"}));
CHECK(referencedKernelArguments((kargExp0 + kargExp0) + XP::literal(5))
== strings({"KernelArg0"}));
CHECK(referencedKernelArguments((kargExp0 + kargExp1) + XP::literal(5))
== strings({"KernelArg0", "KernelArg1"}));
CHECK(referencedKernelArguments(
Expression::conditional(kargExp0, kargExp1, (kargExp2 + XP::literal(5))))
== strings({"KernelArg0", "KernelArg1", "KernelArg2"}));
SECTION("a")
{
auto scaledMM = std::make_shared<XP::Expression>(Expression::ScaledMatrixMultiply{
kargExp0, XP::literal(3), XP::literal(2), XP::literal(3), XP::literal(9)});
CHECK(referencedKernelArguments(scaledMM) == strings({"KernelArg0"}));
}
SECTION("b")
{
auto scaledMM = std::make_shared<XP::Expression>(Expression::ScaledMatrixMultiply{
XP::literal(1), kargExp1, XP::literal(3), XP::literal(2), XP::literal(3)});
CHECK(referencedKernelArguments(scaledMM) == strings({"KernelArg1"}));
}
SECTION("c")
{
auto scaledMM = std::make_shared<XP::Expression>(Expression::ScaledMatrixMultiply{
XP::literal(3), XP::literal(1), kargExp2, XP::literal(2), XP::literal(3)});
CHECK(referencedKernelArguments(scaledMM) == strings({"KernelArg2"}));
}
SECTION("sa")
{
auto scaledMM = std::make_shared<XP::Expression>(Expression::ScaledMatrixMultiply{
XP::literal(2), XP::literal(3), XP::literal(1), kargExp0, XP::literal(3)});
CHECK(referencedKernelArguments(scaledMM) == strings({"KernelArg0"}));
}
SECTION("sb")
{
auto scaledMM = std::make_shared<XP::Expression>(Expression::ScaledMatrixMultiply{
XP::literal(3), XP::literal(2), XP::literal(3), XP::literal(1), kargExp1});
CHECK(referencedKernelArguments(scaledMM) == strings({"KernelArg1"}));
}
}
TEST_CASE("Raw32 expression and evaluation", "[expression][raw32]")
{
using Expression::literal;
auto raw32One = literal(rocRoller::Raw32(1u));
auto unsignedOne = literal(uint32_t(1u));
auto unsignedTwo = literal(uint32_t(2u));
auto signedOne = literal(int32_t(1));
auto floatOne = literal(1.0f);
CHECK_THROWS_AS(unsignedOne << raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne >> raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne + raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne - raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne * raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne % raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne >= raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne <= raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne > raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne < raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne == raw32One, FatalError);
CHECK_THROWS_AS(unsignedOne != raw32One, FatalError);
CHECK_THROWS_AS(Expression::exp(raw32One), FatalError);
CHECK_THROWS_AS(Expression::exp2(raw32One), FatalError);
auto raw32Two = literal(rocRoller::Raw32(2u));
CHECK_NOTHROW(raw32Two == raw32One);
CHECK_NOTHROW(raw32Two != raw32One);
{
// Test shift left
auto expr = raw32One << unsignedOne;
CHECK_NOTHROW(evaluate(expr));
}
{
// Test shift right
auto expr = raw32One >> unsignedOne;
CHECK_NOTHROW(evaluate(expr));
}
{
// Test bitwise OR
auto expr = unsignedOne | raw32One;
CHECK_NOTHROW(evaluate(expr));
auto expr2 = raw32One | unsignedOne;
CHECK_NOTHROW(evaluate(expr2));
}
{
// Test bitwise AND
auto expr = unsignedOne & raw32One;
CHECK_NOTHROW(evaluate(expr));
auto expr2 = raw32One & unsignedOne;
CHECK_NOTHROW(evaluate(expr2));
}
{
// Test bitwise negate
auto expr = ~raw32One;
CHECK_NOTHROW(evaluate(expr));
}
{
// Test conversion
auto expr = std::make_shared<Expression::Expression>(
Expression::Convert{raw32One, "", rocRoller::DataType::UInt32});
CHECK_NOTHROW(evaluate(expr + unsignedOne));
}
SECTION("multiplyHigh")
{
CHECK_THROWS_AS(Expression::multiplyHigh(raw32One, unsignedOne), FatalError);
CHECK_THROWS_AS(Expression::multiplyHigh(unsignedOne, raw32One), FatalError);
CHECK_THROWS_AS(Expression::multiplyHigh(raw32One, raw32Two), FatalError);
CHECK_NOTHROW(evaluate(Expression::multiplyHigh(unsignedOne, unsignedTwo)));
}
SECTION("multiplyAdd")
{
CHECK_THROWS_AS(Expression::multiplyAdd(raw32One, unsignedOne, unsignedTwo),
FatalError);
CHECK_THROWS_AS(Expression::multiplyAdd(unsignedOne, raw32One, unsignedTwo),
FatalError);
CHECK_THROWS_AS(Expression::multiplyAdd(unsignedOne, unsignedTwo, raw32One),
FatalError);
CHECK_THROWS_AS(Expression::multiplyAdd(raw32One, raw32Two, unsignedOne), FatalError);
CHECK_NOTHROW(evaluate(Expression::multiplyAdd(unsignedOne, unsignedTwo, unsignedOne)));
}
SECTION("addShiftL")
{
CHECK_THROWS_AS(Expression::addShiftL(raw32One, unsignedOne, unsignedTwo), FatalError);
CHECK_THROWS_AS(Expression::addShiftL(unsignedOne, raw32One, unsignedTwo), FatalError);
CHECK_THROWS_AS(Expression::addShiftL(unsignedOne, unsignedTwo, raw32One), FatalError);
CHECK_NOTHROW(evaluate(Expression::addShiftL(unsignedOne, unsignedTwo, unsignedOne)));
}
SECTION("shiftLAdd")
{
CHECK_THROWS_AS(evaluate(Expression::shiftLAdd(raw32One, unsignedOne, unsignedTwo)),
FatalError); // Type mismatch after shift
CHECK_THROWS_AS(Expression::shiftLAdd(unsignedOne, raw32One, unsignedTwo), FatalError);
CHECK_THROWS_AS(Expression::shiftLAdd(unsignedOne, unsignedTwo, raw32One), FatalError);
CHECK_NOTHROW(evaluate(Expression::shiftLAdd(unsignedOne, unsignedTwo, unsignedOne)));
}
SECTION("magicMultiple")
{
CHECK_THROWS_AS(Expression::magicMultiple(raw32One), FatalError);
CHECK_NOTHROW(evaluate(Expression::magicMultiple(unsignedTwo)));
}
SECTION("magicShifts")
{
CHECK_THROWS_AS(Expression::magicShifts(raw32One), FatalError);
CHECK_NOTHROW(evaluate(Expression::magicShifts(unsignedTwo)));
}
SECTION("magicShiftAndSign")
{
CHECK_THROWS_AS(Expression::magicShiftAndSign(raw32One), FatalError);
CHECK_NOTHROW(evaluate(Expression::magicShiftAndSign(signedOne)));
}
}
TEST_CASE("Code gen for BitfieldCombine", "[expression][codegen]")
{
auto context = TestContext::ForDefaultTarget();
auto ra = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
ra->setName("ra");
ra->allocateNow();
auto rb = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::Int32, 1);
rb->setName("rb");
rb->allocateNow();
auto srcExpr = ra->expression();
auto dstExpr = rb->expression();
auto const srcOffset = 10u;
auto const dstOffset = 4u;
auto const width = 7u;
SECTION("Lowering Basic")
{
auto bfc = std::make_shared<Expression::Expression>(
Expression::BitfieldCombine{srcExpr, dstExpr, "", srcOffset, dstOffset, width});
Register::ValuePtr dest;
context.get()->schedule(Expression::generate(dest, bfc, context.get()));
// 130048 = 00000000000000011111110000000000
// 4294965263 = 11111111111111111111100000001111
std::string expected = R"(
v_and_b32 v2, 130048, v0
v_lshrrev_b32 v3, 6, v2
v_and_b32 v2, 4294965263, v1
v_or_b32 v4, v3, v2
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(expected));
}
SECTION("Lowering with srcIsZero")
{
auto bfc = std::make_shared<Expression::Expression>(Expression::BitfieldCombine{
srcExpr, dstExpr, "", srcOffset, dstOffset, width, true});
Register::ValuePtr dest;
context.get()->schedule(Expression::generate(dest, bfc, context.get()));
std::string expected = R"(
v_lshrrev_b32 v2, 6, v0
v_and_b32 v3, 4294965263, v1
v_or_b32 v4, v2, v3
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(expected));
}
SECTION("Lowering with dstIsZero")
{
auto bfc = std::make_shared<Expression::Expression>(Expression::BitfieldCombine{
srcExpr, dstExpr, "", srcOffset, dstOffset, width, std::nullopt, true});
Register::ValuePtr dest;
context.get()->schedule(Expression::generate(dest, bfc, context.get()));
std::string expected = R"(
v_and_b32 v2, 130048, v0
v_lshrrev_b32 v3, 6, v2
v_or_b32 v2, v3, v1
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(expected));
}
SECTION("Lowering with srcIsZero & dstIsZero")
{
auto bfc = std::make_shared<Expression::Expression>(Expression::BitfieldCombine{
srcExpr, dstExpr, "", srcOffset, dstOffset, width, true, true});
Register::ValuePtr dest;
context.get()->schedule(Expression::generate(dest, bfc, context.get()));
std::string expected = R"(
v_lshrrev_b32 v2, 6, v0
v_or_b32 v3, v2, v1
)";
CHECK(NormalizedSource(context.output()) == NormalizedSource(expected));
}
}
}