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:
Ben Clayton 2022-05-24 21:42:03 +00:00 committed by Dawn LUCI CQ
parent 575b27512e
commit 932418ef46
10 changed files with 610 additions and 11 deletions

View File

@ -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",

View File

@ -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

View File

@ -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;
}; };

View File

@ -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

View File

@ -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);
} }

View File

@ -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*);

View File

@ -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,

View File

@ -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

View File

@ -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.

View File

@ -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",