| // 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 "src/validator/validator_impl.h" |
| |
| #include <cassert> |
| #include <unordered_set> |
| #include <utility> |
| |
| #include "src/ast/call_statement.h" |
| #include "src/ast/fallthrough_statement.h" |
| #include "src/ast/function.h" |
| #include "src/ast/int_literal.h" |
| #include "src/ast/module.h" |
| #include "src/ast/sint_literal.h" |
| #include "src/ast/stage_decoration.h" |
| #include "src/ast/struct.h" |
| #include "src/ast/switch_statement.h" |
| #include "src/ast/uint_literal.h" |
| #include "src/ast/variable_decl_statement.h" |
| #include "src/semantic/call.h" |
| #include "src/semantic/expression.h" |
| #include "src/semantic/intrinsic.h" |
| #include "src/semantic/variable.h" |
| #include "src/type/alias_type.h" |
| #include "src/type/array_type.h" |
| #include "src/type/bool_type.h" |
| #include "src/type/f32_type.h" |
| #include "src/type/i32_type.h" |
| #include "src/type/matrix_type.h" |
| #include "src/type/pointer_type.h" |
| #include "src/type/struct_type.h" |
| #include "src/type/u32_type.h" |
| #include "src/type/vector_type.h" |
| #include "src/type/void_type.h" |
| |
| namespace tint { |
| |
| namespace { |
| |
| using IntrinsicType = semantic::IntrinsicType; |
| |
| enum class IntrinsicDataType { |
| kMixed, |
| kFloatOrIntScalarOrVector, |
| kFloatScalarOrVector, |
| kIntScalarOrVector, |
| kFloatVector, |
| kFloatScalar, |
| kMatrix, |
| kBoolVector, |
| kBoolScalar, |
| kBoolScalarOrVector, |
| }; |
| |
| struct IntrinsicData { |
| IntrinsicType intrinsic; |
| uint32_t param_count; |
| IntrinsicDataType data_type; |
| uint32_t vector_size; |
| bool all_types_match; |
| }; |
| |
| // Note, this isn't all the intrinsics. Some are handled specially before |
| // we get to the generic code. See the ValidateCallExpr code below. |
| constexpr const IntrinsicData kIntrinsicData[] = { |
| {IntrinsicType::kAbs, 1, IntrinsicDataType::kFloatOrIntScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kAcos, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kAll, 1, IntrinsicDataType::kBoolVector, 0, false}, |
| {IntrinsicType::kAny, 1, IntrinsicDataType::kBoolVector, 0, false}, |
| {IntrinsicType::kArrayLength, 1, IntrinsicDataType::kMixed, 0, false}, |
| {IntrinsicType::kAsin, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kAtan, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kAtan2, 2, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kCeil, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kClamp, 3, IntrinsicDataType::kFloatOrIntScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kCos, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kCosh, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kCountOneBits, 1, IntrinsicDataType::kIntScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kCross, 2, IntrinsicDataType::kFloatVector, 3, true}, |
| {IntrinsicType::kDeterminant, 1, IntrinsicDataType::kMatrix, 0, false}, |
| {IntrinsicType::kDistance, 2, IntrinsicDataType::kFloatScalarOrVector, 0, |
| false}, |
| {IntrinsicType::kDot, 2, IntrinsicDataType::kFloatVector, 0, false}, |
| {IntrinsicType::kDpdx, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kDpdxCoarse, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kDpdxFine, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kDpdy, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kDpdyCoarse, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kDpdyFine, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kExp, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kExp2, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kFaceForward, 3, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kFloor, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kFma, 3, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kFract, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kFrexp, 2, IntrinsicDataType::kMixed, 0, false}, |
| {IntrinsicType::kFwidth, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kFwidthCoarse, 1, IntrinsicDataType::kFloatScalarOrVector, |
| 0, true}, |
| {IntrinsicType::kFwidthFine, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kInverseSqrt, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kLdexp, 2, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kLength, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| false}, |
| {IntrinsicType::kLog, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kLog2, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kMax, 2, IntrinsicDataType::kFloatOrIntScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kMin, 2, IntrinsicDataType::kFloatOrIntScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kMix, 3, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kModf, 2, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kNormalize, 1, IntrinsicDataType::kFloatVector, 0, true}, |
| {IntrinsicType::kPack4x8Snorm, 1, IntrinsicDataType::kFloatVector, 4, |
| false}, |
| {IntrinsicType::kPack4x8Unorm, 1, IntrinsicDataType::kFloatVector, 4, |
| false}, |
| {IntrinsicType::kPack2x16Snorm, 1, IntrinsicDataType::kFloatVector, 2, |
| false}, |
| {IntrinsicType::kPack2x16Unorm, 1, IntrinsicDataType::kFloatVector, 2, |
| false}, |
| {IntrinsicType::kPack2x16Float, 1, IntrinsicDataType::kFloatVector, 2, |
| false}, |
| {IntrinsicType::kPow, 2, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kReflect, 2, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kReverseBits, 1, IntrinsicDataType::kIntScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kRound, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kSelect, 3, IntrinsicDataType::kMixed, 0, false}, |
| {IntrinsicType::kSign, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kSin, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kSinh, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kSmoothStep, 3, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| {IntrinsicType::kSqrt, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kStep, 2, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kTan, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kTanh, 1, IntrinsicDataType::kFloatScalarOrVector, 0, true}, |
| {IntrinsicType::kTrunc, 1, IntrinsicDataType::kFloatScalarOrVector, 0, |
| true}, |
| }; |
| |
| constexpr const uint32_t kIntrinsicDataCount = |
| sizeof(kIntrinsicData) / sizeof(IntrinsicData); |
| |
| bool IsValidType(type::Type* type, |
| const Source& source, |
| const std::string& name, |
| const IntrinsicDataType& data_type, |
| uint32_t vector_size, |
| ValidatorImpl* impl) { |
| type = type->UnwrapPtrIfNeeded(); |
| switch (data_type) { |
| case IntrinsicDataType::kFloatOrIntScalarOrVector: |
| if (!type->is_float_scalar_or_vector() && |
| !type->is_integer_scalar_or_vector()) { |
| impl->add_error(source, |
| "incorrect type for " + name + |
| ". Requires int or float, scalar or vector value"); |
| return false; |
| } |
| break; |
| case IntrinsicDataType::kFloatScalarOrVector: |
| if (!type->is_float_scalar_or_vector()) { |
| impl->add_error(source, "incorrect type for " + name + |
| ". Requires float scalar or vector value"); |
| return false; |
| } |
| break; |
| case IntrinsicDataType::kIntScalarOrVector: |
| if (!type->is_integer_scalar_or_vector()) { |
| impl->add_error(source, "incorrect type for " + name + |
| ". Requires int scalar or vector value"); |
| return false; |
| } |
| break; |
| case IntrinsicDataType::kFloatVector: |
| if (!type->is_float_vector()) { |
| impl->add_error(source, "incorrect type for " + name + |
| ". Requires float vector value"); |
| return false; |
| } |
| if (vector_size > 0 && vector_size != type->As<type::Vector>()->size()) { |
| impl->add_error(source, "incorrect vector size for " + name + |
| ". Requires " + |
| std::to_string(vector_size) + " elements"); |
| return false; |
| } |
| break; |
| case IntrinsicDataType::kFloatScalar: |
| if (!type->Is<type::F32>()) { |
| impl->add_error(source, "incorrect type for " + name + |
| ". Requires float scalar value"); |
| return false; |
| } |
| break; |
| case IntrinsicDataType::kMatrix: |
| if (!type->Is<type::Matrix>()) { |
| impl->add_error( |
| source, "incorrect type for " + name + ". Requires matrix value"); |
| return false; |
| } |
| break; |
| case IntrinsicDataType::kBoolVector: |
| if (!type->is_bool_vector()) { |
| impl->add_error(source, "incorrect type for " + name + |
| ". Requires bool vector value"); |
| return false; |
| } |
| if (vector_size > 0 && vector_size != type->As<type::Vector>()->size()) { |
| impl->add_error(source, "incorrect vector size for " + name + |
| ". Requires " + |
| std::to_string(vector_size) + " elements"); |
| return false; |
| } |
| break; |
| case IntrinsicDataType::kBoolScalar: |
| if (!type->Is<type::Bool>()) { |
| impl->add_error(source, "incorrect type for " + name + |
| ". Requires bool scalar value"); |
| return false; |
| } |
| break; |
| case IntrinsicDataType::kBoolScalarOrVector: |
| if (!type->is_bool_scalar_or_vector()) { |
| impl->add_error(source, "incorrect type for " + name + |
| ". Requires bool scalar or vector value"); |
| return false; |
| } |
| break; |
| default: |
| break; |
| } |
| |
| return true; |
| } |
| |
| } // namespace |
| |
| ValidatorImpl::ValidatorImpl(const Program* program) : program_(program) {} |
| |
| ValidatorImpl::~ValidatorImpl() = default; |
| |
| void ValidatorImpl::add_error(const Source& src, |
| const char* code, |
| const std::string& msg) { |
| diag::Diagnostic diag; |
| diag.severity = diag::Severity::Error; |
| diag.source = src; |
| diag.message = msg; |
| diag.code = code; |
| diags_.add(std::move(diag)); |
| } |
| |
| void ValidatorImpl::add_error(const Source& src, const std::string& msg) { |
| diags_.add_error(msg, src); |
| } |
| |
| bool ValidatorImpl::Validate() { |
| function_stack_.push_scope(); |
| if (!ValidateGlobalVariables(program_->AST().GlobalVariables())) { |
| return false; |
| } |
| if (!ValidateConstructedTypes(program_->AST().ConstructedTypes())) { |
| return false; |
| } |
| if (!ValidateFunctions(program_->AST().Functions())) { |
| return false; |
| } |
| if (!ValidateEntryPoint(program_->AST().Functions())) { |
| return false; |
| } |
| function_stack_.pop_scope(); |
| |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateConstructedTypes( |
| const std::vector<type::Type*>& constructed_types) { |
| for (auto* const ct : constructed_types) { |
| if (auto* st = ct->As<type::Struct>()) { |
| for (auto* member : st->impl()->members()) { |
| if (auto* r = member->type()->UnwrapAll()->As<type::Array>()) { |
| if (r->IsRuntimeArray()) { |
| if (member != st->impl()->members().back()) { |
| add_error(member->source(), "v-0015", |
| "runtime arrays may only appear as the last member of " |
| "a struct"); |
| return false; |
| } |
| if (!st->IsBlockDecorated()) { |
| add_error(member->source(), "v-0015", |
| "a struct containing a runtime-sized array " |
| "requires the [[block]] attribute: '" + |
| program_->Symbols().NameFor(st->symbol()) + "'"); |
| return false; |
| } |
| } |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateGlobalVariables( |
| const ast::VariableList& global_vars) { |
| for (auto* var : global_vars) { |
| auto* sem = program_->Sem().Get(var); |
| if (!sem) { |
| add_error(var->source(), "no semantic information for variable '" + |
| program_->Symbols().NameFor(var->symbol()) + |
| "'"); |
| return false; |
| } |
| |
| if (variable_stack_.has(var->symbol())) { |
| add_error(var->source(), "v-0011", |
| "redeclared global identifier '" + |
| program_->Symbols().NameFor(var->symbol()) + "'"); |
| return false; |
| } |
| if (!var->is_const() && sem->StorageClass() == ast::StorageClass::kNone) { |
| add_error(var->source(), "v-0022", |
| "global variables must have a storage class"); |
| return false; |
| } |
| if (var->is_const() && !(sem->StorageClass() == ast::StorageClass::kNone)) { |
| add_error(var->source(), "v-global01", |
| "global constants shouldn't have a storage class"); |
| return false; |
| } |
| variable_stack_.set_global(var->symbol(), var); |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateFunctions(const ast::FunctionList& funcs) { |
| for (auto* func : funcs) { |
| if (function_stack_.has(func->symbol())) { |
| add_error(func->source(), "v-0016", |
| "function names must be unique '" + |
| program_->Symbols().NameFor(func->symbol()) + "'"); |
| return false; |
| } |
| |
| function_stack_.set(func->symbol(), func); |
| current_function_ = func; |
| if (!ValidateFunction(func)) { |
| return false; |
| } |
| current_function_ = nullptr; |
| } |
| |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateEntryPoint(const ast::FunctionList& funcs) { |
| auto shader_is_present = false; |
| for (auto* func : funcs) { |
| if (func->IsEntryPoint()) { |
| shader_is_present = true; |
| if (!func->params().empty()) { |
| add_error(func->source(), "v-0023", |
| "Entry point function must accept no parameters: '" + |
| program_->Symbols().NameFor(func->symbol()) + "'"); |
| return false; |
| } |
| |
| if (!func->return_type()->Is<type::Void>()) { |
| add_error(func->source(), "v-0024", |
| "Entry point function must return void: '" + |
| program_->Symbols().NameFor(func->symbol()) + "'"); |
| return false; |
| } |
| auto stage_deco_count = 0; |
| for (auto* deco : func->decorations()) { |
| if (deco->Is<ast::StageDecoration>()) { |
| stage_deco_count++; |
| } |
| } |
| if (stage_deco_count > 1) { |
| add_error(func->source(), "v-0020", |
| "only one stage decoration permitted per entry point"); |
| return false; |
| } |
| } |
| } |
| if (!shader_is_present) { |
| add_error(Source{}, "v-0003", |
| "At least one of vertex, fragment or compute shader must " |
| "be present"); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateFunction(const ast::Function* func) { |
| variable_stack_.push_scope(); |
| |
| for (auto* param : func->params()) { |
| variable_stack_.set(param->symbol(), param); |
| if (!ValidateParameter(param)) { |
| return false; |
| } |
| } |
| if (!ValidateStatements(func->body())) { |
| return false; |
| } |
| variable_stack_.pop_scope(); |
| |
| if (!current_function_->return_type()->Is<type::Void>()) { |
| if (!func->get_last_statement() || |
| !func->get_last_statement()->Is<ast::ReturnStatement>()) { |
| add_error(func->source(), "v-0002", |
| "non-void function must end with a return statement"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateParameter(const ast::Variable* param) { |
| if (auto* r = param->type()->UnwrapAll()->As<type::Array>()) { |
| if (r->IsRuntimeArray()) { |
| add_error( |
| param->source(), "v-0015", |
| "runtime arrays may only appear as the last member of a struct"); |
| 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 |
| type::Type* func_type = current_function_->return_type(); |
| |
| type::Void void_type; |
| auto* ret_type = ret->has_value() |
| ? program_->Sem().Get(ret->value())->Type()->UnwrapAll() |
| : &void_type; |
| |
| if (func_type->type_name() != ret_type->type_name()) { |
| add_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; |
| } |
| |
| bool is_valid = true; |
| variable_stack_.push_scope(); |
| for (auto* stmt : *block) { |
| if (!ValidateStatement(stmt)) { |
| is_valid = false; |
| break; |
| } |
| } |
| variable_stack_.pop_scope(); |
| |
| return is_valid; |
| } |
| |
| bool ValidatorImpl::ValidateDeclStatement( |
| const ast::VariableDeclStatement* decl) { |
| auto symbol = decl->variable()->symbol(); |
| bool is_global = false; |
| if (variable_stack_.get(symbol, nullptr, &is_global)) { |
| const char* error_code = "v-0014"; |
| if (is_global) { |
| error_code = "v-0013"; |
| } |
| add_error( |
| decl->source(), error_code, |
| "redeclared identifier '" + program_->Symbols().NameFor(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<type::Array>()) { |
| if (arr->IsRuntimeArray()) { |
| add_error( |
| decl->source(), "v-0015", |
| "runtime arrays may only appear as the last member of a struct"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateStatement(const ast::Statement* stmt) { |
| if (!stmt) { |
| return false; |
| } |
| if (auto* v = stmt->As<ast::VariableDeclStatement>()) { |
| bool constructor_valid = |
| v->variable()->has_constructor() |
| ? ValidateExpression(v->variable()->constructor()) |
| : true; |
| |
| return constructor_valid && ValidateDeclStatement(v); |
| } |
| if (auto* a = stmt->As<ast::AssignmentStatement>()) { |
| return ValidateAssign(a); |
| } |
| if (auto* r = stmt->As<ast::ReturnStatement>()) { |
| return ValidateReturnStatement(r); |
| } |
| if (auto* c = stmt->As<ast::CallStatement>()) { |
| return ValidateCallExpr(c->expr()); |
| } |
| if (auto* s = stmt->As<ast::SwitchStatement>()) { |
| return ValidateSwitch(s); |
| } |
| if (auto* c = stmt->As<ast::CaseStatement>()) { |
| return ValidateCase(c); |
| } |
| if (auto* b = stmt->As<ast::BlockStatement>()) { |
| return ValidateStatements(b); |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateSwitch(const ast::SwitchStatement* s) { |
| if (!ValidateExpression(s->condition())) { |
| return false; |
| } |
| |
| auto* cond_type = program_->Sem().Get(s->condition())->Type()->UnwrapAll(); |
| if (!cond_type->is_integer_scalar()) { |
| add_error(s->condition()->source(), "v-0025", |
| "switch statement selector expression must be of a " |
| "scalar integer type"); |
| return false; |
| } |
| |
| int default_counter = 0; |
| std::unordered_set<int32_t> selector_set; |
| for (auto* case_stmt : s->body()) { |
| if (!ValidateStatement(case_stmt)) { |
| return false; |
| } |
| |
| if (case_stmt->IsDefault()) { |
| default_counter++; |
| } |
| |
| for (auto* selector : case_stmt->selectors()) { |
| if (cond_type != selector->type()) { |
| add_error(case_stmt->source(), "v-0026", |
| "the case selector values must have the same " |
| "type as the selector expression."); |
| return false; |
| } |
| |
| auto v = |
| static_cast<int32_t>(selector->type()->Is<type::U32>() |
| ? selector->As<ast::UintLiteral>()->value() |
| : selector->As<ast::SintLiteral>()->value()); |
| if (selector_set.count(v)) { |
| add_error(case_stmt->source(), "v-0027", |
| "a literal value must not appear more than once in " |
| "the case selectors for a switch statement: '" + |
| program_->str(selector) + "'"); |
| return false; |
| } |
| selector_set.emplace(v); |
| } |
| } |
| |
| if (default_counter != 1) { |
| add_error(s->source(), "v-0008", |
| "switch statement must have exactly one default clause"); |
| return false; |
| } |
| |
| auto* last_clause = s->body().back(); |
| auto* last_stmt_of_last_clause = |
| last_clause->As<ast::CaseStatement>()->body()->last(); |
| if (last_stmt_of_last_clause && |
| last_stmt_of_last_clause->Is<ast::FallthroughStatement>()) { |
| add_error(last_stmt_of_last_clause->source(), "v-0028", |
| "a fallthrough statement must not appear as " |
| "the last statement in last clause of a switch"); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateCase(const ast::CaseStatement* c) { |
| if (!ValidateStatement(c->body())) { |
| return false; |
| } |
| 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; |
| } |
| |
| auto* call = program_->Sem().Get(expr); |
| if (call == nullptr) { |
| add_error(expr->source(), "CallExpression is missing semantic information"); |
| return false; |
| } |
| |
| if (auto* intrinsic = call->Target()->As<semantic::Intrinsic>()) { |
| const IntrinsicData* data = nullptr; |
| for (uint32_t i = 0; i < kIntrinsicDataCount; ++i) { |
| if (intrinsic->Type() == kIntrinsicData[i].intrinsic) { |
| data = &kIntrinsicData[i]; |
| break; |
| } |
| } |
| |
| if (data != nullptr) { |
| std::string builtin = intrinsic->str(); |
| if (expr->params().size() != data->param_count) { |
| add_error(expr->source(), |
| "incorrect number of parameters for " + builtin + |
| " expected " + std::to_string(data->param_count) + |
| " got " + std::to_string(expr->params().size())); |
| return false; |
| } |
| |
| if (data->all_types_match) { |
| // Check that the type is an acceptable one. |
| if (!IsValidType(program_->TypeOf(expr), expr->source(), builtin, |
| data->data_type, data->vector_size, this)) { |
| return false; |
| } |
| |
| // Check that all params match the result type. |
| for (uint32_t i = 0; i < data->param_count; ++i) { |
| if (program_->TypeOf(expr)->UnwrapPtrIfNeeded() != |
| program_->TypeOf(expr->params()[i])->UnwrapPtrIfNeeded()) { |
| add_error(expr->params()[i]->source(), |
| "expected parameter " + std::to_string(i) + |
| "'s unwrapped type to match result type for " + |
| builtin); |
| return false; |
| } |
| } |
| } else { |
| if (data->data_type != IntrinsicDataType::kMixed) { |
| auto* p0 = expr->params()[0]; |
| if (!IsValidType(program_->TypeOf(p0), p0->source(), builtin, |
| data->data_type, data->vector_size, this)) { |
| return false; |
| } |
| |
| // Check that parameters are valid types. |
| for (uint32_t i = 1; i < expr->params().size(); ++i) { |
| if (program_->TypeOf(p0)->UnwrapPtrIfNeeded() != |
| program_->TypeOf(expr->params()[i])->UnwrapPtrIfNeeded()) { |
| add_error(expr->source(), |
| "parameter " + std::to_string(i) + |
| "'s unwrapped type must match parameter 0's type"); |
| return false; |
| } |
| } |
| } else { |
| // Special cases. |
| if (data->intrinsic == IntrinsicType::kFrexp) { |
| auto* p0 = expr->params()[0]; |
| auto* p1 = expr->params()[1]; |
| auto* t0 = program_->TypeOf(p0)->UnwrapPtrIfNeeded(); |
| auto* t1 = program_->TypeOf(p1)->UnwrapPtrIfNeeded(); |
| if (!IsValidType(t0, p0->source(), builtin, |
| IntrinsicDataType::kFloatScalarOrVector, 0, |
| this)) { |
| return false; |
| } |
| if (!IsValidType(t1, p1->source(), builtin, |
| IntrinsicDataType::kIntScalarOrVector, 0, this)) { |
| return false; |
| } |
| |
| if (t0->is_scalar()) { |
| if (!t1->is_scalar()) { |
| add_error( |
| expr->source(), |
| "incorrect types for " + builtin + |
| ". Parameters must be matched scalars or vectors"); |
| return false; |
| } |
| } else { |
| if (t1->is_integer_scalar()) { |
| add_error( |
| expr->source(), |
| "incorrect types for " + builtin + |
| ". Parameters must be matched scalars or vectors"); |
| return false; |
| } |
| const auto* v0 = t0->As<type::Vector>(); |
| const auto* v1 = t1->As<type::Vector>(); |
| if (v0->size() != v1->size()) { |
| add_error(expr->source(), |
| "incorrect types for " + builtin + |
| ". Parameter vector sizes must match"); |
| return false; |
| } |
| } |
| } |
| |
| if (data->intrinsic == IntrinsicType::kSelect) { |
| auto* type = program_->TypeOf(expr); |
| auto* t0 = program_->TypeOf(expr->params()[0])->UnwrapPtrIfNeeded(); |
| auto* t1 = program_->TypeOf(expr->params()[1])->UnwrapPtrIfNeeded(); |
| auto* t2 = program_->TypeOf(expr->params()[2])->UnwrapPtrIfNeeded(); |
| if (!type->is_scalar() && !type->Is<type::Vector>()) { |
| add_error(expr->source(), |
| "incorrect type for " + builtin + |
| ". Requires bool, int or float scalar or vector"); |
| return false; |
| } |
| |
| if (type != t0 || type != t1) { |
| add_error(expr->source(), |
| "incorrect type for " + builtin + |
| ". Value parameter types must match result type"); |
| return false; |
| } |
| |
| if (!t2->is_bool_scalar_or_vector()) { |
| add_error(expr->params()[2]->source(), |
| "incorrect type for " + builtin + |
| ". Selector must be a bool scalar or vector value"); |
| return false; |
| } |
| |
| if (type->Is<type::Vector>()) { |
| auto size = type->As<type::Vector>()->size(); |
| if (t2->is_scalar() || size != t2->As<type::Vector>()->size()) { |
| add_error(expr->params()[2]->source(), |
| "incorrect type for " + builtin + |
| ". Selector must be a vector with the same " |
| "number of elements as the result type"); |
| return false; |
| } |
| } else { |
| if (!t2->is_scalar()) { |
| add_error(expr->params()[2]->source(), |
| "incorrect type for " + builtin + |
| ". Selector must be a bool scalar to match " |
| "scalar result type"); |
| return false; |
| } |
| } |
| } |
| |
| if (data->intrinsic == IntrinsicType::kArrayLength) { |
| if (!program_->TypeOf(expr)->UnwrapPtrIfNeeded()->Is<type::U32>()) { |
| add_error( |
| expr->source(), |
| "incorrect type for " + builtin + |
| ". Result type must be an unsigned int scalar value"); |
| return false; |
| } |
| |
| auto* p0 = program_->TypeOf(expr->params()[0])->UnwrapPtrIfNeeded(); |
| if (!p0->Is<type::Array>() || |
| !p0->As<type::Array>()->IsRuntimeArray()) { |
| add_error(expr->params()[0]->source(), |
| "incorrect type for " + builtin + |
| ". Input must be a runtime array"); |
| return false; |
| } |
| } |
| } |
| |
| // Result types don't match parameter types. |
| if (data->intrinsic == IntrinsicType::kAll || |
| data->intrinsic == IntrinsicType::kAny) { |
| if (!IsValidType(program_->TypeOf(expr), expr->source(), builtin, |
| IntrinsicDataType::kBoolScalar, 0, this)) { |
| return false; |
| } |
| } |
| |
| if (data->intrinsic == IntrinsicType::kDot) { |
| if (!IsValidType(program_->TypeOf(expr), expr->source(), builtin, |
| IntrinsicDataType::kFloatScalar, 0, this)) { |
| return false; |
| } |
| } |
| |
| if (semantic::IsDataPackingIntrinsic(data->intrinsic)) { |
| if (!program_->TypeOf(expr)->Is<type::U32>()) { |
| add_error(expr->source(), |
| "incorrect type for " + builtin + |
| ". Result type must be an unsigned int scalar"); |
| return false; |
| } |
| } |
| |
| if (data->intrinsic == IntrinsicType::kLength || |
| data->intrinsic == IntrinsicType::kDistance || |
| data->intrinsic == IntrinsicType::kDeterminant) { |
| if (!IsValidType(program_->TypeOf(expr), expr->source(), builtin, |
| IntrinsicDataType::kFloatScalar, 0, this)) { |
| return false; |
| } |
| } |
| |
| // Must be a square matrix. |
| if (data->intrinsic == IntrinsicType::kDeterminant) { |
| const auto* matrix = |
| program_->TypeOf(expr->params()[0])->As<type::Matrix>(); |
| if (matrix->rows() != matrix->columns()) { |
| add_error( |
| expr->params()[0]->source(), |
| "incorrect type for " + builtin + ". Requires a square matrix"); |
| return false; |
| } |
| } |
| } |
| |
| // Last parameter must be a pointer. |
| if (data->intrinsic == IntrinsicType::kFrexp || |
| data->intrinsic == IntrinsicType::kModf) { |
| auto* last_param = expr->params()[data->param_count - 1]; |
| if (!program_->TypeOf(last_param)->Is<type::Pointer>()) { |
| add_error(last_param->source(), "incorrect type for " + builtin + |
| ". Requires pointer value"); |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| if (auto* ident = expr->func()->As<ast::IdentifierExpression>()) { |
| auto symbol = ident->symbol(); |
| if (!function_stack_.has(symbol)) { |
| add_error(expr->source(), "v-0005", |
| "function must be declared before use: '" + |
| program_->Symbols().NameFor(symbol) + "'"); |
| return false; |
| } |
| if (symbol == current_function_->symbol()) { |
| add_error(expr->source(), "v-0004", |
| "recursion is not allowed: '" + |
| program_->Symbols().NameFor(symbol) + "'"); |
| return false; |
| } |
| |
| } else { |
| add_error(expr->source(), "Invalid function call expression"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| 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: '" + |
| program_->Symbols().NameFor(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", |
| "'" + program_->Symbols().NameFor(ident->symbol()) + |
| "' is not declared"); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateAssign(const ast::AssignmentStatement* assign) { |
| if (!assign) { |
| 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 = program_->Sem().Get(rhs)->Type()->UnwrapAll(); |
| if (!IsStorable(rhs_result_type)) { |
| add_error(assign->source(), "v-000x", |
| "invalid assignment: right-hand-side is not storable: " + |
| program_->Sem().Get(rhs)->Type()->type_name()); |
| return false; |
| } |
| auto* lhs_result_type = program_->Sem().Get(lhs)->Type()->UnwrapIfNeeded(); |
| if (auto* lhs_reference_type = As<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; |
| } |
| } 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: " + |
| program_->Sem().Get(lhs)->Type()->type_name()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateExpression(const ast::Expression* expr) { |
| if (!expr) { |
| return false; |
| } |
| if (auto* i = expr->As<ast::IdentifierExpression>()) { |
| return ValidateIdentifier(i); |
| } |
| |
| if (auto* c = expr->As<ast::CallExpression>()) { |
| return ValidateCallExpr(c); |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::ValidateIdentifier(const ast::IdentifierExpression* ident) { |
| ast::Variable* var; |
| if (!variable_stack_.get(ident->symbol(), &var)) { |
| add_error(ident->source(), "v-0006", |
| "'" + program_->Symbols().NameFor(ident->symbol()) + |
| "' is not declared"); |
| return false; |
| } |
| return true; |
| } |
| |
| bool ValidatorImpl::IsStorable(type::Type* type) { |
| if (type == nullptr) { |
| return false; |
| } |
| if (type->is_scalar() || type->Is<type::Vector>() || |
| type->Is<type::Matrix>()) { |
| return true; |
| } |
| if (type::Array* array_type = type->As<type::Array>()) { |
| return IsStorable(array_type->type()); |
| } |
| if (type::Struct* struct_type = type->As<type::Struct>()) { |
| for (const auto* member : struct_type->impl()->members()) { |
| if (!IsStorable(member->type())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| if (type::Alias* alias_type = type->As<type::Alias>()) { |
| return IsStorable(alias_type->type()); |
| } |
| return false; |
| } |
| |
| } // namespace tint |