validator: Support assignment through pointer

Also enable a test to check assigning to scalar literal.

Fixed: tint:419
Change-Id: Ic565af22c4ef6b60c41faaf9fabe3bd55fe48d2d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/37961
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: David Neto <dneto@google.com>
Auto-Submit: David Neto <dneto@google.com>
This commit is contained in:
David Neto 2021-01-18 22:17:25 +00:00 committed by Commit Bot service account
parent 579f6a0450
commit 71012dcc2f
4 changed files with 192 additions and 56 deletions

View File

@ -30,6 +30,7 @@
#include "src/ast/type/array_type.h"
#include "src/ast/type/i32_type.h"
#include "src/ast/type/matrix_type.h"
#include "src/ast/type/pointer_type.h"
#include "src/ast/type/struct_type.h"
#include "src/ast/type/u32_type.h"
#include "src/ast/type/vector_type.h"
@ -277,6 +278,10 @@ bool ValidatorImpl::ValidateDeclStatement(
"redeclared identifier '" + module_.SymbolToName(symbol) + "'");
return false;
}
// TODO(dneto): Check type compatibility of the initializer.
// - if it's non-constant, then is storable or can be dereferenced to be
// storable.
// - types match or the RHS can be dereferenced to equal the LHS type.
variable_stack_.set(symbol, decl->variable());
if (auto* arr =
decl->variable()->type()->UnwrapAll()->As<ast::type::Array>()) {
@ -429,57 +434,78 @@ bool ValidatorImpl::ValidateCallExpr(const ast::CallExpression* expr) {
return true;
}
bool ValidatorImpl::ValidateAssign(const ast::AssignmentStatement* a) {
if (!a) {
bool ValidatorImpl::ValidateBadAssignmentToIdentifier(
const ast::AssignmentStatement* assign) {
auto* ident = assign->lhs()->As<ast::IdentifierExpression>();
if (!ident) {
// It wasn't an identifier in the first place.
return true;
}
ast::Variable* var;
if (variable_stack_.get(ident->symbol(), &var)) {
// Give a nicer message if the LHS of the assignment is a const identifier.
// It's likely to be a common programmer error.
if (var->is_const()) {
add_error(assign->source(), "v-0021",
"cannot re-assign a constant: '" +
module_.SymbolToName(ident->symbol()) + "'");
return false;
}
} else {
// The identifier is not defined. This should already have been caught
// when validating the subexpression.
add_error(
ident->source(), "v-0006",
"'" + module_.SymbolToName(ident->symbol()) + "' is not declared");
return false;
}
if (!(ValidateConstant(a))) {
return false;
}
if (!(ValidateExpression(a->lhs()) && ValidateExpression(a->rhs()))) {
return false;
}
if (!ValidateResultTypes(a)) {
return false;
}
return true;
}
bool ValidatorImpl::ValidateConstant(const ast::AssignmentStatement* assign) {
bool ValidatorImpl::ValidateAssign(const ast::AssignmentStatement* assign) {
if (!assign) {
return false;
}
if (auto* ident = assign->lhs()->As<ast::IdentifierExpression>()) {
ast::Variable* var;
if (variable_stack_.get(ident->symbol(), &var)) {
if (var->is_const()) {
add_error(assign->source(), "v-0021",
"cannot re-assign a constant: '" +
module_.SymbolToName(ident->symbol()) + "'");
return false;
}
auto* lhs = assign->lhs();
auto* rhs = assign->rhs();
if (!ValidateExpression(lhs)) {
return false;
}
if (!ValidateExpression(rhs)) {
return false;
}
// Pointers are not storable in WGSL, but the right-hand side must be
// storable. The raw right-hand side might be a pointer value which must be
// loaded (dereferenced) to provide the value to be stored.
auto* rhs_result_type = rhs->result_type()->UnwrapAll();
if (!IsStorable(rhs_result_type)) {
add_error(assign->source(), "v-000x",
"invalid assignment: right-hand-side is not storable: " +
rhs->result_type()->type_name());
return false;
}
auto* lhs_result_type = lhs->result_type()->UnwrapIfNeeded();
if (auto* lhs_reference_type = As<ast::type::Pointer>(lhs_result_type)) {
auto* lhs_store_type = lhs_reference_type->type()->UnwrapIfNeeded();
if (lhs_store_type != rhs_result_type) {
add_error(assign->source(), "v-000x",
"invalid assignment: can't assign value of type '" +
rhs_result_type->type_name() + "' to '" +
lhs_store_type->type_name() + "'");
return false;
}
}
return true;
}
bool ValidatorImpl::ValidateResultTypes(const ast::AssignmentStatement* a) {
if (!a->lhs()->result_type() || !a->rhs()->result_type()) {
add_error(a->source(), "result_type() is nullptr");
} else {
if (!ValidateBadAssignmentToIdentifier(assign)) {
return false;
}
// Issue a generic error.
add_error(
assign->source(), "v-000x",
"invalid assignment: left-hand-side does not reference storage: " +
lhs->result_type()->type_name());
return false;
}
auto* lhs_result_type = a->lhs()->result_type()->UnwrapAll();
auto* rhs_result_type = a->rhs()->result_type()->UnwrapAll();
if (lhs_result_type != rhs_result_type) {
// TODO(sarahM0): figur out what should be the error number.
add_error(a->source(), "v-000x",
"invalid assignment of '" + lhs_result_type->type_name() +
"' to '" + rhs_result_type->type_name() + "'");
return false;
}
return true;
}

View File

@ -100,6 +100,12 @@ class ValidatorImpl {
/// @param assign the assignment to check
/// @returns true if the validation was successful
bool ValidateAssign(const ast::AssignmentStatement* assign);
/// Validates a bad assignment to an identifier. Issues an error
/// and returns false if the left hand side is an identifier.
/// @param assign the assignment to check
/// @returns true if the LHS of theassignment is not an identifier expression
bool ValidateBadAssignmentToIdentifier(
const ast::AssignmentStatement* assign);
/// Validates an expression
/// @param expr the expression to check
/// @return true if the expression is valid
@ -108,14 +114,6 @@ class ValidatorImpl {
/// @param ident the identifer to check if its in the scope
/// @return true if idnet was defined
bool ValidateIdentifier(const ast::IdentifierExpression* ident);
/// Validates if the input follows type checking rules
/// @param assign the assignment to check
/// @returns ture if successful
bool ValidateResultTypes(const ast::AssignmentStatement* assign);
/// Validate v-0021: Cannot re-assign a constant
/// @param assign is the assigment to check if its lhs is a const
/// @returns false if lhs of assign is a constant identifier
bool ValidateConstant(const ast::AssignmentStatement* assign);
/// Validates declaration name uniqueness
/// @param decl is the new declaration to be added
/// @returns true if no previous declaration with the `decl` 's name
@ -154,6 +152,11 @@ class ValidatorImpl {
/// @returns true if the given type is storable.
bool IsStorable(ast::type::Type* type);
/// Testing method to inserting a given variable into the current scope.
void RegisterVariableForTesting(ast::Variable* var) {
variable_stack_.set(var->symbol(), var);
}
private:
const ast::Module& module_;
diag::List diags_;

View File

@ -60,18 +60,27 @@ namespace {
class ValidatorTest : public ValidatorTestHelper, public testing::Test {};
TEST_F(ValidatorTest, DISABLED_AssignToScalar_Fail) {
TEST_F(ValidatorTest, AssignToScalar_Fail) {
// var my_var : i32 = 2;
// 1 = my_var;
auto* var = Var("my_var", ast::StorageClass::kNone, ty.i32, Expr(2),
ast::VariableDecorationList{});
auto* lhs = Expr(1);
auto* rhs = Expr("my_var");
SetSource(Source{Source::Location{12, 34}});
create<ast::AssignmentStatement>(lhs, rhs);
auto* assign = create<ast::AssignmentStatement>(lhs, rhs);
RegisterVariable(var);
EXPECT_TRUE(td()->DetermineResultType(assign));
// TODO(sarahM0): Invalidate assignment to scalar.
EXPECT_FALSE(v()->ValidateAssign(assign));
ASSERT_TRUE(v()->has_error());
// TODO(sarahM0): figure out what should be the error number.
EXPECT_EQ(v()->error(), "12:34 v-000x: invalid assignment");
EXPECT_EQ(v()->error(),
"12:34 v-000x: invalid assignment: left-hand-side does not "
"reference storage: __i32");
}
TEST_F(ValidatorTest, UsingUndefinedVariable_Fail) {
@ -116,11 +125,75 @@ TEST_F(ValidatorTest, AssignCompatibleTypes_Pass) {
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
td()->RegisterVariableForTesting(var);
RegisterVariable(var);
EXPECT_TRUE(td()->DetermineResultType(assign)) << td()->error();
ASSERT_NE(lhs->result_type(), nullptr);
ASSERT_NE(rhs->result_type(), nullptr);
EXPECT_TRUE(v()->ValidateResultTypes(assign));
EXPECT_TRUE(v()->ValidateAssign(assign)) << v()->error();
}
TEST_F(ValidatorTest, AssignCompatibleTypesThroughAlias_Pass) {
// alias myint = i32;
// var a :myint = 2;
// a = 2
auto* myint = ty.alias("myint", ty.i32);
auto* var = Var("a", ast::StorageClass::kNone, myint, Expr(2),
ast::VariableDecorationList{});
auto* lhs = Expr("a");
auto* rhs = Expr(2);
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
RegisterVariable(var);
EXPECT_TRUE(td()->DetermineResultType(assign)) << td()->error();
ASSERT_NE(lhs->result_type(), nullptr);
ASSERT_NE(rhs->result_type(), nullptr);
EXPECT_TRUE(v()->ValidateAssign(assign)) << v()->error();
}
TEST_F(ValidatorTest, AssignCompatibleTypesInferRHSLoad_Pass) {
// var a :i32 = 2;
// var b :i32 = 3;
// a = b;
auto* var_a = Var("a", ast::StorageClass::kNone, ty.i32, Expr(2),
ast::VariableDecorationList{});
auto* var_b = Var("b", ast::StorageClass::kNone, ty.i32, Expr(3),
ast::VariableDecorationList{});
auto* lhs = Expr("a");
auto* rhs = Expr("b");
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
RegisterVariable(var_a);
RegisterVariable(var_b);
EXPECT_TRUE(td()->DetermineResultType(assign)) << td()->error();
ASSERT_NE(lhs->result_type(), nullptr);
ASSERT_NE(rhs->result_type(), nullptr);
EXPECT_TRUE(v()->ValidateAssign(assign)) << v()->error();
}
TEST_F(ValidatorTest, AssignThroughPointer_Pass) {
// var a :i32;
// const b : ptr<function,i32> = a;
// b = 2;
const auto func = ast::StorageClass::kFunction;
auto* var_a = Var("a", func, ty.i32, Expr(2), {});
auto* var_b = Const("b", ast::StorageClass::kNone, ty.pointer<int>(func),
Expr("a"), {});
auto* lhs = Expr("b");
auto* rhs = Expr(2);
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
RegisterVariable(var_a);
RegisterVariable(var_b);
EXPECT_TRUE(td()->DetermineResultType(assign)) << td()->error();
ASSERT_NE(lhs->result_type(), nullptr);
ASSERT_NE(rhs->result_type(), nullptr);
EXPECT_TRUE(v()->ValidateAssign(assign)) << v()->error();
}
TEST_F(ValidatorTest, AssignIncompatibleTypes_Fail) {
@ -137,16 +210,42 @@ TEST_F(ValidatorTest, AssignIncompatibleTypes_Fail) {
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
td()->RegisterVariableForTesting(var);
RegisterVariable(var);
EXPECT_TRUE(td()->DetermineResultType(assign)) << td()->error();
ASSERT_NE(lhs->result_type(), nullptr);
ASSERT_NE(rhs->result_type(), nullptr);
EXPECT_FALSE(v()->ValidateResultTypes(assign));
EXPECT_FALSE(v()->ValidateAssign(assign));
ASSERT_TRUE(v()->has_error());
// TODO(sarahM0): figure out what should be the error number.
EXPECT_EQ(v()->error(),
"12:34 v-000x: invalid assignment of '__i32' to '__f32'");
"12:34 v-000x: invalid assignment: can't assign value of type "
"'__f32' to '__i32'");
}
TEST_F(ValidatorTest, AssignThroughPointerWrongeStoreType_Fail) {
// var a :f32;
// const b : ptr<function,f32> = a;
// b = 2;
const auto priv = ast::StorageClass::kFunction;
auto* var_a = Var("a", priv, ty.f32, Expr(2), {});
auto* var_b = Const("b", ast::StorageClass::kNone, ty.pointer<float>(priv),
Expr("a"), {});
auto* lhs = Expr("a");
auto* rhs = Expr(2);
auto* assign = create<ast::AssignmentStatement>(
Source{Source::Location{12, 34}}, lhs, rhs);
RegisterVariable(var_a);
RegisterVariable(var_b);
EXPECT_TRUE(td()->DetermineResultType(assign)) << td()->error();
ASSERT_NE(lhs->result_type(), nullptr);
ASSERT_NE(rhs->result_type(), nullptr);
EXPECT_FALSE(v()->ValidateAssign(assign));
EXPECT_EQ(v()->error(),
"12:34 v-000x: invalid assignment: can't assign value of type "
"'__i32' to '__f32'");
}
TEST_F(ValidatorTest, AssignCompatibleTypesInBlockStatement_Pass) {
@ -199,7 +298,8 @@ TEST_F(ValidatorTest, AssignIncompatibleTypesInBlockStatement_Fail) {
ASSERT_TRUE(v()->has_error());
// TODO(sarahM0): figure out what should be the error number.
EXPECT_EQ(v()->error(),
"12:34 v-000x: invalid assignment of '__i32' to '__f32'");
"12:34 v-000x: invalid assignment: can't assign value of type "
"'__f32' to '__i32'");
}
TEST_F(ValidatorTest, GlobalVariableWithStorageClass_Pass) {

View File

@ -39,6 +39,13 @@ class ValidatorTestHelper : public ast::BuilderWithModule {
/// @returns a pointer to the type_determiner object
TypeDeterminer* td() const { return td_.get(); }
/// Inserts a variable into the current scope.
/// @param var the variable to register.
void RegisterVariable(ast::Variable* var) {
v_->RegisterVariableForTesting(var);
td_->RegisterVariableForTesting(var);
}
private:
std::unique_ptr<ValidatorImpl> v_;
std::unique_ptr<TypeDeterminer> td_;