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

1572 lines
61 KiB
C++

// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT
#include <rocRoller/AssemblyKernel.hpp>
#include <rocRoller/Expression.hpp>
#include <rocRoller/ExpressionTransformations.hpp>
#include <rocRoller/KernelOptions_detail.hpp>
#include <rocRoller/Operations/Command.hpp>
#include "CustomMatchers.hpp"
#include "CustomSections.hpp"
#include "ExpressionMatchers.hpp"
#include "TestContext.hpp"
#include "TestKernels.hpp"
#include <common/SourceMatcher.hpp>
#include <common/TestValues.hpp>
#include <catch2/catch_test_macros.hpp>
TEST_CASE("Simplify ExpressionTransformation works", "[expression][expression-transformation]")
{
using namespace rocRoller;
using namespace Expression;
auto context = TestContext::ForDefaultTarget();
auto r
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Int32, 1);
r->allocateNow();
auto v = r->expression();
auto r2
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Int32, 1);
r2->allocateNow();
auto v2 = r2->expression();
auto r3
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::UInt64, 1);
r3->allocateNow();
auto v3 = r3->expression();
auto zero = literal(0);
auto one = literal(1);
auto a = literal(33);
auto b = literal(100);
auto c = literal(12.f);
auto True = literal(true);
auto False = literal(false);
// negate
SECTION("Negate")
{
CHECK_THAT(simplify(-(one + one)), IdenticalTo(literal(-2)));
}
SECTION("Multiply")
{
CHECK_THAT(simplify(zero * one), IdenticalTo(zero));
CHECK_THROWS_AS(simplify(c * zero), FatalError);
CHECK_THAT(simplify(c * literal(0.f)), IdenticalTo(literal(0.f)));
CHECK_THROWS_AS(simplify(c * one), FatalError);
CHECK_THAT(simplify(c * convert<DataType::Float>(one)), IdenticalTo(c));
CHECK_THAT(simplify(v * zero), IdenticalTo(zero));
CHECK_THAT(simplify(a * b), IdenticalTo(literal(3300)));
}
SECTION("Add")
{
CHECK_THAT(simplify(zero + one), IdenticalTo(one));
CHECK_THAT(simplify(v + zero), IdenticalTo(v));
CHECK_THAT(simplify(a + b), IdenticalTo(literal(133)));
}
SECTION("Divide")
{
CHECK_THAT(simplify(a / one), IdenticalTo(a));
CHECK_THAT(simplify(v / one), IdenticalTo(v));
CHECK_THAT(simplify(v / a), IdenticalTo(v / a));
CHECK_THAT(simplify(b / v), IdenticalTo(b / v));
}
SECTION("Modulo")
{
CHECK_THAT(simplify(a % one), IdenticalTo(zero));
CHECK_THAT(simplify(v % one), IdenticalTo(zero));
CHECK_THAT(simplify(v % a), IdenticalTo(v % a));
CHECK_THAT(simplify(b % v), IdenticalTo(b % v));
}
SECTION("Bitwise And")
{
CHECK_THAT(simplify(one & b), IdenticalTo(zero));
CHECK_THAT(simplify(one & a), IdenticalTo(one));
CHECK_THAT(simplify(v & zero), IdenticalTo(zero));
CHECK_THAT(simplify(v & (zero + zero)), IdenticalTo(zero));
CHECK_THAT(simplify(v2 & (zero + zero)), IdenticalTo(zero));
CHECK_THAT(simplify(v & v2), IdenticalTo(v & v2));
}
SECTION("Logical And")
{
CHECK_THAT(simplify((v < one) && False), IdenticalTo(convert<DataType::Bool64>(False)));
CHECK_THAT(simplify((v < one) && True), IdenticalTo(v < one));
}
SECTION("Logical Or")
{
CHECK_THAT(simplify((v < one) || True), IdenticalTo(convert<DataType::Bool64>(True)));
CHECK_THAT(simplify((v < one) || False), IdenticalTo(v < one));
}
auto fast = FastArithmetic(context.get());
SECTION("ShiftL")
{
CHECK_THAT(simplify(v << zero), IdenticalTo(v));
CHECK_THAT(simplify((one << one) << one), IdenticalTo(literal(4)));
CHECK_THAT(simplify(v << (zero << zero)), IdenticalTo(v));
CHECK_THAT(simplify(v << (one << one)), IdenticalTo(v << literal(2)));
CHECK_THAT(fast((v << one) << one), IdenticalTo(v << literal(2)));
CHECK_THAT(fast(logicalShiftR((v << one), one)), IdenticalTo(v & literal<int>((~0u) >> 1)));
}
SECTION("SignedShiftR")
{
CHECK_THAT(simplify(v >> zero), IdenticalTo(v));
CHECK_THAT(simplify((a >> one) >> one), IdenticalTo(literal(8)));
CHECK_THAT(simplify(v >> (zero >> zero)), IdenticalTo(v));
CHECK_THAT(simplify(literal(-2) >> one), IdenticalTo(literal(-1)));
CHECK_THAT(fast((v >> one) >> one), IdenticalTo(v >> literal(2)));
}
SECTION("LogicalShiftR")
{
CHECK_THAT(simplify(logicalShiftR(v, zero)), IdenticalTo(v));
CHECK_THAT(simplify(logicalShiftR(logicalShiftR(a, one), one)), IdenticalTo(literal(8)));
CHECK_THAT(simplify(logicalShiftR(literal(-2), one)), IdenticalTo(literal(-1 ^ (1 << 31))));
CHECK_THAT(simplify(logicalShiftR(v, logicalShiftR(zero, zero))), IdenticalTo(v));
}
SECTION("nullptr")
{
CHECK_THROWS_AS(simplify(zero * nullptr), FatalError);
CHECK_THROWS_AS(simplify(nullptr * zero), FatalError);
ExpressionPtr nullexpr;
CHECK_THROWS_AS(simplify(nullptr * nullexpr), FatalError);
CHECK_THAT(simplify(nullptr), IdenticalTo(nullptr));
}
SECTION("addShiftLeft")
{
// addShiftLeft
CHECK_THAT(simplify(fuseTernary(a + b << one + one)),
IdenticalTo(std::make_shared<rocRoller::Expression::Expression>(
AddShiftL{literal(33), literal(100), literal(2)})));
}
SECTION("bitFieldExtract")
{
CHECK_THAT(simplify(bfe(DataType::Int32, v, 0, 32)), IdenticalTo(v));
CHECK_THAT(simplify(bfe(DataType::UInt32, v, 0, 32)),
IdenticalTo(reinterpret(DataType::UInt32, v)));
auto expr = bfe(DataType::Int32, v, 16, 16);
auto expr2 = bfe(DataType::Int32, expr, 0, 16);
CHECK_THAT(simplify(expr2), IdenticalTo(expr));
expr = bfe(DataType::Int32, v, 8, 16);
expr2 = bfe(DataType::Int32, expr, 0, 4);
CHECK_THAT(simplify(expr2), IdenticalTo(bfe(DataType::Int32, v, 8, 4)));
expr = bfe(DataType::Int32, v, 8, 16);
expr2 = bfe(DataType::Int32, expr, 8, 16);
CHECK_THAT(simplify(expr2), IdenticalTo(expr2));
}
SECTION("bitFieldCombine")
{
CHECK_THAT(simplify(bfc(v2, v, 16, 8, 0)), IdenticalTo(v));
CHECK_THAT(simplify(bfc(v2, v, 0, 0, 32)), IdenticalTo(v2));
CHECK_THAT(simplify(bfc(v3, v, 16, 0, 32)), IdenticalTo(bfe(DataType::Int32, v3, 16, 32)));
auto expr = bfc(v2, zero, 0, 0, 16);
auto expr2 = bfc(v, expr, 0, 0, 16);
CHECK_THAT(simplify(expr2), IdenticalTo(bfc(v, zero, 0, 0, 16)));
expr = bfc(v2, zero, 0, 8, 16);
expr2 = bfc(v, expr, 0, 0, 32);
CHECK_THAT(simplify(expr2), IdenticalTo(v));
expr = bfc(v2, zero, 0, 8, 16);
expr2 = bfc(v, expr, 0, 9, 32);
CHECK_THAT(simplify(expr2), IdenticalTo(expr2));
}
SECTION("concatenate")
{
CHECK_THAT(simplify(concat({v}, {DataType::Int32})), IdenticalTo(v));
CHECK_THAT(simplify(concat({literal(1u), literal(0u)}, {DataType::UInt64})),
IdenticalTo(literal(1ull, DataType::UInt64)));
CHECK_THAT(
simplify(concat({bfe(DataType::UInt32, v3, 0, 32), bfe(DataType::UInt32, v3, 32, 32)},
{DataType::UInt64})),
IdenticalTo(v3));
}
SECTION("convert")
{
CHECK_THAT(simplify(convert(DataType::Int32, v)), IdenticalTo(v));
CHECK_THAT(simplify(convert(DataType::UInt64, v3)), IdenticalTo(v3));
}
SECTION("reinterpret")
{
CHECK_THAT(simplify(reinterpret(DataType::Int32, v)), IdenticalTo(v));
CHECK_THAT(simplify(reinterpret(DataType::UInt64, v3)), IdenticalTo(v3));
}
SECTION("conditional")
{
auto expr = conditional(b > zero, v2, v2 + v);
CHECK_THAT(simplify(expr), IdenticalTo(v2));
}
}
TEST_CASE("FuseAssociative ExpressionTransformation works",
"[expression][expression-transformation]")
{
using namespace rocRoller;
auto context = TestContext::ForDefaultTarget();
auto r
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Int32, 1);
r->allocateNow();
auto v = r->expression();
auto zero = Expression::literal(0);
auto one = Expression::literal(1);
auto a = Expression::literal(33);
auto b = Expression::literal(100);
auto c = Expression::literal(12.f);
SECTION("BitwiseAnd")
{
CHECK_THAT(fuseAssociative(v & Expression::fuseTernary(a + b << one)),
IdenticalTo(v & Expression::addShiftL(a, b, one)));
CHECK_THAT(fuseAssociative(v & -one), IdenticalTo(v & -one));
CHECK_THAT(fuseAssociative(v & one), EquivalentTo(v & one));
CHECK_THAT(fuseAssociative((v & one) & a), EquivalentTo(v & one));
CHECK_THAT(fuseAssociative(simplify((one & a) & v)), EquivalentTo(v & one));
CHECK_THAT(fuseAssociative(a & (v & one)), EquivalentTo(v & one));
CHECK_THAT(fuseAssociative((one & v) & a), EquivalentTo(v & one));
CHECK_THAT(fuseAssociative(a & (one & v)), EquivalentTo(v & one));
CHECK_THAT(fuseAssociative(((((v & one) & a) & a) & a) & a), EquivalentTo(v & one));
CHECK_THAT(fuseAssociative(((((v & one) & a) + a) & a) & one),
IdenticalTo(((v & one) + a) & one));
}
SECTION("BitwiseOr")
{
CHECK_THAT(fuseAssociative(v | Expression::fuseTernary(a + b << one)),
IdenticalTo(v | Expression::addShiftL(a, b, one)));
CHECK_THAT(fuseAssociative(v | -one), IdenticalTo(v | -one));
CHECK_THAT(fuseAssociative(v | one), EquivalentTo(v | one));
CHECK_THAT(fuseAssociative((v | one) | a), EquivalentTo(v | a));
CHECK_THAT(fuseAssociative(simplify((one | a) | v)), EquivalentTo(v | a));
CHECK_THAT(fuseAssociative(a | (v | one)), EquivalentTo(v | a));
CHECK_THAT(fuseAssociative((one | v) | a), EquivalentTo(v | a));
CHECK_THAT(fuseAssociative(a | (one | v)), EquivalentTo(v | a));
CHECK_THAT(fuseAssociative(((((v | one) | a) | a) | a) | a), EquivalentTo(v | a));
CHECK_THAT(fuseAssociative(((((v | one) | a) + a) | a) | one),
IdenticalTo(((v | a) + a) | a));
}
SECTION("Add")
{
CHECK_THAT(fuseAssociative((v + one) + a), IdenticalTo(v + Expression::literal(34)));
CHECK_THAT(fuseAssociative(v + (one + a)), IdenticalTo(v + Expression::literal(34)));
CHECK_THAT(fuseAssociative((one + v) + a), EquivalentTo(v + Expression::literal(34)));
}
SECTION("Multiply")
{
CHECK_THAT(fuseAssociative((v * one) * a), IdenticalTo(v * Expression::literal(33)));
CHECK_THAT(fuseAssociative((v * a) * a), IdenticalTo(v * Expression::literal(33 * 33)));
CHECK_THAT(fuseAssociative(v * (a * a)), IdenticalTo(v * Expression::literal(33 * 33)));
CHECK_THAT(fuseAssociative((a * v) * a), EquivalentTo(v * Expression::literal(33 * 33)));
}
SECTION("ShiftL")
{
CHECK_THAT(fuseAssociative((v << one) << one), IdenticalTo(v << Expression::literal(2)));
auto codegenOnly = Expression::EvaluationTimes{Expression::EvaluationTime::KernelExecute};
CHECK(evaluationTimes(((a << v) << one) << one) == codegenOnly);
CHECK(evaluationTimes((a << v) << one) == codegenOnly);
CHECK(evaluationTimes(a << v) == codegenOnly);
CHECK_THAT(fuseAssociative(((a << v) << one) << one),
IdenticalTo((a << v) << Expression::literal(2)));
}
SECTION("Arithmetic ShiftR")
{
CHECK_THAT(fuseAssociative((v >> one) >> one), IdenticalTo(v >> Expression::literal(2)));
CHECK_THAT(fuseAssociative(((v >> one) >> one) >> one),
IdenticalTo(v >> Expression::literal(3)));
CHECK_THAT(fuseAssociative(((one >> v) >> one) >> one),
IdenticalTo((one >> v) >> Expression::literal(2)));
}
SECTION("Logical shiftR")
{
CHECK_THAT(fuseAssociative(logicalShiftR(logicalShiftR(v, one), one)),
IdenticalTo(logicalShiftR(v, Expression::literal(2))));
CHECK_THAT(fuseAssociative(logicalShiftR(logicalShiftR(logicalShiftR(v, one), one), one)),
IdenticalTo(logicalShiftR(v, Expression::literal(3))));
CHECK_THAT(fuseAssociative(logicalShiftR(logicalShiftR(logicalShiftR(one, v), one), one)),
IdenticalTo(logicalShiftR(logicalShiftR(one, v), Expression::literal(2))));
}
SECTION("Subtraction")
{
// fuseAssociative does not affect non-associative ops
CHECK_THAT(fuseAssociative((v - one) - a), IdenticalTo((v - one) - a));
}
SECTION("nullptr")
{
CHECK_THROWS_AS(fuseAssociative(std::make_shared<Expression::Expression>(
Expression::Multiply{zero, nullptr})),
FatalError);
CHECK_THROWS_AS(fuseAssociative(std::make_shared<Expression::Expression>(
Expression::Multiply{nullptr, zero})),
FatalError);
CHECK_THROWS_AS(fuseAssociative(std::make_shared<Expression::Expression>(
Expression::Multiply{nullptr, nullptr})),
FatalError);
CHECK_THAT(Expression::fuseAssociative(nullptr), IdenticalTo(nullptr));
}
}
TEST_CASE("FuseTernary ExpressionTransformation works", "[expression][expression-transformation]")
{
using namespace rocRoller;
auto context = TestContext::ForDefaultTarget();
auto r
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Int32, 1);
r->allocateNow();
auto v = r->expression();
auto r2
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Int32, 1);
r2->allocateNow();
auto v2 = r2->expression();
auto zero = Expression::literal(0);
auto one = Expression::literal(1);
auto a = Expression::literal(33);
auto b = Expression::literal(100);
auto c = Expression::literal(12.f);
SECTION("Non-transformed")
{
CHECK_THAT(fuseTernary(-one + one), IdenticalTo(-one + one));
CHECK_THAT(fuseTernary(one + one), IdenticalTo(one + one));
}
SECTION("AddShiftL")
{
CHECK_THAT(fuseTernary((b + a) << one), IdenticalTo(addShiftL(b, a, one)));
CHECK_THAT(fuseTernary((v + a) << one), IdenticalTo(addShiftL(v, a, one)));
CHECK_THAT(fuseTernary((a + v) << one), IdenticalTo(addShiftL(a, v, one)));
CHECK_THAT(fuseTernary((v + v2) << one), IdenticalTo(addShiftL(v, v2, one)));
CHECK_THAT(fuseTernary(((v + a) << one) + v), IdenticalTo(addShiftL(v, a, one) + v));
CHECK_THAT(fuseTernary(((b + v + a) << one) + v),
IdenticalTo(addShiftL((b + v), a, one) + v));
CHECK_THAT(fuseTernary((((v + v2) << one) + v) + v2),
IdenticalTo((addShiftL(v, v2, one) + v) + v2));
CHECK_THAT(fuseTernary((b + a) << v), IdenticalTo((b + a) << v));
}
SECTION("ShiftLAdd")
{
CHECK_THAT(fuseTernary((b << one) + a), IdenticalTo(shiftLAdd(b, one, a)));
CHECK_THAT(fuseTernary((v << one) + a), IdenticalTo(shiftLAdd(v, one, a)));
CHECK_THAT(fuseTernary((v << one) + v), IdenticalTo(shiftLAdd(v, one, v)));
CHECK_THAT(fuseTernary((((v * v2) << one) + v) + v2),
IdenticalTo(shiftLAdd((v * v2), one, v) + v2));
CHECK_THAT(fuseTernary((b << v) + a), IdenticalTo((b << v) + a));
}
SECTION("ShiftLAddShiftL")
{
//
// Original: ShiftL(Add(ShiftL(v0:I, 1:I)I, 33:I)I, 1:I)I
// Expected: ShiftL(ShiftLAdd(v0:I, 1:I, 33:I)I, 1:I)I
//
CHECK_THAT(fuseTernary(((v << one) + a) << one),
IdenticalTo((shiftLAdd(v, one, a)) << one));
}
SECTION("FMA")
{
CHECK_THAT(fuseTernary((b * one) + a), IdenticalTo(multiplyAdd(b, one, a)));
CHECK_THAT(fuseTernary((v * one) + a), IdenticalTo(multiplyAdd(v, one, a)));
CHECK_THAT(fuseTernary((v * one) + v), IdenticalTo(multiplyAdd(v, one, v)));
CHECK_THAT(fuseTernary((((v + v2) * one) + v) + v2),
IdenticalTo(multiplyAdd((v + v2), one, v) + v2));
CHECK_THAT(fuseTernary((((v * v2) * one) + v) + v2),
IdenticalTo(multiplyAdd((v * v2), one, v) + v2));
auto fast = Expression::FastArithmetic(context.get());
CHECK_THAT(fast((((v * v2) * one) + v) + v2), IdenticalTo(multiplyAdd(v, v2, v) + v2));
}
SECTION("nullptr")
{
CHECK_THROWS_AS(fuseTernary(std::make_shared<Expression::Expression>(
Expression::Multiply{zero, nullptr})),
FatalError);
CHECK_THROWS_AS(fuseTernary(std::make_shared<Expression::Expression>(
Expression::Multiply{nullptr, zero})),
FatalError);
CHECK_THROWS_AS(fuseTernary(std::make_shared<Expression::Expression>(
Expression::Multiply{nullptr, nullptr})),
FatalError);
CHECK_THROWS_AS(Expression::fuseTernary(nullptr), FatalError);
}
}
TEST_CASE("FastArithmetic includes translate time evaluation",
"[expression][expression-transformation]")
{
using namespace rocRoller;
using Expression::literal;
auto context = TestContext::ForDefaultTarget();
auto zero = literal(0.f);
auto c = literal(12.f);
Expression::FastArithmetic fastArith(context.get());
CHECK(fastArith(nullptr).get() == nullptr);
CHECK_THAT(fastArith(c * zero), IdenticalTo(literal(0.f)));
}
TEST_CASE("FastArithmetic pipeline properly simplifies expressions",
"[expression][expression-transformation]")
{
using namespace rocRoller;
using Expression::literal;
auto context = TestContext::ForDefaultTarget();
Expression::FastArithmetic fastArith(context.get());
auto transforms = fastArith.getTransforms();
auto isSimplify = [](const Expression::ExpressionTransformType& transformFunction) {
using ExprTransformFuncPtrType = Expression::ExpressionPtr (*)(Expression::ExpressionPtr);
const auto* funcPtr = transformFunction.target<ExprTransformFuncPtrType>();
return funcPtr && *funcPtr == Expression::simplify;
};
// Create a version with extra simplifies after each non-simplify transform
std::vector<Expression::ExpressionTransformType> transformsExtraSimplify;
for(const auto& transform : transforms)
{
transformsExtraSimplify.push_back(transform);
if(!isSimplify(transform))
transformsExtraSimplify.push_back(Expression::simplify);
}
// Create a version with only one simplify
std::vector<Expression::ExpressionTransformType> transformsOneSimplify;
bool hasSimplify = false;
for(const auto& transform : transforms)
{
if(isSimplify(transform))
{
if(hasSimplify)
continue;
hasSimplify = true;
}
transformsOneSimplify.push_back(transform);
}
auto tag83 = Expression::dataFlowTag(83, Register::Type::Vector, DataType::UInt32);
auto expr = (tag83 % literal(4)) * literal(32);
expr = (tag83 / literal(4)) * literal(128u) + expr;
expr = expr * literal(4u);
expr = expr / literal(8u);
expr = expr + literal(0u);
expr = std::make_shared<Expression::Expression>(Expression::ToScalar{expr});
expr = Expression::convert(DataType::UInt32, expr);
// One simplify is not enough
CHECK(!Expression::identical(fastArith.applyTransforms(expr, transforms),
fastArith.applyTransforms(expr, transformsOneSimplify)));
// There are enough simplifies
CHECK_THAT(fastArith.applyTransforms(expr, transforms),
IdenticalTo(fastArith.applyTransforms(expr, transformsExtraSimplify)));
}
TEST_CASE("ConvertPropagation", "[expression][expression-transformation]")
{
using namespace rocRoller;
using enum DataType;
using Expression::literal;
auto context = TestContext::ForDefaultTarget();
std::vector<Expression::ExpressionPtr> r64{
3,
Register::Value::Placeholder(context.get(), Register::Type::Vector, Int64, 1)
->expression()};
std::vector<Expression::ExpressionPtr> r32{
3,
Register::Value::Placeholder(context.get(), Register::Type::Vector, Int32, 1)
->expression()};
Expression::FastArithmetic fastArith(context.get());
CHECK(fastArith(nullptr).get() == nullptr);
SECTION("basic")
{
// Int32(r64 + r64) -> Int32(Int32(r64) + Int32(r64))
CHECK_THAT(convertPropagation(convert(Int32, r64[0] + r64[1])),
IdenticalTo(convert(Int32, convert(Int32, r64[0]) + convert(Int32, r64[1]))));
// Int32(r64 + r64 * r64) -> Int32(Int32(r64) + Int32(r64) * Int32(r64))
CHECK_THAT(
convertPropagation(convert(Int32, r64[0] + r64[1] * r64[2])),
IdenticalTo(convert(
Int32, convert(Int32, r64[0]) + convert(Int32, r64[1]) * convert(Int32, r64[2]))));
// r64 + Int32(r64 * r64) -> r64 + Int32(Int32(r64) * Int32(r64))
CHECK_THAT(
convertPropagation(r64[0] + convert(Int32, r64[1] * r64[2])),
IdenticalTo(r64[0] + convert(Int32, convert(Int32, r64[1]) * convert(Int32, r64[2]))));
// Int64(r64) -> no change
CHECK_THAT(convertPropagation(convert(Int64, r64[0])), IdenticalTo(convert(Int64, r64[0])));
// Int32(r64) -> Int32(Int32(r64))
CHECK_THAT(convertPropagation(convert(Int32, r64[0])),
IdenticalTo(convert(Int32, convert(Int32, r64[0]))));
// Int32(r64 << r64) -> Int32(Int32(r64) << r64)
CHECK_THAT(convertPropagation(convert(Int32, r64[0] << r64[1])),
IdenticalTo(convert(Int32, convert(Int32, r64[0]) << r64[1])));
// Int32((r64 + r64) << r64) -> Int32((Int32(r64) + Int32(r64)) << r64)
CHECK_THAT(convertPropagation(convert(Int32, addShiftL(r64[0], r64[1], r64[2]))),
IdenticalTo(convert(
Int32, addShiftL(convert(Int32, r64[0]), convert(Int32, r64[1]), r64[2]))));
// Int32((r64 << r64) + r64) -> Int32((Int32(r64) << r64) + Int32(r64))
CHECK_THAT(convertPropagation(convert(Int32, shiftLAdd(r64[0], r64[1], r64[2]))),
IdenticalTo(convert(
Int32, shiftLAdd(convert(Int32, r64[0]), r64[1], convert(Int32, r64[2])))));
}
SECTION("skipped datatypes")
{
Expression::ExpressionPtr f1 = literal(1.0);
CHECK_THAT(convertPropagation(convert(Float, f1 + f1)),
IdenticalTo(convert(Float, f1 + f1)));
Expression::ExpressionPtr halfx2
= Register::Value::Placeholder(context.get(), Register::Type::Vector, Halfx2, 1)
->expression();
CHECK_THAT(convertPropagation(convert(Float, halfx2 + halfx2)),
IdenticalTo((convert(Float, halfx2 + halfx2))));
}
SECTION("nested convert")
{
// Int32(r64 + Int64(r32 * r32)) -> Int32(Int32(r64) + Int32(r32 * r32))
CHECK_THAT(
convertPropagation(convert(Int32, r64[0] + convert(Int64, r32[1] * r32[2]))),
IdenticalTo(convert(Int32, convert(Int32, r64[0]) + convert(Int32, r32[1] * r32[2]))));
// Do not propagate existing converts to larger types
// Int32(r64 + Int64(r64 * r64)) -> Int32(Int32(r64) + Int64(r64 * r64))
auto expr = convertPropagation(convert(Int32, r64[0] + convert(Int64, r64[1] * r64[2])));
CHECK_THAT(expr,
IdenticalTo(convert(
Int32,
convert(Int32, r64[0])
+ convert(Int32, convert(Int32, r64[1]) * convert(Int32, r64[2])))));
}
SECTION("conditional")
{
auto cond = Register::Value::Placeholder(context.get(), Register::Type::Vector, Bool32, 1)
->expression();
auto expr
= convertPropagation(convert(Int32, Expression::conditional(cond, r64[0], r64[1])));
CHECK_THAT(expr,
IdenticalTo(convert(Int32,
Expression::conditional(
cond, convert(Int32, r64[0]), convert(Int32, r64[1])))));
}
SECTION("special value types")
{
using namespace Expression;
const auto tag = dataFlowTag(72, Register::Type::Vector, Int64);
const auto expr = convertPropagation(convert(Int32, tag + r64[0]));
CHECK_THAT(expr, IdenticalTo(convert(Int32, convert(Int32, tag) + convert(Int32, r64[0]))));
CHECK_THAT(simplify(expr), IdenticalTo(convert(Int32, tag) + convert(Int32, r64[0])));
CHECK_THAT(convertPropagation(convert(
UInt32, literal(32u, DataType::UInt32) + literal(4u, DataType::UInt64))),
IdenticalTo(convert(UInt32,
literal(32u, DataType::UInt32)
+ convert(UInt32, literal(4u, DataType::UInt64)))));
CHECK_NOTHROW(evaluate(convertPropagation(
convert(UInt32, literal(32u, DataType::UInt32) + literal(4u, DataType::UInt64)))));
}
SECTION(
"Propagation stops on Div/ArithmeticShiftR/LogicalShiftR when converting 64-bit to 32-bit")
{
CHECK_THAT(convertPropagation(convert(Int32, logicalShiftR(r64[0], r32[0]))),
IdenticalTo(convert(Int32, convert(Int32, logicalShiftR(r64[0], r32[0])))));
CHECK_THAT(convertPropagation(convert(Int32, (r64[0] >> r32[0]))),
IdenticalTo(convert(Int32, convert(Int32, (r64[0] >> r32[0])))));
CHECK_THAT(convertPropagation(convert(Int32, (r64[0] / r64[1]))),
IdenticalTo(convert(Int32, convert(Int32, (r64[0] / r64[1])))));
}
SECTION("Other expressions")
{
CHECK_THAT(Expression::convertPropagation(Expression::convert(Int32, -r64[0])),
IdenticalTo(Expression::convert(Int32, -Expression::convert(Int32, r64[0]))));
CHECK_THAT(
convertPropagation(convert(
Int32,
Expression::concat({r64[0], r64[1]}, {DataType::None, PointerType::Buffer}))),
IdenticalTo(convert(Int32,
Expression::concat({convert(Int32, r64[0]), convert(Int32, r64[1])},
{DataType::None, PointerType::Buffer}))));
}
}
TEST_CASE("Nested Reinterpret simplification", "[expression][expression-transformation]")
{
using namespace rocRoller;
using namespace Expression;
auto context = TestContext::ForDefaultTarget();
auto r_int32
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Int32, 1);
r_int32->allocateNow();
auto v = r_int32->expression();
SECTION("Double reinterpret same size types")
{
auto expr = reinterpret(DataType::Int32, reinterpret(DataType::Float, v));
auto simplified = simplify(expr);
CHECK_THAT(simplified, IdenticalTo(v));
}
SECTION("Reinterpret chain")
{
auto expr = reinterpret(DataType::UInt32, reinterpret(DataType::Float, v));
auto simplified = simplify(expr);
CHECK_THAT(simplified, IdenticalTo(reinterpret(DataType::UInt32, v)));
}
SECTION("Reinterpret of literal")
{
auto lit = literal(10.0f);
auto expr = reinterpret(DataType::UInt32, lit);
auto simplified = simplify(expr);
CHECK(std::holds_alternative<CommandArgumentValue>(*simplified));
CHECK(std::get<uint32_t>(evaluate(simplified)) == 1092616192);
}
}
TEST_CASE("launchTimeSubExpressions works", "[expression][expression-transformation]")
{
using namespace rocRoller;
using Expression::literal;
KernelOptions opts;
opts->minLaunchTimeExpressionComplexity = 5;
auto context = TestContext::ForDefaultTarget(opts);
auto command = std::make_shared<Command>();
auto arg1Tag = command->allocateTag();
auto arg1 = command->allocateArgument(
DataType::Int32, arg1Tag, ArgumentType::Value, DataDirection::ReadOnly, "arg1");
auto arg2Tag = command->allocateTag();
auto arg2 = command->allocateArgument(
DataType::Int32, arg2Tag, ArgumentType::Value, DataDirection::ReadOnly, "arg2");
auto arg3Tag = command->allocateTag();
auto arg3 = command->allocateArgument(
DataType::Int64, arg3Tag, ArgumentType::Value, DataDirection::ReadOnly, "arg3");
auto arg1e = arg1->expression();
auto arg2e = arg2->expression();
auto arg3e = arg3->expression();
auto reg1e
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Int32, 1)
->expression();
auto reg2e
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::Int64, 1)
->expression();
auto ex1 = (arg1e * Expression::literal(5)) * arg2e * arg3e;
auto ex1_launch = launchTimeSubExpressions(ex1, context.get());
auto argExpr = [&]() {
auto arg = context->kernel()->arguments().at(0);
CHECK_THAT(arg.getExpression(), IdenticalTo(ex1));
auto argPtr = std::make_shared<AssemblyKernelArgument>(arg);
return std::make_shared<Expression::Expression>(argPtr);
}();
auto arg1e2 = launchTimeSubExpressions(arg1e, context.get());
CHECK_THAT(ex1_launch, IdenticalTo(argExpr));
auto restored = restoreCommandArguments(ex1_launch);
CHECK_THAT(restored, IdenticalTo(ex1));
auto ex2 = ex1 + arg1e;
auto ex2_launch = launchTimeSubExpressions(ex2, context.get());
auto arg2Expr = [&]() {
auto arg = context->kernel()->arguments().at(1);
CHECK_THAT(arg.getExpression(), IdenticalTo(arg1e));
auto argPtr = std::make_shared<AssemblyKernelArgument>(arg);
return std::make_shared<Expression::Expression>(argPtr);
}();
CHECK_THAT(ex2_launch, IdenticalTo(argExpr + arg2Expr));
std::vector<AssemblyKernelArgument> expectedArgs{
{"Multiply_0", DataType::Int64, DataDirection::ReadOnly, ex1, 0, 8},
{"arg1_1", DataType::Int32, DataDirection::ReadOnly, arg1e, 8, 4}};
CHECK(expectedArgs == context->kernel()->arguments());
}
TEST_CASE("lowerPRNG transformation works", "[expression][expression-transformation]")
{
using namespace rocRoller;
using Expression::literal;
auto context = TestContext::ForDefaultTarget();
// Test replacing random number expression with equivalent expressions
// when PRNG instruction is not available
auto seed
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::UInt32, 1);
seed->allocateNow();
auto seedExpr = seed->expression();
auto expr = std::make_shared<Expression::Expression>(Expression::RandomNumber{seedExpr});
CHECK_THAT(lowerPRNG(expr, context.get()),
IdenticalTo(
conditional((logicalShiftR(seedExpr, literal(31u)) & literal(1u)) == literal(1u),
literal(197u) ^ (seedExpr << literal(1u)),
seedExpr << literal(1u))));
}
TEST_CASE("combineShifts works", "[expression][expression-transformation]")
{
using namespace rocRoller;
using Expression::literal;
auto ctx = TestContext::ForDefaultTarget();
Expression::FastArithmetic fast(ctx.get());
auto command = std::make_shared<Command>();
auto argTag = command->allocateTag();
auto dataType = GENERATE(DataType::Int32, DataType::UInt32);
DYNAMIC_SECTION(dataType)
{
auto dataTypeInfo = DataTypeInfo::Get(dataType);
auto arg = command->allocateArgument(dataType, argTag, ArgumentType::Size);
auto argExp = fast(arg->expression());
SECTION("Shift left then right masks off MSBs.")
{
auto exp = logicalShiftR((argExp << literal(4u)), literal(4u));
auto fastExp = fast(exp);
auto maskExp = argExp & literal(0b00001111111111111111111111111111u, dataType);
CHECK_THAT(fastExp, IdenticalTo(maskExp));
for(auto v : TestValues::byType(dataType))
{
KernelArguments args;
args.append("", v);
auto bytes = args.runtimeArguments();
CAPTURE(v);
CHECK(evaluate(fastExp, bytes) == evaluate(exp, bytes));
}
}
SECTION("Shift left then right (arithmetic) masks off MSBs for unsigned.")
{
auto exp = (argExp << literal(4u)) >> literal(4u);
auto fastExp = fast(exp);
auto maskExp = argExp & literal(0b00001111111111111111111111111111u, dataType);
if(dataTypeInfo.isSigned)
{
CHECK_THAT(fastExp, IdenticalTo(exp));
}
else
{
CHECK_THAT(fastExp, IdenticalTo(maskExp));
}
for(auto v : TestValues::byType(dataType))
{
KernelArguments args;
args.append("", v);
auto bytes = args.runtimeArguments();
CAPTURE(v);
CHECK(evaluate(fastExp, bytes) == evaluate(exp, bytes));
}
}
SECTION("Shift left then right by different amounts is left alone.")
{
auto exp = (argExp << literal(5u)) >> literal(4u);
auto fastExp = fast(exp);
if(dataTypeInfo.isSigned)
CHECK_THAT(fastExp, IdenticalTo(exp));
else
CHECK_THAT(fastExp,
IdenticalTo(logicalShiftR((argExp << literal(5u)), literal(4u))));
}
SECTION("Shift right then left masks off LSBs.")
{
auto exp = (argExp >> literal(5u)) << literal(5u);
auto fastExp = fast(exp);
auto maskExp = argExp & literal(0b11111111111111111111111111100000u, dataType);
CHECK_THAT(fastExp, IdenticalTo(maskExp));
for(auto v : TestValues::byType(dataType))
{
KernelArguments args;
args.append("", v);
auto bytes = args.runtimeArguments();
CAPTURE(v);
CHECK(evaluate(fastExp, bytes) == evaluate(exp, bytes));
}
}
SECTION("Shift right then left by different amounts is left alone.")
{
auto exp = (argExp >> literal(2u)) << literal(5u);
auto fastExp = fast(exp);
if(dataTypeInfo.isSigned)
CHECK_THAT(fastExp, IdenticalTo(exp));
else
CHECK_THAT(fastExp, IdenticalTo(logicalShiftR(argExp, literal(2u)) << literal(5u)));
}
SECTION("Direct combineShift with shift left then right (arithmetic) masks off MSBs for "
"unsigned.")
{
auto exp = (argExp << literal(4u)) >> literal(4u);
auto newExp = combineShifts(exp);
auto maskExp = argExp & literal(0b00001111111111111111111111111111u, dataType);
if(dataTypeInfo.isSigned)
{
CHECK_THAT(newExp, IdenticalTo(exp));
}
else
{
CHECK_THAT(newExp, IdenticalTo(maskExp));
}
}
}
dataType = GENERATE(DataType::Int64, DataType::UInt64);
DYNAMIC_SECTION(dataType)
{
auto dataTypeInfo = DataTypeInfo::Get(dataType);
auto arg = command->allocateArgument(dataType, argTag, ArgumentType::Size);
auto argExp = fast(arg->expression());
SECTION("Shift left then right masks off MSBs.")
{
auto exp = logicalShiftR((argExp << literal(4u)), literal(4u));
auto fastExp = fast(exp);
auto maskExp
= argExp
& literal(0b0000111111111111111111111111111111111111111111111111111111111111ull,
dataType);
CHECK_THAT(fastExp, IdenticalTo(maskExp));
for(auto v : TestValues::byType(dataType))
{
KernelArguments args;
args.append("", v);
auto bytes = args.runtimeArguments();
CAPTURE(v);
CHECK(evaluate(fastExp, bytes) == evaluate(exp, bytes));
}
}
SECTION("Shift left then right (arithmetic) masks off MSBs for unsigned.")
{
auto exp = (argExp << literal(4u)) >> literal(4u);
auto fastExp = fast(exp);
auto maskExp
= argExp
& literal(0b0000111111111111111111111111111111111111111111111111111111111111ull,
dataType);
if(dataTypeInfo.isSigned)
{
CHECK_THAT(fastExp, IdenticalTo(exp));
}
else
{
CHECK_THAT(fastExp, IdenticalTo(maskExp));
}
for(auto v : TestValues::byType(dataType))
{
KernelArguments args;
args.append("", v);
auto bytes = args.runtimeArguments();
CAPTURE(v);
CHECK(evaluate(fastExp, bytes) == evaluate(exp, bytes));
}
}
SECTION("Shift left then right by different amounts is left alone.")
{
auto exp = (argExp << literal(5u)) >> literal(4u);
auto fastExp = fast(exp);
if(dataTypeInfo.isSigned)
CHECK_THAT(fastExp, IdenticalTo(exp));
else
CHECK_THAT(fastExp, IdenticalTo(logicalShiftR(argExp << literal(5u), literal(4u))));
}
SECTION("Shift right then left masks off LSBs.")
{
auto exp = (argExp >> literal(5u)) << literal(5u);
auto fastExp = fast(exp);
auto maskExp
= argExp
& literal(0b1111111111111111111111111111111111111111111111111111111111100000ull,
dataType);
CHECK_THAT(fastExp, IdenticalTo(maskExp));
for(auto v : TestValues::byType(dataType))
{
KernelArguments args;
args.append("", v);
auto bytes = args.runtimeArguments();
CAPTURE(v);
CHECK(evaluate(fastExp, bytes) == evaluate(exp, bytes));
}
}
SECTION("Shift right then left by different amounts is left alone.")
{
auto exp = (argExp >> literal(2u)) << literal(5u);
auto fastExp = fast(exp);
if(dataTypeInfo.isSigned)
CHECK_THAT(fastExp, IdenticalTo(exp));
else
CHECK_THAT(fastExp,
IdenticalTo((logicalShiftR(argExp, literal(2u)) << literal(5u))));
}
SECTION("Direct combineShift with shift left then right (arithmetic) masks off MSBs for "
"unsigned.")
{
auto exp = (argExp << literal(4u)) >> literal(4u);
auto newExp = combineShifts(exp);
auto maskExp
= argExp
& literal(0b0000111111111111111111111111111111111111111111111111111111111111ull,
dataType);
if(dataTypeInfo.isSigned)
{
CHECK_THAT(newExp, IdenticalTo(exp));
}
else
{
CHECK_THAT(newExp, IdenticalTo(maskExp));
}
}
}
}
TEST_CASE("splitBitFieldCombine works", "[expression][expression-transformation]")
{
using namespace rocRoller;
auto context = TestContext::ForDefaultTarget();
auto zero64 = Expression::literal(0, DataType::UInt64);
auto zero32 = Expression::literal(0, DataType::Raw32);
auto zero128 = Expression::literal(Buffer{0, 0, 0, 0});
auto ones64 = Expression::literal(0xffffffffffffffffull, DataType::UInt64);
auto ones32 = Expression::literal(0xfffffffful, DataType::UInt32);
auto four = Expression::literal(4, DataType::UInt32);
auto r
= Register::Value::Placeholder(context.get(), Register::Type::Scalar, DataType::UInt32, 1);
r->allocateNow();
auto reg32 = r->expression();
auto r2
= Register::Value::Placeholder(context.get(), Register::Type::Scalar, DataType::UInt64, 1);
r2->allocateNow();
auto reg64 = r2->expression();
SECTION("Combine into the first dword of 64 bit dst and fold to constant")
{
auto expr = bfc(ones32, zero64, 0, 16, 8);
auto expected = Expression::literal(0x0000000000ff0000ull, DataType::UInt64);
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Combine into the second dword of 64 bit dst and fold to constant")
{
auto expr = bfc(ones32, zero64, 0, 48, 8);
auto expected = Expression::literal(0x00ff000000000000ull, DataType::UInt64);
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Combine into the first and second dwords of 64 bit dst and fold to constant")
{
auto expr = bfc(ones32, zero64, 0, 24, 16);
auto expected = Expression::literal(0x000000ffff000000ull, DataType::UInt64);
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Combine into the first dword of 64 bit dst")
{
auto expr = bfc(reg32, zero64, 0, 16, 8);
auto expect_1 = bfc(reg32, zero32, 0, 16, 8);
std::vector<Expression::ExpressionPtr> operands{expect_1, zero32};
auto expected = concat(operands, DataType::UInt64);
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Combine into the second dword of 64 bit dst")
{
auto expr = bfc(reg32, zero64, 0, 48, 8);
auto expect1 = bfc(reg32, zero32, 0, 16, 8);
std::vector<Expression::ExpressionPtr> operands{zero32, expect1};
auto expected = concat(operands, DataType::UInt64);
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Combine into the first and second dwords of 64 bit dst")
{
auto expr = bfc(reg32, zero64, 0, 24, 16);
// zero64 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// expr 00000000 00000000 00000000 XXXXXXXX XXXXXXXX 00000000 00000000 00000000
// expect1 XXXXXXXX 00000000 00000000 00000000
// expect2 00000000 00000000 00000000 XXXXXXXX
auto expect1 = bfc(reg32, zero32, 0, 24, 8);
auto expect2 = bfc(reg32, zero32, 8, 0, 8);
std::vector<Expression::ExpressionPtr> operands{expect1, expect2};
auto expected = concat(operands, DataType::UInt64);
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Chain two BitfieldCombines into 64 bit dst")
{
auto expr = bfc(reg32, zero64, 0, 16, 8);
auto expr2 = bfc(ones32, expr, 0, 48, 8);
// zero64 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// expr 00000000 00000000 00000000 00000000 00000000 XXXXXXXX 00000000 00000000
// expr2 00000000 11111111 00000000 00000000 00000000 XXXXXXXX 00000000 00000000
// expect1 00000000 XXXXXXXX 00000000 00000000
// expect2 00000000 11111111 00000000 00000000
auto expect1 = bfc(reg32, zero32, 0, 16, 8);
std::vector<Expression::ExpressionPtr> operands{
expect1, Expression::literal(0x00ff0000ul, DataType::Raw32)};
auto expected = concat(operands, DataType::UInt64);
CHECK_THAT(splitBitfieldCombine(expr2), IdenticalTo(expected));
}
SECTION("Chain two BitfieldCombines into 64 bit dst, the second bfc goes into the first and "
"second dwords of dst")
{
auto expr = bfc(reg32, zero64, 0, 16, 8);
auto expr2 = bfc(ones32, expr, 0, 24, 16);
// zero64 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// expr 00000000 00000000 00000000 00000000 00000000 XXXXXXXX 00000000 00000000
// expr2 00000000 00000000 00000000 11111111 11111111 XXXXXXXX 00000000 00000000
// expect1 11111111 XXXXXXXX 00000000 00000000
// expect2 00000000 00000000 00000000 11111111
auto expect1 = bfc(reg32, Expression::literal(0xff000000ul, DataType::Raw32), 0, 16, 8);
std::vector<Expression::ExpressionPtr> operands{
expect1, Expression::literal(0x000000fful, DataType::Raw32)};
auto expected = concat(operands, DataType::UInt64);
CHECK_THAT(splitBitfieldCombine(expr2), IdenticalTo(expected));
}
SECTION("Chain two BitfieldCombines into 64 bit dst and fold to constant")
{
auto expr = bfc(ones32, zero64, 0, 16, 8);
auto expr2 = bfc(ones32, expr, 0, 40, 8);
auto expected = Expression::literal(0x0000ff0000ff0000ull, DataType::UInt64);
CHECK_THAT(splitBitfieldCombine(expr2), IdenticalTo(expected));
}
SECTION("Combine into the first dword of 128 bit dst and fold to constant")
{
auto expr = bfc(ones32, zero128, 0, 16, 8);
std::vector<Expression::ExpressionPtr> operands{
Expression::literal(0x00ff0000ul, DataType::Raw32), zero32, zero32, zero32};
auto expected = concat(operands, {DataType::None, PointerType::Buffer});
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Combine 64 bit register into 128 bit dst")
{
auto expr = bfc(reg64, zero128, 0, 0, 64);
// zero128 0x 00000000 00000000 00000000 00000000
// expr 0x 00000000 00000000 XXXXXXXX XXXXXXXX
std::vector<Expression::ExpressionPtr> operands{reg64, zero32, zero32};
auto expected = concat(operands, {DataType::None, PointerType::Buffer});
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Combine 64 bit register into second, third, and fourth dwords of 128 bit dst")
{
auto expr = bfc(reg64, zero128, 0, 57, 45);
// zero128 0x 00000000 00000000 00000000 00000000
// reg64 0x XXXXXXXX XXXXXXXX (45 bits used: bits 0-44)
// expr 0x 000000XX XXXXXXXX XXXXXXX0 00000000
//
// Bit layout (45 bits from reg64[0:44] inserted at dest bits 57-101):
// dword0 (bits 0-31): unchanged = zero32
// dword1 (bits 32-63): bits 57-63 = reg64[0:6] (7 bits at positions 25-31)
// dword2 (bits 64-95): bits 64-95 = reg64[7:38] (32 bits, crosses dword boundary)
// split into: bfe(reg64, 7, 25) | (bfe(reg64, 32, 7) << 25)
// dword3 (bits 96-127): bits 96-101 = reg64[39:44] (6 bits at positions 0-5)
auto dword2_lower = bfe(DataType::Raw32, reg64, 7, 25);
auto dword2_upper = bfe(DataType::Raw32, reg64, 32, 7);
auto dword2 = dword2_lower | (dword2_upper << Expression::literal(25u));
std::vector<Expression::ExpressionPtr> operands{
zero32,
bfc(bfe(DataType::Raw32, reg64, 0, 7), zero32, 0, 25, 7),
dword2,
bfc(bfe(DataType::Raw32, reg64, 39, 6), zero32, 0, 0, 6)};
auto expected = concat(operands, {DataType::None, PointerType::Buffer});
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Combine into the first dword of 128 bit dst, across src dword boundary")
{
auto expr = bfc(reg64, zero128, 16, 0, 32);
// zero128 0x 00000000 00000000 00000000 00000000
// reg64 0x 0000XXXX XXXX0000
// expr 0x 00000000 00000000 00000000 XXXXXXXX
//
// bfe(reg64, 16, 32) extracts bits 16-47, crosses dword boundary at bit 32
// Split into: bfe(reg64, 16, 16) | (bfe(reg64, 32, 16) << 16)
auto dword0_lower = bfe(DataType::Raw32, reg64, 16, 16);
auto dword0_upper = bfe(DataType::Raw32, reg64, 32, 16);
auto dword0 = dword0_lower | (dword0_upper << Expression::literal(16u));
std::vector<Expression::ExpressionPtr> operands{dword0, zero32, zero32, zero32};
auto expected = concat(operands, {DataType::None, PointerType::Buffer});
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("Combine into the first and second dwords of 128 bit dst, across src dword boundary")
{
auto expr = bfc(reg64, zero128, 16, 16, 32);
// zero128 0x 00000000 00000000 00000000 00000000
// reg64 0x 0000XXXX XXXX0000
// expr 0x 00000000 00000000 0000XXXX XXXX0000
std::vector<Expression::ExpressionPtr> operands{
bfc(bfe(DataType::Raw32, reg64, 16, 16), zero32, 0, 16, 16),
bfc(bfe(DataType::Raw32, reg64, 32, 16), zero32, 0, 0, 16),
zero32,
zero32};
auto expected = concat(operands, {DataType::None, PointerType::Buffer});
CHECK_THAT(splitBitfieldCombine(expr), IdenticalTo(expected));
}
SECTION("BitfieldCombine chain into 128 bit")
{
auto expr = bfc(reg32, zero128, 0, 90, 12);
auto expr2 = bfc(four, expr, 0, 110, 8);
// zero128 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// expr 00000000 00000000 00000000 00XXXXXX XXXXXX00 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// four 00000000 00000000 00000000 00000100
// expr2 00000000 00000001 00000000 00XXXXXX XXXXXX00 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// expect1 XXXXXX00 00000000 00000000 00000000
// expect2 00000000 00000001 00000000 00XXXXXX
// four is combined with an offset of 110, so its bit 1 goes to position 110 + 2. The other 7 bits from four are all zeros.
auto expect1 = bfc(reg32, zero32, 0, 26, 6);
auto expect2 = bfc(reg32, Expression::literal(0x00010000ul, DataType::Raw32), 6, 0, 6);
std::vector<Expression::ExpressionPtr> operands{zero32, zero32, expect1, expect2};
auto expected = concat(operands, {DataType::None, PointerType::Buffer});
CHECK_THAT(splitBitfieldCombine(expr2), IdenticalTo(expected));
}
}
TEST_CASE("Simplify Shift ExpressionTransformation works",
"[expression][expression-transformation]")
{
using namespace rocRoller;
auto context = TestContext::ForDefaultTarget();
auto zero = Expression::literal(0);
auto one = Expression::literal(1);
auto a = Expression::literal(33);
auto b = Expression::literal(35);
auto c = Expression::literal(30);
auto e = Expression::literal(64);
SECTION("Shift right by more than 31 bits")
{
auto r = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Int32, 1);
r->allocateNow();
auto v = r->expression();
auto expr = fuseAssociative(logicalShiftR(logicalShiftR(logicalShiftR(v, one), one), a));
CHECK_THAT(expr, IdenticalTo(logicalShiftR(v, b)));
auto expected = Expression::literal(0, DataType::Int32);
CHECK_THAT(simplify(expr), IdenticalTo(expected));
}
SECTION("Shift right by more than 63 bits")
{
auto r = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Int64, 1);
r->allocateNow();
auto v = r->expression();
auto expr = fuseAssociative(logicalShiftR(logicalShiftR(logicalShiftR(v, one), a), c));
CHECK_THAT(expr, IdenticalTo(logicalShiftR(v, e)));
auto expected = Expression::literal(0, DataType::Int64);
CHECK_THAT(simplify(expr), IdenticalTo(expected));
}
SECTION("Shift left by more than 31 bits")
{
auto r = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Int32, 1);
r->allocateNow();
auto v = r->expression();
auto expr = v << a;
auto expected = Expression::literal(0, DataType::Int32);
CHECK_THAT(simplify(expr), IdenticalTo(expected));
}
SECTION("Shift left by more than 63 bits")
{
auto r = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Int64, 1);
r->allocateNow();
auto v = r->expression();
auto expr = v << e;
auto expected = Expression::literal(0, DataType::Int64);
CHECK_THAT(simplify(expr), IdenticalTo(expected));
}
}
TEST_CASE("LowerUnsignedArithmeticShiftR ExpressionTransformation works",
"[expression][expression-transformation]")
{
using namespace rocRoller;
auto context = TestContext::ForDefaultTarget();
auto a = Expression::literal(21);
SECTION("Unsigned ArithmeticShiftR should be lowered to LogicalShiftR")
{
auto r = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::UInt32, 1);
r->allocateNow();
auto v = r->expression();
auto expr = std::make_shared<rocRoller::Expression::Expression>(
rocRoller::Expression::ArithmeticShiftR{v, a});
CHECK_THAT(lowerUnsignedArithmeticShiftR(expr), IdenticalTo(logicalShiftR(v, a)));
}
SECTION("Signed ArithmeticShiftR should NOT be lowered to LogicalShiftR")
{
auto r = Register::Value::Placeholder(
context.get(), Register::Type::Vector, DataType::Int32, 1);
r->allocateNow();
auto v = r->expression();
auto expr = std::make_shared<rocRoller::Expression::Expression>(
rocRoller::Expression::ArithmeticShiftR{v, a});
CHECK_THAT(lowerUnsignedArithmeticShiftR(expr), IdenticalTo(expr));
}
}
TEST_CASE("SplitConcatenate ExpressionTransformation works",
"[expression][expression-transformation]")
{
using namespace rocRoller;
using namespace Expression;
auto context = TestContext::ForDefaultTarget();
auto r
= Register::Value::Placeholder(context.get(), Register::Type::Vector, DataType::UInt64, 1);
r->allocateNow();
auto v = r->expression();
SECTION("Split uint64_t literal into two Raw32 operands")
{
auto uint64Literal = literal(0x0123456789ABCDEFull, DataType::UInt64);
std::vector<ExpressionPtr> operands{uint64Literal, v};
auto concatExpr = Concatenate{{operands}, {DataType::None, PointerType::Buffer}};
Concatenate result = splitConcatenate(concatExpr);
std::vector<ExpressionPtr> expectedOperands{
literal(Raw32(0x89ABCDEF)), literal(Raw32(0x01234567)), v};
auto expectedExpr = Concatenate{{expectedOperands}, {DataType::None, PointerType::Buffer}};
CHECK_THAT(std::make_shared<rocRoller::Expression::Expression>(result),
IdenticalTo(std::make_shared<rocRoller::Expression::Expression>(expectedExpr)));
}
SECTION("Multiple uint64_t literals are all split")
{
auto uint64Literal1 = literal(0xFFFFFFFF00000000ull, DataType::UInt64);
auto uint64Literal2 = literal(0x00000000FFFFFFFFull, DataType::UInt64);
std::vector<ExpressionPtr> operands{uint64Literal1, uint64Literal2};
auto concatExpr = Concatenate{{operands}, {DataType::None, PointerType::Buffer}};
Concatenate result = splitConcatenate(concatExpr);
std::vector<ExpressionPtr> expectedOperands{literal(Raw32(0x00000000)),
literal(Raw32(0xFFFFFFFF)),
literal(Raw32(0xFFFFFFFF)),
literal(Raw32(0x00000000))};
auto expectedExpr = Concatenate{{expectedOperands}, {DataType::None, PointerType::Buffer}};
CHECK_THAT(std::make_shared<rocRoller::Expression::Expression>(result),
IdenticalTo(std::make_shared<rocRoller::Expression::Expression>(expectedExpr)));
}
}
TEST_CASE("BitfieldCombine expression and lowering", "[expression][expression-transformation]")
{
using namespace rocRoller;
auto context = TestContext::ForDefaultTarget();
Register::AllocationOptions allocOptions{.contiguousChunkWidth = Register::FULLY_CONTIGUOUS};
auto src = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt32, 1, allocOptions);
src->allocateNow();
auto srcExpr = src->expression();
auto dst = std::make_shared<Register::Value>(
context.get(), Register::Type::Vector, DataType::UInt32, 1, allocOptions);
dst->allocateNow();
auto dstExpr = dst->expression();
auto const srcOffset = 10u;
auto const dstOffset = 4u;
auto const width = 7u;
auto offsetDiff = Expression::literal(srcOffset - dstOffset);
SECTION("Lowering Basic")
{
auto bfc = std::make_shared<Expression::Expression>(
Expression::BitfieldCombine{srcExpr, dstExpr, "", srcOffset, dstOffset, width});
auto expected
= logicalShiftR(
(Expression::literal(Raw32(((1u << width) - 1u) << srcOffset)) & srcExpr),
offsetDiff)
| (Expression::literal(Raw32(~(((1u << width) - 1u) << dstOffset))) & dstExpr);
CHECK_THAT(lowerBitfieldCombine(bfc), IdenticalTo(expected));
}
SECTION("Lowering with srcIsZero")
{
auto bfc = std::make_shared<Expression::Expression>(
Expression::BitfieldCombine{srcExpr, dstExpr, "", srcOffset, dstOffset, width, true});
auto expected
= logicalShiftR(srcExpr, offsetDiff)
| (Expression::literal(Raw32(~(((1u << width) - 1u) << dstOffset))) & dstExpr);
CHECK_THAT(lowerBitfieldCombine(bfc), IdenticalTo(expected));
}
SECTION("Lowering with dstIsZero")
{
auto bfc = std::make_shared<Expression::Expression>(Expression::BitfieldCombine{
srcExpr, dstExpr, "", srcOffset, dstOffset, width, std::nullopt, true});
auto expected = logicalShiftR((Expression::literal(Raw32(((1u << width) - 1u) << srcOffset))
& srcExpr),
offsetDiff)
| dstExpr;
CHECK_THAT(lowerBitfieldCombine(bfc), IdenticalTo(expected));
}
SECTION("Lowering with srcIsZero & dstIsZero")
{
auto bfc = std::make_shared<Expression::Expression>(Expression::BitfieldCombine{
srcExpr, dstExpr, "", srcOffset, dstOffset, width, true, true});
auto expected = logicalShiftR(srcExpr, offsetDiff) | dstExpr;
CHECK_THAT(lowerBitfieldCombine(bfc), IdenticalTo(expected));
}
SECTION("Lowering with width=32 (full width)")
{
auto const fullWidth = 32u;
auto const srcOffset32 = 0u;
auto const dstOffset32 = 0u;
auto bfc = std::make_shared<Expression::Expression>(
Expression::BitfieldCombine{srcExpr, dstExpr, "", srcOffset32, dstOffset32, fullWidth});
// When width=32, srcMask should be 0xFFFFFFFF (all bits)
// and dstMask should be 0x00000000 (clear all bits)
auto expected = (Expression::literal(Raw32(0xFFFFFFFFu)) & srcExpr)
| (Expression::literal(Raw32(0x00000000u)) & dstExpr);
CHECK_THAT(lowerBitfieldCombine(bfc), IdenticalTo(expected));
}
}
TEST_CASE("Code gen with ConvertPropagation", "[expression][expression-transformation][codegen]")
{
using namespace rocRoller;
auto context = TestContext::ForDefaultTarget();
const auto srcDatatype = GENERATE(DataType::Int64, DataType::UInt64);
const auto dstDatatype = GENERATE(DataType::Int32, DataType::UInt32);
auto r64a
= std::make_shared<Register::Value>(context.get(), Register::Type::Vector, srcDatatype, 1);
r64a->allocateNow();
auto r64b
= std::make_shared<Register::Value>(context.get(), Register::Type::Vector, srcDatatype, 1);
r64b->allocateNow();
Register::ValuePtr d32;
auto kb = [&]() -> Generator<Instruction> {
co_yield Expression::generate(
d32, convert(dstDatatype, r64a->expression() + r64b->expression()), context.get());
};
context.get()->schedule(kb());
std::string expected;
if(DataTypeInfo::Get(dstDatatype).isSigned)
expected = R"(
v_add_i32 v4, v0, v2
)";
else
expected = R"(
v_add_u32 v4, v0, v2
)";
INFO(context.output());
CHECK(NormalizedSource(context.output()) == NormalizedSource(expected));
}