blob: 006ac7a597e017bcedbb638d9af91a85e874b9fe [file] [log] [blame]
// Copyright 2020 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 <cassert>
#include "src/validator_impl.h"
#include "src/ast/call_statement.h"
#include "src/ast/function.h"
#include "src/ast/intrinsic.h"
#include "src/ast/type/void_type.h"
#include "src/ast/variable_decl_statement.h"
namespace tint {
ValidatorImpl::ValidatorImpl() = default;
ValidatorImpl::~ValidatorImpl() = default;
void ValidatorImpl::set_error(const Source& src, const std::string& msg) {
error_ +=
std::to_string(src.line) + ":" + std::to_string(src.column) + ": " + msg;
}
bool ValidatorImpl::Validate(const ast::Module* module) {
if (!module) {
return false;
}
function_stack_.push_scope();
if (!ValidateGlobalVariables(module->global_variables())) {
return false;
}
if (!CheckImports(module)) {
return false;
}
if (!ValidateFunctions(module->functions())) {
return false;
}
// ValidateEntryPoints must be done after populating function_stack_
if (!ValidateEntryPoints(module->entry_points())) {
return false;
}
function_stack_.pop_scope();
return true;
}
bool ValidatorImpl::ValidateGlobalVariables(
const ast::VariableList& global_vars) {
for (const auto& var : global_vars) {
if (variable_stack_.has(var->name())) {
set_error(var->source(),
"v-0011: redeclared global identifier '" + var->name() + "'");
return false;
}
if (!var->is_const() && var->storage_class() == ast::StorageClass::kNone) {
set_error(var->source(),
"v-0022: global variables must have a storage class");
return false;
}
if (var->is_const() &&
!(var->storage_class() == ast::StorageClass::kNone)) {
set_error(var->source(),
"v-global01: global constants shouldn't have a storage class");
return false;
}
variable_stack_.set_global(var->name(), var.get());
}
return true;
}
bool ValidatorImpl::ValidateEntryPoints(const ast::EntryPointList& eps) {
ScopeStack<ast::PipelineStage> entry_point_map;
entry_point_map.push_scope();
for (const auto& ep : eps) {
auto* ep_ptr = ep.get();
if (!function_stack_.has(ep_ptr->function_name())) {
set_error(ep_ptr->source(),
"v-0019: Function used in entry point does not exist: '" +
ep_ptr->function_name() + "'");
return false;
}
ast::Function* func = nullptr;
function_stack_.get(ep_ptr->function_name(), &func);
if (!func->return_type()->IsVoid()) {
set_error(ep_ptr->source(),
"v-0024: Entry point function must return void: '" +
ep_ptr->function_name() + "'");
return false;
}
if (func->params().size() != 0) {
set_error(ep_ptr->source(),
"v-0023: Entry point function must accept no parameters: '" +
ep_ptr->function_name() + "'");
return false;
}
ast::PipelineStage pipeline_stage;
if (entry_point_map.get(ep_ptr->name(), &pipeline_stage)) {
if (pipeline_stage == ep_ptr->stage()) {
set_error(ep_ptr->source(),
"v-0020: The pair of <entry point name, pipeline stage> must "
"be unique");
return false;
}
}
entry_point_map.set(ep_ptr->name(), ep_ptr->stage());
}
if (eps.empty()) {
set_error(Source{0, 0},
"v-0003: At least one of vertex, fragment or compute shader must "
"be present");
return false;
}
entry_point_map.pop_scope();
return true;
}
bool ValidatorImpl::ValidateFunctions(const ast::FunctionList& funcs) {
for (const auto& func : funcs) {
auto* func_ptr = func.get();
if (function_stack_.has(func_ptr->name())) {
set_error(func_ptr->source(), "v-0016: function names must be unique '" +
func_ptr->name() + "'");
return false;
}
function_stack_.set(func_ptr->name(), func_ptr);
current_function_ = func_ptr;
if (!ValidateFunction(func_ptr)) {
return false;
}
current_function_ = nullptr;
}
return true;
}
bool ValidatorImpl::ValidateFunction(const ast::Function* func) {
variable_stack_.push_scope();
for (const auto& param : func->params()) {
variable_stack_.set(param->name(), param.get());
}
if (!ValidateStatements(func->body())) {
return false;
}
variable_stack_.pop_scope();
if (!func->get_last_statement() || !func->get_last_statement()->IsReturn()) {
set_error(func->source(),
"v-0002: function must end with a return statement");
return false;
}
return true;
}
bool ValidatorImpl::ValidateReturnStatement(const ast::ReturnStatement* ret) {
// TODO(sarahM0): update this when this issue resolves:
// https://github.com/gpuweb/gpuweb/issues/996
ast::type::Type* func_type = current_function_->return_type();
ast::type::VoidType void_type;
auto* ret_type = ret->has_value()
? ret->value()->result_type()->UnwrapAliasPtrAlias()
: &void_type;
if (func_type->type_name() != ret_type->type_name()) {
set_error(ret->source(),
"v-000y: return statement type must match its function return "
"type, returned '" +
ret_type->type_name() + "', expected '" +
func_type->type_name() + "'");
return false;
}
return true;
}
bool ValidatorImpl::ValidateStatements(const ast::BlockStatement* block) {
if (!block) {
return false;
}
for (const auto& stmt : *block) {
if (!ValidateStatement(stmt.get())) {
return false;
}
}
return true;
}
bool ValidatorImpl::ValidateDeclStatement(
const ast::VariableDeclStatement* decl) {
auto name = decl->variable()->name();
bool is_global = false;
if (variable_stack_.get(name, nullptr, &is_global)) {
std::string error_number = "v-0014: ";
if (is_global) {
error_number = "v-0013: ";
}
set_error(decl->source(),
error_number + "redeclared identifier '" + name + "'");
return false;
}
variable_stack_.set(name, decl->variable());
return true;
}
bool ValidatorImpl::ValidateStatement(const ast::Statement* stmt) {
if (!stmt) {
return false;
}
if (stmt->IsVariableDecl()) {
auto* v = stmt->AsVariableDecl();
bool constructor_valid =
v->variable()->has_constructor()
? ValidateExpression(v->variable()->constructor())
: true;
return constructor_valid && ValidateDeclStatement(stmt->AsVariableDecl());
}
if (stmt->IsAssign()) {
return ValidateAssign(stmt->AsAssign());
}
if (stmt->IsReturn()) {
return ValidateReturnStatement(stmt->AsReturn());
}
if (stmt->IsCall()) {
return ValidateCallExpr(stmt->AsCall()->expr());
}
return true;
}
bool ValidatorImpl::ValidateCallExpr(const ast::CallExpression* expr) {
if (!expr) {
// TODO(sarahM0): Here and other Validate.*: figure out whether return false
// or true
return false;
}
if (expr->func()->IsIdentifier()) {
auto* ident = expr->func()->AsIdentifier();
auto func_name = ident->name();
if (ident->has_path()) {
// TODO(sarahM0): validate import statements
} else if (ast::intrinsic::IsIntrinsic(ident->name())) {
// TODO(sarahM0): validate intrinsics - tied with type-determiner
} else {
if (!function_stack_.has(func_name)) {
set_error(expr->source(),
"v-0005: function must be declared before use: '" +
func_name + "'");
return false;
}
if (func_name == current_function_->name()) {
set_error(expr->source(),
"v-0004: recursion is not allowed: '" + func_name + "'");
return false;
}
}
} else {
set_error(expr->source(), "Invalid function call expression");
return false;
}
return true;
}
bool ValidatorImpl::ValidateAssign(const ast::AssignmentStatement* a) {
if (!a) {
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) {
if (!assign) {
return false;
}
if (assign->lhs()->IsIdentifier()) {
ast::Variable* var;
auto* ident = assign->lhs()->AsIdentifier();
if (variable_stack_.get(ident->name(), &var)) {
if (var->is_const()) {
set_error(assign->source(), "v-0021: cannot re-assign a constant: '" +
ident->name() + "'");
return false;
}
}
}
return true;
}
bool ValidatorImpl::ValidateResultTypes(const ast::AssignmentStatement* a) {
if (!a->lhs()->result_type() || !a->rhs()->result_type()) {
set_error(a->source(), "result_type() is nullptr");
return false;
}
auto* lhs_result_type = a->lhs()->result_type()->UnwrapAliasPtrAlias();
auto* rhs_result_type = a->rhs()->result_type()->UnwrapAliasPtrAlias();
if (lhs_result_type != rhs_result_type) {
// TODO(sarahM0): figur out what should be the error number.
set_error(a->source(), "v-000x: invalid assignment of '" +
lhs_result_type->type_name() + "' to '" +
rhs_result_type->type_name() + "'");
return false;
}
return true;
}
bool ValidatorImpl::ValidateExpression(const ast::Expression* expr) {
if (!expr) {
return false;
}
if (expr->IsIdentifier()) {
return ValidateIdentifier(expr->AsIdentifier());
}
if (expr->IsCall()) {
return ValidateCallExpr(expr->AsCall());
}
return true;
}
bool ValidatorImpl::ValidateIdentifier(const ast::IdentifierExpression* ident) {
ast::Variable* var;
if (!variable_stack_.get(ident->name(), &var)) {
set_error(ident->source(),
"v-0006: '" + ident->name() + "' is not declared");
return false;
}
return true;
}
bool ValidatorImpl::CheckImports(const ast::Module* module) {
for (const auto& import : module->imports()) {
if (import->path() != "GLSL.std.450") {
set_error(import->source(), "v-0001: unknown import: " + import->path());
return false;
}
}
return true;
}
} // namespace tint