Resolver: Check that every AST node is reached once
AST nodes must not be shared. Diamonds in the AST will cause all sorts of exciting, non trivial bugs. All AST nodes must be reached by the Resolver. There are two common reasons why they may not be: (a) They were constructed and not attached to the AST. Several transforms scan the full list of constructed AST nodes to find nodes of a given type. Having detached nodes will likely cause bugs in these transforms. Detached nodes is also just a waste of memory. (b) They are attached to the AST, but the resolver did not traverse them. Having the resolver skip over parts of the AST will fail to catch validation issues, and will leave semantic gaps, likely breaking downstream logic. Bug: tint:469 Change-Id: I143b84fd830699f874d2936146f0e93197db610c Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/47778 Kokoro: Kokoro <noreply+kokoro@google.com> Commit-Queue: Ben Clayton <bclayton@chromium.org> Reviewed-by: Antonio Maiorano <amaiorano@google.com>
This commit is contained in:
parent
b8ea59149e
commit
6fcefe4c25
|
@ -420,6 +420,20 @@ INSTANTIATE_TEST_SUITE_P(
|
|||
Params{ty_mat3x3<f32>, (default_mat3x3.align - 1) * 7, false},
|
||||
Params{ty_mat4x4<f32>, (default_mat4x4.align - 1) * 7, false}));
|
||||
|
||||
TEST_F(ArrayStrideTest, MultipleDecorations) {
|
||||
auto* arr = create<type::Array>(ty.i32(), 4,
|
||||
ast::DecorationList{
|
||||
create<ast::StrideDecoration>(4),
|
||||
create<ast::StrideDecoration>(4),
|
||||
});
|
||||
|
||||
Global(Source{{12, 34}}, "myarray", arr, ast::StorageClass::kInput);
|
||||
|
||||
EXPECT_FALSE(r()->Resolve());
|
||||
EXPECT_EQ(r()->error(),
|
||||
"12:34 error: array must have at most one [[stride]] decoration");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace ArrayStrideTests
|
||||
} // namespace resolver
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include "src/ast/access_decoration.h"
|
||||
#include "src/ast/assignment_statement.h"
|
||||
#include "src/ast/bitcast_expression.h"
|
||||
#include "src/ast/break_statement.h"
|
||||
|
@ -178,20 +179,22 @@ bool Resolver::IsValidAssignment(type::Type* lhs, type::Type* rhs) {
|
|||
}
|
||||
|
||||
bool Resolver::ResolveInternal() {
|
||||
Mark(&builder_->AST());
|
||||
|
||||
// Process everything else in the order they appear in the module. This is
|
||||
// necessary for validation of use-before-declaration.
|
||||
for (auto* decl : builder_->AST().GlobalDeclarations()) {
|
||||
if (decl->Is<type::Type>()) {
|
||||
if (auto* str = decl->As<type::Struct>()) {
|
||||
if (!Structure(str)) {
|
||||
if (auto* ty = decl->As<type::Type>()) {
|
||||
if (!Type(ty)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (auto* func = decl->As<ast::Function>()) {
|
||||
Mark(func);
|
||||
if (!Function(func)) {
|
||||
return false;
|
||||
}
|
||||
} else if (auto* var = decl->As<ast::Variable>()) {
|
||||
Mark(var);
|
||||
if (!GlobalVariable(var)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -202,6 +205,34 @@ bool Resolver::ResolveInternal() {
|
|||
}
|
||||
}
|
||||
|
||||
for (auto* node : builder_->ASTNodes().Objects()) {
|
||||
if (marked_.count(node) == 0) {
|
||||
if (node->Is<ast::AccessDecoration>()) {
|
||||
// These are generated by the WGSL parser, used to build
|
||||
// type::AccessControls and then leaked.
|
||||
// Once we introduce AST types, this should be fixed.
|
||||
continue;
|
||||
}
|
||||
TINT_ICE(diagnostics_) << "AST node '" << node->TypeInfo().name
|
||||
<< "' was not reached by the resolver\n"
|
||||
<< "At: " << node->source();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Resolver::Type(type::Type* ty) {
|
||||
ty = ty->UnwrapAliasIfNeeded();
|
||||
if (auto* str = ty->As<type::Struct>()) {
|
||||
if (!Structure(str)) {
|
||||
return false;
|
||||
}
|
||||
} else if (auto* arr = ty->As<type::Array>()) {
|
||||
if (!Array(arr, Source{})) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -275,6 +306,7 @@ bool Resolver::GlobalVariable(ast::Variable* var) {
|
|||
}
|
||||
|
||||
for (auto* deco : var->decorations()) {
|
||||
Mark(deco);
|
||||
if (!(deco->Is<ast::BindingDecoration>() ||
|
||||
deco->Is<ast::BuiltinDecoration>() ||
|
||||
deco->Is<ast::ConstantIdDecoration>() ||
|
||||
|
@ -287,6 +319,7 @@ bool Resolver::GlobalVariable(ast::Variable* var) {
|
|||
}
|
||||
|
||||
if (var->has_constructor()) {
|
||||
Mark(var->constructor());
|
||||
if (!Expression(var->constructor())) {
|
||||
return false;
|
||||
}
|
||||
|
@ -570,10 +603,17 @@ bool Resolver::Function(ast::Function* func) {
|
|||
|
||||
variable_stack_.push_scope();
|
||||
for (auto* param : func->params()) {
|
||||
Mark(param);
|
||||
auto* param_info = Variable(param);
|
||||
if (!param_info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(amaiorano): Validate parameter decorations
|
||||
for (auto* deco : param->decorations()) {
|
||||
Mark(deco);
|
||||
}
|
||||
|
||||
variable_stack_.set(param->symbol(), param_info);
|
||||
func_info->parameters.emplace_back(param_info);
|
||||
|
||||
|
@ -642,12 +682,20 @@ bool Resolver::Function(ast::Function* func) {
|
|||
}
|
||||
|
||||
if (func->body()) {
|
||||
Mark(func->body());
|
||||
if (!BlockStatement(func->body())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
variable_stack_.pop_scope();
|
||||
|
||||
for (auto* deco : func->decorations()) {
|
||||
Mark(deco);
|
||||
}
|
||||
for (auto* deco : func->return_type_decorations()) {
|
||||
Mark(deco);
|
||||
}
|
||||
|
||||
if (!ValidateFunction(func)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -668,6 +716,7 @@ bool Resolver::BlockStatement(const ast::BlockStatement* stmt) {
|
|||
|
||||
bool Resolver::Statements(const ast::StatementList& stmts) {
|
||||
for (auto* stmt : stmts) {
|
||||
Mark(stmt);
|
||||
if (!Statement(stmt)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -698,6 +747,7 @@ bool Resolver::Statement(ast::Statement* stmt) {
|
|||
return true;
|
||||
}
|
||||
if (auto* c = stmt->As<ast::CallStatement>()) {
|
||||
Mark(c->expr());
|
||||
return Expression(c->expr());
|
||||
}
|
||||
if (auto* c = stmt->As<ast::CaseStatement>()) {
|
||||
|
@ -732,11 +782,15 @@ bool Resolver::Statement(ast::Statement* stmt) {
|
|||
// these would make their BlockInfo siblings as in the AST, but we want the
|
||||
// body BlockInfo to parent the continuing BlockInfo for semantics and
|
||||
// validation. Also, we need to set their types differently.
|
||||
Mark(l->body());
|
||||
return BlockScope(l->body(), BlockInfo::Type::kLoop, [&] {
|
||||
if (!Statements(l->body()->list())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (l->continuing()) { // has_continuing() also checks for empty()
|
||||
Mark(l->continuing());
|
||||
}
|
||||
if (l->has_continuing()) {
|
||||
if (!BlockScope(l->continuing(), BlockInfo::Type::kLoopContinuing,
|
||||
[&] { return Statements(l->continuing()->list()); })) {
|
||||
|
@ -764,11 +818,16 @@ bool Resolver::Statement(ast::Statement* stmt) {
|
|||
}
|
||||
|
||||
bool Resolver::CaseStatement(ast::CaseStatement* stmt) {
|
||||
Mark(stmt->body());
|
||||
for (auto* sel : stmt->selectors()) {
|
||||
Mark(sel);
|
||||
}
|
||||
return BlockScope(stmt->body(), BlockInfo::Type::kSwitchCase,
|
||||
[&] { return Statements(stmt->body()->list()); });
|
||||
}
|
||||
|
||||
bool Resolver::IfStatement(ast::IfStatement* stmt) {
|
||||
Mark(stmt->condition());
|
||||
if (!Expression(stmt->condition())) {
|
||||
return false;
|
||||
}
|
||||
|
@ -781,11 +840,13 @@ bool Resolver::IfStatement(ast::IfStatement* stmt) {
|
|||
return false;
|
||||
}
|
||||
|
||||
Mark(stmt->body());
|
||||
if (!BlockStatement(stmt->body())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto* else_stmt : stmt->else_statements()) {
|
||||
Mark(else_stmt);
|
||||
// Else statements are a bit unusual - they're owned by the if-statement,
|
||||
// not a BlockStatement.
|
||||
constexpr ast::BlockStatement* no_block_statement = nullptr;
|
||||
|
@ -793,9 +854,13 @@ bool Resolver::IfStatement(ast::IfStatement* stmt) {
|
|||
builder_->create<sem::Statement>(else_stmt, no_block_statement);
|
||||
builder_->Sem().Add(else_stmt, sem_else_stmt);
|
||||
ScopedAssignment<sem::Statement*> sa(current_statement_, sem_else_stmt);
|
||||
if (!Expression(else_stmt->condition())) {
|
||||
if (auto* cond = else_stmt->condition()) {
|
||||
Mark(cond);
|
||||
if (!Expression(cond)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Mark(else_stmt->body());
|
||||
if (!BlockStatement(else_stmt->body())) {
|
||||
return false;
|
||||
}
|
||||
|
@ -805,6 +870,7 @@ bool Resolver::IfStatement(ast::IfStatement* stmt) {
|
|||
|
||||
bool Resolver::Expressions(const ast::ExpressionList& list) {
|
||||
for (auto* expr : list) {
|
||||
Mark(expr);
|
||||
if (!Expression(expr)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -813,11 +879,6 @@ bool Resolver::Expressions(const ast::ExpressionList& list) {
|
|||
}
|
||||
|
||||
bool Resolver::Expression(ast::Expression* expr) {
|
||||
// This is blindly called above, so in some cases the expression won't exist.
|
||||
if (!expr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TypeOf(expr)) {
|
||||
return true; // Already resolved
|
||||
}
|
||||
|
@ -853,9 +914,11 @@ bool Resolver::Expression(ast::Expression* expr) {
|
|||
}
|
||||
|
||||
bool Resolver::ArrayAccessor(ast::ArrayAccessorExpression* expr) {
|
||||
Mark(expr->array());
|
||||
if (!Expression(expr->array())) {
|
||||
return false;
|
||||
}
|
||||
Mark(expr->idx_expr());
|
||||
if (!Expression(expr->idx_expr())) {
|
||||
return false;
|
||||
}
|
||||
|
@ -892,6 +955,7 @@ bool Resolver::ArrayAccessor(ast::ArrayAccessorExpression* expr) {
|
|||
}
|
||||
|
||||
bool Resolver::Bitcast(ast::BitcastExpression* expr) {
|
||||
Mark(expr->expr());
|
||||
if (!Expression(expr->expr())) {
|
||||
return false;
|
||||
}
|
||||
|
@ -907,6 +971,7 @@ bool Resolver::Call(ast::CallExpression* call) {
|
|||
// The expression has to be an identifier as you can't store function pointers
|
||||
// but, if it isn't we'll just use the normal result determination to be on
|
||||
// the safe side.
|
||||
Mark(call->func());
|
||||
auto* ident = call->func()->As<ast::IdentifierExpression>();
|
||||
if (!ident) {
|
||||
diagnostics_.add_error("call target is not an identifier", call->source());
|
||||
|
@ -993,6 +1058,7 @@ bool Resolver::IntrinsicCall(ast::CallExpression* call,
|
|||
bool Resolver::Constructor(ast::ConstructorExpression* expr) {
|
||||
if (auto* type_ctor = expr->As<ast::TypeConstructorExpression>()) {
|
||||
for (auto* value : type_ctor->values()) {
|
||||
Mark(value);
|
||||
if (!Expression(value)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1003,13 +1069,14 @@ bool Resolver::Constructor(ast::ConstructorExpression* expr) {
|
|||
// obey the constructor type rules laid out in
|
||||
// https://gpuweb.github.io/gpuweb/wgsl.html#type-constructor-expr.
|
||||
if (auto* vec_type = type_ctor->type()->As<type::Vector>()) {
|
||||
return VectorConstructor(vec_type, type_ctor->values());
|
||||
return ValidateVectorConstructor(vec_type, type_ctor->values());
|
||||
}
|
||||
if (auto* mat_type = type_ctor->type()->As<type::Matrix>()) {
|
||||
return MatrixConstructor(mat_type, type_ctor->values());
|
||||
return ValidateMatrixConstructor(mat_type, type_ctor->values());
|
||||
}
|
||||
// TODO(crbug.com/tint/634): Validate array constructor
|
||||
} else if (auto* scalar_ctor = expr->As<ast::ScalarConstructorExpression>()) {
|
||||
Mark(scalar_ctor->literal());
|
||||
SetType(expr, scalar_ctor->literal()->type());
|
||||
} else {
|
||||
TINT_ICE(diagnostics_) << "unexpected constructor expression type";
|
||||
|
@ -1017,7 +1084,7 @@ bool Resolver::Constructor(ast::ConstructorExpression* expr) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Resolver::VectorConstructor(const type::Vector* vec_type,
|
||||
bool Resolver::ValidateVectorConstructor(const type::Vector* vec_type,
|
||||
const ast::ExpressionList& values) {
|
||||
type::Type* elem_type = vec_type->type()->UnwrapAll();
|
||||
size_t value_cardinality_sum = 0;
|
||||
|
@ -1085,7 +1152,7 @@ bool Resolver::VectorConstructor(const type::Vector* vec_type,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Resolver::MatrixConstructor(const type::Matrix* matrix_type,
|
||||
bool Resolver::ValidateMatrixConstructor(const type::Matrix* matrix_type,
|
||||
const ast::ExpressionList& values) {
|
||||
// Zero Value expression
|
||||
if (values.empty()) {
|
||||
|
@ -1198,6 +1265,7 @@ bool Resolver::Identifier(ast::IdentifierExpression* expr) {
|
|||
}
|
||||
|
||||
bool Resolver::MemberAccessor(ast::MemberAccessorExpression* expr) {
|
||||
Mark(expr->structure());
|
||||
if (!Expression(expr->structure())) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1209,8 +1277,9 @@ bool Resolver::MemberAccessor(ast::MemberAccessorExpression* expr) {
|
|||
std::vector<uint32_t> swizzle;
|
||||
|
||||
if (auto* ty = data_type->As<type::Struct>()) {
|
||||
auto* str = Structure(ty);
|
||||
Mark(expr->member());
|
||||
auto symbol = expr->member()->symbol();
|
||||
auto* str = Structure(ty);
|
||||
|
||||
const sem::StructMember* member = nullptr;
|
||||
for (auto* m : str->members) {
|
||||
|
@ -1236,6 +1305,7 @@ bool Resolver::MemberAccessor(ast::MemberAccessorExpression* expr) {
|
|||
builder_->Sem().Add(expr, builder_->create<sem::StructMemberAccess>(
|
||||
expr, ret, current_statement_, member));
|
||||
} else if (auto* vec = data_type->As<type::Vector>()) {
|
||||
Mark(expr->member());
|
||||
std::string str = builder_->Symbols().NameFor(expr->member()->symbol());
|
||||
auto size = str.size();
|
||||
swizzle.reserve(str.size());
|
||||
|
@ -1481,6 +1551,8 @@ bool Resolver::ValidateBinary(ast::BinaryExpression* expr) {
|
|||
}
|
||||
|
||||
bool Resolver::Binary(ast::BinaryExpression* expr) {
|
||||
Mark(expr->lhs());
|
||||
Mark(expr->rhs());
|
||||
if (!Expression(expr->lhs()) || !Expression(expr->rhs())) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1557,6 +1629,8 @@ bool Resolver::Binary(ast::BinaryExpression* expr) {
|
|||
}
|
||||
|
||||
bool Resolver::UnaryOp(ast::UnaryOpExpression* expr) {
|
||||
Mark(expr->expr());
|
||||
|
||||
// Result type matches the parameter type.
|
||||
if (!Expression(expr->expr())) {
|
||||
return false;
|
||||
|
@ -1569,6 +1643,8 @@ bool Resolver::UnaryOp(ast::UnaryOpExpression* expr) {
|
|||
|
||||
bool Resolver::VariableDeclStatement(const ast::VariableDeclStatement* stmt) {
|
||||
ast::Variable* var = stmt->variable();
|
||||
Mark(var);
|
||||
|
||||
type::Type* type = var->declared_type();
|
||||
|
||||
bool is_global = false;
|
||||
|
@ -1582,6 +1658,7 @@ bool Resolver::VariableDeclStatement(const ast::VariableDeclStatement* stmt) {
|
|||
}
|
||||
|
||||
if (auto* ctor = stmt->variable()->constructor()) {
|
||||
Mark(ctor);
|
||||
if (!Expression(ctor)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1602,11 +1679,16 @@ bool Resolver::VariableDeclStatement(const ast::VariableDeclStatement* stmt) {
|
|||
}
|
||||
}
|
||||
|
||||
for (auto* deco : var->decorations()) {
|
||||
// TODO(bclayton): Validate decorations
|
||||
Mark(deco);
|
||||
}
|
||||
|
||||
auto* info = Variable(var, type);
|
||||
if (!info) {
|
||||
return false;
|
||||
}
|
||||
// TODO(amaiorano): Remove this and fix tests. We're overriding the semantic
|
||||
// TODO(bclayton): Remove this and fix tests. We're overriding the semantic
|
||||
// type stored in info->type here with a possibly non-canonicalized type.
|
||||
info->type = type;
|
||||
variable_stack_.set(var->symbol(), info);
|
||||
|
@ -1860,9 +1942,16 @@ const sem::Array* Resolver::Array(type::Array* arr, const Source& source) {
|
|||
};
|
||||
|
||||
// Look for explicit stride via [[stride(n)]] decoration
|
||||
uint32_t explicit_stride = 0;
|
||||
for (auto* deco : arr->decorations()) {
|
||||
Mark(deco);
|
||||
if (auto* stride = deco->As<ast::StrideDecoration>()) {
|
||||
auto explicit_stride = stride->stride();
|
||||
if (explicit_stride) {
|
||||
diagnostics_.add_error(
|
||||
"array must have at most one [[stride]] decoration", source);
|
||||
return nullptr;
|
||||
}
|
||||
explicit_stride = stride->stride();
|
||||
bool is_valid_stride = (explicit_stride >= el_size) &&
|
||||
(explicit_stride >= el_align) &&
|
||||
(explicit_stride % el_align == 0);
|
||||
|
@ -1878,10 +1967,11 @@ const sem::Array* Resolver::Array(type::Array* arr, const Source& source) {
|
|||
source);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return create_semantic(explicit_stride);
|
||||
}
|
||||
}
|
||||
if (explicit_stride) {
|
||||
return create_semantic(explicit_stride);
|
||||
}
|
||||
|
||||
// Calculate implicit stride
|
||||
auto implicit_stride = utils::RoundUp(el_align, el_size);
|
||||
|
@ -1950,6 +2040,11 @@ Resolver::StructInfo* Resolver::Structure(type::Struct* str) {
|
|||
return info_it->second;
|
||||
}
|
||||
|
||||
Mark(str->impl());
|
||||
for (auto* deco : str->impl()->decorations()) {
|
||||
Mark(deco);
|
||||
}
|
||||
|
||||
if (!ValidateStructure(str)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -1972,6 +2067,8 @@ Resolver::StructInfo* Resolver::Structure(type::Struct* str) {
|
|||
uint32_t struct_align = 1;
|
||||
|
||||
for (auto* member : str->impl()->members()) {
|
||||
Mark(member);
|
||||
|
||||
// First check the member type is legal
|
||||
if (!IsStorable(member->type())) {
|
||||
builder_->Diagnostics().add_error(
|
||||
|
@ -1991,6 +2088,7 @@ Resolver::StructInfo* Resolver::Structure(type::Struct* str) {
|
|||
bool has_align_deco = false;
|
||||
bool has_size_deco = false;
|
||||
for (auto* deco : member->decorations()) {
|
||||
Mark(deco);
|
||||
if (auto* o = deco->As<ast::StructMemberOffsetDecoration>()) {
|
||||
// Offset decorations are not part of the WGSL spec, but are emitted by
|
||||
// the SPIR-V reader.
|
||||
|
@ -2077,11 +2175,15 @@ bool Resolver::ValidateReturn(const ast::ReturnStatement* ret) {
|
|||
bool Resolver::Return(ast::ReturnStatement* ret) {
|
||||
current_function_->return_statements.push_back(ret);
|
||||
|
||||
auto result = Expression(ret->value());
|
||||
if (auto* value = ret->value()) {
|
||||
Mark(value);
|
||||
|
||||
// Validate after processing the return value expression so that its type is
|
||||
// available for validation
|
||||
return result && ValidateReturn(ret);
|
||||
return Expression(value) && ValidateReturn(ret);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Resolver::ValidateSwitch(const ast::SwitchStatement* s) {
|
||||
|
@ -2155,10 +2257,12 @@ bool Resolver::ValidateSwitch(const ast::SwitchStatement* s) {
|
|||
}
|
||||
|
||||
bool Resolver::Switch(ast::SwitchStatement* s) {
|
||||
Mark(s->condition());
|
||||
if (!Expression(s->condition())) {
|
||||
return false;
|
||||
}
|
||||
for (auto* case_stmt : s->body()) {
|
||||
Mark(case_stmt);
|
||||
if (!CaseStatement(case_stmt)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2231,6 +2335,9 @@ bool Resolver::ValidateAssignment(const ast::AssignmentStatement* a) {
|
|||
}
|
||||
|
||||
bool Resolver::Assignment(ast::AssignmentStatement* a) {
|
||||
Mark(a->lhs());
|
||||
Mark(a->rhs());
|
||||
|
||||
if (!Expression(a->lhs()) || !Expression(a->rhs())) {
|
||||
return false;
|
||||
}
|
||||
|
@ -2330,6 +2437,19 @@ type::Type* Resolver::Canonical(type::Type* type) {
|
|||
[&] { return make_canonical(type); });
|
||||
}
|
||||
|
||||
void Resolver::Mark(ast::Node* node) {
|
||||
if (node == nullptr) {
|
||||
TINT_ICE(diagnostics_) << "Resolver::Mark() called with nullptr";
|
||||
}
|
||||
if (marked_.emplace(node).second) {
|
||||
return;
|
||||
}
|
||||
TINT_ICE(diagnostics_)
|
||||
<< "AST node '" << node->TypeInfo().name
|
||||
<< "' was encountered twice in the same AST of a Program\n"
|
||||
<< "At: " << node->source();
|
||||
}
|
||||
|
||||
Resolver::VariableInfo::VariableInfo(ast::Variable* decl, type::Type* ctype)
|
||||
: declaration(decl),
|
||||
type(ctype),
|
||||
|
|
|
@ -210,43 +210,45 @@ class Resolver {
|
|||
// AST and Type traversal methods
|
||||
// Each return true on success, false on failure.
|
||||
bool ArrayAccessor(ast::ArrayAccessorExpression*);
|
||||
bool Assignment(ast::AssignmentStatement* a);
|
||||
bool Binary(ast::BinaryExpression*);
|
||||
bool Bitcast(ast::BitcastExpression*);
|
||||
bool BlockStatement(const ast::BlockStatement*);
|
||||
bool Call(ast::CallExpression*);
|
||||
bool CaseStatement(ast::CaseStatement*);
|
||||
bool Constructor(ast::ConstructorExpression*);
|
||||
bool VectorConstructor(const type::Vector* vec_type,
|
||||
const ast::ExpressionList& values);
|
||||
bool MatrixConstructor(const type::Matrix* matrix_type,
|
||||
const ast::ExpressionList& values);
|
||||
bool Expression(ast::Expression*);
|
||||
bool Expressions(const ast::ExpressionList&);
|
||||
bool Function(ast::Function*);
|
||||
bool GlobalVariable(ast::Variable* var);
|
||||
bool Identifier(ast::IdentifierExpression*);
|
||||
bool IfStatement(ast::IfStatement*);
|
||||
bool IntrinsicCall(ast::CallExpression*, sem::IntrinsicType);
|
||||
bool MemberAccessor(ast::MemberAccessorExpression*);
|
||||
bool Parameter(ast::Variable* param);
|
||||
bool Return(ast::ReturnStatement* ret);
|
||||
bool Statement(ast::Statement*);
|
||||
bool Statements(const ast::StatementList&);
|
||||
bool Switch(ast::SwitchStatement* s);
|
||||
bool Type(type::Type* ty);
|
||||
bool UnaryOp(ast::UnaryOpExpression*);
|
||||
bool VariableDeclStatement(const ast::VariableDeclStatement*);
|
||||
bool Return(ast::ReturnStatement* ret);
|
||||
bool Switch(ast::SwitchStatement* s);
|
||||
bool Assignment(ast::AssignmentStatement* a);
|
||||
bool GlobalVariable(ast::Variable* var);
|
||||
|
||||
// AST and Type validation methods
|
||||
// Each return true on success, false on failure.
|
||||
bool ValidateBinary(ast::BinaryExpression* expr);
|
||||
bool ValidateVariable(const ast::Variable* param);
|
||||
bool ValidateParameter(const ast::Variable* param);
|
||||
bool ValidateFunction(const ast::Function* func);
|
||||
bool ValidateEntryPoint(const ast::Function* func);
|
||||
bool ValidateStructure(const type::Struct* st);
|
||||
bool ValidateReturn(const ast::ReturnStatement* ret);
|
||||
bool ValidateSwitch(const ast::SwitchStatement* s);
|
||||
bool ValidateAssignment(const ast::AssignmentStatement* a);
|
||||
bool ValidateBinary(ast::BinaryExpression* expr);
|
||||
bool ValidateEntryPoint(const ast::Function* func);
|
||||
bool ValidateFunction(const ast::Function* func);
|
||||
bool ValidateMatrixConstructor(const type::Matrix* matrix_type,
|
||||
const ast::ExpressionList& values);
|
||||
bool ValidateParameter(const ast::Variable* param);
|
||||
bool ValidateReturn(const ast::ReturnStatement* ret);
|
||||
bool ValidateStructure(const type::Struct* st);
|
||||
bool ValidateSwitch(const ast::SwitchStatement* s);
|
||||
bool ValidateVariable(const ast::Variable* param);
|
||||
bool ValidateVectorConstructor(const type::Vector* vec_type,
|
||||
const ast::ExpressionList& values);
|
||||
|
||||
/// @returns the semantic information for the array `arr`, building it if it
|
||||
/// hasn't been constructed already. If an error is raised, nullptr is
|
||||
|
@ -312,6 +314,11 @@ class Resolver {
|
|||
/// @return pretty string representation
|
||||
std::string VectorPretty(uint32_t size, type::Type* element_type);
|
||||
|
||||
/// Mark records that the given AST node has been visited, and asserts that
|
||||
/// the given node has not already been seen. Diamonds in the AST are illegal.
|
||||
/// @param node the AST node.
|
||||
void Mark(ast::Node* node);
|
||||
|
||||
ProgramBuilder* const builder_;
|
||||
std::unique_ptr<IntrinsicTable> const intrinsic_table_;
|
||||
diag::List diagnostics_;
|
||||
|
@ -324,6 +331,7 @@ class Resolver {
|
|||
std::unordered_map<ast::Expression*, ExpressionInfo> expr_info_;
|
||||
std::unordered_map<type::Struct*, StructInfo*> struct_info_;
|
||||
std::unordered_map<type::Type*, type::Type*> type_to_canonical_;
|
||||
std::unordered_set<ast::Node*> marked_;
|
||||
FunctionInfo* current_function_ = nullptr;
|
||||
sem::Statement* current_statement_ = nullptr;
|
||||
BlockAllocator<VariableInfo> variable_infos_;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <tuple>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest-spi.h"
|
||||
#include "src/ast/assignment_statement.h"
|
||||
#include "src/ast/bitcast_expression.h"
|
||||
#include "src/ast/break_statement.h"
|
||||
|
@ -1619,6 +1620,31 @@ TEST_F(ResolverTest, Function_EntryPoints_LinearTime) {
|
|||
ASSERT_TRUE(r()->Resolve()) << r()->error();
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, ASTNodeNotReached) {
|
||||
EXPECT_FATAL_FAILURE(
|
||||
{
|
||||
ProgramBuilder builder;
|
||||
builder.Expr("1");
|
||||
Resolver(&builder).Resolve();
|
||||
},
|
||||
"internal compiler error: AST node 'tint::ast::IdentifierExpression' was "
|
||||
"not reached by the resolver");
|
||||
}
|
||||
|
||||
TEST_F(ResolverTest, ASTNodeReachedTwice) {
|
||||
EXPECT_FATAL_FAILURE(
|
||||
{
|
||||
ProgramBuilder builder;
|
||||
auto* expr = builder.Expr("1");
|
||||
auto* usesExprTwice = builder.Add(expr, expr);
|
||||
builder.Global("g", builder.ty.i32(), ast::StorageClass::kPrivate,
|
||||
usesExprTwice);
|
||||
Resolver(&builder).Resolve();
|
||||
},
|
||||
"internal compiler error: AST node 'tint::ast::IdentifierExpression' was "
|
||||
"encountered twice in the same AST of a Program");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace resolver
|
||||
} // namespace tint
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "src/source.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
|
@ -37,4 +38,57 @@ Source::FileContent::~FileContent() = default;
|
|||
|
||||
Source::File::~File() = default;
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const Source& source) {
|
||||
auto rng = source.range;
|
||||
|
||||
if (!source.file_path.empty()) {
|
||||
out << source.file_path << ":";
|
||||
}
|
||||
if (rng.begin.line) {
|
||||
out << rng.begin.line << ":";
|
||||
if (rng.begin.column) {
|
||||
out << rng.begin.column;
|
||||
}
|
||||
|
||||
if (source.file_content) {
|
||||
out << std::endl << std::endl;
|
||||
|
||||
auto repeat = [&](char c, size_t n) {
|
||||
while (n--) {
|
||||
out << c;
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t line = rng.begin.line; line <= rng.end.line; line++) {
|
||||
if (line < source.file_content->lines.size() + 1) {
|
||||
auto len = source.file_content->lines[line - 1].size();
|
||||
|
||||
out << source.file_content->lines[line - 1];
|
||||
|
||||
out << std::endl;
|
||||
|
||||
if (line == rng.begin.line && line == rng.end.line) {
|
||||
// Single line
|
||||
repeat(' ', rng.begin.column - 1);
|
||||
repeat('^', std::max<size_t>(rng.end.column - rng.begin.column, 1));
|
||||
} else if (line == rng.begin.line) {
|
||||
// Start of multi-line
|
||||
repeat(' ', rng.begin.column - 1);
|
||||
repeat('^', len - (rng.begin.column - 1));
|
||||
} else if (line == rng.end.line) {
|
||||
// End of multi-line
|
||||
repeat('^', rng.end.column - 1);
|
||||
} else {
|
||||
// Middle of multi-line
|
||||
repeat('^', len);
|
||||
}
|
||||
|
||||
out << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace tint
|
||||
|
|
|
@ -147,6 +147,12 @@ class Source {
|
|||
const FileContent* file_content = nullptr;
|
||||
};
|
||||
|
||||
/// Writes the Source to the std::ostream.
|
||||
/// @param out the std::ostream to write to
|
||||
/// @param source the source to write
|
||||
/// @returns out so calls can be chained
|
||||
std::ostream& operator<<(std::ostream& out, const Source& source);
|
||||
|
||||
/// Writes the Source::FileContent to the std::ostream.
|
||||
/// @param out the std::ostream to write to
|
||||
/// @param content the file content to write
|
||||
|
|
Loading…
Reference in New Issue