tint: Implement abstract-numeric materialization
Implement materialization of abstract-numeric typed expressions to concrete types. TODO: Validation to ensure that the abstract-numeric values actually fit in their materialized types. Bug: tint:1504 Change-Id: I72b3a6a8801d872a4c4dfb85741073a05847ad48 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/91028 Reviewed-by: David Neto <dneto@google.com> Reviewed-by: Dan Sinclair <dsinclair@chromium.org> Commit-Queue: Ben Clayton <bclayton@google.com> Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Ben Clayton <bclayton@chromium.org>
This commit is contained in:
parent
575b27512e
commit
932418ef46
|
@ -410,6 +410,7 @@ libtint_source_set("libtint_core_all_src") {
|
||||||
"sem/if_statement.h",
|
"sem/if_statement.h",
|
||||||
"sem/info.h",
|
"sem/info.h",
|
||||||
"sem/loop_statement.h",
|
"sem/loop_statement.h",
|
||||||
|
"sem/materialize.h",
|
||||||
"sem/matrix.h",
|
"sem/matrix.h",
|
||||||
"sem/module.h",
|
"sem/module.h",
|
||||||
"sem/multisampled_texture.h",
|
"sem/multisampled_texture.h",
|
||||||
|
|
|
@ -774,6 +774,7 @@ if(TINT_BUILD_TESTS)
|
||||||
resolver/intrinsic_table_test.cc
|
resolver/intrinsic_table_test.cc
|
||||||
resolver/is_host_shareable_test.cc
|
resolver/is_host_shareable_test.cc
|
||||||
resolver/is_storeable_test.cc
|
resolver/is_storeable_test.cc
|
||||||
|
resolver/materialize_test.cc
|
||||||
resolver/pipeline_overridable_constant_test.cc
|
resolver/pipeline_overridable_constant_test.cc
|
||||||
resolver/ptr_ref_test.cc
|
resolver/ptr_ref_test.cc
|
||||||
resolver/ptr_ref_validation_test.cc
|
resolver/ptr_ref_validation_test.cc
|
||||||
|
|
|
@ -43,17 +43,17 @@ class IntrinsicTable {
|
||||||
struct UnaryOperator {
|
struct UnaryOperator {
|
||||||
/// The result type of the unary operator
|
/// The result type of the unary operator
|
||||||
const sem::Type* result;
|
const sem::Type* result;
|
||||||
/// The type of the arg of the unary operator
|
/// The type of the parameter of the unary operator
|
||||||
const sem::Type* arg;
|
const sem::Type* parameter;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// BinaryOperator describes a resolved binary operator
|
/// BinaryOperator describes a resolved binary operator
|
||||||
struct BinaryOperator {
|
struct BinaryOperator {
|
||||||
/// The result type of the binary operator
|
/// The result type of the binary operator
|
||||||
const sem::Type* result;
|
const sem::Type* result;
|
||||||
/// The type of LHS of the binary operator
|
/// The type of LHS parameter of the binary operator
|
||||||
const sem::Type* lhs;
|
const sem::Type* lhs;
|
||||||
/// The type of RHS of the binary operator
|
/// The type of RHS parameter of the binary operator
|
||||||
const sem::Type* rhs;
|
const sem::Type* rhs;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,391 @@
|
||||||
|
// 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
|
|
@ -62,6 +62,7 @@
|
||||||
#include "src/tint/sem/function.h"
|
#include "src/tint/sem/function.h"
|
||||||
#include "src/tint/sem/if_statement.h"
|
#include "src/tint/sem/if_statement.h"
|
||||||
#include "src/tint/sem/loop_statement.h"
|
#include "src/tint/sem/loop_statement.h"
|
||||||
|
#include "src/tint/sem/materialize.h"
|
||||||
#include "src/tint/sem/member_accessor_expression.h"
|
#include "src/tint/sem/member_accessor_expression.h"
|
||||||
#include "src/tint/sem/module.h"
|
#include "src/tint/sem/module.h"
|
||||||
#include "src/tint/sem/multisampled_texture.h"
|
#include "src/tint/sem/multisampled_texture.h"
|
||||||
|
@ -318,7 +319,11 @@ sem::Variable* Resolver::Variable(const ast::Variable* var,
|
||||||
|
|
||||||
// Does the variable have a constructor?
|
// Does the variable have a constructor?
|
||||||
if (var->constructor) {
|
if (var->constructor) {
|
||||||
rhs = Expression(var->constructor);
|
auto* ctor = Expression(var->constructor);
|
||||||
|
if (!ctor) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
rhs = Materialize(ctor, storage_ty);
|
||||||
if (!rhs) {
|
if (!rhs) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -1100,6 +1105,83 @@ sem::Expression* Resolver::Expression(const ast::Expression* root) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sem::Expression* Resolver::Materialize(const sem::Expression* expr,
|
||||||
|
const sem::Type* target_type /* = nullptr */) {
|
||||||
|
// Helper for actually creating the the materialize node, performing the constant cast, updating
|
||||||
|
// the ast -> sem binding, and performing validation.
|
||||||
|
auto materialize = [&](const sem::Type* target_ty) -> sem::Materialize* {
|
||||||
|
auto expr_val = EvaluateConstantValue(expr->Declaration(), expr->Type());
|
||||||
|
if (!expr_val.IsValid()) {
|
||||||
|
TINT_ICE(Resolver, builder_->Diagnostics())
|
||||||
|
<< expr->Declaration()->source
|
||||||
|
<< " EvaluateConstantValue() returned invalid value for materialized "
|
||||||
|
"value of type: "
|
||||||
|
<< (expr->Type() ? expr->Type()->FriendlyName(builder_->Symbols()) : "<null>");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto materialized_val = ConstantCast(expr_val, target_ty);
|
||||||
|
auto* m = builder_->create<sem::Materialize>(expr, current_statement_, materialized_val);
|
||||||
|
m->Behaviors() = expr->Behaviors();
|
||||||
|
builder_->Sem().Replace(expr->Declaration(), m);
|
||||||
|
return validator_.Materialize(m) ? m : nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helpers for constructing semantic types
|
||||||
|
auto i32 = [&] { return builder_->create<sem::I32>(); };
|
||||||
|
auto f32 = [&] { return builder_->create<sem::F32>(); };
|
||||||
|
auto i32v = [&](uint32_t width) { return builder_->create<sem::Vector>(i32(), width); };
|
||||||
|
auto f32v = [&](uint32_t width) { return builder_->create<sem::Vector>(f32(), width); };
|
||||||
|
auto f32m = [&](uint32_t columns, uint32_t rows) {
|
||||||
|
return builder_->create<sem::Matrix>(f32v(columns), rows);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Type dispatch based on the expression type
|
||||||
|
return Switch<sem::Expression*>(
|
||||||
|
expr->Type(), //
|
||||||
|
[&](const sem::AbstractInt*) { return materialize(target_type ? target_type : i32()); },
|
||||||
|
[&](const sem::AbstractFloat*) { return materialize(target_type ? target_type : f32()); },
|
||||||
|
[&](const sem::Vector* v) {
|
||||||
|
return Switch(
|
||||||
|
v->type(), //
|
||||||
|
[&](const sem::AbstractInt*) {
|
||||||
|
return materialize(target_type ? target_type : i32v(v->Width()));
|
||||||
|
},
|
||||||
|
[&](const sem::AbstractFloat*) {
|
||||||
|
return materialize(target_type ? target_type : f32v(v->Width()));
|
||||||
|
},
|
||||||
|
[&](Default) { return expr; });
|
||||||
|
},
|
||||||
|
[&](const sem::Matrix* m) {
|
||||||
|
return Switch(
|
||||||
|
m->type(), //
|
||||||
|
[&](const sem::AbstractFloat*) {
|
||||||
|
return materialize(target_type ? target_type : f32m(m->columns(), m->rows()));
|
||||||
|
},
|
||||||
|
[&](Default) { return expr; });
|
||||||
|
},
|
||||||
|
[&](Default) { return expr; });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Resolver::MaterializeArguments(std::vector<const sem::Expression*>& args,
|
||||||
|
const sem::CallTarget* target) {
|
||||||
|
for (size_t i = 0, n = std::min(args.size(), target->Parameters().size()); i < n; i++) {
|
||||||
|
const auto* param_ty = target->Parameters()[i]->Type();
|
||||||
|
if (ShouldMaterializeArgument(param_ty)) {
|
||||||
|
auto* materialized = Materialize(args[i], param_ty);
|
||||||
|
if (!materialized) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
args[i] = materialized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Resolver::ShouldMaterializeArgument(const sem::Type* parameter_ty) const {
|
||||||
|
const auto* param_el_ty = sem::Type::ElementOf(parameter_ty);
|
||||||
|
return param_el_ty && !param_el_ty->Is<sem::AbstractNumeric>();
|
||||||
|
}
|
||||||
|
|
||||||
sem::Expression* Resolver::IndexAccessor(const ast::IndexAccessorExpression* expr) {
|
sem::Expression* Resolver::IndexAccessor(const ast::IndexAccessorExpression* expr) {
|
||||||
auto* idx = sem_.Get(expr->index);
|
auto* idx = sem_.Get(expr->index);
|
||||||
auto* obj = sem_.Get(expr->object);
|
auto* obj = sem_.Get(expr->object);
|
||||||
|
@ -1192,6 +1274,9 @@ sem::Call* Resolver::Call(const ast::CallExpression* expr) {
|
||||||
if (!call_target) {
|
if (!call_target) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
if (!MaterializeArguments(args, call_target)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
auto value = EvaluateConstantValue(expr, call_target->ReturnType());
|
auto value = EvaluateConstantValue(expr, call_target->ReturnType());
|
||||||
return builder_->create<sem::Call>(expr, call_target, std::move(args), current_statement_,
|
return builder_->create<sem::Call>(expr, call_target, std::move(args), current_statement_,
|
||||||
value, has_side_effects);
|
value, has_side_effects);
|
||||||
|
@ -1227,6 +1312,9 @@ sem::Call* Resolver::Call(const ast::CallExpression* expr) {
|
||||||
}
|
}
|
||||||
return builder_->create<sem::TypeConstructor>(arr, std::move(params));
|
return builder_->create<sem::TypeConstructor>(arr, std::move(params));
|
||||||
});
|
});
|
||||||
|
if (!MaterializeArguments(args, call_target)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
auto value = EvaluateConstantValue(expr, call_target->ReturnType());
|
auto value = EvaluateConstantValue(expr, call_target->ReturnType());
|
||||||
return builder_->create<sem::Call>(expr, call_target, std::move(args),
|
return builder_->create<sem::Call>(expr, call_target, std::move(args),
|
||||||
current_statement_, value, has_side_effects);
|
current_statement_, value, has_side_effects);
|
||||||
|
@ -1246,6 +1334,9 @@ sem::Call* Resolver::Call(const ast::CallExpression* expr) {
|
||||||
}
|
}
|
||||||
return builder_->create<sem::TypeConstructor>(str, std::move(params));
|
return builder_->create<sem::TypeConstructor>(str, std::move(params));
|
||||||
});
|
});
|
||||||
|
if (!MaterializeArguments(args, call_target)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
auto value = EvaluateConstantValue(expr, call_target->ReturnType());
|
auto value = EvaluateConstantValue(expr, call_target->ReturnType());
|
||||||
return builder_->create<sem::Call>(expr, call_target, std::move(args),
|
return builder_->create<sem::Call>(expr, call_target, std::move(args),
|
||||||
current_statement_, value, has_side_effects);
|
current_statement_, value, has_side_effects);
|
||||||
|
@ -1368,6 +1459,10 @@ sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!MaterializeArguments(args, builtin)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (builtin->IsDeprecated()) {
|
if (builtin->IsDeprecated()) {
|
||||||
AddWarning("use of deprecated builtin", expr->source);
|
AddWarning("use of deprecated builtin", expr->source);
|
||||||
}
|
}
|
||||||
|
@ -1425,6 +1520,10 @@ sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr,
|
||||||
auto sym = expr->target.name->symbol;
|
auto sym = expr->target.name->symbol;
|
||||||
auto name = builder_->Symbols().NameFor(sym);
|
auto name = builder_->Symbols().NameFor(sym);
|
||||||
|
|
||||||
|
if (!MaterializeArguments(args, target)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(crbug.com/tint/1420): For now, assume all function calls have side
|
// TODO(crbug.com/tint/1420): For now, assume all function calls have side
|
||||||
// effects.
|
// effects.
|
||||||
bool has_side_effects = true;
|
bool has_side_effects = true;
|
||||||
|
@ -1715,6 +1814,18 @@ sem::Expression* Resolver::Binary(const ast::BinaryExpression* expr) {
|
||||||
if (!op.result) {
|
if (!op.result) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
if (ShouldMaterializeArgument(op.lhs)) {
|
||||||
|
lhs = Materialize(lhs, op.lhs);
|
||||||
|
if (!lhs) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ShouldMaterializeArgument(op.rhs)) {
|
||||||
|
rhs = Materialize(rhs, op.rhs);
|
||||||
|
if (!rhs) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto val = EvaluateConstantValue(expr, op.result);
|
auto val = EvaluateConstantValue(expr, op.result);
|
||||||
bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
|
bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
|
||||||
|
@ -1775,10 +1886,17 @@ sem::Expression* Resolver::UnaryOp(const ast::UnaryOpExpression* unary) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: {
|
default: {
|
||||||
ty = intrinsic_table_->Lookup(unary->op, expr_ty, unary->source).result;
|
auto op = intrinsic_table_->Lookup(unary->op, expr_ty, unary->source);
|
||||||
if (!ty) {
|
if (!op.result) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
if (ShouldMaterializeArgument(op.parameter)) {
|
||||||
|
expr = Materialize(expr, op.parameter);
|
||||||
|
if (!expr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ty = op.result;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2118,7 +2236,11 @@ sem::Statement* Resolver::ReturnStatement(const ast::ReturnStatement* stmt) {
|
||||||
|
|
||||||
const sem::Type* value_ty = nullptr;
|
const sem::Type* value_ty = nullptr;
|
||||||
if (auto* value = stmt->value) {
|
if (auto* value = stmt->value) {
|
||||||
auto* expr = Expression(value);
|
const auto* expr = Expression(value);
|
||||||
|
if (!expr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
expr = Materialize(expr, current_function_->ReturnType());
|
||||||
if (!expr) {
|
if (!expr) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -2141,22 +2263,54 @@ sem::SwitchStatement* Resolver::SwitchStatement(const ast::SwitchStatement* stmt
|
||||||
return StatementScope(stmt, sem, [&] {
|
return StatementScope(stmt, sem, [&] {
|
||||||
auto& behaviors = sem->Behaviors();
|
auto& behaviors = sem->Behaviors();
|
||||||
|
|
||||||
auto* cond = Expression(stmt->condition);
|
const auto* cond = Expression(stmt->condition);
|
||||||
if (!cond) {
|
if (!cond) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
behaviors = cond->Behaviors() - sem::Behavior::kNext;
|
behaviors = cond->Behaviors() - sem::Behavior::kNext;
|
||||||
|
|
||||||
|
auto* cond_ty = cond->Type()->UnwrapRef();
|
||||||
|
|
||||||
|
utils::UniqueVector<const sem::Type*> types;
|
||||||
|
types.add(cond_ty);
|
||||||
|
|
||||||
|
std::vector<sem::CaseStatement*> cases;
|
||||||
|
cases.reserve(stmt->body.size());
|
||||||
for (auto* case_stmt : stmt->body) {
|
for (auto* case_stmt : stmt->body) {
|
||||||
Mark(case_stmt);
|
Mark(case_stmt);
|
||||||
auto* c = CaseStatement(case_stmt);
|
auto* c = CaseStatement(case_stmt);
|
||||||
if (!c) {
|
if (!c) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
for (auto* expr : c->Selectors()) {
|
||||||
|
types.add(expr->Type()->UnwrapRef());
|
||||||
|
}
|
||||||
|
cases.emplace_back(c);
|
||||||
behaviors.Add(c->Behaviors());
|
behaviors.Add(c->Behaviors());
|
||||||
sem->Cases().emplace_back(c);
|
sem->Cases().emplace_back(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the common type across all selectors and the switch expression
|
||||||
|
// This must materialize to an integer scalar (non-abstract).
|
||||||
|
auto* common_ty = sem::Type::Common(types.data(), types.size());
|
||||||
|
if (!common_ty || !common_ty->is_integer_scalar()) {
|
||||||
|
// No common type found or the common type was abstract.
|
||||||
|
// Pick i32 and let validation deal with any mismatches.
|
||||||
|
common_ty = builder_->create<sem::I32>();
|
||||||
|
}
|
||||||
|
cond = Materialize(cond, common_ty);
|
||||||
|
if (!cond) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (auto* c : cases) {
|
||||||
|
for (auto*& sel : c->Selectors()) { // Note: pointer reference
|
||||||
|
sel = Materialize(sel, common_ty);
|
||||||
|
if (!sel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (behaviors.Contains(sem::Behavior::kBreak)) {
|
if (behaviors.Contains(sem::Behavior::kBreak)) {
|
||||||
behaviors.Add(sem::Behavior::kNext);
|
behaviors.Add(sem::Behavior::kNext);
|
||||||
}
|
}
|
||||||
|
|
|
@ -198,6 +198,30 @@ class Resolver {
|
||||||
sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
|
sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
|
||||||
sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
|
sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
|
||||||
|
|
||||||
|
/// If `expr` is not of an abstract-numeric type, then Materialize() will just return `expr`.
|
||||||
|
/// If `expr` is of an abstract-numeric type:
|
||||||
|
/// * Materialize will create and return a sem::Materialize node wrapping `expr`.
|
||||||
|
/// * The AST -> Sem binding will be updated to point to the new sem::Materialize node.
|
||||||
|
/// * The sem::Materialize node will have a new concrete type, which will be `target_type` if
|
||||||
|
/// not nullptr, otherwise:
|
||||||
|
/// * a type with the element type of `i32` (e.g. `i32`, `vec2<i32>`) if `expr` has a
|
||||||
|
/// element type of abstract-integer...
|
||||||
|
/// * ... or a type with the element type of `f32` (e.g. `f32`, vec3<f32>`, `mat2x3<f32>`)
|
||||||
|
/// if `expr` has a element type of abstract-float.
|
||||||
|
/// * The sem::Materialize constant value will be the value of `expr` value-converted to the
|
||||||
|
/// materialized type.
|
||||||
|
const sem::Expression* Materialize(const sem::Expression* expr,
|
||||||
|
const sem::Type* target_type = nullptr);
|
||||||
|
|
||||||
|
/// Materializes all the arguments in `args` to the parameter types of `target`.
|
||||||
|
/// @returns true on success, false on failure.
|
||||||
|
bool MaterializeArguments(std::vector<const sem::Expression*>& args,
|
||||||
|
const sem::CallTarget* target);
|
||||||
|
|
||||||
|
/// @returns true if an argument of an abstract numeric type, passed to a parameter of type
|
||||||
|
/// `parameter_ty` should be materialized.
|
||||||
|
bool ShouldMaterializeArgument(const sem::Type* parameter_ty) const;
|
||||||
|
|
||||||
// Statement resolving methods
|
// Statement resolving methods
|
||||||
// Each return true on success, false on failure.
|
// Each return true on success, false on failure.
|
||||||
sem::Statement* AssignmentStatement(const ast::AssignmentStatement*);
|
sem::Statement* AssignmentStatement(const ast::AssignmentStatement*);
|
||||||
|
|
|
@ -57,6 +57,7 @@
|
||||||
#include "src/tint/sem/function.h"
|
#include "src/tint/sem/function.h"
|
||||||
#include "src/tint/sem/if_statement.h"
|
#include "src/tint/sem/if_statement.h"
|
||||||
#include "src/tint/sem/loop_statement.h"
|
#include "src/tint/sem/loop_statement.h"
|
||||||
|
#include "src/tint/sem/materialize.h"
|
||||||
#include "src/tint/sem/member_accessor_expression.h"
|
#include "src/tint/sem/member_accessor_expression.h"
|
||||||
#include "src/tint/sem/multisampled_texture.h"
|
#include "src/tint/sem/multisampled_texture.h"
|
||||||
#include "src/tint/sem/pointer.h"
|
#include "src/tint/sem/pointer.h"
|
||||||
|
@ -276,6 +277,19 @@ bool Validator::StorageTexture(const ast::StorageTexture* t) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Validator::Materialize(const sem::Materialize* m) const {
|
||||||
|
auto* from = m->Expr()->Type();
|
||||||
|
auto* to = m->Type();
|
||||||
|
|
||||||
|
if (sem::Type::ConversionRank(from, to) == sem::Type::kNoConversion) {
|
||||||
|
AddError("cannot convert value of type '" + sem_.TypeNameOf(from) + "' to type '" +
|
||||||
|
sem_.TypeNameOf(to) + "'",
|
||||||
|
m->Expr()->Declaration()->source);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Validator::VariableConstructorOrCast(const ast::Variable* var,
|
bool Validator::VariableConstructorOrCast(const ast::Variable* var,
|
||||||
ast::StorageClass storage_class,
|
ast::StorageClass storage_class,
|
||||||
const sem::Type* storage_ty,
|
const sem::Type* storage_ty,
|
||||||
|
|
|
@ -54,6 +54,7 @@ class CaseStatement;
|
||||||
class ForLoopStatement;
|
class ForLoopStatement;
|
||||||
class IfStatement;
|
class IfStatement;
|
||||||
class LoopStatement;
|
class LoopStatement;
|
||||||
|
class Materialize;
|
||||||
class Statement;
|
class Statement;
|
||||||
class SwitchStatement;
|
class SwitchStatement;
|
||||||
class TypeConstructor;
|
class TypeConstructor;
|
||||||
|
@ -275,6 +276,11 @@ class Validator {
|
||||||
/// @returns true on success, false otherwise.
|
/// @returns true on success, false otherwise.
|
||||||
bool LoopStatement(const sem::LoopStatement* stmt) const;
|
bool LoopStatement(const sem::LoopStatement* stmt) const;
|
||||||
|
|
||||||
|
/// Validates a materialize of an abstract numeric value
|
||||||
|
/// @param m the materialize to validate
|
||||||
|
/// @returns true on success, false otherwise
|
||||||
|
bool Materialize(const sem::Materialize* m) const;
|
||||||
|
|
||||||
/// Validates a matrix
|
/// Validates a matrix
|
||||||
/// @param ty the matrix to validate
|
/// @param ty the matrix to validate
|
||||||
/// @param source the source of the matrix
|
/// @param source the source of the matrix
|
||||||
|
|
|
@ -70,8 +70,7 @@ class Info {
|
||||||
return As<RESULT>(it->second);
|
return As<RESULT>(it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add registers the semantic node `sem_node` for the AST or type node
|
/// Add registers the semantic node `sem_node` for the AST or type node `node`.
|
||||||
/// `node`.
|
|
||||||
/// @param node the AST or type node
|
/// @param node the AST or type node
|
||||||
/// @param sem_node the semantic node
|
/// @param sem_node the semantic node
|
||||||
template <typename AST_OR_TYPE>
|
template <typename AST_OR_TYPE>
|
||||||
|
@ -81,6 +80,14 @@ class Info {
|
||||||
map_.emplace(node, sem_node);
|
map_.emplace(node, sem_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace replaces any existing semantic node `sem_node` for the AST or type node `node`.
|
||||||
|
/// @param node the AST or type node
|
||||||
|
/// @param sem_node the new semantic node
|
||||||
|
template <typename AST_OR_TYPE>
|
||||||
|
void Replace(const AST_OR_TYPE* node, const SemanticNodeTypeFor<AST_OR_TYPE>* sem_node) {
|
||||||
|
map_[node] = sem_node;
|
||||||
|
}
|
||||||
|
|
||||||
/// Wrap returns a new Info created with the contents of `inner`.
|
/// Wrap returns a new Info created with the contents of `inner`.
|
||||||
/// The Info returned by Wrap is intended to temporarily extend the contents
|
/// The Info returned by Wrap is intended to temporarily extend the contents
|
||||||
/// of an existing immutable Info.
|
/// of an existing immutable Info.
|
||||||
|
|
|
@ -257,6 +257,7 @@ tint_unittests_source_set("tint_unittests_resolver_src") {
|
||||||
"../../src/tint/resolver/intrinsic_table_test.cc",
|
"../../src/tint/resolver/intrinsic_table_test.cc",
|
||||||
"../../src/tint/resolver/is_host_shareable_test.cc",
|
"../../src/tint/resolver/is_host_shareable_test.cc",
|
||||||
"../../src/tint/resolver/is_storeable_test.cc",
|
"../../src/tint/resolver/is_storeable_test.cc",
|
||||||
|
"../../src/tint/resolver/materialize_test.cc",
|
||||||
"../../src/tint/resolver/pipeline_overridable_constant_test.cc",
|
"../../src/tint/resolver/pipeline_overridable_constant_test.cc",
|
||||||
"../../src/tint/resolver/ptr_ref_test.cc",
|
"../../src/tint/resolver/ptr_ref_test.cc",
|
||||||
"../../src/tint/resolver/ptr_ref_validation_test.cc",
|
"../../src/tint/resolver/ptr_ref_validation_test.cc",
|
||||||
|
|
Loading…
Reference in New Issue