blob: e384387175583851a6bd187d72726880af9dd028 [file] [log] [blame]
// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "src/writer/spirv/spv_dump.h"
#include "src/writer/spirv/test_helper.h"
namespace tint {
namespace writer {
namespace spirv {
namespace {
using BuilderTest = TestHelper;
struct BinaryData {
ast::BinaryOp op;
std::string name;
};
inline std::ostream& operator<<(std::ostream& out, BinaryData data) {
out << data.op;
return out;
}
using BinaryArithSignedIntegerTest = TestParamHelper<BinaryData>;
TEST_P(BinaryArithSignedIntegerTest, Scalar) {
auto param = GetParam();
auto* lhs = Expr(3);
auto* rhs = Expr(4);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
%2 = OpConstant %1 3
%3 = OpConstant %1 4
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%4 = " + param.name + " %1 %2 %3\n");
}
TEST_P(BinaryArithSignedIntegerTest, Vector) {
auto param = GetParam();
// Skip ops that are illegal for this type
if (param.op == ast::BinaryOp::kAnd || param.op == ast::BinaryOp::kOr ||
param.op == ast::BinaryOp::kXor) {
return;
}
auto* lhs = vec3<i32>(1, 1, 1);
auto* rhs = vec3<i32>(1, 1, 1);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
%1 = OpTypeVector %2 3
%3 = OpConstant %2 1
%4 = OpConstantComposite %1 %3 %3 %3
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%5 = " + param.name + " %1 %4 %4\n");
}
TEST_P(BinaryArithSignedIntegerTest, Scalar_Loads) {
auto param = GetParam();
auto* var = Var("param", ty.i32());
auto* expr =
create<ast::BinaryExpression>(param.op, Expr("param"), Expr("param"));
WrapInFunction(var, expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_TRUE(b.GenerateFunctionVariable(var)) << b.error();
EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.error();
ASSERT_FALSE(b.has_error()) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%3 = OpTypeInt 32 1
%2 = OpTypePointer Function %3
%4 = OpConstantNull %3
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].variables()),
R"(%1 = OpVariable %2 Function %4
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%5 = OpLoad %3 %1
%6 = OpLoad %3 %1
%7 = )" + param.name +
R"( %3 %5 %6
)");
}
INSTANTIATE_TEST_SUITE_P(
BuilderTest,
BinaryArithSignedIntegerTest,
// NOTE: No left and right shift as they require u32 for rhs operand
testing::Values(BinaryData{ast::BinaryOp::kAdd, "OpIAdd"},
BinaryData{ast::BinaryOp::kAnd, "OpBitwiseAnd"},
BinaryData{ast::BinaryOp::kDivide, "OpSDiv"},
BinaryData{ast::BinaryOp::kModulo, "OpSMod"},
BinaryData{ast::BinaryOp::kMultiply, "OpIMul"},
BinaryData{ast::BinaryOp::kOr, "OpBitwiseOr"},
BinaryData{ast::BinaryOp::kSubtract, "OpISub"},
BinaryData{ast::BinaryOp::kXor, "OpBitwiseXor"}));
using BinaryArithUnsignedIntegerTest = TestParamHelper<BinaryData>;
TEST_P(BinaryArithUnsignedIntegerTest, Scalar) {
auto param = GetParam();
auto* lhs = Expr(3u);
auto* rhs = Expr(4u);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
%2 = OpConstant %1 3
%3 = OpConstant %1 4
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%4 = " + param.name + " %1 %2 %3\n");
}
TEST_P(BinaryArithUnsignedIntegerTest, Vector) {
auto param = GetParam();
// Skip ops that are illegal for this type
if (param.op == ast::BinaryOp::kAnd || param.op == ast::BinaryOp::kOr ||
param.op == ast::BinaryOp::kXor) {
return;
}
auto* lhs = vec3<u32>(1u, 1u, 1u);
auto* rhs = vec3<u32>(1u, 1u, 1u);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
%1 = OpTypeVector %2 3
%3 = OpConstant %2 1
%4 = OpConstantComposite %1 %3 %3 %3
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%5 = " + param.name + " %1 %4 %4\n");
}
INSTANTIATE_TEST_SUITE_P(
BuilderTest,
BinaryArithUnsignedIntegerTest,
testing::Values(BinaryData{ast::BinaryOp::kAdd, "OpIAdd"},
BinaryData{ast::BinaryOp::kAnd, "OpBitwiseAnd"},
BinaryData{ast::BinaryOp::kDivide, "OpUDiv"},
BinaryData{ast::BinaryOp::kModulo, "OpUMod"},
BinaryData{ast::BinaryOp::kMultiply, "OpIMul"},
BinaryData{ast::BinaryOp::kOr, "OpBitwiseOr"},
BinaryData{ast::BinaryOp::kShiftLeft, "OpShiftLeftLogical"},
BinaryData{ast::BinaryOp::kShiftRight,
"OpShiftRightLogical"},
BinaryData{ast::BinaryOp::kSubtract, "OpISub"},
BinaryData{ast::BinaryOp::kXor, "OpBitwiseXor"}));
using BinaryArithFloatTest = TestParamHelper<BinaryData>;
TEST_P(BinaryArithFloatTest, Scalar) {
auto param = GetParam();
auto* lhs = Expr(3.2f);
auto* rhs = Expr(4.5f);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
%2 = OpConstant %1 3.20000005
%3 = OpConstant %1 4.5
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%4 = " + param.name + " %1 %2 %3\n");
}
TEST_P(BinaryArithFloatTest, Vector) {
auto param = GetParam();
auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
%1 = OpTypeVector %2 3
%3 = OpConstant %2 1
%4 = OpConstantComposite %1 %3 %3 %3
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%5 = " + param.name + " %1 %4 %4\n");
}
INSTANTIATE_TEST_SUITE_P(
BuilderTest,
BinaryArithFloatTest,
testing::Values(BinaryData{ast::BinaryOp::kAdd, "OpFAdd"},
BinaryData{ast::BinaryOp::kDivide, "OpFDiv"},
BinaryData{ast::BinaryOp::kModulo, "OpFRem"},
BinaryData{ast::BinaryOp::kMultiply, "OpFMul"},
BinaryData{ast::BinaryOp::kSubtract, "OpFSub"}));
using BinaryOperatorBoolTest = TestParamHelper<BinaryData>;
TEST_P(BinaryOperatorBoolTest, Scalar) {
auto param = GetParam();
auto* lhs = Expr(true);
auto* rhs = Expr(false);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeBool
%2 = OpConstantTrue %1
%3 = OpConstantFalse %1
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%4 = " + param.name + " %1 %2 %3\n");
}
TEST_P(BinaryOperatorBoolTest, Vector) {
auto param = GetParam();
auto* lhs = vec3<bool>(false, true, false);
auto* rhs = vec3<bool>(true, false, true);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 7u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
%1 = OpTypeVector %2 3
%3 = OpConstantFalse %2
%4 = OpConstantTrue %2
%5 = OpConstantComposite %1 %3 %4 %3
%6 = OpConstantComposite %1 %4 %3 %4
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%7 = " + param.name + " %1 %5 %6\n");
}
INSTANTIATE_TEST_SUITE_P(
BuilderTest,
BinaryOperatorBoolTest,
testing::Values(BinaryData{ast::BinaryOp::kEqual, "OpLogicalEqual"},
BinaryData{ast::BinaryOp::kNotEqual, "OpLogicalNotEqual"},
BinaryData{ast::BinaryOp::kAnd, "OpLogicalAnd"},
BinaryData{ast::BinaryOp::kOr, "OpLogicalOr"}));
using BinaryCompareUnsignedIntegerTest = TestParamHelper<BinaryData>;
TEST_P(BinaryCompareUnsignedIntegerTest, Scalar) {
auto param = GetParam();
auto* lhs = Expr(3u);
auto* rhs = Expr(4u);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 0
%2 = OpConstant %1 3
%3 = OpConstant %1 4
%5 = OpTypeBool
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%4 = " + param.name + " %5 %2 %3\n");
}
TEST_P(BinaryCompareUnsignedIntegerTest, Vector) {
auto param = GetParam();
auto* lhs = vec3<u32>(1u, 1u, 1u);
auto* rhs = vec3<u32>(1u, 1u, 1u);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 0
%1 = OpTypeVector %2 3
%3 = OpConstant %2 1
%4 = OpConstantComposite %1 %3 %3 %3
%7 = OpTypeBool
%6 = OpTypeVector %7 3
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%5 = " + param.name + " %6 %4 %4\n");
}
INSTANTIATE_TEST_SUITE_P(
BuilderTest,
BinaryCompareUnsignedIntegerTest,
testing::Values(
BinaryData{ast::BinaryOp::kEqual, "OpIEqual"},
BinaryData{ast::BinaryOp::kGreaterThan, "OpUGreaterThan"},
BinaryData{ast::BinaryOp::kGreaterThanEqual, "OpUGreaterThanEqual"},
BinaryData{ast::BinaryOp::kLessThan, "OpULessThan"},
BinaryData{ast::BinaryOp::kLessThanEqual, "OpULessThanEqual"},
BinaryData{ast::BinaryOp::kNotEqual, "OpINotEqual"}));
using BinaryCompareSignedIntegerTest = TestParamHelper<BinaryData>;
TEST_P(BinaryCompareSignedIntegerTest, Scalar) {
auto param = GetParam();
auto* lhs = Expr(3);
auto* rhs = Expr(4);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeInt 32 1
%2 = OpConstant %1 3
%3 = OpConstant %1 4
%5 = OpTypeBool
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%4 = " + param.name + " %5 %2 %3\n");
}
TEST_P(BinaryCompareSignedIntegerTest, Vector) {
auto param = GetParam();
auto* lhs = vec3<i32>(1, 1, 1);
auto* rhs = vec3<i32>(1, 1, 1);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeInt 32 1
%1 = OpTypeVector %2 3
%3 = OpConstant %2 1
%4 = OpConstantComposite %1 %3 %3 %3
%7 = OpTypeBool
%6 = OpTypeVector %7 3
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%5 = " + param.name + " %6 %4 %4\n");
}
INSTANTIATE_TEST_SUITE_P(
BuilderTest,
BinaryCompareSignedIntegerTest,
testing::Values(
BinaryData{ast::BinaryOp::kEqual, "OpIEqual"},
BinaryData{ast::BinaryOp::kGreaterThan, "OpSGreaterThan"},
BinaryData{ast::BinaryOp::kGreaterThanEqual, "OpSGreaterThanEqual"},
BinaryData{ast::BinaryOp::kLessThan, "OpSLessThan"},
BinaryData{ast::BinaryOp::kLessThanEqual, "OpSLessThanEqual"},
BinaryData{ast::BinaryOp::kNotEqual, "OpINotEqual"}));
using BinaryCompareFloatTest = TestParamHelper<BinaryData>;
TEST_P(BinaryCompareFloatTest, Scalar) {
auto param = GetParam();
auto* lhs = Expr(3.2f);
auto* rhs = Expr(4.5f);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 4u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%1 = OpTypeFloat 32
%2 = OpConstant %1 3.20000005
%3 = OpConstant %1 4.5
%5 = OpTypeBool
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%4 = " + param.name + " %5 %2 %3\n");
}
TEST_P(BinaryCompareFloatTest, Vector) {
auto param = GetParam();
auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeFloat 32
%1 = OpTypeVector %2 3
%3 = OpConstant %2 1
%4 = OpConstantComposite %1 %3 %3 %3
%7 = OpTypeBool
%6 = OpTypeVector %7 3
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%5 = " + param.name + " %6 %4 %4\n");
}
INSTANTIATE_TEST_SUITE_P(
BuilderTest,
BinaryCompareFloatTest,
testing::Values(
BinaryData{ast::BinaryOp::kEqual, "OpFOrdEqual"},
BinaryData{ast::BinaryOp::kGreaterThan, "OpFOrdGreaterThan"},
BinaryData{ast::BinaryOp::kGreaterThanEqual, "OpFOrdGreaterThanEqual"},
BinaryData{ast::BinaryOp::kLessThan, "OpFOrdLessThan"},
BinaryData{ast::BinaryOp::kLessThanEqual, "OpFOrdLessThanEqual"},
BinaryData{ast::BinaryOp::kNotEqual, "OpFOrdNotEqual"}));
TEST_F(BuilderTest, Binary_Multiply_VectorScalar) {
auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
auto* rhs = Expr(1.f);
auto* expr =
create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()),
R"(%2 = OpTypeFloat 32
%1 = OpTypeVector %2 3
%3 = OpConstant %2 1
%4 = OpConstantComposite %1 %3 %3 %3
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%5 = OpVectorTimesScalar %1 %4 %3\n");
}
TEST_F(BuilderTest, Binary_Multiply_ScalarVector) {
auto* lhs = Expr(1.f);
auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
auto* expr =
create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
EXPECT_EQ(b.GenerateBinaryExpression(expr), 5u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()),
R"(%1 = OpTypeFloat 32
%2 = OpConstant %1 1
%3 = OpTypeVector %1 3
%4 = OpConstantComposite %3 %2 %2 %2
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
"%5 = OpVectorTimesScalar %3 %4 %2\n");
}
TEST_F(BuilderTest, Binary_Multiply_MatrixScalar) {
auto* var = Var("mat", ty.mat3x3<f32>());
auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
Expr("mat"), Expr(1.f));
WrapInFunction(var, expr);
spirv::Builder& b = Build();
b.push_function(Function{});
ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()),
R"(%5 = OpTypeFloat 32
%4 = OpTypeVector %5 3
%3 = OpTypeMatrix %4 3
%2 = OpTypePointer Function %3
%1 = OpVariable %2 Function
%7 = OpConstant %5 1
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%6 = OpLoad %3 %1
%8 = OpMatrixTimesScalar %3 %6 %7
)");
}
TEST_F(BuilderTest, Binary_Multiply_ScalarMatrix) {
auto* var = Var("mat", ty.mat3x3<f32>());
auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
Expr(1.f), Expr("mat"));
WrapInFunction(var, expr);
spirv::Builder& b = Build();
b.push_function(Function{});
ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()),
R"(%5 = OpTypeFloat 32
%4 = OpTypeVector %5 3
%3 = OpTypeMatrix %4 3
%2 = OpTypePointer Function %3
%1 = OpVariable %2 Function
%6 = OpConstant %5 1
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%7 = OpLoad %3 %1
%8 = OpMatrixTimesScalar %3 %7 %6
)");
}
TEST_F(BuilderTest, Binary_Multiply_MatrixVector) {
auto* var = Var("mat", ty.mat3x3<f32>());
auto* rhs = vec3<f32>(1.f, 1.f, 1.f);
auto* expr =
create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, Expr("mat"), rhs);
WrapInFunction(var, expr);
spirv::Builder& b = Build();
b.push_function(Function{});
ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()),
R"(%5 = OpTypeFloat 32
%4 = OpTypeVector %5 3
%3 = OpTypeMatrix %4 3
%2 = OpTypePointer Function %3
%1 = OpVariable %2 Function
%7 = OpConstant %5 1
%8 = OpConstantComposite %4 %7 %7 %7
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%6 = OpLoad %3 %1
%9 = OpMatrixTimesVector %4 %6 %8
)");
}
TEST_F(BuilderTest, Binary_Multiply_VectorMatrix) {
auto* var = Var("mat", ty.mat3x3<f32>());
auto* lhs = vec3<f32>(1.f, 1.f, 1.f);
auto* expr =
create<ast::BinaryExpression>(ast::BinaryOp::kMultiply, lhs, Expr("mat"));
WrapInFunction(var, expr);
spirv::Builder& b = Build();
b.push_function(Function{});
ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
EXPECT_EQ(b.GenerateBinaryExpression(expr), 9u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()),
R"(%5 = OpTypeFloat 32
%4 = OpTypeVector %5 3
%3 = OpTypeMatrix %4 3
%2 = OpTypePointer Function %3
%1 = OpVariable %2 Function
%6 = OpConstant %5 1
%7 = OpConstantComposite %4 %6 %6 %6
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%8 = OpLoad %3 %1
%9 = OpVectorTimesMatrix %4 %7 %8
)");
}
TEST_F(BuilderTest, Binary_Multiply_MatrixMatrix) {
auto* var = Var("mat", ty.mat3x3<f32>());
auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kMultiply,
Expr("mat"), Expr("mat"));
WrapInFunction(var, expr);
spirv::Builder& b = Build();
b.push_function(Function{});
ASSERT_TRUE(b.GenerateGlobalVariable(var)) << b.error();
EXPECT_EQ(b.GenerateBinaryExpression(expr), 8u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()),
R"(%5 = OpTypeFloat 32
%4 = OpTypeVector %5 3
%3 = OpTypeMatrix %4 3
%2 = OpTypePointer Function %3
%1 = OpVariable %2 Function
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%6 = OpLoad %3 %1
%7 = OpLoad %3 %1
%8 = OpMatrixTimesMatrix %3 %6 %7
)");
}
TEST_F(BuilderTest, Binary_LogicalAnd) {
auto* lhs =
create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(1), Expr(2));
auto* rhs =
create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(3), Expr(4));
auto* expr =
create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
b.GenerateLabel(b.next_id());
EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()),
R"(%2 = OpTypeInt 32 1
%3 = OpConstant %2 1
%4 = OpConstant %2 2
%6 = OpTypeBool
%9 = OpConstant %2 3
%10 = OpConstant %2 4
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%1 = OpLabel
%5 = OpIEqual %6 %3 %4
OpSelectionMerge %7 None
OpBranchConditional %5 %8 %7
%8 = OpLabel
%11 = OpIEqual %6 %9 %10
OpBranch %7
%7 = OpLabel
%12 = OpPhi %6 %5 %1 %11 %8
)");
}
TEST_F(BuilderTest, Binary_LogicalAnd_WithLoads) {
auto* a_var =
Global("a", ty.bool_(), ast::StorageClass::kPrivate, Expr(true));
auto* b_var =
Global("b", ty.bool_(), ast::StorageClass::kPrivate, Expr(false));
auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
Expr("a"), Expr("b"));
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
b.GenerateLabel(b.next_id());
ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.error();
ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.error();
EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
%3 = OpConstantTrue %2
%5 = OpTypePointer Private %2
%4 = OpVariable %5 Private %3
%6 = OpConstantFalse %2
%7 = OpVariable %5 Private %6
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%1 = OpLabel
%8 = OpLoad %2 %4
OpSelectionMerge %9 None
OpBranchConditional %8 %10 %9
%10 = OpLabel
%11 = OpLoad %2 %7
OpBranch %9
%9 = OpLabel
%12 = OpPhi %2 %8 %1 %11 %10
)");
}
TEST_F(BuilderTest, Binary_logicalOr_Nested_LogicalAnd) {
// Test an expression like
// a || (b && c)
// From: crbug.com/tint/355
auto* logical_and_expr = create<ast::BinaryExpression>(
ast::BinaryOp::kLogicalAnd, Expr(true), Expr(false));
auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
Expr(true), logical_and_expr);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
b.GenerateLabel(b.next_id());
EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
%3 = OpConstantTrue %2
%8 = OpConstantFalse %2
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%1 = OpLabel
OpSelectionMerge %4 None
OpBranchConditional %3 %4 %5
%5 = OpLabel
OpSelectionMerge %6 None
OpBranchConditional %3 %7 %6
%7 = OpLabel
OpBranch %6
%6 = OpLabel
%9 = OpPhi %2 %3 %5 %8 %7
OpBranch %4
%4 = OpLabel
%10 = OpPhi %2 %3 %1 %9 %6
)");
}
TEST_F(BuilderTest, Binary_logicalAnd_Nested_LogicalOr) {
// Test an expression like
// a && (b || c)
// From: crbug.com/tint/355
auto* logical_or_expr = create<ast::BinaryExpression>(
ast::BinaryOp::kLogicalOr, Expr(true), Expr(false));
auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalAnd,
Expr(true), logical_or_expr);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
b.GenerateLabel(b.next_id());
EXPECT_EQ(b.GenerateBinaryExpression(expr), 10u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
%3 = OpConstantTrue %2
%8 = OpConstantFalse %2
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%1 = OpLabel
OpSelectionMerge %4 None
OpBranchConditional %3 %5 %4
%5 = OpLabel
OpSelectionMerge %6 None
OpBranchConditional %3 %6 %7
%7 = OpLabel
OpBranch %6
%6 = OpLabel
%9 = OpPhi %2 %3 %5 %8 %7
OpBranch %4
%4 = OpLabel
%10 = OpPhi %2 %3 %1 %9 %6
)");
}
TEST_F(BuilderTest, Binary_LogicalOr) {
auto* lhs =
create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(1), Expr(2));
auto* rhs =
create<ast::BinaryExpression>(ast::BinaryOp::kEqual, Expr(3), Expr(4));
auto* expr =
create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
b.GenerateLabel(b.next_id());
EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()),
R"(%2 = OpTypeInt 32 1
%3 = OpConstant %2 1
%4 = OpConstant %2 2
%6 = OpTypeBool
%9 = OpConstant %2 3
%10 = OpConstant %2 4
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%1 = OpLabel
%5 = OpIEqual %6 %3 %4
OpSelectionMerge %7 None
OpBranchConditional %5 %7 %8
%8 = OpLabel
%11 = OpIEqual %6 %9 %10
OpBranch %7
%7 = OpLabel
%12 = OpPhi %6 %5 %1 %11 %8
)");
}
TEST_F(BuilderTest, Binary_LogicalOr_WithLoads) {
auto* a_var =
Global("a", ty.bool_(), ast::StorageClass::kPrivate, Expr(true));
auto* b_var =
Global("b", ty.bool_(), ast::StorageClass::kPrivate, Expr(false));
auto* expr = create<ast::BinaryExpression>(ast::BinaryOp::kLogicalOr,
Expr("a"), Expr("b"));
WrapInFunction(expr);
spirv::Builder& b = Build();
b.push_function(Function{});
b.GenerateLabel(b.next_id());
ASSERT_TRUE(b.GenerateGlobalVariable(a_var)) << b.error();
ASSERT_TRUE(b.GenerateGlobalVariable(b_var)) << b.error();
EXPECT_EQ(b.GenerateBinaryExpression(expr), 12u) << b.error();
EXPECT_EQ(DumpInstructions(b.types()), R"(%2 = OpTypeBool
%3 = OpConstantTrue %2
%5 = OpTypePointer Private %2
%4 = OpVariable %5 Private %3
%6 = OpConstantFalse %2
%7 = OpVariable %5 Private %6
)");
EXPECT_EQ(DumpInstructions(b.functions()[0].instructions()),
R"(%1 = OpLabel
%8 = OpLoad %2 %4
OpSelectionMerge %9 None
OpBranchConditional %8 %9 %10
%10 = OpLabel
%11 = OpLoad %2 %7
OpBranch %9
%9 = OpLabel
%12 = OpPhi %2 %8 %1 %11 %10
)");
}
namespace BinaryArithVectorScalar {
enum class Type { f32, i32, u32 };
static ast::Expression* MakeVectorExpr(ProgramBuilder* builder, Type type) {
switch (type) {
case Type::f32:
return builder->vec3<ProgramBuilder::f32>(1.f, 1.f, 1.f);
case Type::i32:
return builder->vec3<ProgramBuilder::i32>(1, 1, 1);
case Type::u32:
return builder->vec3<ProgramBuilder::u32>(1u, 1u, 1u);
}
return nullptr;
}
static ast::Expression* MakeScalarExpr(ProgramBuilder* builder, Type type) {
switch (type) {
case Type::f32:
return builder->Expr(1.f);
case Type::i32:
return builder->Expr(1);
case Type::u32:
return builder->Expr(1u);
}
return nullptr;
}
static std::string OpTypeDecl(Type type) {
switch (type) {
case Type::f32:
return "OpTypeFloat 32";
case Type::i32:
return "OpTypeInt 32 1";
case Type::u32:
return "OpTypeInt 32 0";
}
return {};
}
struct Param {
Type type;
ast::BinaryOp op;
std::string name;
};
using BinaryArithVectorScalarTest = TestParamHelper<Param>;
TEST_P(BinaryArithVectorScalarTest, VectorScalar) {
auto& param = GetParam();
ast::Expression* lhs = MakeVectorExpr(this, param.type);
ast::Expression* rhs = MakeScalarExpr(this, param.type);
std::string op_type_decl = OpTypeDecl(param.type);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
ASSERT_TRUE(b.Build()) << b.error();
EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %3 "test_function"
OpExecutionMode %3 LocalSize 1 1 1
OpName %3 "test_function"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%6 = )" + op_type_decl + R"(
%5 = OpTypeVector %6 3
%7 = OpConstant %6 1
%8 = OpConstantComposite %5 %7 %7 %7
%11 = OpTypePointer Function %5
%12 = OpConstantNull %5
%3 = OpFunction %2 None %1
%4 = OpLabel
%10 = OpVariable %11 Function %12
%13 = OpCompositeConstruct %5 %7 %7 %7
%9 = )" + param.name + R"( %5 %8 %13
OpReturn
OpFunctionEnd
)");
Validate(b);
}
TEST_P(BinaryArithVectorScalarTest, ScalarVector) {
auto& param = GetParam();
ast::Expression* lhs = MakeScalarExpr(this, param.type);
ast::Expression* rhs = MakeVectorExpr(this, param.type);
std::string op_type_decl = OpTypeDecl(param.type);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
ASSERT_TRUE(b.Build()) << b.error();
EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %3 "test_function"
OpExecutionMode %3 LocalSize 1 1 1
OpName %3 "test_function"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%5 = )" + op_type_decl + R"(
%6 = OpConstant %5 1
%7 = OpTypeVector %5 3
%8 = OpConstantComposite %7 %6 %6 %6
%11 = OpTypePointer Function %7
%12 = OpConstantNull %7
%3 = OpFunction %2 None %1
%4 = OpLabel
%10 = OpVariable %11 Function %12
%13 = OpCompositeConstruct %7 %6 %6 %6
%9 = )" + param.name + R"( %7 %13 %8
OpReturn
OpFunctionEnd
)");
Validate(b);
}
INSTANTIATE_TEST_SUITE_P(
BuilderTest,
BinaryArithVectorScalarTest,
testing::Values(Param{Type::f32, ast::BinaryOp::kAdd, "OpFAdd"},
Param{Type::f32, ast::BinaryOp::kDivide, "OpFDiv"},
// NOTE: Modulo not allowed on mixed float scalar-vector
// Param{Type::f32, ast::BinaryOp::kModulo, "OpFMod"},
// NOTE: We test f32 multiplies separately as we emit
// OpVectorTimesScalar for this case
// Param{Type::i32, ast::BinaryOp::kMultiply, "OpIMul"},
Param{Type::f32, ast::BinaryOp::kSubtract, "OpFSub"},
Param{Type::i32, ast::BinaryOp::kAdd, "OpIAdd"},
Param{Type::i32, ast::BinaryOp::kDivide, "OpSDiv"},
Param{Type::i32, ast::BinaryOp::kModulo, "OpSMod"},
Param{Type::i32, ast::BinaryOp::kMultiply, "OpIMul"},
Param{Type::i32, ast::BinaryOp::kSubtract, "OpISub"},
Param{Type::u32, ast::BinaryOp::kAdd, "OpIAdd"},
Param{Type::u32, ast::BinaryOp::kDivide, "OpUDiv"},
Param{Type::u32, ast::BinaryOp::kModulo, "OpUMod"},
Param{Type::u32, ast::BinaryOp::kMultiply, "OpIMul"},
Param{Type::u32, ast::BinaryOp::kSubtract, "OpISub"}));
using BinaryArithVectorScalarMultiplyTest = TestParamHelper<Param>;
TEST_P(BinaryArithVectorScalarMultiplyTest, VectorScalar) {
auto& param = GetParam();
ast::Expression* lhs = MakeVectorExpr(this, param.type);
ast::Expression* rhs = MakeScalarExpr(this, param.type);
std::string op_type_decl = OpTypeDecl(param.type);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
ASSERT_TRUE(b.Build()) << b.error();
EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %3 "test_function"
OpExecutionMode %3 LocalSize 1 1 1
OpName %3 "test_function"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%6 = )" + op_type_decl + R"(
%5 = OpTypeVector %6 3
%7 = OpConstant %6 1
%8 = OpConstantComposite %5 %7 %7 %7
%3 = OpFunction %2 None %1
%4 = OpLabel
%9 = OpVectorTimesScalar %5 %8 %7
OpReturn
OpFunctionEnd
)");
Validate(b);
}
TEST_P(BinaryArithVectorScalarMultiplyTest, ScalarVector) {
auto& param = GetParam();
ast::Expression* lhs = MakeScalarExpr(this, param.type);
ast::Expression* rhs = MakeVectorExpr(this, param.type);
std::string op_type_decl = OpTypeDecl(param.type);
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
ASSERT_TRUE(b.Build()) << b.error();
EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %3 "test_function"
OpExecutionMode %3 LocalSize 1 1 1
OpName %3 "test_function"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%5 = )" + op_type_decl + R"(
%6 = OpConstant %5 1
%7 = OpTypeVector %5 3
%8 = OpConstantComposite %7 %6 %6 %6
%3 = OpFunction %2 None %1
%4 = OpLabel
%9 = OpVectorTimesScalar %7 %8 %6
OpReturn
OpFunctionEnd
)");
Validate(b);
}
INSTANTIATE_TEST_SUITE_P(BuilderTest,
BinaryArithVectorScalarMultiplyTest,
testing::Values(Param{
Type::f32, ast::BinaryOp::kMultiply, "OpFMul"}));
} // namespace BinaryArithVectorScalar
namespace BinaryArithMatrixMatrix {
struct Param {
ast::BinaryOp op;
std::string name;
};
using BinaryArithMatrixMatrix = TestParamHelper<Param>;
TEST_P(BinaryArithMatrixMatrix, AddOrSubtract) {
auto& param = GetParam();
ast::Expression* lhs = mat3x4<f32>();
ast::Expression* rhs = mat3x4<f32>();
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
ASSERT_TRUE(b.Build()) << b.error();
EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %3 "test_function"
OpExecutionMode %3 LocalSize 1 1 1
OpName %3 "test_function"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%7 = OpTypeFloat 32
%6 = OpTypeVector %7 4
%5 = OpTypeMatrix %6 3
%8 = OpConstantNull %5
%3 = OpFunction %2 None %1
%4 = OpLabel
%10 = OpCompositeExtract %6 %8 0
%11 = OpCompositeExtract %6 %8 0
%12 = )" + param.name + R"( %6 %10 %11
%13 = OpCompositeExtract %6 %8 1
%14 = OpCompositeExtract %6 %8 1
%15 = )" + param.name + R"( %6 %13 %14
%16 = OpCompositeExtract %6 %8 2
%17 = OpCompositeExtract %6 %8 2
%18 = )" + param.name + R"( %6 %16 %17
%19 = OpCompositeConstruct %5 %12 %15 %18
OpReturn
OpFunctionEnd
)");
Validate(b);
}
INSTANTIATE_TEST_SUITE_P( //
BuilderTest,
BinaryArithMatrixMatrix,
testing::Values(Param{ast::BinaryOp::kAdd, "OpFAdd"},
Param{ast::BinaryOp::kSubtract, "OpFSub"}));
using BinaryArithMatrixMatrixMultiply = TestParamHelper<Param>;
TEST_P(BinaryArithMatrixMatrixMultiply, Multiply) {
auto& param = GetParam();
ast::Expression* lhs = mat3x4<f32>();
ast::Expression* rhs = mat4x3<f32>();
auto* expr = create<ast::BinaryExpression>(param.op, lhs, rhs);
WrapInFunction(expr);
spirv::Builder& b = Build();
ASSERT_TRUE(b.Build()) << b.error();
EXPECT_EQ(DumpBuilder(b), R"(OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %3 "test_function"
OpExecutionMode %3 LocalSize 1 1 1
OpName %3 "test_function"
%2 = OpTypeVoid
%1 = OpTypeFunction %2
%7 = OpTypeFloat 32
%6 = OpTypeVector %7 4
%5 = OpTypeMatrix %6 3
%8 = OpConstantNull %5
%10 = OpTypeVector %7 3
%9 = OpTypeMatrix %10 4
%11 = OpConstantNull %9
%13 = OpTypeMatrix %6 4
%3 = OpFunction %2 None %1
%4 = OpLabel
%12 = OpMatrixTimesMatrix %13 %8 %11
OpReturn
OpFunctionEnd
)");
Validate(b);
}
INSTANTIATE_TEST_SUITE_P( //
BuilderTest,
BinaryArithMatrixMatrixMultiply,
testing::Values(Param{ast::BinaryOp::kMultiply, "OpFMul"}));
} // namespace BinaryArithMatrixMatrix
} // namespace
} // namespace spirv
} // namespace writer
} // namespace tint