blob: e2e1a02ff15f2a012c65d1b90df18fbe7feca377 [file] [log] [blame]
// Copyright 2022 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/tint/sem/materialize.h"
#include "src/tint/resolver/resolver.h"
#include "src/tint/resolver/resolver_test_helper.h"
#include "src/tint/sem/test_helper.h"
#include "gmock/gmock.h"
using namespace tint::number_suffixes; // NOLINT
namespace tint::resolver {
namespace {
using AFloatV = builder::vec<3, AFloat>;
using AFloatM = builder::mat<3, 2, AFloat>;
using AIntV = builder::vec<3, AInt>;
using f32V = builder::vec<3, f32>;
using f16V = builder::vec<3, f16>;
using i32V = builder::vec<3, i32>;
using u32V = builder::vec<3, u32>;
using f32M = builder::mat<3, 2, f32>;
////////////////////////////////////////////////////////////////////////////////
// MaterializeTests
////////////////////////////////////////////////////////////////////////////////
namespace MaterializeTests {
// How should the materialization occur?
enum class Method {
// var a : T = literal;
kVar,
// let a : T = literal;
kLet,
// fn F(v : T) {}
// fn x() {
// F(literal);
// }
kFnArg,
// min(target_expr, literal);
kBuiltinArg,
// fn F() : T {
// return literal;
// }
kReturn,
// array<T, 1>(literal);
kArray,
// struct S {
// v : T
// };
// fn x() {
// _ = S(literal)
// }
kStruct,
// target_expr + literal
kBinaryOp,
// switch (literal) {
// case target_expr: {}
// default: {}
// }
kSwitchCond,
// switch (target_expr) {
// case literal: {}
// default: {}
// }
kSwitchCase,
// switch (literal) {
// case 123: {}
// case target_expr: {}
// default: {}
// }
kSwitchCondWithAbstractCase,
// switch (target_expr) {
// case 123: {}
// case literal: {}
// default: {}
// }
kSwitchCaseWithAbstractCase,
};
static std::ostream& operator<<(std::ostream& o, Method m) {
switch (m) {
case Method::kVar:
return o << "var";
case Method::kLet:
return o << "let";
case Method::kFnArg:
return o << "fn-arg";
case Method::kBuiltinArg:
return o << "builtin-arg";
case Method::kReturn:
return o << "return";
case Method::kArray:
return o << "array";
case Method::kStruct:
return o << "struct";
case Method::kBinaryOp:
return o << "binary-op";
case Method::kSwitchCond:
return o << "switch-cond";
case Method::kSwitchCase:
return o << "switch-case";
case Method::kSwitchCondWithAbstractCase:
return o << "switch-cond-with-abstract";
case Method::kSwitchCaseWithAbstractCase:
return o << "switch-case-with-abstract";
}
return o << "<unknown>";
}
struct Data {
std::string target_type_name;
builder::ast_type_func_ptr target_ast_ty;
builder::sem_type_func_ptr target_sem_ty;
builder::ast_expr_func_ptr target_expr;
std::string literal_type_name;
builder::ast_expr_func_ptr literal_value;
std::variant<AInt, AFloat> materialized_value;
};
template <typename TARGET_TYPE, typename LITERAL_TYPE, typename MATERIALIZED_TYPE = AInt>
Data Types(MATERIALIZED_TYPE materialized_value = 0_a) {
return {
builder::DataType<TARGET_TYPE>::Name(), //
builder::DataType<TARGET_TYPE>::AST, //
builder::DataType<TARGET_TYPE>::Sem, //
builder::DataType<TARGET_TYPE>::Expr, //
builder::DataType<LITERAL_TYPE>::Name(), //
builder::DataType<LITERAL_TYPE>::Expr, //
materialized_value,
};
}
static std::ostream& operator<<(std::ostream& o, const Data& c) {
return o << "[" << c.target_type_name << " <- " << c.literal_type_name << "]";
}
enum class Expectation {
kMaterialize,
kNoMaterialize,
kInvalidCast,
};
static std::ostream& operator<<(std::ostream& o, Expectation m) {
switch (m) {
case Expectation::kMaterialize:
return o << "pass";
case Expectation::kNoMaterialize:
return o << "no-materialize";
case Expectation::kInvalidCast:
return o << "invalid-cast";
}
return o << "<unknown>";
}
using MaterializeAbstractNumeric =
resolver::ResolverTestWithParam<std::tuple<Expectation, Method, Data>>;
TEST_P(MaterializeAbstractNumeric, Test) {
// Once F16 is properly supported, we'll need to enable this:
// Enable(ast::Extension::kF16);
const auto& param = GetParam();
const auto& expectation = std::get<0>(param);
const auto& method = std::get<1>(param);
const auto& data = std::get<2>(param);
auto target_ty = [&] { return data.target_ast_ty(*this); };
auto target_expr = [&] { return data.target_expr(*this, 42); };
auto* literal = data.literal_value(*this, 1);
switch (method) {
case Method::kVar:
WrapInFunction(Decl(Var("a", target_ty(), literal)));
break;
case Method::kLet:
WrapInFunction(Decl(Let("a", target_ty(), literal)));
break;
case Method::kFnArg:
Func("F", {Param("P", target_ty())}, ty.void_(), {});
WrapInFunction(CallStmt(Call("F", literal)));
break;
case Method::kBuiltinArg:
WrapInFunction(CallStmt(Call("min", target_expr(), literal)));
break;
case Method::kReturn:
Func("F", {}, target_ty(), {Return(literal)});
break;
case Method::kArray:
WrapInFunction(Construct(ty.array(target_ty(), 1_i), literal));
break;
case Method::kStruct:
Structure("S", {Member("v", target_ty())});
WrapInFunction(Construct(ty.type_name("S"), literal));
break;
case Method::kBinaryOp:
WrapInFunction(Add(target_expr(), literal));
break;
case Method::kSwitchCond:
WrapInFunction(Switch(literal, //
Case(target_expr()->As<ast::IntLiteralExpression>()), //
DefaultCase()));
break;
case Method::kSwitchCase:
WrapInFunction(Switch(target_expr(), //
Case(literal->As<ast::IntLiteralExpression>()), //
DefaultCase()));
break;
case Method::kSwitchCondWithAbstractCase:
WrapInFunction(Switch(literal, //
Case(Expr(123_a)), //
Case(target_expr()->As<ast::IntLiteralExpression>()), //
DefaultCase()));
break;
case Method::kSwitchCaseWithAbstractCase:
WrapInFunction(Switch(target_expr(), //
Case(Expr(123_a)), //
Case(literal->As<ast::IntLiteralExpression>()), //
DefaultCase()));
break;
}
auto check_types_and_values = [&](const sem::Expression* expr) {
auto* target_sem_ty = data.target_sem_ty(*this);
EXPECT_TYPE(expr->Type(), target_sem_ty);
EXPECT_TYPE(expr->ConstantValue().Type(), target_sem_ty);
uint32_t num_elems = 0;
const sem::Type* target_sem_el_ty = sem::Type::ElementOf(target_sem_ty, &num_elems);
EXPECT_TYPE(expr->ConstantValue().ElementType(), target_sem_el_ty);
std::visit(
[&](auto&& v) {
EXPECT_EQ(expr->ConstantValue().Elements(), sem::Constant::Scalars(num_elems, {v}));
},
data.materialized_value);
};
switch (expectation) {
case Expectation::kMaterialize: {
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* materialize = Sem().Get<sem::Materialize>(literal);
ASSERT_NE(materialize, nullptr);
check_types_and_values(materialize);
break;
}
case Expectation::kNoMaterialize: {
ASSERT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(literal);
ASSERT_NE(sem, nullptr);
EXPECT_FALSE(sem->Is<sem::Materialize>());
check_types_and_values(sem);
break;
}
case Expectation::kInvalidCast: {
ASSERT_FALSE(r()->Resolve());
std::string expect;
switch (method) {
case Method::kBuiltinArg:
expect = "error: no matching call to min(" + data.target_type_name + ", " +
data.literal_type_name + ")";
break;
case Method::kBinaryOp:
expect = "error: no matching overload for operator + (" +
data.target_type_name + ", " + data.literal_type_name + ")";
break;
default:
expect = "error: cannot convert value of type '" + data.literal_type_name +
"' to type '" + data.target_type_name + "'";
break;
}
EXPECT_THAT(r()->error(), testing::StartsWith(expect));
break;
}
}
}
// TODO(crbug.com/tint/1504): Test for abstract-numeric values not fitting in materialized types.
INSTANTIATE_TEST_SUITE_P(MaterializeScalar,
MaterializeAbstractNumeric, //
testing::Combine(testing::Values(Expectation::kMaterialize), //
testing::Values(Method::kLet, //
Method::kVar, //
Method::kFnArg, //
Method::kBuiltinArg, //
Method::kReturn, //
Method::kArray, //
Method::kStruct, //
Method::kBinaryOp), //
testing::Values(Types<i32, AInt>(1_a), //
Types<u32, AInt>(1_a), //
Types<f32, AFloat>(1.0_a) //
/* Types<f16, AFloat>(1.0_a), */ //
/* Types<f16, AFloat>(1.0_a), */)));
INSTANTIATE_TEST_SUITE_P(MaterializeVector,
MaterializeAbstractNumeric, //
testing::Combine(testing::Values(Expectation::kMaterialize), //
testing::Values(Method::kLet, //
Method::kVar, //
Method::kFnArg, //
Method::kBuiltinArg, //
Method::kReturn, //
Method::kArray, //
Method::kStruct, //
Method::kBinaryOp), //
testing::Values(Types<i32V, AIntV>(1_a), //
Types<u32V, AIntV>(1_a), //
Types<f32V, AFloatV>(1.0_a) //
/* Types<f16V, AFloatV>(1.0_a), */ //
/* Types<f16V, AFloatV>(1.0_a), */)));
INSTANTIATE_TEST_SUITE_P(MaterializeMatrix,
MaterializeAbstractNumeric, //
testing::Combine(testing::Values(Expectation::kMaterialize), //
testing::Values(Method::kLet, //
Method::kVar, //
Method::kFnArg, //
Method::kReturn, //
Method::kArray, //
Method::kStruct, //
Method::kBinaryOp), //
testing::Values(Types<f32M, AFloatM>(1.0_a) //
/* Types<f16V, AFloatM>(1.0_a), */ //
)));
INSTANTIATE_TEST_SUITE_P(MaterializeSwitch,
MaterializeAbstractNumeric, //
testing::Combine(testing::Values(Expectation::kMaterialize), //
testing::Values(Method::kSwitchCond, //
Method::kSwitchCase, //
Method::kSwitchCondWithAbstractCase, //
Method::kSwitchCaseWithAbstractCase), //
testing::Values(Types<i32, AInt>(1_a), //
Types<u32, AInt>(1_a))));
// TODO(crbug.com/tint/1504): Enable once we have abstract overloads of builtins / binary ops.
INSTANTIATE_TEST_SUITE_P(DISABLED_NoMaterialize,
MaterializeAbstractNumeric, //
testing::Combine(testing::Values(Expectation::kNoMaterialize), //
testing::Values(Method::kBuiltinArg, //
Method::kBinaryOp), //
testing::Values(Types<AInt, AInt>(1_a), //
Types<AFloat, AFloat>(1.0_a), //
Types<AIntV, AIntV>(1_a), //
Types<AFloatV, AFloatV>(1.0_a), //
Types<AFloatM, AFloatM>(1.0_a))));
INSTANTIATE_TEST_SUITE_P(InvalidCast,
MaterializeAbstractNumeric, //
testing::Combine(testing::Values(Expectation::kInvalidCast), //
testing::Values(Method::kLet, //
Method::kVar, //
Method::kFnArg, //
Method::kBuiltinArg, //
Method::kReturn, //
Method::kArray, //
Method::kStruct, //
Method::kBinaryOp), //
testing::Values(Types<i32, AFloat>(), //
Types<u32, AFloat>(), //
Types<i32V, AFloatV>(), //
Types<u32V, AFloatV>())));
} // namespace MaterializeTests
} // namespace
} // namespace tint::resolver