tint/resolver: Evaluate constant index accessors

If the object and index are both constant expressions, resolve the
index as a constant.

Note: Expectations have been updated for indexing on 'let's. Once
'const' is introduced, 'let' will no longer be treated as a constant
expression, and these expectations will be reverted.

Bug: tint:1580
Change-Id: I42793d36c1a5f82890ccaa5dbbb71b4346493bef
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/94329
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Commit-Queue: Ben Clayton <bclayton@chromium.org>
This commit is contained in:
Ben Clayton
2022-06-23 15:46:54 +00:00
committed by Dawn LUCI CQ
parent c9787e774a
commit c1cb9dc1a5
30 changed files with 230 additions and 75 deletions

View File

@@ -171,17 +171,25 @@ class Resolver {
const ast::ExpressionList& params,
uint32_t* id);
//////////////////////////////////////////////////////////////////////////////
// AST and Type traversal methods
//////////////////////////////////////////////////////////////////////////////
/// Expression traverses the graph of expressions starting at `expr`, building a postordered
/// list (leaf-first) of all the expression nodes. Each of the expressions are then resolved by
/// dispatching to the appropriate expression handlers below.
/// @returns the resolved semantic node for the expression `expr`, or nullptr on failure.
sem::Expression* Expression(const ast::Expression* expr);
////////////////////////////////////////////////////////////////////////////////////////////////
// Expression resolving methods
//
// Returns the semantic node pointer on success, nullptr on failure.
//
// These methods are invoked by Expression(), in postorder (child-first). These methods should
// not attempt to resolve their children. This design avoids recursion, which is a common cause
// of stack-overflows.
////////////////////////////////////////////////////////////////////////////////////////////////
sem::Expression* IndexAccessor(const ast::IndexAccessorExpression*);
sem::Expression* Binary(const ast::BinaryExpression*);
sem::Expression* Bitcast(const ast::BitcastExpression*);
sem::Call* Call(const ast::CallExpression*);
sem::Expression* Expression(const ast::Expression*);
sem::Function* Function(const ast::Function*);
sem::Call* FunctionCall(const ast::CallExpression*,
sem::Function* target,
@@ -195,6 +203,29 @@ class Resolver {
sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
////////////////////////////////////////////////////////////////////////////////////////////////
/// Constant value evaluation methods
///
/// These methods are called from the expression resolving methods, and so child-expression
/// nodes are guaranteed to have been already resolved and any constant values calculated.
////////////////////////////////////////////////////////////////////////////////////////////////
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);
sem::Constant EvaluateConstantValue(const ast::IndexAccessorExpression* call,
const sem::Type* type);
/// 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
ConstantResult ConvertValue(const sem::Constant& value,
const sem::Type* target_type,
const Source& source);
/// 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`.
@@ -379,23 +410,6 @@ class Resolver {
/// Adds the given note message to the diagnostics
void AddNote(const std::string& msg, const Source& source) const;
//////////////////////////////////////////////////////////////////////////////
/// 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
ConstantResult ConvertValue(const sem::Constant& value,
const sem::Type* target_type,
const Source& source);
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);
/// @returns true if the symbol is the name of a builtin function.
bool IsBuiltin(Symbol) const;

View File

@@ -155,7 +155,8 @@ sem::Constant Resolver::EvaluateConstantValue(const ast::Expression* expr, const
return Switch(
expr, //
[&](const ast::LiteralExpression* e) { return EvaluateConstantValue(e, type); },
[&](const ast::CallExpression* e) { return EvaluateConstantValue(e, type); });
[&](const ast::CallExpression* e) { return EvaluateConstantValue(e, type); },
[&](const ast::IndexAccessorExpression* e) { return EvaluateConstantValue(e, type); });
}
sem::Constant Resolver::EvaluateConstantValue(const ast::LiteralExpression* literal,
@@ -255,6 +256,54 @@ sem::Constant Resolver::EvaluateConstantValue(const ast::CallExpression* call,
elements.value());
}
sem::Constant Resolver::EvaluateConstantValue(const ast::IndexAccessorExpression* accessor,
const sem::Type* el_ty) {
auto* obj_sem = builder_->Sem().Get(accessor->object);
if (!obj_sem) {
return {};
}
auto obj_val = obj_sem->ConstantValue();
if (!obj_val) {
return {};
}
auto* idx_sem = builder_->Sem().Get(accessor->index);
if (!idx_sem) {
return {};
}
auto idx_val = idx_sem->ConstantValue();
if (!idx_val || idx_val.ElementCount() != 1) {
return {};
}
AInt idx = idx_val.Element<AInt>(0);
// The immediate child element count.
uint32_t el_count = 0;
sem::Type::ElementOf(obj_val.Type(), &el_count);
// The total number of most-nested elements per child element type.
uint32_t step = 0;
sem::Type::DeepestElementOf(el_ty, &step);
if (idx < 0 || idx >= el_count) {
auto clamped = std::min<AInt::type>(std::max<AInt::type>(idx, 0), el_count - 1);
AddWarning("index " + std::to_string(idx) + " out of bounds [0.." +
std::to_string(el_count - 1) + "]. Clamping index to " +
std::to_string(clamped),
accessor->index->source);
idx = clamped;
}
return sem::Constant{el_ty, obj_val.WithElements([&](auto&& v) {
using VEC = std::decay_t<decltype(v)>;
return sem::Constant::Elements(
VEC(v.begin() + (idx * step), v.begin() + (idx + 1) * step));
})};
}
utils::Result<sem::Constant> Resolver::ConvertValue(const sem::Constant& value,
const sem::Type* ty,
const Source& source) {

View File

@@ -608,5 +608,113 @@ TEST_F(ResolverConstantsTest, Mat3x2_Construct_Columns_af) {
EXPECT_EQ(sem->ConstantValue().Element<AFloat>(5).value, 6._a);
}
TEST_F(ResolverConstantsTest, Vec3_Index) {
auto* expr = IndexAccessor(vec3<i32>(1_i, 2_i, 3_i), 2_i);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::I32>());
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 1u);
EXPECT_EQ(sem->ConstantValue().Element<i32>(0).value, 3_i);
}
TEST_F(ResolverConstantsTest, Vec3_Index_OOB_High) {
auto* expr = IndexAccessor(vec3<i32>(1_i, 2_i, 3_i), Expr(Source{{12, 34}}, 3_i));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 warning: index 3 out of bounds [0..2]. Clamping index to 2");
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::I32>());
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 1u);
EXPECT_EQ(sem->ConstantValue().Element<i32>(0).value, 3_i);
}
TEST_F(ResolverConstantsTest, Vec3_Index_OOB_Low) {
auto* expr = IndexAccessor(vec3<i32>(1_i, 2_i, 3_i), Expr(Source{{12, 34}}, -3_i));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 warning: index -3 out of bounds [0..2]. Clamping index to 0");
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
ASSERT_TRUE(sem->Type()->Is<sem::I32>());
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::I32>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 1u);
EXPECT_EQ(sem->ConstantValue().Element<i32>(0).value, 1_i);
}
TEST_F(ResolverConstantsTest, Mat3x2_Index) {
auto* expr = IndexAccessor(
mat3x2<f32>(vec2<f32>(1._a, 2._a), vec2<f32>(3._a, 4._a), vec2<f32>(5._a, 6._a)), 2_i);
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_EQ(vec->Width(), 2u);
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 2u);
EXPECT_EQ(sem->ConstantValue().Element<f32>(0).value, 5._a);
EXPECT_EQ(sem->ConstantValue().Element<f32>(1).value, 6._a);
}
TEST_F(ResolverConstantsTest, Mat3x2_Index_OOB_High) {
auto* expr = IndexAccessor(
mat3x2<f32>(vec2<f32>(1._a, 2._a), vec2<f32>(3._a, 4._a), vec2<f32>(5._a, 6._a)),
Expr(Source{{12, 34}}, 3_i));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 warning: index 3 out of bounds [0..2]. Clamping index to 2");
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_EQ(vec->Width(), 2u);
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 2u);
EXPECT_EQ(sem->ConstantValue().Element<f32>(0).value, 5._a);
EXPECT_EQ(sem->ConstantValue().Element<f32>(1).value, 6._a);
}
TEST_F(ResolverConstantsTest, Mat3x2_Index_OOB_Low) {
auto* expr = IndexAccessor(
mat3x2<f32>(vec2<f32>(1._a, 2._a), vec2<f32>(3._a, 4._a), vec2<f32>(5._a, 6._a)),
Expr(Source{{12, 34}}, -3_i));
WrapInFunction(expr);
EXPECT_TRUE(r()->Resolve()) << r()->error();
EXPECT_EQ(r()->error(), "12:34 warning: index -3 out of bounds [0..2]. Clamping index to 0");
auto* sem = Sem().Get(expr);
EXPECT_NE(sem, nullptr);
auto* vec = sem->Type()->As<sem::Vector>();
ASSERT_NE(vec, nullptr);
EXPECT_EQ(vec->Width(), 2u);
EXPECT_EQ(sem->ConstantValue().Type(), sem->Type());
EXPECT_TRUE(sem->ConstantValue().ElementType()->Is<sem::F32>());
ASSERT_EQ(sem->ConstantValue().ElementCount(), 2u);
EXPECT_EQ(sem->ConstantValue().Element<f32>(0).value, 1._a);
EXPECT_EQ(sem->ConstantValue().Element<f32>(1).value, 2._a);
}
} // namespace
} // namespace tint::resolver