tint/resolver: Ensure materialized values are representable
by the materialized type.
Bug: tint:1504
Change-Id: I3534ce62308ba2ff32c52a2f5bc8480d102153a1
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/91422
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/resolver/materialize_test.cc b/src/tint/resolver/materialize_test.cc
index d2cb472..9f7be54 100644
--- a/src/tint/resolver/materialize_test.cc
+++ b/src/tint/resolver/materialize_test.cc
@@ -134,35 +134,65 @@
struct Data {
std::string target_type_name;
+ std::string target_element_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::string source_type_name;
+ builder::ast_expr_func_ptr source_builder;
std::variant<AInt, AFloat> materialized_value;
+ double literal_value;
};
-template <typename TARGET_TYPE, typename LITERAL_TYPE, typename MATERIALIZED_TYPE = AInt>
-Data Types(MATERIALIZED_TYPE materialized_value = 0_a) {
+template <typename TARGET_TYPE, typename SOURCE_TYPE, typename MATERIALIZED_TYPE>
+Data Types(MATERIALIZED_TYPE materialized_value, double literal_value) {
+ using TargetDataType = builder::DataType<TARGET_TYPE>;
+ using SourceDataType = builder::DataType<SOURCE_TYPE>;
+ using TargetElementDataType = builder::DataType<typename TargetDataType::ElementType>;
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, //
+ TargetDataType::Name(), // target_type_name
+ TargetElementDataType::Name(), // target_element_type_name
+ TargetDataType::AST, // target_ast_ty
+ TargetDataType::Sem, // target_sem_ty
+ TargetDataType::Expr, // target_expr
+ SourceDataType::Name(), // literal_type_name
+ SourceDataType::Expr, // literal_builder
materialized_value,
+ literal_value,
+ };
+}
+
+template <typename TARGET_TYPE, typename SOURCE_TYPE>
+Data Types() {
+ using TargetDataType = builder::DataType<TARGET_TYPE>;
+ using SourceDataType = builder::DataType<SOURCE_TYPE>;
+ using TargetElementDataType = builder::DataType<typename TargetDataType::ElementType>;
+ return {
+ TargetDataType::Name(), // target_type_name
+ TargetElementDataType::Name(), // target_element_type_name
+ TargetDataType::AST, // target_ast_ty
+ TargetDataType::Sem, // target_sem_ty
+ TargetDataType::Expr, // target_expr
+ SourceDataType::Name(), // literal_type_name
+ SourceDataType::Expr, // literal_builder
+ 0_a,
+ 0.0,
};
}
static std::ostream& operator<<(std::ostream& o, const Data& c) {
- return o << "[" << c.target_type_name << " <- " << c.literal_type_name << "]";
+ auto print_value = [&](auto&& v) { o << v; };
+ o << "[" << c.target_type_name << " <- " << c.source_type_name << "] [";
+ std::visit(print_value, c.materialized_value);
+ o << " <- " << c.literal_value << "]";
+ return o;
}
enum class Expectation {
kMaterialize,
kNoMaterialize,
kInvalidCast,
+ kValueCannotBeRepresented,
};
static std::ostream& operator<<(std::ostream& o, Expectation m) {
@@ -173,6 +203,8 @@
return o << "no-materialize";
case Expectation::kInvalidCast:
return o << "invalid-cast";
+ case Expectation::kValueCannotBeRepresented:
+ return o << "value too low or high";
}
return o << "<unknown>";
}
@@ -191,7 +223,7 @@
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);
+ auto* literal = data.source_builder(*this, data.literal_value);
switch (method) {
case Method::kVar:
WrapInFunction(Decl(Var("a", target_ty(), literal)));
@@ -283,110 +315,191 @@
switch (method) {
case Method::kBuiltinArg:
expect = "error: no matching call to min(" + data.target_type_name + ", " +
- data.literal_type_name + ")";
+ data.source_type_name + ")";
break;
case Method::kBinaryOp:
expect = "error: no matching overload for operator + (" +
- data.target_type_name + ", " + data.literal_type_name + ")";
+ data.target_type_name + ", " + data.source_type_name + ")";
break;
default:
- expect = "error: cannot convert value of type '" + data.literal_type_name +
+ expect = "error: cannot convert value of type '" + data.source_type_name +
"' to type '" + data.target_type_name + "'";
break;
}
EXPECT_THAT(r()->error(), testing::StartsWith(expect));
break;
}
+ case Expectation::kValueCannotBeRepresented:
+ ASSERT_FALSE(r()->Resolve());
+ EXPECT_THAT(r()->error(), testing::HasSubstr("cannot be represented as '" +
+ data.target_element_type_name + "'"));
+ break;
}
}
-// TODO(crbug.com/tint/1504): Test for abstract-numeric values not fitting in materialized types.
+/// Methods that support scalar materialization
+constexpr Method kScalarMethods[] = {Method::kLet, //
+ Method::kVar, //
+ Method::kFnArg, //
+ Method::kBuiltinArg, //
+ Method::kReturn, //
+ Method::kArray, //
+ Method::kStruct, //
+ Method::kBinaryOp};
-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), */)));
+/// Methods that support vector materialization
+constexpr Method kVectorMethods[] = {Method::kLet, //
+ Method::kVar, //
+ Method::kFnArg, //
+ Method::kBuiltinArg, //
+ Method::kReturn, //
+ Method::kArray, //
+ Method::kStruct, //
+ Method::kBinaryOp};
-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), */)));
+/// Methods that support matrix materialization
+constexpr Method kMatrixMethods[] = {Method::kLet, //
+ Method::kVar, //
+ Method::kFnArg, //
+ Method::kReturn, //
+ Method::kArray, //
+ Method::kStruct, //
+ Method::kBinaryOp};
-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), */ //
- )));
+/// Methods that support materialization for switch cases
+constexpr Method kSwitchMethods[] = {Method::kSwitchCond, //
+ Method::kSwitchCase, //
+ Method::kSwitchCondWithAbstractCase, //
+ Method::kSwitchCaseWithAbstractCase};
-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))));
+constexpr double kMaxF32 = static_cast<double>(f32::kHighest);
+constexpr double kPiF64 = 3.141592653589793;
+constexpr double kPiF32 = 3.1415927410125732; // kPiF64 quantized to f32
+
+// (2^-127)×(1+(0xfffffffffffff÷0x10000000000000))
+constexpr double kTooSmallF32 = 1.1754943508222874e-38;
+
+INSTANTIATE_TEST_SUITE_P(
+ MaterializeScalar,
+ MaterializeAbstractNumeric, //
+ testing::Combine(testing::Values(Expectation::kMaterialize), //
+ testing::ValuesIn(kScalarMethods), //
+ testing::Values(Types<i32, AInt>(0_a, 0.0), //
+ Types<i32, AInt>(2147483647_a, 2147483647.0), //
+ Types<i32, AInt>(-2147483648_a, -2147483648.0), //
+ Types<u32, AInt>(0_a, 0.0), //
+ Types<u32, AInt>(4294967295_a, 4294967295.0), //
+ Types<f32, AFloat>(0.0_a, 0.0), //
+ Types<f32, AFloat>(AFloat(kMaxF32), kMaxF32), //
+ Types<f32, AFloat>(AFloat(-kMaxF32), -kMaxF32), //
+ Types<f32, AFloat>(AFloat(kPiF32), kPiF64), //
+ Types<f32, AFloat>(0.0_a, kTooSmallF32), //
+ Types<f32, AFloat>(-0.0_a, -kTooSmallF32) //
+ /* 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::ValuesIn(kVectorMethods), //
+ testing::Values(Types<i32V, AIntV>(0_a, 0.0), //
+ Types<i32V, AIntV>(2147483647_a, 2147483647.0), //
+ Types<i32V, AIntV>(-2147483648_a, -2147483648.0), //
+ Types<u32V, AIntV>(0_a, 0.0), //
+ Types<u32V, AIntV>(4294967295_a, 4294967295.0), //
+ Types<f32V, AFloatV>(0.0_a, 0.0), //
+ Types<f32V, AFloatV>(AFloat(kMaxF32), kMaxF32), //
+ Types<f32V, AFloatV>(AFloat(-kMaxF32), -kMaxF32), //
+ Types<f32V, AFloatV>(AFloat(kPiF32), kPiF64), //
+ Types<f32V, AFloatV>(0.0_a, kTooSmallF32), //
+ Types<f32V, AFloatV>(-0.0_a, -kTooSmallF32) //
+ /* 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::ValuesIn(kMatrixMethods), //
+ testing::Values(Types<f32M, AFloatM>(0.0_a, 0.0), //
+ Types<f32M, AFloatM>(AFloat(kMaxF32), kMaxF32), //
+ Types<f32M, AFloatM>(AFloat(-kMaxF32), -kMaxF32), //
+ Types<f32M, AFloatM>(AFloat(kPiF32), kPiF64), //
+ Types<f32M, AFloatM>(0.0_a, kTooSmallF32), //
+ Types<f32M, AFloatM>(-0.0_a, -kTooSmallF32) //
+ /* Types<f16V, AFloatM>(1.0_a), */ //
+ )));
+
+INSTANTIATE_TEST_SUITE_P(
+ MaterializeSwitch,
+ MaterializeAbstractNumeric, //
+ testing::Combine(testing::Values(Expectation::kMaterialize), //
+ testing::ValuesIn(kSwitchMethods), //
+ testing::Values(Types<i32, AInt>(0_a, 0.0), //
+ Types<i32, AInt>(2147483647_a, 2147483647.0), //
+ Types<i32, AInt>(-2147483648_a, -2147483648.0), //
+ Types<u32, AInt>(0_a, 0.0), //
+ Types<u32, AInt>(4294967295_a, 4294967295.0))));
// 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))));
+ MaterializeAbstractNumeric, //
+ testing::Combine(testing::Values(Expectation::kNoMaterialize), //
+ testing::Values(Method::kBuiltinArg, //
+ Method::kBinaryOp), //
+ testing::Values(Types<AInt, AInt>(), //
+ Types<AFloat, AFloat>(), //
+ Types<AIntV, AIntV>(), //
+ Types<AFloatV, AFloatV>(), //
+ Types<AFloatM, AFloatM>())));
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::ValuesIn(kScalarMethods), //
testing::Values(Types<i32, AFloat>(), //
Types<u32, AFloat>(), //
Types<i32V, AFloatV>(), //
Types<u32V, AFloatV>())));
+INSTANTIATE_TEST_SUITE_P(
+ ScalarValueCannotBeRepresented,
+ MaterializeAbstractNumeric, //
+ testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented), //
+ testing::ValuesIn(kScalarMethods), //
+ testing::Values(Types<i32, AInt>(0_a, 2147483648.0), //
+ Types<i32, AInt>(0_a, -2147483649.0), //
+ Types<u32, AInt>(0_a, 4294967296), //
+ Types<u32, AInt>(0_a, -1.0), //
+ Types<f32, AFloat>(0.0_a, 3.5e+38), //
+ Types<f32, AFloat>(0.0_a, -3.5e+38) //
+ /* Types<f16, AFloat>(), */ //
+ /* Types<f16, AFloat>(), */)));
+
+INSTANTIATE_TEST_SUITE_P(
+ VectorValueCannotBeRepresented,
+ MaterializeAbstractNumeric, //
+ testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented), //
+ testing::ValuesIn(kVectorMethods), //
+ testing::Values(Types<i32V, AIntV>(0_a, 2147483648.0), //
+ Types<i32V, AIntV>(0_a, -2147483649.0), //
+ Types<u32V, AIntV>(0_a, 4294967296), //
+ Types<u32V, AIntV>(0_a, -1.0), //
+ Types<f32V, AFloatV>(0.0_a, 3.5e+38), //
+ Types<f32V, AFloatV>(0.0_a, -3.5e+38) //
+ /* Types<f16V, AFloatV>(), */ //
+ /* Types<f16V, AFloatV>(), */)));
+
+INSTANTIATE_TEST_SUITE_P(
+ MatrixValueCannotBeRepresented,
+ MaterializeAbstractNumeric, //
+ testing::Combine(testing::Values(Expectation::kValueCannotBeRepresented), //
+ testing::ValuesIn(kMatrixMethods), //
+ testing::Values(Types<f32M, AFloatM>(0.0_a, 3.5e+38), //
+ Types<f32M, AFloatM>(0.0_a, -3.5e+38) //
+ /* Types<f16M, AFloatM>(), */ //
+ /* Types<f16M, AFloatM>(), */)));
+
} // namespace MaterializeTests
} // namespace
diff --git a/src/tint/resolver/resolver.cc b/src/tint/resolver/resolver.cc
index afa41c4..1dcedf9 100644
--- a/src/tint/resolver/resolver.cc
+++ b/src/tint/resolver/resolver.cc
@@ -1110,19 +1110,27 @@
// 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()) {
+ auto* decl = expr->Declaration();
+ auto expr_val = EvaluateConstantValue(decl, expr->Type());
+ if (!expr_val) {
+ return nullptr;
+ }
+ if (!expr_val->IsValid()) {
TINT_ICE(Resolver, builder_->Diagnostics())
- << expr->Declaration()->source
+ << decl->source
<< " EvaluateConstantValue() returned invalid value for materialized "
"value of type: "
<< (expr->Type() ? expr->Type()->FriendlyName(builder_->Symbols()) : "<null>");
return nullptr;
}
- auto materialized_val = ConvertValue(expr_val, target_ty);
- auto* m = builder_->create<sem::Materialize>(expr, current_statement_, materialized_val);
+ auto materialized_val = ConvertValue(expr_val.Get(), target_ty, decl->source);
+ if (!materialized_val) {
+ return nullptr;
+ }
+ auto* m =
+ builder_->create<sem::Materialize>(expr, current_statement_, materialized_val.Get());
m->Behaviors() = expr->Behaviors();
- builder_->Sem().Replace(expr->Declaration(), m);
+ builder_->Sem().Replace(decl, m);
return validator_.Materialize(m) ? m : nullptr;
};
@@ -1215,8 +1223,11 @@
}
auto val = EvaluateConstantValue(expr, ty);
+ if (!val) {
+ return nullptr;
+ }
bool has_side_effects = idx->HasSideEffects() || obj->HasSideEffects();
- auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, val,
+ auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, val.Get(),
has_side_effects, obj->SourceVariable());
sem->Behaviors() = idx->Behaviors() + obj->Behaviors();
return sem;
@@ -1230,7 +1241,10 @@
}
auto val = EvaluateConstantValue(expr, ty);
- auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, val,
+ if (!val) {
+ return nullptr;
+ }
+ auto* sem = builder_->create<sem::Expression>(expr, ty, current_statement_, val.Get(),
inner->HasSideEffects());
sem->Behaviors() = inner->Behaviors();
@@ -1277,9 +1291,12 @@
if (!MaterializeArguments(args, call_target)) {
return nullptr;
}
- auto value = EvaluateConstantValue(expr, call_target->ReturnType());
+ auto val = EvaluateConstantValue(expr, call_target->ReturnType());
+ if (!val) {
+ return nullptr;
+ }
return builder_->create<sem::Call>(expr, call_target, std::move(args), current_statement_,
- value, has_side_effects);
+ val.Get(), has_side_effects);
};
// ct_ctor_or_conv is a helper for building either a sem::TypeConstructor or sem::TypeConversion
@@ -1315,9 +1332,12 @@
if (!MaterializeArguments(args, call_target)) {
return nullptr;
}
- auto value = EvaluateConstantValue(expr, call_target->ReturnType());
+ auto val = EvaluateConstantValue(expr, call_target->ReturnType());
+ if (!val) {
+ return nullptr;
+ }
return builder_->create<sem::Call>(expr, call_target, std::move(args),
- current_statement_, value, has_side_effects);
+ current_statement_, val.Get(), has_side_effects);
},
[&](const sem::Struct* str) -> sem::Call* {
auto* call_target = utils::GetOrCreate(
@@ -1337,9 +1357,12 @@
if (!MaterializeArguments(args, call_target)) {
return nullptr;
}
- auto value = EvaluateConstantValue(expr, call_target->ReturnType());
+ auto val = EvaluateConstantValue(expr, call_target->ReturnType());
+ if (!val) {
+ return nullptr;
+ }
return builder_->create<sem::Call>(expr, call_target, std::move(args),
- current_statement_, value, has_side_effects);
+ current_statement_, val.Get(), has_side_effects);
},
[&](Default) {
AddError("type is not constructible", expr->source);
@@ -1616,7 +1639,10 @@
}
auto val = EvaluateConstantValue(literal, ty);
- return builder_->create<sem::Expression>(literal, ty, current_statement_, val,
+ if (!val) {
+ return nullptr;
+ }
+ return builder_->create<sem::Expression>(literal, ty, current_statement_, val.Get(),
/* has_side_effects */ false);
}
@@ -1828,8 +1854,11 @@
}
auto val = EvaluateConstantValue(expr, op.result);
+ if (!val) {
+ return nullptr;
+ }
bool has_side_effects = lhs->HasSideEffects() || rhs->HasSideEffects();
- auto* sem = builder_->create<sem::Expression>(expr, op.result, current_statement_, val,
+ auto* sem = builder_->create<sem::Expression>(expr, op.result, current_statement_, val.Get(),
has_side_effects);
sem->Behaviors() = lhs->Behaviors() + rhs->Behaviors();
@@ -1902,7 +1931,10 @@
}
auto val = EvaluateConstantValue(unary, ty);
- auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_, val,
+ if (!val) {
+ return nullptr;
+ }
+ auto* sem = builder_->create<sem::Expression>(unary, ty, current_statement_, val.Get(),
expr->HasSideEffects(), source_var);
sem->Behaviors() = expr->Behaviors();
return sem;
diff --git a/src/tint/resolver/resolver.h b/src/tint/resolver/resolver.h
index 07e9636..7d01934 100644
--- a/src/tint/resolver/resolver.h
+++ b/src/tint/resolver/resolver.h
@@ -34,6 +34,7 @@
#include "src/tint/sem/constant.h"
#include "src/tint/sem/function.h"
#include "src/tint/sem/struct.h"
+#include "src/tint/utils/result.h"
#include "src/tint/utils/unique_vector.h"
// Forward declarations
@@ -354,15 +355,19 @@
//////////////////////////////////////////////////////////////////////////////
/// Constant value evaluation methods
//////////////////////////////////////////////////////////////////////////////
+ /// The result type of a ConstantEvaluation method. Holds the constant value and a boolean,
+ /// which is true on success, false on an error.
+ using ConstantResult = utils::Result<sem::Constant>;
/// Convert the `value` to `target_type`
/// @return the converted value
- sem::Constant ConvertValue(const sem::Constant& value, const sem::Type* target_type);
-
- sem::Constant EvaluateConstantValue(const ast::Expression* expr, const sem::Type* type);
- sem::Constant EvaluateConstantValue(const ast::LiteralExpression* literal,
- const sem::Type* type);
- sem::Constant EvaluateConstantValue(const ast::CallExpression* call, const sem::Type* type);
+ ConstantResult ConvertValue(const sem::Constant& value,
+ const sem::Type* target_type,
+ const Source& source);
+ ConstantResult EvaluateConstantValue(const ast::Expression* expr, const sem::Type* type);
+ ConstantResult EvaluateConstantValue(const ast::LiteralExpression* literal,
+ const sem::Type* type);
+ ConstantResult EvaluateConstantValue(const ast::CallExpression* call, const sem::Type* type);
/// @returns true if the symbol is the name of a builtin function.
bool IsBuiltin(Symbol) const;
diff --git a/src/tint/resolver/resolver_constants.cc b/src/tint/resolver/resolver_constants.cc
index 5d1f389..dd846f1 100644
--- a/src/tint/resolver/resolver_constants.cc
+++ b/src/tint/resolver/resolver_constants.cc
@@ -14,7 +14,9 @@
#include "src/tint/resolver/resolver.h"
-#include <optional>
+#include <cmath>
+// TODO(https://crbug.com/dawn/1379) Update cpplint and remove NOLINT
+#include <optional> // NOLINT(build/include_order))
#include "src/tint/sem/abstract_float.h"
#include "src/tint/sem/abstract_int.h"
@@ -30,46 +32,53 @@
namespace {
-/// Converts all the element values of `in` to the type `T`.
+/// Converts and returns all the element values of `in` to the type `T`, using the converter
+/// function `CONVERTER`.
/// @param elements_in the vector of elements to be converted
+/// @param converter a function-like with the signature `void(TO&, FROM)`
/// @returns the elements converted to type T.
-template <typename T, typename ELEMENTS_IN>
-sem::Constant::Elements Convert(const ELEMENTS_IN& elements_in) {
+template <typename T, typename ELEMENTS_IN, typename CONVERTER>
+sem::Constant::Elements Transform(const ELEMENTS_IN& elements_in, CONVERTER&& converter) {
TINT_BEGIN_DISABLE_WARNING_UNREACHABLE_CODE();
- using E = UnwrapNumber<T>;
return utils::Transform(elements_in, [&](auto value_in) {
- if constexpr (std::is_same_v<E, bool>) {
+ if constexpr (std::is_same_v<UnwrapNumber<T>, bool>) {
return AInt(value_in != 0);
- }
-
- E converted = static_cast<E>(value_in);
- if constexpr (IsFloatingPoint<E>) {
- return AFloat(converted);
} else {
- return AInt(converted);
+ T converted{};
+ converter(converted, value_in);
+ if constexpr (IsFloatingPoint<UnwrapNumber<T>>) {
+ return AFloat(converted);
+ } else {
+ return AInt(converted);
+ }
}
});
TINT_END_DISABLE_WARNING_UNREACHABLE_CODE();
}
-/// Converts and returns all the element values of `in` to the semantic type `el_ty`.
+/// Converts and returns all the element values of `in` to the semantic type `el_ty`, using the
+/// converter function `CONVERTER`.
/// @param in the constant to convert
/// @param el_ty the target element type
-/// @returns the elements converted to `type`
-sem::Constant::Elements Convert(const sem::Constant::Elements& in, const sem::Type* el_ty) {
+/// @param converter a function-like with the signature `void(TO&, FROM)`
+/// @returns the elements converted to `el_ty`
+template <typename CONVERTER>
+sem::Constant::Elements Transform(const sem::Constant::Elements& in,
+ const sem::Type* el_ty,
+ CONVERTER&& converter) {
return std::visit(
[&](auto&& v) {
return Switch(
el_ty, //
- [&](const sem::AbstractInt*) { return Convert<AInt>(v); },
- [&](const sem::AbstractFloat*) { return Convert<AFloat>(v); },
- [&](const sem::I32*) { return Convert<i32>(v); },
- [&](const sem::U32*) { return Convert<u32>(v); },
- [&](const sem::F32*) { return Convert<f32>(v); },
- [&](const sem::F16*) { return Convert<f16>(v); },
- [&](const sem::Bool*) { return Convert<bool>(v); },
+ [&](const sem::AbstractInt*) { return Transform<AInt>(v, converter); },
+ [&](const sem::AbstractFloat*) { return Transform<AFloat>(v, converter); },
+ [&](const sem::I32*) { return Transform<i32>(v, converter); },
+ [&](const sem::U32*) { return Transform<u32>(v, converter); },
+ [&](const sem::F32*) { return Transform<f32>(v, converter); },
+ [&](const sem::F16*) { return Transform<f16>(v, converter); },
+ [&](const sem::Bool*) { return Transform<bool>(v, converter); },
[&](Default) -> sem::Constant::Elements {
diag::List diags;
TINT_UNREACHABLE(Semantic, diags)
@@ -80,44 +89,91 @@
in);
}
+/// Converts and returns all the elements in `in` to the type `el_ty`, by performing a `static_cast`
+/// on each element value. No checks will be performed that the value fits in the target type.
+/// @param in the input elements
+/// @param el_ty the target element type
+/// @returns the elements converted to `el_ty`
+sem::Constant::Elements ConvertElements(const sem::Constant::Elements& in, const sem::Type* el_ty) {
+ return Transform(in, el_ty, [](auto& el_out, auto el_in) {
+ el_out = std::decay_t<decltype(el_out)>(el_in);
+ });
+}
+
+/// Converts and returns all the elements in `in` to the type `el_ty`, by performing a
+/// `CheckedConvert` on each element value. A single error diagnostic will be raised if an element
+/// value cannot be represented by the target type.
+/// @param in the input elements
+/// @param el_ty the target element type
+/// @returns the elements converted to `el_ty`, or a Failure if some elements could not be
+/// represented by the target type.
+utils::Result<sem::Constant::Elements> MaterializeElements(const sem::Constant::Elements& in,
+ const sem::Type* el_ty,
+ ProgramBuilder& builder,
+ Source source) {
+ std::optional<std::string> failure;
+
+ auto out = Transform(in, el_ty, [&](auto& el_out, auto el_in) {
+ using OUT = std::decay_t<decltype(el_out)>;
+ if (auto conv = CheckedConvert<OUT>(el_in)) {
+ el_out = conv.Get();
+ } else if (conv.Failure() == ConversionFailure::kTooSmall) {
+ el_out = OUT(el_in < 0 ? -0.0 : 0.0);
+ } else if (!failure.has_value()) {
+ std::stringstream ss;
+ ss << "value " << el_in << " cannot be represented as ";
+ ss << "'" << builder.FriendlyName(el_ty) << "'";
+ failure = ss.str();
+ }
+ });
+
+ if (failure.has_value()) {
+ builder.Diagnostics().add_error(diag::System::Resolver, std::move(failure.value()), source);
+ return utils::Failure;
+ }
+
+ return out;
+}
+
} // namespace
-sem::Constant Resolver::EvaluateConstantValue(const ast::Expression* expr, const sem::Type* type) {
+utils::Result<sem::Constant> Resolver::EvaluateConstantValue(const ast::Expression* expr,
+ const sem::Type* type) {
if (auto* e = expr->As<ast::LiteralExpression>()) {
return EvaluateConstantValue(e, type);
}
if (auto* e = expr->As<ast::CallExpression>()) {
return EvaluateConstantValue(e, type);
}
- return {};
+ return sem::Constant{};
}
-sem::Constant Resolver::EvaluateConstantValue(const ast::LiteralExpression* literal,
- const sem::Type* type) {
+utils::Result<sem::Constant> Resolver::EvaluateConstantValue(const ast::LiteralExpression* literal,
+ const sem::Type* type) {
return Switch(
literal,
+ [&](const ast::BoolLiteralExpression* lit) {
+ return sem::Constant{type, {AInt(lit->value ? 1 : 0)}};
+ },
[&](const ast::IntLiteralExpression* lit) {
return sem::Constant{type, {AInt(lit->value)}};
},
[&](const ast::FloatLiteralExpression* lit) {
return sem::Constant{type, {AFloat(lit->value)}};
- },
- [&](const ast::BoolLiteralExpression* lit) {
- return sem::Constant{type, {AInt(lit->value ? 1 : 0)}};
});
}
-sem::Constant Resolver::EvaluateConstantValue(const ast::CallExpression* call,
- const sem::Type* ty) {
+utils::Result<sem::Constant> Resolver::EvaluateConstantValue(const ast::CallExpression* call,
+ const sem::Type* ty) {
uint32_t result_size = 0;
auto* el_ty = sem::Type::ElementOf(ty, &result_size);
if (!el_ty) {
- return {};
+ return sem::Constant{};
}
// ElementOf() will also return the element type of array, which we do not support.
if (ty->Is<sem::Array>()) {
- return {};
+ return sem::Constant{};
}
// For zero value init, return 0s
@@ -142,15 +198,15 @@
for (auto* expr : call->args) {
auto* arg = builder_->Sem().Get(expr);
if (!arg) {
- return {};
+ return sem::Constant{};
}
auto value = arg->ConstantValue();
if (!value) {
- return {};
+ return sem::Constant{};
}
// Convert the elements to the desired type.
- auto converted = Convert(value.GetElements(), el_ty);
+ auto converted = ConvertElements(value.GetElements(), el_ty);
if (elements.has_value()) {
// Append the converted vector to elements
@@ -180,20 +236,25 @@
return sem::Constant(ty, std::move(elements.value()));
}
-sem::Constant Resolver::ConvertValue(const sem::Constant& value, const sem::Type* ty) {
+utils::Result<sem::Constant> Resolver::ConvertValue(const sem::Constant& value,
+ const sem::Type* ty,
+ const Source& source) {
if (value.Type() == ty) {
return value;
}
auto* el_ty = sem::Type::ElementOf(ty);
if (el_ty == nullptr) {
- return {};
+ return sem::Constant{};
}
if (value.ElementType() == el_ty) {
return sem::Constant(ty, value.GetElements());
}
- return sem::Constant(ty, Convert(value.GetElements(), el_ty));
+ if (auto res = MaterializeElements(value.GetElements(), el_ty, *builder_, source)) {
+ return sem::Constant(ty, std::move(res.Get()));
+ }
+ return utils::Failure;
}
} // namespace tint::resolver