| // Copyright 2016 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/asmjs/asm-typer.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <memory> |
| #include <string> |
| |
| #include "src/v8.h" |
| |
| #include "src/asmjs/asm-types.h" |
| #include "src/ast/ast.h" |
| #include "src/ast/scopes.h" |
| #include "src/base/bits.h" |
| #include "src/codegen.h" |
| #include "src/globals.h" |
| #include "src/utils.h" |
| |
| #define FAIL(node, msg) \ |
| do { \ |
| int line = node->position() == kNoSourcePosition \ |
| ? -1 \ |
| : script_->GetLineNumber(node->position()); \ |
| base::OS::SNPrintF(error_message_, sizeof(error_message_), \ |
| "asm: line %d: %s\n", line + 1, msg); \ |
| return AsmType::None(); \ |
| } while (false) |
| |
| #define RECURSE(call) \ |
| do { \ |
| if (GetCurrentStackPosition() < stack_limit_) { \ |
| stack_overflow_ = true; \ |
| FAIL(root_, "Stack overflow while parsing asm.js module."); \ |
| } \ |
| \ |
| AsmType* result = (call); \ |
| if (stack_overflow_) { \ |
| return AsmType::None(); \ |
| } \ |
| \ |
| if (result == AsmType::None()) { \ |
| return AsmType::None(); \ |
| } \ |
| } while (false) |
| |
| namespace v8 { |
| namespace internal { |
| namespace wasm { |
| namespace { |
| static const uint32_t LargestFixNum = std::numeric_limits<int32_t>::max(); |
| } // namespace |
| |
| using v8::internal::AstNode; |
| using v8::internal::GetCurrentStackPosition; |
| |
| // ---------------------------------------------------------------------------- |
| // Implementation of AsmTyper::FlattenedStatements |
| |
| AsmTyper::FlattenedStatements::FlattenedStatements(Zone* zone, |
| ZoneList<Statement*>* s) |
| : context_stack_(zone) { |
| context_stack_.emplace_back(Context(s)); |
| } |
| |
| Statement* AsmTyper::FlattenedStatements::Next() { |
| for (;;) { |
| if (context_stack_.empty()) { |
| return nullptr; |
| } |
| |
| Context* current = &context_stack_.back(); |
| |
| if (current->statements_->length() <= current->next_index_) { |
| context_stack_.pop_back(); |
| continue; |
| } |
| |
| Statement* current_statement = |
| current->statements_->at(current->next_index_++); |
| if (current_statement->IsBlock()) { |
| context_stack_.emplace_back( |
| Context(current_statement->AsBlock()->statements())); |
| continue; |
| } |
| |
| return current_statement; |
| } |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // Implementation of AsmTyper::VariableInfo |
| |
| AsmTyper::VariableInfo* AsmTyper::VariableInfo::ForSpecialSymbol( |
| Zone* zone, StandardMember standard_member) { |
| DCHECK(standard_member == kStdlib || standard_member == kFFI || |
| standard_member == kHeap || standard_member == kModule); |
| auto* new_var_info = new (zone) VariableInfo(AsmType::None()); |
| new_var_info->standard_member_ = standard_member; |
| new_var_info->mutability_ = kImmutableGlobal; |
| return new_var_info; |
| } |
| |
| AsmTyper::VariableInfo* AsmTyper::VariableInfo::Clone(Zone* zone) const { |
| CHECK(standard_member_ != kNone); |
| CHECK(!type_->IsA(AsmType::None())); |
| auto* new_var_info = new (zone) VariableInfo(type_); |
| new_var_info->standard_member_ = standard_member_; |
| new_var_info->mutability_ = mutability_; |
| return new_var_info; |
| } |
| |
| void AsmTyper::VariableInfo::FirstForwardUseIs(VariableProxy* var) { |
| DCHECK(first_forward_use_ == nullptr); |
| missing_definition_ = true; |
| first_forward_use_ = var; |
| } |
| |
| // ---------------------------------------------------------------------------- |
| // Implementation of AsmTyper |
| |
| AsmTyper::AsmTyper(Isolate* isolate, Zone* zone, Script* script, |
| FunctionLiteral* root) |
| : isolate_(isolate), |
| zone_(zone), |
| script_(script), |
| root_(root), |
| forward_definitions_(zone), |
| ffi_use_signatures_(zone), |
| stdlib_types_(zone), |
| stdlib_math_types_(zone), |
| module_info_(VariableInfo::ForSpecialSymbol(zone_, kModule)), |
| global_scope_(ZoneHashMap::PointersMatch, |
| ZoneHashMap::kDefaultHashMapCapacity, |
| ZoneAllocationPolicy(zone)), |
| local_scope_(ZoneHashMap::PointersMatch, |
| ZoneHashMap::kDefaultHashMapCapacity, |
| ZoneAllocationPolicy(zone)), |
| stack_limit_(isolate->stack_guard()->real_climit()), |
| node_types_(zone_), |
| fround_type_(AsmType::FroundType(zone_)), |
| ffi_type_(AsmType::FFIType(zone_)) { |
| InitializeStdlib(); |
| } |
| |
| namespace { |
| bool ValidAsmIdentifier(Handle<String> name) { |
| static const char* kInvalidAsmNames[] = {"eval", "arguments"}; |
| |
| for (size_t ii = 0; ii < arraysize(kInvalidAsmNames); ++ii) { |
| if (strcmp(name->ToCString().get(), kInvalidAsmNames[ii]) == 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } // namespace |
| |
| void AsmTyper::InitializeStdlib() { |
| auto* d = AsmType::Double(); |
| auto* dq = AsmType::DoubleQ(); |
| auto* dq2d = AsmType::Function(zone_, d); |
| dq2d->AsFunctionType()->AddArgument(dq); |
| |
| auto* dqdq2d = AsmType::Function(zone_, d); |
| dqdq2d->AsFunctionType()->AddArgument(dq); |
| dqdq2d->AsFunctionType()->AddArgument(dq); |
| |
| auto* f = AsmType::Float(); |
| auto* fq = AsmType::FloatQ(); |
| auto* fq2f = AsmType::Function(zone_, f); |
| fq2f->AsFunctionType()->AddArgument(fq); |
| |
| auto* s = AsmType::Signed(); |
| auto* s2s = AsmType::Function(zone_, s); |
| s2s->AsFunctionType()->AddArgument(s); |
| |
| auto* i = AsmType::Int(); |
| auto* i2s = AsmType::Function(zone_, s); |
| i2s->AsFunctionType()->AddArgument(i); |
| |
| auto* ii2s = AsmType::Function(zone_, s); |
| ii2s->AsFunctionType()->AddArgument(i); |
| ii2s->AsFunctionType()->AddArgument(i); |
| |
| auto* minmax_d = AsmType::MinMaxType(zone_, d, d); |
| // *VIOLATION* The float variant is not part of the spec, but firefox accepts |
| // it. |
| auto* minmax_f = AsmType::MinMaxType(zone_, f, f); |
| auto* minmax_i = AsmType::MinMaxType(zone_, s, i); |
| auto* minmax = AsmType::OverloadedFunction(zone_); |
| minmax->AsOverloadedFunctionType()->AddOverload(minmax_i); |
| minmax->AsOverloadedFunctionType()->AddOverload(minmax_f); |
| minmax->AsOverloadedFunctionType()->AddOverload(minmax_d); |
| |
| auto* fround = fround_type_; |
| |
| auto* abs = AsmType::OverloadedFunction(zone_); |
| abs->AsOverloadedFunctionType()->AddOverload(s2s); |
| abs->AsOverloadedFunctionType()->AddOverload(dq2d); |
| abs->AsOverloadedFunctionType()->AddOverload(fq2f); |
| |
| auto* ceil = AsmType::OverloadedFunction(zone_); |
| ceil->AsOverloadedFunctionType()->AddOverload(dq2d); |
| ceil->AsOverloadedFunctionType()->AddOverload(fq2f); |
| |
| auto* floor = ceil; |
| auto* sqrt = ceil; |
| |
| struct StandardMemberInitializer { |
| const char* name; |
| StandardMember standard_member; |
| AsmType* type; |
| }; |
| |
| const StandardMemberInitializer stdlib[] = {{"Infinity", kInfinity, d}, |
| {"NaN", kNaN, d}, |
| #define ASM_TYPED_ARRAYS(V) \ |
| V(Uint8) \ |
| V(Int8) \ |
| V(Uint16) \ |
| V(Int16) \ |
| V(Uint32) \ |
| V(Int32) \ |
| V(Float32) \ |
| V(Float64) |
| |
| #define ASM_TYPED_ARRAY(TypeName) \ |
| {#TypeName "Array", kNone, AsmType::TypeName##Array()}, |
| ASM_TYPED_ARRAYS(ASM_TYPED_ARRAY) |
| #undef ASM_TYPED_ARRAY |
| }; |
| for (size_t ii = 0; ii < arraysize(stdlib); ++ii) { |
| stdlib_types_[stdlib[ii].name] = new (zone_) VariableInfo(stdlib[ii].type); |
| stdlib_types_[stdlib[ii].name]->set_standard_member( |
| stdlib[ii].standard_member); |
| stdlib_types_[stdlib[ii].name]->set_mutability( |
| VariableInfo::kImmutableGlobal); |
| } |
| |
| const StandardMemberInitializer math[] = { |
| {"PI", kMathPI, d}, |
| {"E", kMathE, d}, |
| {"LN2", kMathLN2, d}, |
| {"LN10", kMathLN10, d}, |
| {"LOG2E", kMathLOG2E, d}, |
| {"LOG10E", kMathLOG10E, d}, |
| {"SQRT2", kMathSQRT2, d}, |
| {"SQRT1_2", kMathSQRT1_2, d}, |
| {"imul", kMathImul, ii2s}, |
| {"abs", kMathAbs, abs}, |
| // NOTE: clz32 should return fixnum. The current typer can only return |
| // Signed, Float, or Double, so it returns Signed in our version of |
| // asm.js. |
| {"clz32", kMathClz32, i2s}, |
| {"ceil", kMathCeil, ceil}, |
| {"floor", kMathFloor, floor}, |
| {"fround", kMathFround, fround}, |
| {"pow", kMathPow, dqdq2d}, |
| {"exp", kMathExp, dq2d}, |
| {"log", kMathLog, dq2d}, |
| {"min", kMathMin, minmax}, |
| {"max", kMathMax, minmax}, |
| {"sqrt", kMathSqrt, sqrt}, |
| {"cos", kMathCos, dq2d}, |
| {"sin", kMathSin, dq2d}, |
| {"tan", kMathTan, dq2d}, |
| {"acos", kMathAcos, dq2d}, |
| {"asin", kMathAsin, dq2d}, |
| {"atan", kMathAtan, dq2d}, |
| {"atan2", kMathAtan2, dqdq2d}, |
| }; |
| for (size_t ii = 0; ii < arraysize(math); ++ii) { |
| stdlib_math_types_[math[ii].name] = new (zone_) VariableInfo(math[ii].type); |
| stdlib_math_types_[math[ii].name]->set_standard_member( |
| math[ii].standard_member); |
| stdlib_math_types_[math[ii].name]->set_mutability( |
| VariableInfo::kImmutableGlobal); |
| } |
| } |
| |
| // Used for 5.5 GlobalVariableTypeAnnotations |
| AsmTyper::VariableInfo* AsmTyper::ImportLookup(Property* import) { |
| auto* obj = import->obj(); |
| auto* key = import->key()->AsLiteral(); |
| |
| ObjectTypeMap* stdlib = &stdlib_types_; |
| if (auto* obj_as_property = obj->AsProperty()) { |
| // This can only be stdlib.Math |
| auto* math_name = obj_as_property->key()->AsLiteral(); |
| if (math_name == nullptr || !math_name->IsPropertyName()) { |
| return nullptr; |
| } |
| |
| if (!math_name->AsPropertyName()->IsUtf8EqualTo(CStrVector("Math"))) { |
| return nullptr; |
| } |
| |
| auto* stdlib_var_proxy = obj_as_property->obj()->AsVariableProxy(); |
| if (stdlib_var_proxy == nullptr) { |
| return nullptr; |
| } |
| obj = stdlib_var_proxy; |
| stdlib = &stdlib_math_types_; |
| } |
| |
| auto* obj_as_var_proxy = obj->AsVariableProxy(); |
| if (obj_as_var_proxy == nullptr) { |
| return nullptr; |
| } |
| |
| auto* obj_info = Lookup(obj_as_var_proxy->var()); |
| if (obj_info == nullptr) { |
| return nullptr; |
| } |
| |
| if (obj_info->IsFFI()) { |
| // For FFI we can't validate import->key, so assume this is OK. |
| return obj_info; |
| } |
| |
| std::unique_ptr<char[]> aname = key->AsPropertyName()->ToCString(); |
| ObjectTypeMap::iterator i = stdlib->find(std::string(aname.get())); |
| if (i == stdlib->end()) { |
| return nullptr; |
| } |
| stdlib_uses_.insert(i->second->standard_member()); |
| return i->second; |
| } |
| |
| AsmTyper::VariableInfo* AsmTyper::Lookup(Variable* variable) const { |
| const ZoneHashMap* scope = in_function_ ? &local_scope_ : &global_scope_; |
| ZoneHashMap::Entry* entry = |
| scope->Lookup(variable, ComputePointerHash(variable)); |
| if (entry == nullptr && in_function_) { |
| entry = global_scope_.Lookup(variable, ComputePointerHash(variable)); |
| } |
| |
| if (entry == nullptr && !module_name_.is_null() && |
| module_name_->Equals(*variable->name())) { |
| return module_info_; |
| } |
| |
| return entry ? reinterpret_cast<VariableInfo*>(entry->value) : nullptr; |
| } |
| |
| void AsmTyper::AddForwardReference(VariableProxy* proxy, VariableInfo* info) { |
| info->FirstForwardUseIs(proxy); |
| forward_definitions_.push_back(info); |
| } |
| |
| bool AsmTyper::AddGlobal(Variable* variable, VariableInfo* info) { |
| // We can't DCHECK(!in_function_) because function may actually install global |
| // names (forward defined functions and function tables.) |
| DCHECK(info->mutability() != VariableInfo::kInvalidMutability); |
| DCHECK(info->IsGlobal()); |
| DCHECK(ValidAsmIdentifier(variable->name())); |
| |
| if (!module_name_.is_null() && module_name_->Equals(*variable->name())) { |
| return false; |
| } |
| |
| ZoneHashMap::Entry* entry = global_scope_.LookupOrInsert( |
| variable, ComputePointerHash(variable), ZoneAllocationPolicy(zone_)); |
| |
| if (entry->value != nullptr) { |
| return false; |
| } |
| |
| entry->value = info; |
| return true; |
| } |
| |
| bool AsmTyper::AddLocal(Variable* variable, VariableInfo* info) { |
| DCHECK(in_function_); |
| DCHECK(info->mutability() != VariableInfo::kInvalidMutability); |
| DCHECK(!info->IsGlobal()); |
| DCHECK(ValidAsmIdentifier(variable->name())); |
| |
| ZoneHashMap::Entry* entry = local_scope_.LookupOrInsert( |
| variable, ComputePointerHash(variable), ZoneAllocationPolicy(zone_)); |
| |
| if (entry->value != nullptr) { |
| return false; |
| } |
| |
| entry->value = info; |
| return true; |
| } |
| |
| void AsmTyper::SetTypeOf(AstNode* node, AsmType* type) { |
| DCHECK_NE(type, AsmType::None()); |
| DCHECK(node_types_.find(node) == node_types_.end()); |
| node_types_.insert(std::make_pair(node, type)); |
| } |
| |
| AsmType* AsmTyper::TypeOf(AstNode* node) const { |
| auto node_type_iter = node_types_.find(node); |
| if (node_type_iter != node_types_.end()) { |
| return node_type_iter->second; |
| } |
| |
| // Sometimes literal nodes are not added to the node_type_ map simply because |
| // their are not visited with ValidateExpression(). |
| if (auto* literal = node->AsLiteral()) { |
| if (literal->raw_value()->ContainsDot()) { |
| return AsmType::Double(); |
| } |
| uint32_t u; |
| if (literal->value()->ToUint32(&u)) { |
| if (u > LargestFixNum) { |
| return AsmType::Unsigned(); |
| } |
| return AsmType::FixNum(); |
| } |
| int32_t i; |
| if (literal->value()->ToInt32(&i)) { |
| return AsmType::Signed(); |
| } |
| } |
| |
| return AsmType::None(); |
| } |
| |
| AsmType* AsmTyper::TypeOf(Variable* v) const { return Lookup(v)->type(); } |
| |
| AsmTyper::StandardMember AsmTyper::VariableAsStandardMember(Variable* var) { |
| auto* var_info = Lookup(var); |
| if (var_info == nullptr) { |
| return kNone; |
| } |
| StandardMember member = var_info->standard_member(); |
| return member; |
| } |
| |
| bool AsmTyper::Validate() { |
| if (!AsmType::None()->IsExactly(ValidateModule(root_))) { |
| return true; |
| } |
| return false; |
| } |
| |
| namespace { |
| bool IsUseAsmDirective(Statement* first_statement) { |
| ExpressionStatement* use_asm = first_statement->AsExpressionStatement(); |
| if (use_asm == nullptr) { |
| return false; |
| } |
| |
| Literal* use_asm_literal = use_asm->expression()->AsLiteral(); |
| |
| if (use_asm_literal == nullptr) { |
| return false; |
| } |
| |
| return use_asm_literal->raw_value()->AsString()->IsOneByteEqualTo("use asm"); |
| } |
| |
| Assignment* ExtractInitializerExpression(Statement* statement) { |
| auto* expr_stmt = statement->AsExpressionStatement(); |
| if (expr_stmt == nullptr) { |
| // Done with initializers. |
| return nullptr; |
| } |
| auto* assign = expr_stmt->expression()->AsAssignment(); |
| if (assign == nullptr) { |
| // Done with initializers. |
| return nullptr; |
| } |
| if (assign->op() != Token::INIT) { |
| // Done with initializers. |
| return nullptr; |
| } |
| return assign; |
| } |
| |
| } // namespace |
| |
| // 6.1 ValidateModule |
| namespace { |
| // SourceLayoutTracker keeps track of the start and end positions of each |
| // section in the asm.js source. The sections should not overlap, otherwise the |
| // asm.js source is invalid. |
| class SourceLayoutTracker { |
| public: |
| SourceLayoutTracker() = default; |
| |
| bool IsValid() const { |
| const Section* kAllSections[] = {&use_asm_, &globals_, &functions_, |
| &tables_, &exports_}; |
| for (size_t ii = 0; ii < arraysize(kAllSections); ++ii) { |
| const auto& curr_section = *kAllSections[ii]; |
| for (size_t jj = ii + 1; jj < arraysize(kAllSections); ++jj) { |
| if (curr_section.OverlapsWith(*kAllSections[jj])) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| void AddUseAsm(const AstNode& node) { use_asm_.AddNewElement(node); } |
| |
| void AddGlobal(const AstNode& node) { globals_.AddNewElement(node); } |
| |
| void AddFunction(const AstNode& node) { functions_.AddNewElement(node); } |
| |
| void AddTable(const AstNode& node) { tables_.AddNewElement(node); } |
| |
| void AddExport(const AstNode& node) { exports_.AddNewElement(node); } |
| |
| private: |
| class Section { |
| public: |
| Section() = default; |
| Section(const Section&) = default; |
| Section& operator=(const Section&) = default; |
| |
| void AddNewElement(const AstNode& node) { |
| const int node_pos = node.position(); |
| if (start_ == kNoSourcePosition) { |
| start_ = node_pos; |
| } else { |
| start_ = std::max(start_, node_pos); |
| } |
| if (end_ == kNoSourcePosition) { |
| end_ = node_pos; |
| } else { |
| end_ = std::max(end_, node_pos); |
| } |
| } |
| |
| bool OverlapsWith(const Section& other) const { |
| if (start_ == kNoSourcePosition) { |
| DCHECK_EQ(end_, kNoSourcePosition); |
| return false; |
| } |
| if (other.start_ == kNoSourcePosition) { |
| DCHECK_EQ(other.end_, kNoSourcePosition); |
| return false; |
| } |
| return other.start_ < end_ || other.end_ < start_; |
| } |
| |
| private: |
| int start_ = kNoSourcePosition; |
| int end_ = kNoSourcePosition; |
| }; |
| |
| Section use_asm_; |
| Section globals_; |
| Section functions_; |
| Section tables_; |
| Section exports_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SourceLayoutTracker); |
| }; |
| } // namespace |
| |
| AsmType* AsmTyper::ValidateModule(FunctionLiteral* fun) { |
| SourceLayoutTracker source_layout; |
| |
| DeclarationScope* scope = fun->scope(); |
| if (!scope->is_function_scope()) FAIL(fun, "Not at function scope."); |
| if (!ValidAsmIdentifier(fun->name())) |
| FAIL(fun, "Invalid asm.js identifier in module name."); |
| module_name_ = fun->name(); |
| |
| // Allowed parameters: Stdlib, FFI, Mem |
| static const uint32_t MaxModuleParameters = 3; |
| if (scope->num_parameters() > MaxModuleParameters) { |
| FAIL(fun, "asm.js modules may not have more than three parameters."); |
| } |
| |
| struct { |
| StandardMember standard_member; |
| } kModuleParamInfo[3] = { |
| {kStdlib}, {kFFI}, {kHeap}, |
| }; |
| |
| for (int ii = 0; ii < scope->num_parameters(); ++ii) { |
| Variable* param = scope->parameter(ii); |
| DCHECK(param); |
| |
| if (!ValidAsmIdentifier(param->name())) { |
| FAIL(fun, "Invalid asm.js identifier in module parameter."); |
| } |
| |
| auto* param_info = VariableInfo::ForSpecialSymbol( |
| zone_, kModuleParamInfo[ii].standard_member); |
| |
| if (!AddGlobal(param, param_info)) { |
| FAIL(fun, "Redeclared identifier in module parameter."); |
| } |
| } |
| |
| ZoneVector<Assignment*> function_pointer_tables(zone_); |
| FlattenedStatements iter(zone_, fun->body()); |
| auto* use_asm_directive = iter.Next(); |
| if (use_asm_directive == nullptr) { |
| FAIL(fun, "Missing \"use asm\"."); |
| } |
| // Check for extra assignment inserted by the parser when in this form: |
| // (function Module(a, b, c) {... }) |
| ExpressionStatement* estatement = use_asm_directive->AsExpressionStatement(); |
| if (estatement != nullptr) { |
| Assignment* assignment = estatement->expression()->AsAssignment(); |
| if (assignment != nullptr && assignment->target()->IsVariableProxy() && |
| assignment->target() |
| ->AsVariableProxy() |
| ->var() |
| ->is_sloppy_function_name()) { |
| use_asm_directive = iter.Next(); |
| } |
| } |
| if (!IsUseAsmDirective(use_asm_directive)) { |
| FAIL(fun, "Missing \"use asm\"."); |
| } |
| source_layout.AddUseAsm(*use_asm_directive); |
| ReturnStatement* module_return = nullptr; |
| |
| // *VIOLATION* The spec states that globals should be followed by function |
| // declarations, which should be followed by function pointer tables, followed |
| // by the module export (return) statement. Our AST might be rearraged by the |
| // parser, so we can't rely on it being in source code order. |
| while (Statement* current = iter.Next()) { |
| if (auto* assign = ExtractInitializerExpression(current)) { |
| if (assign->value()->IsArrayLiteral()) { |
| // Save function tables for later validation. |
| function_pointer_tables.push_back(assign); |
| } else { |
| RECURSE(ValidateGlobalDeclaration(assign)); |
| source_layout.AddGlobal(*assign); |
| } |
| continue; |
| } |
| |
| if (auto* current_as_return = current->AsReturnStatement()) { |
| if (module_return != nullptr) { |
| FAIL(fun, "Multiple export statements."); |
| } |
| module_return = current_as_return; |
| source_layout.AddExport(*module_return); |
| continue; |
| } |
| |
| FAIL(current, "Invalid top-level statement in asm.js module."); |
| } |
| |
| ZoneList<Declaration*>* decls = scope->declarations(); |
| |
| for (int ii = 0; ii < decls->length(); ++ii) { |
| Declaration* decl = decls->at(ii); |
| |
| if (FunctionDeclaration* fun_decl = decl->AsFunctionDeclaration()) { |
| RECURSE(ValidateFunction(fun_decl)); |
| source_layout.AddFunction(*fun_decl); |
| continue; |
| } |
| } |
| |
| for (auto* function_table : function_pointer_tables) { |
| RECURSE(ValidateFunctionTable(function_table)); |
| source_layout.AddTable(*function_table); |
| } |
| |
| for (int ii = 0; ii < decls->length(); ++ii) { |
| Declaration* decl = decls->at(ii); |
| |
| if (decl->IsFunctionDeclaration()) { |
| continue; |
| } |
| |
| VariableDeclaration* var_decl = decl->AsVariableDeclaration(); |
| if (var_decl == nullptr) { |
| FAIL(decl, "Invalid asm.js declaration."); |
| } |
| |
| auto* var_proxy = var_decl->proxy(); |
| if (var_proxy == nullptr) { |
| FAIL(decl, "Invalid asm.js declaration."); |
| } |
| |
| if (Lookup(var_proxy->var()) == nullptr) { |
| FAIL(decl, "Global variable missing initializer in asm.js module."); |
| } |
| } |
| |
| // 6.2 ValidateExport |
| if (module_return == nullptr) { |
| FAIL(fun, "Missing asm.js module export."); |
| } |
| |
| for (auto* forward_def : forward_definitions_) { |
| if (forward_def->missing_definition()) { |
| FAIL(forward_def->first_forward_use(), |
| "Missing definition for forward declared identifier."); |
| } |
| } |
| |
| RECURSE(ValidateExport(module_return)); |
| |
| if (!source_layout.IsValid()) { |
| FAIL(fun, "Invalid asm.js source code layout."); |
| } |
| |
| return AsmType::Int(); // Any type that is not AsmType::None(); |
| } |
| |
| namespace { |
| bool IsDoubleAnnotation(BinaryOperation* binop) { |
| // *VIOLATION* The parser replaces uses of +x with x*1.0. |
| if (binop->op() != Token::MUL) { |
| return false; |
| } |
| |
| auto* right_as_literal = binop->right()->AsLiteral(); |
| if (right_as_literal == nullptr) { |
| return false; |
| } |
| |
| return right_as_literal->raw_value()->ContainsDot() && |
| right_as_literal->raw_value()->AsNumber() == 1.0; |
| } |
| |
| bool IsIntAnnotation(BinaryOperation* binop) { |
| if (binop->op() != Token::BIT_OR) { |
| return false; |
| } |
| |
| auto* right_as_literal = binop->right()->AsLiteral(); |
| if (right_as_literal == nullptr) { |
| return false; |
| } |
| |
| return !right_as_literal->raw_value()->ContainsDot() && |
| right_as_literal->raw_value()->AsNumber() == 0.0; |
| } |
| } // namespace |
| |
| AsmType* AsmTyper::ValidateGlobalDeclaration(Assignment* assign) { |
| DCHECK(!assign->is_compound()); |
| if (assign->is_compound()) { |
| FAIL(assign, |
| "Compound assignment not supported when declaring global variables."); |
| } |
| |
| auto* target = assign->target(); |
| if (!target->IsVariableProxy()) { |
| FAIL(target, "Module assignments may only assign to globals."); |
| } |
| auto* target_variable = target->AsVariableProxy()->var(); |
| auto* target_info = Lookup(target_variable); |
| |
| if (target_info != nullptr) { |
| FAIL(target, "Redefined global variable."); |
| } |
| |
| auto* value = assign->value(); |
| // Not all types of assignment are allowed by asm.js. See |
| // 5.5 Global Variable Type Annotations. |
| bool global_variable = false; |
| if (value->IsLiteral() || value->IsCall()) { |
| AsmType* type = nullptr; |
| RECURSE(type = VariableTypeAnnotations(value)); |
| target_info = new (zone_) VariableInfo(type); |
| target_info->set_mutability(VariableInfo::kMutableGlobal); |
| global_variable = true; |
| } else if (value->IsProperty()) { |
| target_info = ImportLookup(value->AsProperty()); |
| if (target_info == nullptr) { |
| FAIL(assign, "Invalid import."); |
| } |
| CHECK(target_info->mutability() == VariableInfo::kImmutableGlobal); |
| if (target_info->IsFFI()) { |
| // create a new target info that represents a foreign variable. |
| target_info = new (zone_) VariableInfo(ffi_type_); |
| target_info->set_mutability(VariableInfo::kImmutableGlobal); |
| } else if (target_info->type()->IsA(AsmType::Heap())) { |
| FAIL(assign, "Heap view types can not be aliased."); |
| } else { |
| target_info = target_info->Clone(zone_); |
| } |
| } else if (value->IsBinaryOperation()) { |
| // This should either be: |
| // |
| // var <> = ffi.<>|0 |
| // |
| // or |
| // |
| // var <> = +ffi.<> |
| auto* value_binop = value->AsBinaryOperation(); |
| auto* left = value_binop->left(); |
| AsmType* import_type = nullptr; |
| |
| if (IsDoubleAnnotation(value_binop)) { |
| import_type = AsmType::Double(); |
| } else if (IsIntAnnotation(value_binop)) { |
| import_type = AsmType::Int(); |
| } else { |
| FAIL(value, |
| "Invalid initializer for foreign import - unrecognized annotation."); |
| } |
| |
| if (!left->IsProperty()) { |
| FAIL(value, |
| "Invalid initializer for foreign import - must import member."); |
| } |
| target_info = ImportLookup(left->AsProperty()); |
| if (target_info == nullptr) { |
| // TODO(jpp): this error message is innacurate: this may fail if the |
| // object lookup fails, or if the property lookup fails, or even if the |
| // import is bogus like a().c. |
| FAIL(value, |
| "Invalid initializer for foreign import - object lookup failed."); |
| } |
| CHECK(target_info->mutability() == VariableInfo::kImmutableGlobal); |
| if (!target_info->IsFFI()) { |
| FAIL(value, |
| "Invalid initializer for foreign import - object is not the ffi."); |
| } |
| |
| // Create a new target info that represents the foreign import. |
| target_info = new (zone_) VariableInfo(import_type); |
| target_info->set_mutability(VariableInfo::kMutableGlobal); |
| } else if (value->IsCallNew()) { |
| AsmType* type = nullptr; |
| RECURSE(type = NewHeapView(value->AsCallNew())); |
| target_info = new (zone_) VariableInfo(type); |
| target_info->set_mutability(VariableInfo::kImmutableGlobal); |
| } |
| |
| if (target_info == nullptr) { |
| FAIL(assign, "Invalid global variable initializer."); |
| } |
| |
| if (!ValidAsmIdentifier(target_variable->name())) { |
| FAIL(target, "Invalid asm.js identifier in global variable."); |
| } |
| |
| if (!AddGlobal(target_variable, target_info)) { |
| FAIL(assign, "Redeclared global identifier."); |
| } |
| |
| DCHECK(target_info->type() != AsmType::None()); |
| if (!global_variable) { |
| // Global variables have their types set in VariableTypeAnnotations. |
| SetTypeOf(value, target_info->type()); |
| } |
| SetTypeOf(assign, target_info->type()); |
| SetTypeOf(target, target_info->type()); |
| return target_info->type(); |
| } |
| |
| // 6.2 ValidateExport |
| AsmType* AsmTyper::ExportType(VariableProxy* fun_export) { |
| auto* fun_info = Lookup(fun_export->var()); |
| if (fun_info == nullptr) { |
| FAIL(fun_export, "Undefined identifier in asm.js module export."); |
| } |
| |
| if (fun_info->standard_member() != kNone) { |
| FAIL(fun_export, "Module cannot export standard library functions."); |
| } |
| |
| auto* type = fun_info->type(); |
| if (type->AsFFIType() != nullptr) { |
| FAIL(fun_export, "Module cannot export foreign functions."); |
| } |
| |
| if (type->AsFunctionTableType() != nullptr) { |
| FAIL(fun_export, "Module cannot export function tables."); |
| } |
| |
| if (fun_info->type()->AsFunctionType() == nullptr) { |
| FAIL(fun_export, "Module export is not an asm.js function."); |
| } |
| |
| return type; |
| } |
| |
| AsmType* AsmTyper::ValidateExport(ReturnStatement* exports) { |
| // asm.js modules can export single functions, or multiple functions in an |
| // object literal. |
| if (auto* fun_export = exports->expression()->AsVariableProxy()) { |
| // Exporting single function. |
| AsmType* export_type; |
| RECURSE(export_type = ExportType(fun_export)); |
| return export_type; |
| } |
| |
| if (auto* obj_export = exports->expression()->AsObjectLiteral()) { |
| // Exporting object literal. |
| for (auto* prop : *obj_export->properties()) { |
| if (!prop->key()->IsLiteral()) { |
| FAIL(prop->key(), |
| "Only normal object properties may be used in the export object " |
| "literal."); |
| } |
| |
| auto* export_obj = prop->value()->AsVariableProxy(); |
| if (export_obj == nullptr) { |
| FAIL(prop->value(), "Exported value must be an asm.js function name."); |
| } |
| |
| RECURSE(ExportType(export_obj)); |
| } |
| |
| return AsmType::Int(); |
| } |
| |
| FAIL(exports, "Unrecognized expression in asm.js module export expression."); |
| } |
| |
| // 6.3 ValidateFunctionTable |
| AsmType* AsmTyper::ValidateFunctionTable(Assignment* assign) { |
| if (assign->is_compound()) { |
| FAIL(assign, |
| "Compound assignment not supported when declaring global variables."); |
| } |
| |
| auto* target = assign->target(); |
| if (!target->IsVariableProxy()) { |
| FAIL(target, "Module assignments may only assign to globals."); |
| } |
| auto* target_variable = target->AsVariableProxy()->var(); |
| |
| auto* value = assign->value()->AsArrayLiteral(); |
| CHECK(value != nullptr); |
| ZoneList<Expression*>* pointers = value->values(); |
| |
| // The function table size must be n = 2 ** m, for m >= 0; |
| // TODO(jpp): should this be capped? |
| if (!base::bits::IsPowerOfTwo32(pointers->length())) { |
| FAIL(assign, "Invalid length for function pointer table."); |
| } |
| |
| AsmType* table_element_type = nullptr; |
| for (auto* initializer : *pointers) { |
| auto* var_proxy = initializer->AsVariableProxy(); |
| if (var_proxy == nullptr) { |
| FAIL(initializer, |
| "Function pointer table initializer must be a function name."); |
| } |
| |
| auto* var_info = Lookup(var_proxy->var()); |
| if (var_info == nullptr) { |
| FAIL(var_proxy, |
| "Undefined identifier in function pointer table initializer."); |
| } |
| |
| if (var_info->standard_member() != kNone) { |
| FAIL(initializer, |
| "Function pointer table must not be a member of the standard " |
| "library."); |
| } |
| |
| auto* initializer_type = var_info->type(); |
| if (initializer_type->AsFunctionType() == nullptr) { |
| FAIL(initializer, |
| "Function pointer table initializer must be an asm.js function."); |
| } |
| |
| DCHECK(var_info->type()->AsFFIType() == nullptr); |
| DCHECK(var_info->type()->AsFunctionTableType() == nullptr); |
| |
| if (table_element_type == nullptr) { |
| table_element_type = initializer_type; |
| } else if (!initializer_type->IsA(table_element_type)) { |
| FAIL(initializer, "Type mismatch in function pointer table initializer."); |
| } |
| } |
| |
| auto* target_info = Lookup(target_variable); |
| if (target_info == nullptr) { |
| // Function pointer tables are the last entities to be validates, so this is |
| // unlikely to happen: only unreferenced function tables will not already |
| // have an entry in the global scope. |
| target_info = new (zone_) VariableInfo(AsmType::FunctionTableType( |
| zone_, pointers->length(), table_element_type)); |
| target_info->set_mutability(VariableInfo::kImmutableGlobal); |
| if (!ValidAsmIdentifier(target_variable->name())) { |
| FAIL(target, "Invalid asm.js identifier in function table name."); |
| } |
| if (!AddGlobal(target_variable, target_info)) { |
| DCHECK(false); |
| FAIL(assign, "Redeclared global identifier in function table name."); |
| } |
| SetTypeOf(value, target_info->type()); |
| return target_info->type(); |
| } |
| |
| auto* target_info_table = target_info->type()->AsFunctionTableType(); |
| if (target_info_table == nullptr) { |
| FAIL(assign, "Identifier redefined as function pointer table."); |
| } |
| |
| if (!target_info->missing_definition()) { |
| FAIL(assign, "Identifier redefined (function table name)."); |
| } |
| |
| if (target_info_table->length() != pointers->length()) { |
| FAIL(assign, "Function table size mismatch."); |
| } |
| |
| DCHECK(target_info_table->signature()->AsFunctionType()); |
| if (!table_element_type->IsA(target_info_table->signature())) { |
| FAIL(assign, "Function table initializer does not match previous type."); |
| } |
| |
| target_info->MarkDefined(); |
| DCHECK(target_info->type() != AsmType::None()); |
| SetTypeOf(value, target_info->type()); |
| |
| return target_info->type(); |
| } |
| |
| // 6.4 ValidateFunction |
| AsmType* AsmTyper::ValidateFunction(FunctionDeclaration* fun_decl) { |
| FunctionScope _(this); |
| |
| // Extract parameter types. |
| auto* fun = fun_decl->fun(); |
| |
| auto* fun_decl_proxy = fun_decl->proxy(); |
| if (fun_decl_proxy == nullptr) { |
| FAIL(fun_decl, "Anonymous functions are not support in asm.js."); |
| } |
| |
| Statement* current; |
| FlattenedStatements iter(zone_, fun->body()); |
| |
| size_t annotated_parameters = 0; |
| |
| // 5.3 Function type annotations |
| // * parameters |
| ZoneVector<AsmType*> parameter_types(zone_); |
| for (; (current = iter.Next()) != nullptr; ++annotated_parameters) { |
| auto* stmt = current->AsExpressionStatement(); |
| if (stmt == nullptr) { |
| // Done with parameters. |
| break; |
| } |
| auto* expr = stmt->expression()->AsAssignment(); |
| if (expr == nullptr || expr->is_compound()) { |
| // Done with parameters. |
| break; |
| } |
| auto* proxy = expr->target()->AsVariableProxy(); |
| if (proxy == nullptr) { |
| // Done with parameters. |
| break; |
| } |
| auto* param = proxy->var(); |
| if (param->location() != VariableLocation::PARAMETER || |
| param->index() != annotated_parameters) { |
| // Done with parameters. |
| break; |
| } |
| |
| AsmType* type; |
| RECURSE(type = ParameterTypeAnnotations(param, expr->value())); |
| DCHECK(type->IsParameterType()); |
| auto* param_info = new (zone_) VariableInfo(type); |
| param_info->set_mutability(VariableInfo::kLocal); |
| if (!ValidAsmIdentifier(proxy->name())) { |
| FAIL(proxy, "Invalid asm.js identifier in parameter name."); |
| } |
| |
| if (!AddLocal(param, param_info)) { |
| FAIL(proxy, "Redeclared parameter."); |
| } |
| parameter_types.push_back(type); |
| SetTypeOf(proxy, type); |
| SetTypeOf(expr, type); |
| } |
| |
| if (annotated_parameters != fun->parameter_count()) { |
| FAIL(fun_decl, "Incorrect parameter type annotations."); |
| } |
| |
| // 5.3 Function type annotations |
| // * locals |
| for (; current; current = iter.Next()) { |
| auto* initializer = ExtractInitializerExpression(current); |
| if (initializer == nullptr) { |
| // Done with locals. |
| break; |
| } |
| |
| auto* local = initializer->target()->AsVariableProxy(); |
| if (local == nullptr) { |
| // Done with locals. It should never happen. Even if it does, the asm.js |
| // code should not declare any other locals after this point, so we assume |
| // this is OK. If any other variable declaration is found we report a |
| // validation error. |
| DCHECK(false); |
| break; |
| } |
| |
| AsmType* type; |
| RECURSE(type = VariableTypeAnnotations(initializer->value())); |
| auto* local_info = new (zone_) VariableInfo(type); |
| local_info->set_mutability(VariableInfo::kLocal); |
| if (!ValidAsmIdentifier(local->name())) { |
| FAIL(local, "Invalid asm.js identifier in local variable."); |
| } |
| |
| if (!AddLocal(local->var(), local_info)) { |
| FAIL(initializer, "Redeclared local."); |
| } |
| |
| SetTypeOf(local, type); |
| SetTypeOf(initializer, type); |
| } |
| |
| // 5.2 Return Type Annotations |
| // *VIOLATION* we peel blocks to find the last statement in the asm module |
| // because the parser may introduce synthetic blocks. |
| ZoneList<Statement*>* statements = fun->body(); |
| |
| do { |
| if (statements->length() == 0) { |
| return_type_ = AsmType::Void(); |
| } else { |
| auto* last_statement = statements->last(); |
| auto* as_block = last_statement->AsBlock(); |
| if (as_block != nullptr) { |
| statements = as_block->statements(); |
| } else { |
| // We don't check whether AsReturnStatement() below returns non-null -- |
| // we leave that to the ReturnTypeAnnotations method. |
| RECURSE(return_type_ = |
| ReturnTypeAnnotations(last_statement->AsReturnStatement())); |
| } |
| } |
| } while (return_type_ == AsmType::None()); |
| |
| DCHECK(return_type_->IsReturnType()); |
| |
| for (auto* decl : *fun->scope()->declarations()) { |
| auto* var_decl = decl->AsVariableDeclaration(); |
| if (var_decl == nullptr) { |
| FAIL(decl, "Functions may only define inner variables."); |
| } |
| |
| auto* var_proxy = var_decl->proxy(); |
| if (var_proxy == nullptr) { |
| FAIL(decl, "Invalid local declaration declaration."); |
| } |
| |
| auto* var_info = Lookup(var_proxy->var()); |
| if (var_info == nullptr || var_info->IsGlobal()) { |
| FAIL(decl, "Local variable missing initializer in asm.js module."); |
| } |
| } |
| |
| for (; current; current = iter.Next()) { |
| AsmType* current_type; |
| RECURSE(current_type = ValidateStatement(current)); |
| } |
| |
| auto* fun_type = AsmType::Function(zone_, return_type_); |
| auto* fun_type_as_function = fun_type->AsFunctionType(); |
| for (auto* param_type : parameter_types) { |
| fun_type_as_function->AddArgument(param_type); |
| } |
| |
| auto* fun_var = fun_decl_proxy->var(); |
| auto* fun_info = new (zone_) VariableInfo(fun_type); |
| fun_info->set_mutability(VariableInfo::kImmutableGlobal); |
| auto* old_fun_info = Lookup(fun_var); |
| if (old_fun_info == nullptr) { |
| if (!ValidAsmIdentifier(fun_var->name())) { |
| FAIL(fun_decl_proxy, "Invalid asm.js identifier in function name."); |
| } |
| if (!AddGlobal(fun_var, fun_info)) { |
| DCHECK(false); |
| FAIL(fun_decl, "Redeclared global identifier."); |
| } |
| |
| SetTypeOf(fun, fun_type); |
| return fun_type; |
| } |
| |
| // Not necessarily an error -- fun_decl might have been used before being |
| // defined. If that's the case, then the type in the global environment must |
| // be the same as the type inferred by the parameter/return type annotations. |
| auto* old_fun_type = old_fun_info->type(); |
| if (old_fun_type->AsFunctionType() == nullptr) { |
| FAIL(fun_decl, "Identifier redefined as function."); |
| } |
| |
| if (!old_fun_info->missing_definition()) { |
| FAIL(fun_decl, "Identifier redefined (function name)."); |
| } |
| |
| if (!fun_type->IsA(old_fun_type)) { |
| FAIL(fun_decl, "Signature mismatch when defining function."); |
| } |
| |
| old_fun_info->MarkDefined(); |
| SetTypeOf(fun, fun_type); |
| |
| return fun_type; |
| } |
| |
| // 6.5 ValidateStatement |
| AsmType* AsmTyper::ValidateStatement(Statement* statement) { |
| switch (statement->node_type()) { |
| default: |
| FAIL(statement, "Statement type invalid for asm.js."); |
| case AstNode::kBlock: |
| return ValidateBlockStatement(statement->AsBlock()); |
| case AstNode::kExpressionStatement: |
| return ValidateExpressionStatement(statement->AsExpressionStatement()); |
| case AstNode::kEmptyStatement: |
| return ValidateEmptyStatement(statement->AsEmptyStatement()); |
| case AstNode::kIfStatement: |
| return ValidateIfStatement(statement->AsIfStatement()); |
| case AstNode::kReturnStatement: |
| return ValidateReturnStatement(statement->AsReturnStatement()); |
| case AstNode::kWhileStatement: |
| return ValidateWhileStatement(statement->AsWhileStatement()); |
| case AstNode::kDoWhileStatement: |
| return ValidateDoWhileStatement(statement->AsDoWhileStatement()); |
| case AstNode::kForStatement: |
| return ValidateForStatement(statement->AsForStatement()); |
| case AstNode::kBreakStatement: |
| return ValidateBreakStatement(statement->AsBreakStatement()); |
| case AstNode::kContinueStatement: |
| return ValidateContinueStatement(statement->AsContinueStatement()); |
| case AstNode::kSwitchStatement: |
| return ValidateSwitchStatement(statement->AsSwitchStatement()); |
| } |
| |
| return AsmType::Void(); |
| } |
| |
| // 6.5.1 BlockStatement |
| AsmType* AsmTyper::ValidateBlockStatement(Block* block) { |
| FlattenedStatements iter(zone_, block->statements()); |
| |
| while (auto* current = iter.Next()) { |
| RECURSE(ValidateStatement(current)); |
| } |
| |
| return AsmType::Void(); |
| } |
| |
| // 6.5.2 ExpressionStatement |
| AsmType* AsmTyper::ValidateExpressionStatement(ExpressionStatement* expr) { |
| auto* expression = expr->expression(); |
| if (auto* call = expression->AsCall()) { |
| RECURSE(ValidateCall(AsmType::Void(), call)); |
| } else { |
| RECURSE(ValidateExpression(expression)); |
| } |
| |
| return AsmType::Void(); |
| } |
| |
| // 6.5.3 EmptyStatement |
| AsmType* AsmTyper::ValidateEmptyStatement(EmptyStatement* empty) { |
| return AsmType::Void(); |
| } |
| |
| // 6.5.4 IfStatement |
| AsmType* AsmTyper::ValidateIfStatement(IfStatement* if_stmt) { |
| AsmType* cond_type; |
| RECURSE(cond_type = ValidateExpression(if_stmt->condition())); |
| if (!cond_type->IsA(AsmType::Int())) { |
| FAIL(if_stmt->condition(), "If condition must be type int."); |
| } |
| RECURSE(ValidateStatement(if_stmt->then_statement())); |
| RECURSE(ValidateStatement(if_stmt->else_statement())); |
| return AsmType::Void(); |
| } |
| |
| // 6.5.5 ReturnStatement |
| AsmType* AsmTyper::ValidateReturnStatement(ReturnStatement* ret_stmt) { |
| AsmType* ret_expr_type = AsmType::Void(); |
| if (auto* ret_expr = ret_stmt->expression()) { |
| RECURSE(ret_expr_type = ValidateExpression(ret_expr)); |
| if (ret_expr_type == AsmType::Void()) { |
| // *VIOLATION* The parser modifies the source code so that expressionless |
| // returns will return undefined, so we need to allow that. |
| if (!ret_expr->IsUndefinedLiteral()) { |
| FAIL(ret_stmt, "Return statement expression can't be void."); |
| } |
| } |
| } |
| |
| if (!ret_expr_type->IsA(return_type_)) { |
| FAIL(ret_stmt, "Type mismatch in return statement."); |
| } |
| |
| return ret_expr_type; |
| } |
| |
| // 6.5.6 IterationStatement |
| // 6.5.6.a WhileStatement |
| AsmType* AsmTyper::ValidateWhileStatement(WhileStatement* while_stmt) { |
| AsmType* cond_type; |
| RECURSE(cond_type = ValidateExpression(while_stmt->cond())); |
| if (!cond_type->IsA(AsmType::Int())) { |
| FAIL(while_stmt->cond(), "While condition must be type int."); |
| } |
| |
| if (auto* body = while_stmt->body()) { |
| RECURSE(ValidateStatement(body)); |
| } |
| return AsmType::Void(); |
| } |
| |
| // 6.5.6.b DoWhileStatement |
| AsmType* AsmTyper::ValidateDoWhileStatement(DoWhileStatement* do_while) { |
| AsmType* cond_type; |
| RECURSE(cond_type = ValidateExpression(do_while->cond())); |
| if (!cond_type->IsA(AsmType::Int())) { |
| FAIL(do_while->cond(), "Do {} While condition must be type int."); |
| } |
| |
| if (auto* body = do_while->body()) { |
| RECURSE(ValidateStatement(body)); |
| } |
| return AsmType::Void(); |
| } |
| |
| // 6.5.6.c ForStatement |
| AsmType* AsmTyper::ValidateForStatement(ForStatement* for_stmt) { |
| if (auto* init = for_stmt->init()) { |
| RECURSE(ValidateStatement(init)); |
| } |
| |
| if (auto* cond = for_stmt->cond()) { |
| AsmType* cond_type; |
| RECURSE(cond_type = ValidateExpression(cond)); |
| if (!cond_type->IsA(AsmType::Int())) { |
| FAIL(cond, "For condition must be type int."); |
| } |
| } |
| |
| if (auto* next = for_stmt->next()) { |
| RECURSE(ValidateStatement(next)); |
| } |
| |
| if (auto* body = for_stmt->body()) { |
| RECURSE(ValidateStatement(body)); |
| } |
| |
| return AsmType::Void(); |
| } |
| |
| // 6.5.7 BreakStatement |
| AsmType* AsmTyper::ValidateBreakStatement(BreakStatement* brk_stmt) { |
| return AsmType::Void(); |
| } |
| |
| // 6.5.8 ContinueStatement |
| AsmType* AsmTyper::ValidateContinueStatement(ContinueStatement* cont_stmt) { |
| return AsmType::Void(); |
| } |
| |
| // 6.5.9 LabelledStatement |
| // No need to handle these here -- see the AsmTyper's definition. |
| |
| // 6.5.10 SwitchStatement |
| AsmType* AsmTyper::ValidateSwitchStatement(SwitchStatement* stmt) { |
| AsmType* cond_type; |
| RECURSE(cond_type = ValidateExpression(stmt->tag())); |
| if (!cond_type->IsA(AsmType::Signed())) { |
| FAIL(stmt, "Switch tag must be signed."); |
| } |
| |
| int default_pos = kNoSourcePosition; |
| int last_case_pos = kNoSourcePosition; |
| ZoneSet<int32_t> cases_seen(zone_); |
| for (auto* a_case : *stmt->cases()) { |
| if (a_case->is_default()) { |
| CHECK(default_pos == kNoSourcePosition); |
| RECURSE(ValidateDefault(a_case)); |
| default_pos = a_case->position(); |
| continue; |
| } |
| |
| if (last_case_pos == kNoSourcePosition) { |
| last_case_pos = a_case->position(); |
| } else { |
| last_case_pos = std::max(last_case_pos, a_case->position()); |
| } |
| |
| int32_t case_lbl; |
| RECURSE(ValidateCase(a_case, &case_lbl)); |
| auto case_lbl_pos = cases_seen.find(case_lbl); |
| if (case_lbl_pos != cases_seen.end() && *case_lbl_pos == case_lbl) { |
| FAIL(a_case, "Duplicated case label."); |
| } |
| cases_seen.insert(case_lbl); |
| } |
| |
| if (!cases_seen.empty()) { |
| const int64_t max_lbl = *cases_seen.rbegin(); |
| const int64_t min_lbl = *cases_seen.begin(); |
| if (max_lbl - min_lbl > std::numeric_limits<int32_t>::max()) { |
| FAIL(stmt, "Out-of-bounds case label range."); |
| } |
| } |
| |
| if (last_case_pos != kNoSourcePosition && default_pos != kNoSourcePosition && |
| default_pos < last_case_pos) { |
| FAIL(stmt, "Switch default must appear last."); |
| } |
| |
| return AsmType::Void(); |
| } |
| |
| // 6.6 ValidateCase |
| namespace { |
| bool ExtractInt32CaseLabel(CaseClause* clause, int32_t* lbl) { |
| auto* lbl_expr = clause->label()->AsLiteral(); |
| |
| if (lbl_expr == nullptr) { |
| return false; |
| } |
| |
| if (lbl_expr->raw_value()->ContainsDot()) { |
| return false; |
| } |
| |
| return lbl_expr->value()->ToInt32(lbl); |
| } |
| } // namespace |
| |
| AsmType* AsmTyper::ValidateCase(CaseClause* label, int32_t* case_lbl) { |
| if (!ExtractInt32CaseLabel(label, case_lbl)) { |
| FAIL(label, "Case label must be a 32-bit signed integer."); |
| } |
| |
| FlattenedStatements iter(zone_, label->statements()); |
| while (auto* current = iter.Next()) { |
| RECURSE(ValidateStatement(current)); |
| } |
| return AsmType::Void(); |
| } |
| |
| // 6.7 ValidateDefault |
| AsmType* AsmTyper::ValidateDefault(CaseClause* label) { |
| FlattenedStatements iter(zone_, label->statements()); |
| while (auto* current = iter.Next()) { |
| RECURSE(ValidateStatement(current)); |
| } |
| return AsmType::Void(); |
| } |
| |
| // 6.8 ValidateExpression |
| AsmType* AsmTyper::ValidateExpression(Expression* expr) { |
| AsmType* expr_ty = AsmType::None(); |
| |
| switch (expr->node_type()) { |
| default: |
| FAIL(expr, "Invalid asm.js expression."); |
| case AstNode::kLiteral: |
| RECURSE(expr_ty = ValidateNumericLiteral(expr->AsLiteral())); |
| break; |
| case AstNode::kVariableProxy: |
| RECURSE(expr_ty = ValidateIdentifier(expr->AsVariableProxy())); |
| break; |
| case AstNode::kCall: |
| RECURSE(expr_ty = ValidateCallExpression(expr->AsCall())); |
| break; |
| case AstNode::kProperty: |
| RECURSE(expr_ty = ValidateMemberExpression(expr->AsProperty())); |
| break; |
| case AstNode::kAssignment: |
| RECURSE(expr_ty = ValidateAssignmentExpression(expr->AsAssignment())); |
| break; |
| case AstNode::kUnaryOperation: |
| RECURSE(expr_ty = ValidateUnaryExpression(expr->AsUnaryOperation())); |
| break; |
| case AstNode::kConditional: |
| RECURSE(expr_ty = ValidateConditionalExpression(expr->AsConditional())); |
| break; |
| case AstNode::kCompareOperation: |
| RECURSE(expr_ty = ValidateCompareOperation(expr->AsCompareOperation())); |
| break; |
| case AstNode::kBinaryOperation: |
| RECURSE(expr_ty = ValidateBinaryOperation(expr->AsBinaryOperation())); |
| break; |
| } |
| |
| SetTypeOf(expr, expr_ty); |
| return expr_ty; |
| } |
| |
| AsmType* AsmTyper::ValidateCompareOperation(CompareOperation* cmp) { |
| switch (cmp->op()) { |
| default: |
| FAIL(cmp, "Invalid asm.js comparison operator."); |
| case Token::LT: |
| case Token::LTE: |
| case Token::GT: |
| case Token::GTE: |
| return ValidateRelationalExpression(cmp); |
| case Token::EQ: |
| case Token::NE: |
| return ValidateEqualityExpression(cmp); |
| } |
| |
| UNREACHABLE(); |
| } |
| |
| namespace { |
| bool IsInvert(BinaryOperation* binop) { |
| if (binop->op() != Token::BIT_XOR) { |
| return false; |
| } |
| |
| auto* right_as_literal = binop->right()->AsLiteral(); |
| if (right_as_literal == nullptr) { |
| return false; |
| } |
| |
| return !right_as_literal->raw_value()->ContainsDot() && |
| right_as_literal->raw_value()->AsNumber() == -1.0; |
| } |
| |
| bool IsUnaryMinus(BinaryOperation* binop) { |
| // *VIOLATION* The parser replaces uses of -x with x*-1. |
| if (binop->op() != Token::MUL) { |
| return false; |
| } |
| |
| auto* right_as_literal = binop->right()->AsLiteral(); |
| if (right_as_literal == nullptr) { |
| return false; |
| } |
| |
| return !right_as_literal->raw_value()->ContainsDot() && |
| right_as_literal->raw_value()->AsNumber() == -1.0; |
| } |
| } // namespace |
| |
| AsmType* AsmTyper::ValidateBinaryOperation(BinaryOperation* expr) { |
| #define UNOP_OVERLOAD(Src, Dest) \ |
| do { \ |
| if (left_type->IsA(AsmType::Src())) { \ |
| return AsmType::Dest(); \ |
| } \ |
| } while (0) |
| |
| switch (expr->op()) { |
| default: |
| FAIL(expr, "Invalid asm.js binary expression."); |
| case Token::COMMA: |
| return ValidateCommaExpression(expr); |
| case Token::MUL: |
| if (IsDoubleAnnotation(expr)) { |
| // *VIOLATION* We can't be 100% sure this really IS a unary + in the asm |
| // source so we have to be lenient, and treat this as a unary +. |
| if (auto* Call = expr->left()->AsCall()) { |
| return ValidateCall(AsmType::Double(), Call); |
| } |
| AsmType* left_type; |
| RECURSE(left_type = ValidateExpression(expr->left())); |
| SetTypeOf(expr->right(), AsmType::Double()); |
| UNOP_OVERLOAD(Signed, Double); |
| UNOP_OVERLOAD(Unsigned, Double); |
| UNOP_OVERLOAD(DoubleQ, Double); |
| UNOP_OVERLOAD(FloatQ, Double); |
| FAIL(expr, "Invalid type for conversion to double."); |
| } |
| |
| if (IsUnaryMinus(expr)) { |
| // *VIOLATION* the parser converts -x to x * -1. |
| AsmType* left_type; |
| RECURSE(left_type = ValidateExpression(expr->left())); |
| SetTypeOf(expr->right(), left_type); |
| UNOP_OVERLOAD(Int, Intish); |
| UNOP_OVERLOAD(DoubleQ, Double); |
| UNOP_OVERLOAD(FloatQ, Floatish); |
| FAIL(expr, "Invalid type for unary -."); |
| } |
| // FALTHROUGH |
| case Token::DIV: |
| case Token::MOD: |
| return ValidateMultiplicativeExpression(expr); |
| case Token::ADD: |
| case Token::SUB: { |
| static const uint32_t kInitialIntishCount = 0; |
| return ValidateAdditiveExpression(expr, kInitialIntishCount); |
| } |
| case Token::SAR: |
| case Token::SHL: |
| case Token::SHR: |
| return ValidateShiftExpression(expr); |
| case Token::BIT_AND: |
| return ValidateBitwiseANDExpression(expr); |
| case Token::BIT_XOR: |
| if (IsInvert(expr)) { |
| auto* left = expr->left(); |
| auto* left_as_binop = left->AsBinaryOperation(); |
| |
| if (left_as_binop != nullptr && IsInvert(left_as_binop)) { |
| // This is the special ~~ operator. |
| AsmType* left_type; |
| RECURSE(left_type = ValidateExpression(left_as_binop->left())); |
| SetTypeOf(left_as_binop->right(), AsmType::FixNum()); |
| SetTypeOf(left_as_binop, AsmType::Signed()); |
| SetTypeOf(expr->right(), AsmType::FixNum()); |
| UNOP_OVERLOAD(Double, Signed); |
| UNOP_OVERLOAD(FloatQ, Signed); |
| FAIL(left_as_binop, "Invalid type for conversion to signed."); |
| } |
| |
| AsmType* left_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| UNOP_OVERLOAD(Intish, Signed); |
| FAIL(left, "Invalid type for ~."); |
| } |
| |
| return ValidateBitwiseXORExpression(expr); |
| case Token::BIT_OR: |
| return ValidateBitwiseORExpression(expr); |
| } |
| #undef UNOP_OVERLOAD |
| UNREACHABLE(); |
| } |
| |
| // 6.8.1 Expression |
| AsmType* AsmTyper::ValidateCommaExpression(BinaryOperation* comma) { |
| // The AST looks like: |
| // (expr COMMA (expr COMMA (expr COMMA (... )))) |
| |
| auto* left = comma->left(); |
| if (auto* left_as_call = left->AsCall()) { |
| RECURSE(ValidateCall(AsmType::Void(), left_as_call)); |
| } else { |
| RECURSE(ValidateExpression(left)); |
| } |
| |
| auto* right = comma->right(); |
| AsmType* right_type = nullptr; |
| if (auto* right_as_call = right->AsCall()) { |
| RECURSE(right_type = ValidateCall(AsmType::Void(), right_as_call)); |
| } else { |
| RECURSE(right_type = ValidateExpression(right)); |
| } |
| |
| return right_type; |
| } |
| |
| // 6.8.2 NumericLiteral |
| AsmType* AsmTyper::ValidateNumericLiteral(Literal* literal) { |
| // *VIOLATION* asm.js does not allow the use of undefined, but our parser |
| // inserts them, so we have to handle them. |
| if (literal->IsUndefinedLiteral()) { |
| return AsmType::Void(); |
| } |
| |
| if (literal->raw_value()->ContainsDot()) { |
| return AsmType::Double(); |
| } |
| |
| // The parser collapses expressions like !0 and !123 to true/false. |
| // We therefore need to permit these as alternate versions of 0 / 1. |
| if (literal->raw_value()->IsTrue() || literal->raw_value()->IsFalse()) { |
| return AsmType::Int(); |
| } |
| |
| uint32_t value; |
| if (!literal->value()->ToUint32(&value)) { |
| int32_t value; |
| if (!literal->value()->ToInt32(&value)) { |
| FAIL(literal, "Integer literal is out of range."); |
| } |
| // *VIOLATION* Not really a violation, but rather a different in the |
| // validation. The spec handles -NumericLiteral in ValidateUnaryExpression, |
| // but V8's AST represents the negative literals as Literals. |
| return AsmType::Signed(); |
| } |
| |
| if (value <= LargestFixNum) { |
| return AsmType::FixNum(); |
| } |
| |
| return AsmType::Unsigned(); |
| } |
| |
| // 6.8.3 Identifier |
| AsmType* AsmTyper::ValidateIdentifier(VariableProxy* proxy) { |
| auto* proxy_info = Lookup(proxy->var()); |
| if (proxy_info == nullptr) { |
| FAIL(proxy, "Undeclared identifier."); |
| } |
| auto* type = proxy_info->type(); |
| if (type->IsA(AsmType::None()) || type->AsCallableType() != nullptr) { |
| FAIL(proxy, "Identifier may not be accessed by ordinary expressions."); |
| } |
| return type; |
| } |
| |
| // 6.8.4 CallExpression |
| AsmType* AsmTyper::ValidateCallExpression(Call* call) { |
| AsmType* return_type; |
| RECURSE(return_type = ValidateFloatCoercion(call)); |
| if (return_type == nullptr) { |
| FAIL(call, "Unanotated call to a function must be a call to fround."); |
| } |
| return return_type; |
| } |
| |
| // 6.8.5 MemberExpression |
| AsmType* AsmTyper::ValidateMemberExpression(Property* prop) { |
| AsmType* return_type; |
| RECURSE(return_type = ValidateHeapAccess(prop, LoadFromHeap)); |
| return return_type; |
| } |
| |
| // 6.8.6 AssignmentExpression |
| AsmType* AsmTyper::ValidateAssignmentExpression(Assignment* assignment) { |
| AsmType* value_type; |
| RECURSE(value_type = ValidateExpression(assignment->value())); |
| |
| if (assignment->op() == Token::INIT) { |
| FAIL(assignment, |
| "Local variable declaration must be at the top of the function."); |
| } |
| |
| if (auto* target_as_proxy = assignment->target()->AsVariableProxy()) { |
| auto* var = target_as_proxy->var(); |
| auto* target_info = Lookup(var); |
| |
| if (target_info == nullptr) { |
| if (var->mode() != TEMPORARY) { |
| FAIL(target_as_proxy, "Undeclared identifier."); |
| } |
| // Temporary variables are special: we add them to the local symbol table |
| // as we see them, with the exact type of the variable's initializer. This |
| // means that temporary variables might have nonsensical types (i.e., |
| // intish, float?, fixnum, and not just the "canonical" types.) |
| auto* var_info = new (zone_) VariableInfo(value_type); |
| var_info->set_mutability(VariableInfo::kLocal); |
| if (!ValidAsmIdentifier(target_as_proxy->name())) { |
| FAIL(target_as_proxy, |
| "Invalid asm.js identifier in temporary variable."); |
| } |
| |
| if (!AddLocal(var, var_info)) { |
| FAIL(assignment, "Failed to add temporary variable to symbol table."); |
| } |
| return value_type; |
| } |
| |
| if (!target_info->IsMutable()) { |
| FAIL(assignment, "Can't assign to immutable symbol."); |
| } |
| |
| DCHECK_NE(AsmType::None(), target_info->type()); |
| if (!value_type->IsA(target_info->type())) { |
| FAIL(assignment, "Type mismatch in assignment."); |
| } |
| |
| return value_type; |
| } |
| |
| if (auto* target_as_property = assignment->target()->AsProperty()) { |
| AsmType* allowed_store_types; |
| RECURSE(allowed_store_types = |
| ValidateHeapAccess(target_as_property, StoreToHeap)); |
| |
| if (!value_type->IsA(allowed_store_types)) { |
| FAIL(assignment, "Type mismatch in heap assignment."); |
| } |
| |
| return value_type; |
| } |
| |
| FAIL(assignment, "Invalid asm.js assignment."); |
| } |
| |
| // 6.8.7 UnaryExpression |
| AsmType* AsmTyper::ValidateUnaryExpression(UnaryOperation* unop) { |
| // *VIOLATION* -NumericLiteral is validated in ValidateLiteral. |
| // *VIOLATION* +UnaryExpression is validated in ValidateBinaryOperation. |
| // *VIOLATION* ~UnaryOperation is validated in ValidateBinaryOperation. |
| // *VIOLATION* ~~UnaryOperation is validated in ValidateBinaryOperation. |
| DCHECK(unop->op() != Token::BIT_NOT); |
| DCHECK(unop->op() != Token::ADD); |
| AsmType* exp_type; |
| RECURSE(exp_type = ValidateExpression(unop->expression())); |
| #define UNOP_OVERLOAD(Src, Dest) \ |
| do { \ |
| if (exp_type->IsA(AsmType::Src())) { \ |
| return AsmType::Dest(); \ |
| } \ |
| } while (0) |
| |
| // 8.1 Unary Operators |
| switch (unop->op()) { |
| default: |
| FAIL(unop, "Invalid unary operator."); |
| case Token::ADD: |
| // We can't test this because of the +x -> x * 1.0 transformation. |
| DCHECK(false); |
| UNOP_OVERLOAD(Signed, Double); |
| UNOP_OVERLOAD(Unsigned, Double); |
| UNOP_OVERLOAD(DoubleQ, Double); |
| UNOP_OVERLOAD(FloatQ, Double); |
| FAIL(unop, "Invalid type for unary +."); |
| case Token::SUB: |
| // We can't test this because of the -x -> x * -1.0 transformation. |
| DCHECK(false); |
| UNOP_OVERLOAD(Int, Intish); |
| UNOP_OVERLOAD(DoubleQ, Double); |
| UNOP_OVERLOAD(FloatQ, Floatish); |
| FAIL(unop, "Invalid type for unary -."); |
| case Token::BIT_NOT: |
| // We can't test this because of the ~x -> x ^ -1 transformation. |
| DCHECK(false); |
| UNOP_OVERLOAD(Intish, Signed); |
| FAIL(unop, "Invalid type for ~."); |
| case Token::NOT: |
| UNOP_OVERLOAD(Int, Int); |
| FAIL(unop, "Invalid type for !."); |
| } |
| |
| #undef UNOP_OVERLOAD |
| |
| UNREACHABLE(); |
| } |
| |
| // 6.8.8 MultiplicativeExpression |
| namespace { |
| bool IsIntishLiteralFactor(Expression* expr, int32_t* factor) { |
| auto* literal = expr->AsLiteral(); |
| if (literal == nullptr) { |
| return false; |
| } |
| |
| if (literal->raw_value()->ContainsDot()) { |
| return false; |
| } |
| |
| if (!literal->value()->ToInt32(factor)) { |
| return false; |
| } |
| static const int32_t kIntishBound = 1 << 20; |
| return -kIntishBound < *factor && *factor < kIntishBound; |
| } |
| } // namespace |
| |
| AsmType* AsmTyper::ValidateMultiplicativeExpression(BinaryOperation* binop) { |
| DCHECK(!IsDoubleAnnotation(binop)); |
| |
| auto* left = binop->left(); |
| auto* right = binop->right(); |
| |
| bool intish_mul_failed = false; |
| if (binop->op() == Token::MUL) { |
| int32_t factor; |
| if (IsIntishLiteralFactor(left, &factor)) { |
| AsmType* right_type; |
| RECURSE(right_type = ValidateExpression(right)); |
| if (right_type->IsA(AsmType::Int())) { |
| return AsmType::Intish(); |
| } |
| // Can't fail here, because the rhs might contain a valid intish factor. |
| // |
| // The solution is to flag that there was an error, and later on -- when |
| // both lhs and rhs are evaluated -- complain. |
| intish_mul_failed = true; |
| } |
| |
| if (IsIntishLiteralFactor(right, &factor)) { |
| AsmType* left_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| if (left_type->IsA(AsmType::Int())) { |
| // *VIOLATION* This will also (and correctly) handle -X, when X is an |
| // integer. Therefore, we don't need to handle this case within the if |
| // block below. |
| return AsmType::Intish(); |
| } |
| intish_mul_failed = true; |
| |
| if (factor == -1) { |
| // *VIOLATION* The frontend transforms -x into x * -1 (not -1.0, because |
| // consistency is overrated.) |
| if (left_type->IsA(AsmType::DoubleQ())) { |
| return AsmType::Double(); |
| } else if (left_type->IsA(AsmType::FloatQ())) { |
| return AsmType::Floatish(); |
| } |
| } |
| } |
| } |
| |
| if (intish_mul_failed) { |
| FAIL(binop, "Invalid types for intish * (or unary -)."); |
| } |
| |
| AsmType* left_type; |
| AsmType* right_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| RECURSE(right_type = ValidateExpression(right)); |
| |
| #define BINOP_OVERLOAD(Src0, Src1, Dest) \ |
| do { \ |
| if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \ |
| return AsmType::Dest(); \ |
| } \ |
| } while (0) |
| switch (binop->op()) { |
| default: |
| FAIL(binop, "Invalid multiplicative expression."); |
| case Token::MUL: |
| BINOP_OVERLOAD(DoubleQ, DoubleQ, Double); |
| BINOP_OVERLOAD(FloatQ, FloatQ, Floatish); |
| FAIL(binop, "Invalid operands for *."); |
| case Token::DIV: |
| BINOP_OVERLOAD(Signed, Signed, Intish); |
| BINOP_OVERLOAD(Unsigned, Unsigned, Intish); |
| BINOP_OVERLOAD(DoubleQ, DoubleQ, Double); |
| BINOP_OVERLOAD(FloatQ, FloatQ, Floatish); |
| FAIL(binop, "Invalid operands for /."); |
| case Token::MOD: |
| BINOP_OVERLOAD(Signed, Signed, Intish); |
| BINOP_OVERLOAD(Unsigned, Unsigned, Intish); |
| BINOP_OVERLOAD(DoubleQ, DoubleQ, Double); |
| FAIL(binop, "Invalid operands for %."); |
| } |
| #undef BINOP_OVERLOAD |
| |
| UNREACHABLE(); |
| } |
| |
| // 6.8.9 AdditiveExpression |
| AsmType* AsmTyper::ValidateAdditiveExpression(BinaryOperation* binop, |
| uint32_t intish_count) { |
| static const uint32_t kMaxIntish = 1 << 20; |
| |
| auto* left = binop->left(); |
| auto* left_as_binop = left->AsBinaryOperation(); |
| AsmType* left_type; |
| |
| // TODO(jpp): maybe use an iterative approach instead of the recursion to |
| // ValidateAdditiveExpression. |
| if (left_as_binop != nullptr && (left_as_binop->op() == Token::ADD || |
| left_as_binop->op() == Token::SUB)) { |
| RECURSE(left_type = |
| ValidateAdditiveExpression(left_as_binop, intish_count + 1)); |
| SetTypeOf(left_as_binop, left_type); |
| } else { |
| RECURSE(left_type = ValidateExpression(left)); |
| } |
| |
| auto* right = binop->right(); |
| auto* right_as_binop = right->AsBinaryOperation(); |
| AsmType* right_type; |
| |
| if (right_as_binop != nullptr && (right_as_binop->op() == Token::ADD || |
| right_as_binop->op() == Token::SUB)) { |
| RECURSE(right_type = |
| ValidateAdditiveExpression(right_as_binop, intish_count + 1)); |
| SetTypeOf(right_as_binop, right_type); |
| } else { |
| RECURSE(right_type = ValidateExpression(right)); |
| } |
| |
| if (left_type->IsA(AsmType::FloatQ()) && right_type->IsA(AsmType::FloatQ())) { |
| return AsmType::Floatish(); |
| } |
| |
| if (left_type->IsA(AsmType::Int()) && right_type->IsA(AsmType::Int())) { |
| if (intish_count == 0) { |
| return AsmType::Intish(); |
| } |
| if (intish_count < kMaxIntish) { |
| return AsmType::Int(); |
| } |
| FAIL(binop, "Too many uncoerced integer additive expressions."); |
| } |
| |
| if (left_type->IsA(AsmType::Double()) && right_type->IsA(AsmType::Double())) { |
| return AsmType::Double(); |
| } |
| |
| if (binop->op() == Token::SUB) { |
| if (left_type->IsA(AsmType::DoubleQ()) && |
| right_type->IsA(AsmType::DoubleQ())) { |
| return AsmType::Double(); |
| } |
| } |
| |
| FAIL(binop, "Invalid operands for additive expression."); |
| } |
| |
| // 6.8.10 ShiftExpression |
| AsmType* AsmTyper::ValidateShiftExpression(BinaryOperation* binop) { |
| auto* left = binop->left(); |
| auto* right = binop->right(); |
| |
| AsmType* left_type; |
| AsmType* right_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| RECURSE(right_type = ValidateExpression(right)); |
| |
| #define BINOP_OVERLOAD(Src0, Src1, Dest) \ |
| do { \ |
| if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \ |
| return AsmType::Dest(); \ |
| } \ |
| } while (0) |
| switch (binop->op()) { |
| default: |
| FAIL(binop, "Invalid shift expression."); |
| case Token::SHL: |
| BINOP_OVERLOAD(Intish, Intish, Signed); |
| FAIL(binop, "Invalid operands for <<."); |
| case Token::SAR: |
| BINOP_OVERLOAD(Intish, Intish, Signed); |
| FAIL(binop, "Invalid operands for >>."); |
| case Token::SHR: |
| BINOP_OVERLOAD(Intish, Intish, Unsigned); |
| FAIL(binop, "Invalid operands for >>>."); |
| } |
| #undef BINOP_OVERLOAD |
| |
| UNREACHABLE(); |
| } |
| |
| // 6.8.11 RelationalExpression |
| AsmType* AsmTyper::ValidateRelationalExpression(CompareOperation* cmpop) { |
| auto* left = cmpop->left(); |
| auto* right = cmpop->right(); |
| |
| AsmType* left_type; |
| AsmType* right_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| RECURSE(right_type = ValidateExpression(right)); |
| |
| #define CMPOP_OVERLOAD(Src0, Src1, Dest) \ |
| do { \ |
| if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \ |
| return AsmType::Dest(); \ |
| } \ |
| } while (0) |
| switch (cmpop->op()) { |
| default: |
| FAIL(cmpop, "Invalid relational expression."); |
| case Token::LT: |
| CMPOP_OVERLOAD(Signed, Signed, Int); |
| CMPOP_OVERLOAD(Unsigned, Unsigned, Int); |
| CMPOP_OVERLOAD(Float, Float, Int); |
| CMPOP_OVERLOAD(Double, Double, Int); |
| FAIL(cmpop, "Invalid operands for <."); |
| case Token::GT: |
| CMPOP_OVERLOAD(Signed, Signed, Int); |
| CMPOP_OVERLOAD(Unsigned, Unsigned, Int); |
| CMPOP_OVERLOAD(Float, Float, Int); |
| CMPOP_OVERLOAD(Double, Double, Int); |
| FAIL(cmpop, "Invalid operands for >."); |
| case Token::LTE: |
| CMPOP_OVERLOAD(Signed, Signed, Int); |
| CMPOP_OVERLOAD(Unsigned, Unsigned, Int); |
| CMPOP_OVERLOAD(Float, Float, Int); |
| CMPOP_OVERLOAD(Double, Double, Int); |
| FAIL(cmpop, "Invalid operands for <=."); |
| case Token::GTE: |
| CMPOP_OVERLOAD(Signed, Signed, Int); |
| CMPOP_OVERLOAD(Unsigned, Unsigned, Int); |
| CMPOP_OVERLOAD(Float, Float, Int); |
| CMPOP_OVERLOAD(Double, Double, Int); |
| FAIL(cmpop, "Invalid operands for >=."); |
| } |
| #undef CMPOP_OVERLOAD |
| |
| UNREACHABLE(); |
| } |
| |
| // 6.8.12 EqualityExpression |
| AsmType* AsmTyper::ValidateEqualityExpression(CompareOperation* cmpop) { |
| auto* left = cmpop->left(); |
| auto* right = cmpop->right(); |
| |
| AsmType* left_type; |
| AsmType* right_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| RECURSE(right_type = ValidateExpression(right)); |
| |
| #define CMPOP_OVERLOAD(Src0, Src1, Dest) \ |
| do { \ |
| if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \ |
| return AsmType::Dest(); \ |
| } \ |
| } while (0) |
| switch (cmpop->op()) { |
| default: |
| FAIL(cmpop, "Invalid equality expression."); |
| case Token::EQ: |
| CMPOP_OVERLOAD(Signed, Signed, Int); |
| CMPOP_OVERLOAD(Unsigned, Unsigned, Int); |
| CMPOP_OVERLOAD(Float, Float, Int); |
| CMPOP_OVERLOAD(Double, Double, Int); |
| FAIL(cmpop, "Invalid operands for ==."); |
| case Token::NE: |
| CMPOP_OVERLOAD(Signed, Signed, Int); |
| CMPOP_OVERLOAD(Unsigned, Unsigned, Int); |
| CMPOP_OVERLOAD(Float, Float, Int); |
| CMPOP_OVERLOAD(Double, Double, Int); |
| FAIL(cmpop, "Invalid operands for !=."); |
| } |
| #undef CMPOP_OVERLOAD |
| |
| UNREACHABLE(); |
| } |
| |
| // 6.8.13 BitwiseANDExpression |
| AsmType* AsmTyper::ValidateBitwiseANDExpression(BinaryOperation* binop) { |
| auto* left = binop->left(); |
| auto* right = binop->right(); |
| |
| AsmType* left_type; |
| AsmType* right_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| RECURSE(right_type = ValidateExpression(right)); |
| |
| if (binop->op() != Token::BIT_AND) { |
| FAIL(binop, "Invalid & expression."); |
| } |
| |
| #define BINOP_OVERLOAD(Src0, Src1, Dest) \ |
| do { \ |
| if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \ |
| return AsmType::Dest(); \ |
| } \ |
| } while (0) |
| BINOP_OVERLOAD(Intish, Intish, Signed); |
| FAIL(binop, "Invalid operands for &."); |
| #undef BINOP_OVERLOAD |
| |
| UNREACHABLE(); |
| } |
| |
| // 6.8.14 BitwiseXORExpression |
| AsmType* AsmTyper::ValidateBitwiseXORExpression(BinaryOperation* binop) { |
| auto* left = binop->left(); |
| auto* right = binop->right(); |
| |
| AsmType* left_type; |
| AsmType* right_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| RECURSE(right_type = ValidateExpression(right)); |
| |
| if (binop->op() != Token::BIT_XOR) { |
| FAIL(binop, "Invalid ^ expression."); |
| } |
| |
| #define BINOP_OVERLOAD(Src0, Src1, Dest) \ |
| do { \ |
| if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \ |
| return AsmType::Dest(); \ |
| } \ |
| } while (0) |
| BINOP_OVERLOAD(Intish, Intish, Signed); |
| FAIL(binop, "Invalid operands for ^."); |
| #undef BINOP_OVERLOAD |
| |
| UNREACHABLE(); |
| } |
| |
| // 6.8.15 BitwiseORExpression |
| AsmType* AsmTyper::ValidateBitwiseORExpression(BinaryOperation* binop) { |
| auto* left = binop->left(); |
| if (IsIntAnnotation(binop)) { |
| if (auto* left_as_call = left->AsCall()) { |
| AsmType* type; |
| RECURSE(type = ValidateCall(AsmType::Signed(), left_as_call)); |
| return type; |
| } |
| |
| // TODO(jpp): at this point we know that binop is expr|0. We could sinply |
| // |
| // RECURSE(t = ValidateExpression(left)); |
| // FAIL_IF(t->IsNotA(Intish)); |
| // return Signed; |
| } |
| |
| auto* right = binop->right(); |
| AsmType* left_type; |
| AsmType* right_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| RECURSE(right_type = ValidateExpression(right)); |
| |
| if (binop->op() != Token::BIT_OR) { |
| FAIL(binop, "Invalid | expression."); |
| } |
| |
| #define BINOP_OVERLOAD(Src0, Src1, Dest) \ |
| do { \ |
| if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \ |
| return AsmType::Dest(); \ |
| } \ |
| } while (0) |
| BINOP_OVERLOAD(Intish, Intish, Signed); |
| FAIL(binop, "Invalid operands for |."); |
| #undef BINOP_OVERLOAD |
| |
| UNREACHABLE(); |
| } |
| |
| // 6.8.16 ConditionalExpression |
| AsmType* AsmTyper::ValidateConditionalExpression(Conditional* cond) { |
| AsmType* cond_type; |
| RECURSE(cond_type = ValidateExpression(cond->condition())); |
| if (!cond_type->IsA(AsmType::Int())) { |
| FAIL(cond, "Ternary operation condition should be int."); |
| } |
| |
| AsmType* then_type; |
| RECURSE(then_type = ValidateExpression(cond->then_expression())); |
| AsmType* else_type; |
| RECURSE(else_type = ValidateExpression(cond->else_expression())); |
| |
| #define SUCCEED_IF_BOTH_ARE(type) \ |
| do { \ |
| if (then_type->IsA(AsmType::type())) { \ |
| if (!else_type->IsA(AsmType::type())) { \ |
| FAIL(cond, "Type mismatch for ternary operation result type."); \ |
| } \ |
| return AsmType::type(); \ |
| } \ |
| } while (0) |
| SUCCEED_IF_BOTH_ARE(Int); |
| SUCCEED_IF_BOTH_ARE(Float); |
| SUCCEED_IF_BOTH_ARE(Double); |
| #undef SUCCEED_IF_BOTH_ARE |
| |
| FAIL(cond, "Ternary operator must return int, float, or double."); |
| } |
| |
| // 6.9 ValidateCall |
| namespace { |
| bool ExtractIndirectCallMask(Expression* expr, uint32_t* value) { |
| auto* as_literal = expr->AsLiteral(); |
| if (as_literal == nullptr) { |
| return false; |
| } |
| |
| if (as_literal->raw_value()->ContainsDot()) { |
| return false; |
| } |
| |
| if (!as_literal->value()->ToUint32(value)) { |
| return false; |
| } |
| |
| return base::bits::IsPowerOfTwo32(1 + *value); |
| } |
| } // namespace |
| |
| AsmType* AsmTyper::ValidateCall(AsmType* return_type, Call* call) { |
| AsmType* float_coercion_type; |
| RECURSE(float_coercion_type = ValidateFloatCoercion(call)); |
| if (float_coercion_type == AsmType::Float()) { |
| SetTypeOf(call, AsmType::Float()); |
| return return_type; |
| } |
| |
| // TODO(jpp): we should be able to reuse the args vector's storage space. |
| ZoneVector<AsmType*> args(zone_); |
| args.reserve(call->arguments()->length()); |
| |
| for (auto* arg : *call->arguments()) { |
| AsmType* arg_type; |
| RECURSE(arg_type = ValidateExpression(arg)); |
| args.emplace_back(arg_type); |
| } |
| |
| auto* call_expr = call->expression(); |
| |
| // identifier(Expression...) |
| if (auto* call_var_proxy = call_expr->AsVariableProxy()) { |
| auto* call_var_info = Lookup(call_var_proxy->var()); |
| |
| if (call_var_info == nullptr) { |
| // We can't fail here: the validator performs a single pass over the AST, |
| // so it is possible for some calls to be currently unresolved. We eagerly |
| // add the function to the table of globals. |
| auto* call_type = AsmType::Function(zone_, return_type)->AsFunctionType(); |
| for (auto* arg : args) { |
| call_type->AddArgument(arg->ToParameterType()); |
| } |
| auto* fun_info = |
| new (zone_) VariableInfo(reinterpret_cast<AsmType*>(call_type)); |
| fun_info->set_mutability(VariableInfo::kImmutableGlobal); |
| AddForwardReference(call_var_proxy, fun_info); |
| if (!ValidAsmIdentifier(call_var_proxy->name())) { |
| FAIL(call_var_proxy, |
| "Invalid asm.js identifier in (forward) function name."); |
| } |
| if (!AddGlobal(call_var_proxy->var(), fun_info)) { |
| DCHECK(false); |
| FAIL(call, "Redeclared global identifier."); |
| } |
| SetTypeOf(call_var_proxy, reinterpret_cast<AsmType*>(call_type)); |
| SetTypeOf(call, return_type); |
| return return_type; |
| } |
| |
| auto* callee_type = call_var_info->type()->AsCallableType(); |
| if (callee_type == nullptr) { |
| FAIL(call, "Calling something that's not a function."); |
| } |
| |
| if (callee_type->AsFFIType() != nullptr) { |
| if (return_type == AsmType::Float()) { |
| FAIL(call, "Foreign functions can't return float."); |
| } |
| // Record FFI use signature, since the asm->wasm translator must know |
| // all uses up-front. |
| ffi_use_signatures_.emplace_back( |
| FFIUseSignature(call_var_proxy->var(), zone_)); |
| FFIUseSignature* sig = &ffi_use_signatures_.back(); |
| sig->return_type_ = return_type; |
| sig->arg_types_.reserve(args.size()); |
| for (size_t i = 0; i < args.size(); ++i) { |
| sig->arg_types_.emplace_back(args[i]); |
| } |
| } |
| |
| if (!callee_type->CanBeInvokedWith(return_type, args)) { |
| FAIL(call, "Function invocation does not match function type."); |
| } |
| |
| SetTypeOf(call_var_proxy, call_var_info->type()); |
| SetTypeOf(call, return_type); |
| return return_type; |
| } |
| |
| // identifier[expr & n](Expression...) |
| if (auto* call_property = call_expr->AsProperty()) { |
| auto* index = call_property->key()->AsBinaryOperation(); |
| if (index == nullptr || index->op() != Token::BIT_AND) { |
| FAIL(call_property->key(), |
| "Indirect call index must be in the expr & mask form."); |
| } |
| |
| auto* left = index->left(); |
| auto* right = index->right(); |
| uint32_t mask; |
| if (!ExtractIndirectCallMask(right, &mask)) { |
| if (!ExtractIndirectCallMask(left, &mask)) { |
| FAIL(right, "Invalid indirect call mask."); |
| } else { |
| left = right; |
| } |
| } |
| const uint32_t table_length = mask + 1; |
| |
| AsmType* left_type; |
| RECURSE(left_type = ValidateExpression(left)); |
| if (!left_type->IsA(AsmType::Intish())) { |
| FAIL(left, "Indirect call index should be an intish."); |
| } |
| |
| auto* name_var = call_property->obj()->AsVariableProxy(); |
| |
| if (name_var == nullptr) { |
| FAIL(call_property, "Invalid call."); |
| } |
| |
| auto* name_info = Lookup(name_var->var()); |
| if (name_info == nullptr) { |
| // We can't fail here -- just like above. |
| auto* call_type = AsmType::Function(zone_, return_type)->AsFunctionType(); |
| for (auto* arg : args) { |
| call_type->AddArgument(arg->ToParameterType()); |
| } |
| auto* table_type = AsmType::FunctionTableType( |
| zone_, table_length, reinterpret_cast<AsmType*>(call_type)); |
| auto* fun_info = |
| new (zone_) VariableInfo(reinterpret_cast<AsmType*>(table_type)); |
| fun_info->set_mutability(VariableInfo::kImmutableGlobal); |
| AddForwardReference(name_var, fun_info); |
| if (!ValidAsmIdentifier(name_var->name())) { |
| FAIL(name_var, |
| "Invalid asm.js identifier in (forward) function table name."); |
| } |
| if (!AddGlobal(name_var->var(), fun_info)) { |
| DCHECK(false); |
| FAIL(call, "Redeclared global identifier."); |
| } |
| SetTypeOf(call_property, reinterpret_cast<AsmType*>(call_type)); |
| SetTypeOf(call, return_type); |
| return return_type; |
| } |
| |
| auto* previous_type = name_info->type()->AsFunctionTableType(); |
| if (previous_type == nullptr) { |
| FAIL(call, "Identifier does not name a function table."); |
| } |
| |
| if (table_length != previous_type->length()) { |
| FAIL(call, "Function table size does not match expected size."); |
| } |
| |
| auto* previous_type_signature = |
| previous_type->signature()->AsFunctionType(); |
| DCHECK(previous_type_signature != nullptr); |
| if (!previous_type_signature->CanBeInvokedWith(return_type, args)) { |
| // TODO(jpp): better error messages. |
| FAIL(call, |
| "Function pointer table signature does not match previous " |
| "signature."); |
| } |
| |
| SetTypeOf(call_property, previous_type->signature()); |
| SetTypeOf(call, return_type); |
| return return_type; |
| } |
| |
| FAIL(call, "Invalid call."); |
| } |
| |
| // 6.10 ValidateHeapAccess |
| namespace { |
| bool ExtractHeapAccessShift(Expression* expr, uint32_t* value) { |
| auto* as_literal = expr->AsLiteral(); |
| if (as_literal == nullptr) { |
| return false; |
| } |
| |
| if (as_literal->raw_value()->ContainsDot()) { |
| return false; |
| } |
| |
| return as_literal->value()->ToUint32(value); |
| } |
| |
| // Returns whether index is too large to access a heap with the given type. |
| bool LiteralIndexOutOfBounds(AsmType* obj_type, uint32_t index) { |
| switch (obj_type->ElementSizeInBytes()) { |
| case 1: |
| return false; |
| case 2: |
| return (index & 0x80000000u) != 0; |
| case 4: |
| return (index & 0xC0000000u) != 0; |
| case 8: |
| return (index & 0xE0000000u) != 0; |
| } |
| UNREACHABLE(); |
| return true; |
| } |
| |
| } // namespace |
| |
| AsmType* AsmTyper::ValidateHeapAccess(Property* heap, |
| HeapAccessType access_type) { |
| auto* obj = heap->obj()->AsVariableProxy(); |
| if (obj == nullptr) { |
| FAIL(heap, "Invalid heap access."); |
| } |
| |
| auto* obj_info = Lookup(obj->var()); |
| if (obj_info == nullptr) { |
| FAIL(heap, "Undeclared identifier in heap access."); |
| } |
| |
| auto* obj_type = obj_info->type(); |
| if (!obj_type->IsA(AsmType::Heap())) { |
| FAIL(heap, "Identifier does not represent a heap view."); |
| } |
| SetTypeOf(obj, obj_type); |
| |
| if (auto* key_as_literal = heap->key()->AsLiteral()) { |
| if (key_as_literal->raw_value()->ContainsDot()) { |
| FAIL(key_as_literal, "Heap access index must be int."); |
| } |
| |
| uint32_t index; |
| if (!key_as_literal->value()->ToUint32(&index)) { |
| FAIL(key_as_literal, |
| "Heap access index must be a 32-bit unsigned integer."); |
| } |
| |
| if (LiteralIndexOutOfBounds(obj_type, index)) { |
| FAIL(key_as_literal, "Heap access index is out of bounds"); |
| } |
| |
| if (access_type == LoadFromHeap) { |
| return obj_type->LoadType(); |
| } |
| return obj_type->StoreType(); |
| } |
| |
| if (auto* key_as_binop = heap->key()->AsBinaryOperation()) { |
| uint32_t shift; |
| if (key_as_binop->op() == Token::SAR && |
| ExtractHeapAccessShift(key_as_binop->right(), &shift) && |
| (1 << shift) == obj_type->ElementSizeInBytes()) { |
| AsmType* type; |
| RECURSE(type = ValidateExpression(key_as_binop->left())); |
| if (type->IsA(AsmType::Intish())) { |
| if (access_type == LoadFromHeap) { |
| return obj_type->LoadType(); |
| } |
| return obj_type->StoreType(); |
| } |
| FAIL(key_as_binop, "Invalid heap access index."); |
| } |
| } |
| |
| if (obj_type->ElementSizeInBytes() == 1) { |
| // Leniency: if this is a byte array, we don't require the shift operation |
| // to be present. |
| AsmType* index_type; |
| RECURSE(index_type = ValidateExpression(heap->key())); |
| if (!index_type->IsA(AsmType::Int())) { |
| FAIL(heap, "Invalid heap access index for byte array."); |
| } |
| if (access_type == LoadFromHeap) { |
| return obj_type->LoadType(); |
| } |
| return obj_type->StoreType(); |
| } |
| |
| FAIL(heap, "Invalid heap access index."); |
| } |
| |
| // 6.11 ValidateFloatCoercion |
| bool AsmTyper::IsCallToFround(Call* call) { |
| if (call->arguments()->length() != 1) { |
| return false; |
| } |
| |
| auto* call_var_proxy = call->expression()->AsVariableProxy(); |
| if (call_var_proxy == nullptr) { |
| return false; |
| } |
| |
| auto* call_var_info = Lookup(call_var_proxy->var()); |
| if (call_var_info == nullptr) { |
| return false; |
| } |
| |
| return call_var_info->standard_member() == kMathFround; |
| } |
| |
| AsmType* AsmTyper::ValidateFloatCoercion(Call* call) { |
| if (!IsCallToFround(call)) { |
| return nullptr; |
| } |
| |
| auto* arg = call->arguments()->at(0); |
| // call is a fround() node. From now, there can be two possible outcomes: |
| // 1. fround is used as a return type annotation. |
| if (auto* arg_as_call = arg->AsCall()) { |
| RECURSE(ValidateCall(AsmType::Float(), arg_as_call)); |
| return AsmType::Float(); |
| } |
| |
| // 2. fround is used for converting to float. |
| AsmType* arg_type; |
| RECURSE(arg_type = ValidateExpression(arg)); |
| if (arg_type->IsA(AsmType::Floatish()) || arg_type->IsA(AsmType::DoubleQ()) || |
| arg_type->IsA(AsmType::Signed()) || arg_type->IsA(AsmType::Unsigned())) { |
| SetTypeOf(call->expression(), fround_type_); |
| return AsmType::Float(); |
| } |
| |
| FAIL(call, "Invalid argument type to fround."); |
| } |
| |
| // 5.1 ParameterTypeAnnotations |
| AsmType* AsmTyper::ParameterTypeAnnotations(Variable* parameter, |
| Expression* annotation) { |
| if (auto* binop = annotation->AsBinaryOperation()) { |
| // Must be: |
| // * x|0 |
| // * x*1 (*VIOLATION* i.e.,, +x) |
| auto* left = binop->left()->AsVariableProxy(); |
| if (left == nullptr) { |
| FAIL( |
| binop->left(), |
| "Invalid parameter type annotation - should annotate an identifier."); |
| } |
| if (left->var() != parameter) { |
| FAIL(binop->left(), |
| "Invalid parameter type annotation - should annotate a parameter."); |
| } |
| if (IsDoubleAnnotation(binop)) { |
| SetTypeOf(left, AsmType::Double()); |
| return AsmType::Double(); |
| } |
| if (IsIntAnnotation(binop)) { |
| SetTypeOf(left, AsmType::Int()); |
| return AsmType::Int(); |
| } |
| FAIL(binop, "Invalid parameter type annotation."); |
| } |
| |
| auto* call = annotation->AsCall(); |
| if (call == nullptr) { |
| FAIL( |
| annotation, |
| "Invalid float parameter type annotation - must be fround(parameter)."); |
| } |
| |
| if (!IsCallToFround(call)) { |
| FAIL(annotation, |
| "Invalid float parameter type annotation - must be call to fround."); |
| } |
| |
| auto* src_expr = call->arguments()->at(0)->AsVariableProxy(); |
| if (src_expr == nullptr) { |
| FAIL(annotation, |
| "Invalid float parameter type annotation - argument to fround is not " |
| "an identifier."); |
| } |
| |
| if (src_expr->var() != parameter) { |
| FAIL(annotation, |
| "Invalid float parameter type annotation - argument to fround is not " |
| "a parameter."); |
| } |
| |
| SetTypeOf(src_expr, AsmType::Float()); |
| return AsmType::Float(); |
| } |
| |
| // 5.2 ReturnTypeAnnotations |
| AsmType* AsmTyper::ReturnTypeAnnotations(ReturnStatement* statement) { |
| if (statement == nullptr) { |
| return AsmType::Void(); |
| } |
| |
| auto* ret_expr = statement->expression(); |
| if (ret_expr == nullptr) { |
| return AsmType::Void(); |
| } |
| |
| if (auto* binop = ret_expr->AsBinaryOperation()) { |
| if (IsDoubleAnnotation(binop)) { |
| return AsmType::Double(); |
| } else if (IsIntAnnotation(binop)) { |
| return AsmType::Signed(); |
| } |
| FAIL(statement, "Invalid return type annotation."); |
| } |
| |
| if (auto* call = ret_expr->AsCall()) { |
| if (IsCallToFround(call)) { |
| return AsmType::Float(); |
| } |
| FAIL(statement, "Invalid function call in return statement."); |
| } |
| |
| if (auto* literal = ret_expr->AsLiteral()) { |
| int32_t _; |
| if (literal->raw_value()->ContainsDot()) { |
| return AsmType::Double(); |
| } else if (literal->value()->ToInt32(&_)) { |
| return AsmType::Signed(); |
| } else if (literal->IsUndefinedLiteral()) { |
| // *VIOLATION* The parser changes |
| // |
| // return; |
| // |
| // into |
| // |
| // return undefined |
| return AsmType::Void(); |
| } |
| FAIL(statement, "Invalid literal in return statement."); |
| } |
| |
| FAIL(statement, "Invalid return type expression."); |
| } |
| |
| // 5.4 VariableTypeAnnotations |
| // Also used for 5.5 GlobalVariableTypeAnnotations |
| AsmType* AsmTyper::VariableTypeAnnotations(Expression* initializer) { |
| if (auto* literal = initializer->AsLiteral()) { |
| if (literal->raw_value()->ContainsDot()) { |
| SetTypeOf(initializer, AsmType::Double()); |
| return AsmType::Double(); |
| } |
| int32_t i32; |
| uint32_t u32; |
| if (literal->value()->ToUint32(&u32)) { |
| if (u32 > LargestFixNum) { |
| SetTypeOf(initializer, AsmType::Unsigned()); |
| } else { |
| SetTypeOf(initializer, AsmType::FixNum()); |
| } |
| } else if (literal->value()->ToInt32(&i32)) { |
| SetTypeOf(initializer, AsmType::Signed()); |
| } else { |
| FAIL(initializer, "Invalid type annotation - forbidden literal."); |
| } |
| return AsmType::Int(); |
| } |
| |
| auto* call = initializer->AsCall(); |
| if (call == nullptr) { |
| FAIL(initializer, |
| "Invalid variable initialization - it should be a literal, or " |
| "fround(literal)."); |
| } |
| |
| if (!IsCallToFround(call)) { |
| FAIL(initializer, |
| "Invalid float coercion - expected call fround(literal)."); |
| } |
| |
| auto* src_expr = call->arguments()->at(0)->AsLiteral(); |
| if (src_expr == nullptr) { |
| FAIL(initializer, |
| "Invalid float type annotation - expected literal argument for call " |
| "to fround."); |
| } |
| |
| if (!src_expr->raw_value()->ContainsDot()) { |
| FAIL(initializer, |
| "Invalid float type annotation - expected literal argument to be a " |
| "floating point literal."); |
| } |
| |
| return AsmType::Float(); |
| } |
| |
| // 5.5 GlobalVariableTypeAnnotations |
| AsmType* AsmTyper::NewHeapView(CallNew* new_heap_view) { |
| auto* heap_type = new_heap_view->expression()->AsProperty(); |
| if (heap_type == nullptr) { |
| FAIL(new_heap_view, "Invalid type after new."); |
| } |
| auto* heap_view_info = ImportLookup(heap_type); |
| |
| if (heap_view_info == nullptr) { |
| FAIL(new_heap_view, "Unknown stdlib member in heap view declaration."); |
| } |
| |
| if (!heap_view_info->type()->IsA(AsmType::Heap())) { |
| FAIL(new_heap_view, "Type is not a heap view type."); |
| } |
| |
| if (new_heap_view->arguments()->length() != 1) { |
| FAIL(new_heap_view, "Invalid number of arguments when creating heap view."); |
| } |
| |
| auto* heap = new_heap_view->arguments()->at(0); |
| auto* heap_var_proxy = heap->AsVariableProxy(); |
| |
| if (heap_var_proxy == nullptr) { |
| FAIL(heap, |
| "Heap view creation parameter should be the module's heap parameter."); |
| } |
| |
| auto* heap_var_info = Lookup(heap_var_proxy->var()); |
| |
| if (heap_var_info == nullptr) { |
| FAIL(heap, "Undeclared identifier instead of heap parameter."); |
| } |
| |
| if (!heap_var_info->IsHeap()) { |
| FAIL(heap, |
| "Heap view creation parameter should be the module's heap parameter."); |
| } |
| |
| DCHECK(heap_view_info->type()->IsA(AsmType::Heap())); |
| return heap_view_info->type(); |
| } |
| |
| bool IsValidAsm(Isolate* isolate, Zone* zone, Script* script, |
| FunctionLiteral* root, std::string* error_message) { |
| error_message->clear(); |
| |
| AsmTyper typer(isolate, zone, script, root); |
| if (typer.Validate()) { |
| return true; |
| } |
| |
| *error_message = typer.error_message(); |
| return false; |
| } |
| |
| } // namespace wasm |
| } // namespace internal |
| } // namespace v8 |