// Copyright 2017 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-parser.h" #include #include #include #include "src/asmjs/asm-js.h" #include "src/asmjs/asm-types.h" #include "src/base/optional.h" #include "src/flags.h" #include "src/parsing/scanner.h" #include "src/wasm/wasm-limits.h" #include "src/wasm/wasm-opcodes.h" namespace v8 { namespace internal { namespace wasm { #ifdef DEBUG #define FAIL_AND_RETURN(ret, msg) \ failed_ = true; \ failure_message_ = msg; \ failure_location_ = static_cast(scanner_.Position()); \ if (FLAG_trace_asm_parser) { \ PrintF("[asm.js failure: %s, token: '%s', see: %s:%d]\n", msg, \ scanner_.Name(scanner_.Token()).c_str(), __FILE__, __LINE__); \ } \ return ret; #else #define FAIL_AND_RETURN(ret, msg) \ failed_ = true; \ failure_message_ = msg; \ failure_location_ = static_cast(scanner_.Position()); \ return ret; #endif #define FAIL(msg) FAIL_AND_RETURN(, msg) #define FAILn(msg) FAIL_AND_RETURN(nullptr, msg) #define EXPECT_TOKEN_OR_RETURN(ret, token) \ do { \ if (scanner_.Token() != token) { \ FAIL_AND_RETURN(ret, "Unexpected token"); \ } \ scanner_.Next(); \ } while (false) #define EXPECT_TOKEN(token) EXPECT_TOKEN_OR_RETURN(, token) #define EXPECT_TOKENn(token) EXPECT_TOKEN_OR_RETURN(nullptr, token) #define RECURSE_OR_RETURN(ret, call) \ do { \ DCHECK(!failed_); \ if (GetCurrentStackPosition() < stack_limit_) { \ FAIL_AND_RETURN(ret, "Stack overflow while parsing asm.js module."); \ } \ call; \ if (failed_) return ret; \ } while (false) #define RECURSE(call) RECURSE_OR_RETURN(, call) #define RECURSEn(call) RECURSE_OR_RETURN(nullptr, call) #define TOK(name) AsmJsScanner::kToken_##name AsmJsParser::AsmJsParser(Zone* zone, uintptr_t stack_limit, Utf16CharacterStream* stream) : zone_(zone), scanner_(stream), module_builder_(new (zone) WasmModuleBuilder(zone)), return_type_(nullptr), stack_limit_(stack_limit), global_var_info_(zone), local_var_info_(zone), failed_(false), failure_location_(kNoSourcePosition), stdlib_name_(kTokenNone), foreign_name_(kTokenNone), heap_name_(kTokenNone), inside_heap_assignment_(false), heap_access_type_(nullptr), block_stack_(zone), call_coercion_(nullptr), call_coercion_deferred_(nullptr), pending_label_(0), global_imports_(zone) { module_builder_->SetMinMemorySize(0); InitializeStdlibTypes(); } void AsmJsParser::InitializeStdlibTypes() { auto* d = AsmType::Double(); auto* dq = AsmType::DoubleQ(); stdlib_dq2d_ = AsmType::Function(zone(), d); stdlib_dq2d_->AsFunctionType()->AddArgument(dq); stdlib_dqdq2d_ = AsmType::Function(zone(), d); stdlib_dqdq2d_->AsFunctionType()->AddArgument(dq); stdlib_dqdq2d_->AsFunctionType()->AddArgument(dq); auto* f = AsmType::Float(); auto* fh = AsmType::Floatish(); auto* fq = AsmType::FloatQ(); auto* fq2fh = AsmType::Function(zone(), fh); fq2fh->AsFunctionType()->AddArgument(fq); auto* s = AsmType::Signed(); auto* u = AsmType::Unsigned(); auto* s2u = AsmType::Function(zone(), u); s2u->AsFunctionType()->AddArgument(s); auto* i = AsmType::Int(); stdlib_i2s_ = AsmType::Function(zone_, s); stdlib_i2s_->AsFunctionType()->AddArgument(i); stdlib_ii2s_ = AsmType::Function(zone(), s); stdlib_ii2s_->AsFunctionType()->AddArgument(i); stdlib_ii2s_->AsFunctionType()->AddArgument(i); // The signatures in "9 Standard Library" of the spec draft are outdated and // have been superseded with the following by an errata: // - Math.min/max : (signed, signed...) -> signed // (double, double...) -> double // (float, float...) -> float auto* minmax_d = AsmType::MinMaxType(zone(), d, d); auto* minmax_f = AsmType::MinMaxType(zone(), f, f); auto* minmax_s = AsmType::MinMaxType(zone(), s, s); stdlib_minmax_ = AsmType::OverloadedFunction(zone()); stdlib_minmax_->AsOverloadedFunctionType()->AddOverload(minmax_s); stdlib_minmax_->AsOverloadedFunctionType()->AddOverload(minmax_f); stdlib_minmax_->AsOverloadedFunctionType()->AddOverload(minmax_d); // The signatures in "9 Standard Library" of the spec draft are outdated and // have been superseded with the following by an errata: // - Math.abs : (signed) -> unsigned // (double?) -> double // (float?) -> floatish stdlib_abs_ = AsmType::OverloadedFunction(zone()); stdlib_abs_->AsOverloadedFunctionType()->AddOverload(s2u); stdlib_abs_->AsOverloadedFunctionType()->AddOverload(stdlib_dq2d_); stdlib_abs_->AsOverloadedFunctionType()->AddOverload(fq2fh); // The signatures in "9 Standard Library" of the spec draft are outdated and // have been superseded with the following by an errata: // - Math.ceil/floor/sqrt : (double?) -> double // (float?) -> floatish stdlib_ceil_like_ = AsmType::OverloadedFunction(zone()); stdlib_ceil_like_->AsOverloadedFunctionType()->AddOverload(stdlib_dq2d_); stdlib_ceil_like_->AsOverloadedFunctionType()->AddOverload(fq2fh); stdlib_fround_ = AsmType::FroundType(zone()); } FunctionSig* AsmJsParser::ConvertSignature(AsmType* return_type, const ZoneVector& params) { FunctionSig::Builder sig_builder( zone(), !return_type->IsA(AsmType::Void()) ? 1 : 0, params.size()); for (auto param : params) { if (param->IsA(AsmType::Double())) { sig_builder.AddParam(kWasmF64); } else if (param->IsA(AsmType::Float())) { sig_builder.AddParam(kWasmF32); } else if (param->IsA(AsmType::Int())) { sig_builder.AddParam(kWasmI32); } else { UNREACHABLE(); } } if (!return_type->IsA(AsmType::Void())) { if (return_type->IsA(AsmType::Double())) { sig_builder.AddReturn(kWasmF64); } else if (return_type->IsA(AsmType::Float())) { sig_builder.AddReturn(kWasmF32); } else if (return_type->IsA(AsmType::Signed())) { sig_builder.AddReturn(kWasmI32); } else { UNREACHABLE(); } } return sig_builder.Build(); } bool AsmJsParser::Run() { ValidateModule(); return !failed_; } class AsmJsParser::TemporaryVariableScope { public: explicit TemporaryVariableScope(AsmJsParser* parser) : parser_(parser) { local_depth_ = parser_->function_temp_locals_depth_; parser_->function_temp_locals_depth_++; } ~TemporaryVariableScope() { DCHECK_EQ(local_depth_, parser_->function_temp_locals_depth_ - 1); parser_->function_temp_locals_depth_--; } uint32_t get() const { return parser_->TempVariable(local_depth_); } private: AsmJsParser* parser_; int local_depth_; }; wasm::AsmJsParser::VarInfo* AsmJsParser::GetVarInfo( AsmJsScanner::token_t token) { if (AsmJsScanner::IsGlobal(token)) { size_t old = global_var_info_.size(); size_t index = AsmJsScanner::GlobalIndex(token); size_t sz = std::max(old, index + 1); if (sz != old) { global_var_info_.resize(sz); } return &global_var_info_[index]; } else if (AsmJsScanner::IsLocal(token)) { size_t old = local_var_info_.size(); size_t index = AsmJsScanner::LocalIndex(token); size_t sz = std::max(old, index + 1); if (sz != old) { local_var_info_.resize(sz); } return &local_var_info_[index]; } UNREACHABLE(); } uint32_t AsmJsParser::VarIndex(VarInfo* info) { DCHECK_EQ(info->kind, VarKind::kGlobal); return info->index + static_cast(global_imports_.size()); } void AsmJsParser::AddGlobalImport(Vector name, AsmType* type, ValueType vtype, bool mutable_variable, VarInfo* info) { // Allocate a separate variable for the import. // TODO(mstarzinger): Consider using the imported global directly instead of // allocating a separate global variable for immutable (i.e. const) imports. DeclareGlobal(info, mutable_variable, type, vtype); // Record the need to initialize the global from the import. global_imports_.push_back({name, vtype, info}); } void AsmJsParser::DeclareGlobal(VarInfo* info, bool mutable_variable, AsmType* type, ValueType vtype, const WasmInitExpr& init) { info->kind = VarKind::kGlobal; info->type = type; info->index = module_builder_->AddGlobal(vtype, false, true, init); info->mutable_variable = mutable_variable; } void AsmJsParser::DeclareStdlibFunc(VarInfo* info, VarKind kind, AsmType* type) { info->kind = kind; info->type = type; info->index = 0; // unused info->mutable_variable = false; } uint32_t AsmJsParser::TempVariable(int index) { if (index + 1 > function_temp_locals_used_) { function_temp_locals_used_ = index + 1; } return function_temp_locals_offset_ + index; } Vector AsmJsParser::CopyCurrentIdentifierString() { const std::string& str = scanner_.GetIdentifierString(); char* buffer = zone()->NewArray(str.size()); str.copy(buffer, str.size()); return Vector(buffer, static_cast(str.size())); } void AsmJsParser::SkipSemicolon() { if (Check(';')) { // Had a semicolon. } else if (!Peek('}') && !scanner_.IsPrecededByNewline()) { FAIL("Expected ;"); } } void AsmJsParser::Begin(AsmJsScanner::token_t label) { BareBegin(BlockKind::kRegular, label); current_function_builder_->EmitWithU8(kExprBlock, kLocalVoid); } void AsmJsParser::Loop(AsmJsScanner::token_t label) { BareBegin(BlockKind::kLoop, label); size_t position = scanner_.Position(); current_function_builder_->AddAsmWasmOffset(position, position); current_function_builder_->EmitWithU8(kExprLoop, kLocalVoid); } void AsmJsParser::End() { BareEnd(); current_function_builder_->Emit(kExprEnd); } void AsmJsParser::BareBegin(BlockKind kind, AsmJsScanner::token_t label) { BlockInfo info; info.kind = kind; info.label = label; block_stack_.push_back(info); } void AsmJsParser::BareEnd() { DCHECK_GT(block_stack_.size(), 0); block_stack_.pop_back(); } int AsmJsParser::FindContinueLabelDepth(AsmJsScanner::token_t label) { int count = 0; for (auto it = block_stack_.rbegin(); it != block_stack_.rend(); ++it, ++count) { if (it->kind == BlockKind::kLoop && (label == kTokenNone || it->label == label)) { return count; } } return -1; } int AsmJsParser::FindBreakLabelDepth(AsmJsScanner::token_t label) { int count = 0; for (auto it = block_stack_.rbegin(); it != block_stack_.rend(); ++it, ++count) { if (it->kind == BlockKind::kRegular && (label == kTokenNone || it->label == label)) { return count; } } return -1; } // 6.1 ValidateModule void AsmJsParser::ValidateModule() { RECURSE(ValidateModuleParameters()); EXPECT_TOKEN('{'); EXPECT_TOKEN(TOK(UseAsm)); SkipSemicolon(); RECURSE(ValidateModuleVars()); while (Peek(TOK(function))) { RECURSE(ValidateFunction()); } while (Peek(TOK(var))) { RECURSE(ValidateFunctionTable()); } RECURSE(ValidateExport()); // Check that all functions were eventually defined. for (auto& info : global_var_info_) { if (info.kind == VarKind::kFunction && !info.function_defined) { FAIL("Undefined function"); } if (info.kind == VarKind::kTable && !info.function_defined) { FAIL("Undefined function table"); } if (info.kind == VarKind::kImportedFunction && !info.function_defined) { // For imported functions without a single call site, we insert a dummy // import here to preserve the fact that there actually was an import. FunctionSig* void_void_sig = FunctionSig::Builder(zone(), 0, 0).Build(); module_builder_->AddImport(info.import->function_name, void_void_sig); } } // Add start function to initialize things. WasmFunctionBuilder* start = module_builder_->AddFunction(); module_builder_->MarkStartFunction(start); for (auto& global_import : global_imports_) { uint32_t import_index = module_builder_->AddGlobalImport( global_import.import_name, global_import.value_type); start->EmitWithI32V(kExprGetGlobal, import_index); start->EmitWithI32V(kExprSetGlobal, VarIndex(global_import.var_info)); } start->Emit(kExprEnd); FunctionSig::Builder b(zone(), 0, 0); start->SetSignature(b.Build()); } // 6.1 ValidateModule - parameters void AsmJsParser::ValidateModuleParameters() { EXPECT_TOKEN('('); stdlib_name_ = 0; foreign_name_ = 0; heap_name_ = 0; if (!Peek(')')) { if (!scanner_.IsGlobal()) { FAIL("Expected stdlib parameter"); } stdlib_name_ = Consume(); if (!Peek(')')) { EXPECT_TOKEN(','); if (!scanner_.IsGlobal()) { FAIL("Expected foreign parameter"); } foreign_name_ = Consume(); if (!Peek(')')) { EXPECT_TOKEN(','); if (!scanner_.IsGlobal()) { FAIL("Expected heap parameter"); } heap_name_ = Consume(); } } } EXPECT_TOKEN(')'); } // 6.1 ValidateModule - variables void AsmJsParser::ValidateModuleVars() { while (Peek(TOK(var)) || Peek(TOK(const))) { bool mutable_variable = true; if (Check(TOK(var))) { // Had a var. } else { EXPECT_TOKEN(TOK(const)); mutable_variable = false; } for (;;) { RECURSE(ValidateModuleVar(mutable_variable)); if (Check(',')) { continue; } break; } SkipSemicolon(); } } // 6.1 ValidateModule - one variable void AsmJsParser::ValidateModuleVar(bool mutable_variable) { if (!scanner_.IsGlobal()) { FAIL("Expected identifier"); } VarInfo* info = GetVarInfo(Consume()); if (info->kind != VarKind::kUnused) { FAIL("Redefinition of variable"); } EXPECT_TOKEN('='); double dvalue = 0.0; uint32_t uvalue = 0; if (CheckForDouble(&dvalue)) { DeclareGlobal(info, mutable_variable, AsmType::Double(), kWasmF64, WasmInitExpr(dvalue)); } else if (CheckForUnsigned(&uvalue)) { if (uvalue > 0x7FFFFFFF) { FAIL("Numeric literal out of range"); } DeclareGlobal(info, mutable_variable, mutable_variable ? AsmType::Int() : AsmType::Signed(), kWasmI32, WasmInitExpr(static_cast(uvalue))); } else if (Check('-')) { if (CheckForDouble(&dvalue)) { DeclareGlobal(info, mutable_variable, AsmType::Double(), kWasmF64, WasmInitExpr(-dvalue)); } else if (CheckForUnsigned(&uvalue)) { if (uvalue > 0x7FFFFFFF) { FAIL("Numeric literal out of range"); } DeclareGlobal(info, mutable_variable, mutable_variable ? AsmType::Int() : AsmType::Signed(), kWasmI32, WasmInitExpr(-static_cast(uvalue))); } else { FAIL("Expected numeric literal"); } } else if (Check(TOK(new))) { RECURSE(ValidateModuleVarNewStdlib(info)); } else if (Check(stdlib_name_)) { EXPECT_TOKEN('.'); RECURSE(ValidateModuleVarStdlib(info)); } else if (Peek(foreign_name_) || Peek('+')) { RECURSE(ValidateModuleVarImport(info, mutable_variable)); } else if (scanner_.IsGlobal()) { RECURSE(ValidateModuleVarFromGlobal(info, mutable_variable)); } else { FAIL("Bad variable declaration"); } } // 6.1 ValidateModule - global float declaration void AsmJsParser::ValidateModuleVarFromGlobal(VarInfo* info, bool mutable_variable) { VarInfo* src_info = GetVarInfo(Consume()); if (!src_info->type->IsA(stdlib_fround_)) { if (src_info->mutable_variable) { FAIL("Can only use immutable variables in global definition"); } if (mutable_variable) { FAIL("Can only define immutable variables with other immutables"); } if (!src_info->type->IsA(AsmType::Int()) && !src_info->type->IsA(AsmType::Float()) && !src_info->type->IsA(AsmType::Double())) { FAIL("Expected int, float, double, or fround for global definition"); } info->kind = VarKind::kGlobal; info->type = src_info->type; info->index = src_info->index; info->mutable_variable = false; return; } EXPECT_TOKEN('('); bool negate = false; if (Check('-')) { negate = true; } double dvalue = 0.0; uint32_t uvalue = 0; if (CheckForDouble(&dvalue)) { if (negate) { dvalue = -dvalue; } DeclareGlobal(info, mutable_variable, AsmType::Float(), kWasmF32, WasmInitExpr(static_cast(dvalue))); } else if (CheckForUnsigned(&uvalue)) { dvalue = uvalue; if (negate) { dvalue = -dvalue; } DeclareGlobal(info, mutable_variable, AsmType::Float(), kWasmF32, WasmInitExpr(static_cast(dvalue))); } else { FAIL("Expected numeric literal"); } EXPECT_TOKEN(')'); } // 6.1 ValidateModule - foreign imports void AsmJsParser::ValidateModuleVarImport(VarInfo* info, bool mutable_variable) { if (Check('+')) { EXPECT_TOKEN(foreign_name_); EXPECT_TOKEN('.'); Vector name = CopyCurrentIdentifierString(); AddGlobalImport(name, AsmType::Double(), kWasmF64, mutable_variable, info); scanner_.Next(); } else { EXPECT_TOKEN(foreign_name_); EXPECT_TOKEN('.'); Vector name = CopyCurrentIdentifierString(); scanner_.Next(); if (Check('|')) { if (!CheckForZero()) { FAIL("Expected |0 type annotation for foreign integer import"); } AddGlobalImport(name, AsmType::Int(), kWasmI32, mutable_variable, info); } else { info->kind = VarKind::kImportedFunction; info->import = new (zone()->New(sizeof(FunctionImportInfo))) FunctionImportInfo(name, zone()); info->mutable_variable = false; } } } // 6.1 ValidateModule - one variable // 9 - Standard Library - heap types void AsmJsParser::ValidateModuleVarNewStdlib(VarInfo* info) { EXPECT_TOKEN(stdlib_name_); EXPECT_TOKEN('.'); switch (Consume()) { #define V(name, _junk1, _junk2, _junk3) \ case TOK(name): \ DeclareStdlibFunc(info, VarKind::kSpecial, AsmType::name()); \ stdlib_uses_.Add(StandardMember::k##name); \ break; STDLIB_ARRAY_TYPE_LIST(V) #undef V default: FAIL("Expected ArrayBuffer view"); break; } EXPECT_TOKEN('('); EXPECT_TOKEN(heap_name_); EXPECT_TOKEN(')'); } // 6.1 ValidateModule - one variable // 9 - Standard Library void AsmJsParser::ValidateModuleVarStdlib(VarInfo* info) { if (Check(TOK(Math))) { EXPECT_TOKEN('.'); switch (Consume()) { #define V(name, const_value) \ case TOK(name): \ DeclareGlobal(info, false, AsmType::Double(), kWasmF64, \ WasmInitExpr(const_value)); \ stdlib_uses_.Add(StandardMember::kMath##name); \ break; STDLIB_MATH_VALUE_LIST(V) #undef V #define V(name, Name, op, sig) \ case TOK(name): \ DeclareStdlibFunc(info, VarKind::kMath##Name, stdlib_##sig##_); \ stdlib_uses_.Add(StandardMember::kMath##Name); \ break; STDLIB_MATH_FUNCTION_LIST(V) #undef V default: FAIL("Invalid member of stdlib.Math"); } } else if (Check(TOK(Infinity))) { DeclareGlobal(info, false, AsmType::Double(), kWasmF64, WasmInitExpr(std::numeric_limits::infinity())); stdlib_uses_.Add(StandardMember::kInfinity); } else if (Check(TOK(NaN))) { DeclareGlobal(info, false, AsmType::Double(), kWasmF64, WasmInitExpr(std::numeric_limits::quiet_NaN())); stdlib_uses_.Add(StandardMember::kNaN); } else { FAIL("Invalid member of stdlib"); } } // 6.2 ValidateExport void AsmJsParser::ValidateExport() { // clang-format off EXPECT_TOKEN(TOK(return)); // clang-format on if (Check('{')) { for (;;) { Vector name = CopyCurrentIdentifierString(); if (!scanner_.IsGlobal() && !scanner_.IsLocal()) { FAIL("Illegal export name"); } Consume(); EXPECT_TOKEN(':'); if (!scanner_.IsGlobal()) { FAIL("Expected function name"); } VarInfo* info = GetVarInfo(Consume()); if (info->kind != VarKind::kFunction) { FAIL("Expected function"); } module_builder_->AddExport(name, info->function_builder); if (Check(',')) { if (!Peek('}')) { continue; } } break; } EXPECT_TOKEN('}'); } else { if (!scanner_.IsGlobal()) { FAIL("Single function export must be a function name"); } VarInfo* info = GetVarInfo(Consume()); if (info->kind != VarKind::kFunction) { FAIL("Single function export must be a function"); } module_builder_->AddExport(CStrVector(AsmJs::kSingleFunctionName), info->function_builder); } } // 6.3 ValidateFunctionTable void AsmJsParser::ValidateFunctionTable() { EXPECT_TOKEN(TOK(var)); if (!scanner_.IsGlobal()) { FAIL("Expected table name"); } VarInfo* table_info = GetVarInfo(Consume()); if (table_info->kind == VarKind::kTable) { if (table_info->function_defined) { FAIL("Function table redefined"); } table_info->function_defined = true; } else if (table_info->kind != VarKind::kUnused) { FAIL("Function table name collides"); } EXPECT_TOKEN('='); EXPECT_TOKEN('['); uint64_t count = 0; for (;;) { if (!scanner_.IsGlobal()) { FAIL("Expected function name"); } VarInfo* info = GetVarInfo(Consume()); if (info->kind != VarKind::kFunction) { FAIL("Expected function"); } // Only store the function into a table if we used the table somewhere // (i.e. tables are first seen at their use sites and allocated there). if (table_info->kind == VarKind::kTable) { if (count >= static_cast(table_info->mask) + 1) { FAIL("Exceeded function table size"); } if (!info->type->IsA(table_info->type)) { FAIL("Function table definition doesn't match use"); } module_builder_->SetIndirectFunction( static_cast(table_info->index + count), info->index); } ++count; if (Check(',')) { if (!Peek(']')) { continue; } } break; } EXPECT_TOKEN(']'); if (table_info->kind == VarKind::kTable && count != static_cast(table_info->mask) + 1) { FAIL("Function table size does not match uses"); } SkipSemicolon(); } // 6.4 ValidateFunction void AsmJsParser::ValidateFunction() { EXPECT_TOKEN(TOK(function)); if (!scanner_.IsGlobal()) { FAIL("Expected function name"); } Vector function_name_str = CopyCurrentIdentifierString(); AsmJsScanner::token_t function_name = Consume(); VarInfo* function_info = GetVarInfo(function_name); if (function_info->kind == VarKind::kUnused) { function_info->kind = VarKind::kFunction; function_info->function_builder = module_builder_->AddFunction(); function_info->index = function_info->function_builder->func_index(); function_info->mutable_variable = false; } else if (function_info->kind != VarKind::kFunction) { FAIL("Function name collides with variable"); } else if (function_info->function_defined) { FAIL("Function redefined"); } function_info->function_defined = true; function_info->function_builder->SetName(function_name_str); current_function_builder_ = function_info->function_builder; return_type_ = nullptr; // Record start of the function, used as position for the stack check. current_function_builder_->SetAsmFunctionStartPosition(scanner_.Position()); CachedVector params(cached_asm_type_p_vectors_); ValidateFunctionParams(¶ms); // Check against limit on number of parameters. if (params.size() >= kV8MaxWasmFunctionParams) { FAIL("Number of parameters exceeds internal limit"); } CachedVector locals(cached_valuetype_vectors_); ValidateFunctionLocals(params.size(), &locals); function_temp_locals_offset_ = static_cast( params.size() + locals.size()); function_temp_locals_used_ = 0; function_temp_locals_depth_ = 0; bool last_statement_is_return = false; while (!failed_ && !Peek('}')) { // clang-format off last_statement_is_return = Peek(TOK(return)); // clang-format on RECURSE(ValidateStatement()); } EXPECT_TOKEN('}'); if (!last_statement_is_return) { if (return_type_ == nullptr) { return_type_ = AsmType::Void(); } else if (!return_type_->IsA(AsmType::Void())) { FAIL("Expected return at end of non-void function"); } } DCHECK_NOT_NULL(return_type_); // TODO(bradnelson): WasmModuleBuilder can't take this in the right order. // We should fix that so we can use it instead. FunctionSig* sig = ConvertSignature(return_type_, params); current_function_builder_->SetSignature(sig); for (auto local : locals) { current_function_builder_->AddLocal(local); } // Add bonus temps. for (int i = 0; i < function_temp_locals_used_; ++i) { current_function_builder_->AddLocal(kWasmI32); } // Check against limit on number of local variables. if (locals.size() + function_temp_locals_used_ > kV8MaxWasmFunctionLocals) { FAIL("Number of local variables exceeds internal limit"); } // End function current_function_builder_->Emit(kExprEnd); // Record (or validate) function type. AsmType* function_type = AsmType::Function(zone(), return_type_); for (auto t : params) { function_type->AsFunctionType()->AddArgument(t); } function_info = GetVarInfo(function_name); if (function_info->type->IsA(AsmType::None())) { DCHECK_EQ(function_info->kind, VarKind::kFunction); function_info->type = function_type; } else if (!function_type->IsA(function_info->type)) { // TODO(bradnelson): Should IsExactly be used here? FAIL("Function definition doesn't match use"); } scanner_.ResetLocals(); local_var_info_.clear(); } // 6.4 ValidateFunction void AsmJsParser::ValidateFunctionParams(ZoneVector* params) { // TODO(bradnelson): Do this differently so that the scanner doesn't need to // have a state transition that needs knowledge of how the scanner works // inside. scanner_.EnterLocalScope(); EXPECT_TOKEN('('); CachedVector function_parameters( cached_token_t_vectors_); while (!failed_ && !Peek(')')) { if (!scanner_.IsLocal()) { FAIL("Expected parameter name"); } function_parameters.push_back(Consume()); if (!Peek(')')) { EXPECT_TOKEN(','); } } EXPECT_TOKEN(')'); scanner_.EnterGlobalScope(); EXPECT_TOKEN('{'); // 5.1 Parameter Type Annotations for (auto p : function_parameters) { EXPECT_TOKEN(p); EXPECT_TOKEN('='); VarInfo* info = GetVarInfo(p); if (info->kind != VarKind::kUnused) { FAIL("Duplicate parameter name"); } if (Check(p)) { EXPECT_TOKEN('|'); if (!CheckForZero()) { FAIL("Bad integer parameter annotation."); } info->kind = VarKind::kLocal; info->type = AsmType::Int(); info->index = static_cast(params->size()); params->push_back(AsmType::Int()); } else if (Check('+')) { EXPECT_TOKEN(p); info->kind = VarKind::kLocal; info->type = AsmType::Double(); info->index = static_cast(params->size()); params->push_back(AsmType::Double()); } else { if (!scanner_.IsGlobal() || !GetVarInfo(Consume())->type->IsA(stdlib_fround_)) { FAIL("Expected fround"); } EXPECT_TOKEN('('); EXPECT_TOKEN(p); EXPECT_TOKEN(')'); info->kind = VarKind::kLocal; info->type = AsmType::Float(); info->index = static_cast(params->size()); params->push_back(AsmType::Float()); } SkipSemicolon(); } } // 6.4 ValidateFunction - locals void AsmJsParser::ValidateFunctionLocals(size_t param_count, ZoneVector* locals) { DCHECK(locals->empty()); // Local Variables. while (Peek(TOK(var))) { scanner_.EnterLocalScope(); EXPECT_TOKEN(TOK(var)); scanner_.EnterGlobalScope(); for (;;) { if (!scanner_.IsLocal()) { FAIL("Expected local variable identifier"); } VarInfo* info = GetVarInfo(Consume()); if (info->kind != VarKind::kUnused) { FAIL("Duplicate local variable name"); } // Store types. EXPECT_TOKEN('='); double dvalue = 0.0; uint32_t uvalue = 0; if (Check('-')) { if (CheckForDouble(&dvalue)) { info->kind = VarKind::kLocal; info->type = AsmType::Double(); info->index = static_cast(param_count + locals->size()); locals->push_back(kWasmF64); current_function_builder_->EmitF64Const(-dvalue); current_function_builder_->EmitSetLocal(info->index); } else if (CheckForUnsigned(&uvalue)) { if (uvalue > 0x7FFFFFFF) { FAIL("Numeric literal out of range"); } info->kind = VarKind::kLocal; info->type = AsmType::Int(); info->index = static_cast(param_count + locals->size()); locals->push_back(kWasmI32); int32_t value = -static_cast(uvalue); current_function_builder_->EmitI32Const(value); current_function_builder_->EmitSetLocal(info->index); } else { FAIL("Expected variable initial value"); } } else if (scanner_.IsGlobal()) { VarInfo* sinfo = GetVarInfo(Consume()); if (sinfo->kind == VarKind::kGlobal) { if (sinfo->mutable_variable) { FAIL("Initializing from global requires const variable"); } info->kind = VarKind::kLocal; info->type = sinfo->type; info->index = static_cast(param_count + locals->size()); if (sinfo->type->IsA(AsmType::Int())) { locals->push_back(kWasmI32); } else if (sinfo->type->IsA(AsmType::Float())) { locals->push_back(kWasmF32); } else if (sinfo->type->IsA(AsmType::Double())) { locals->push_back(kWasmF64); } else { FAIL("Bad local variable definition"); } current_function_builder_->EmitWithI32V(kExprGetGlobal, VarIndex(sinfo)); current_function_builder_->EmitSetLocal(info->index); } else if (sinfo->type->IsA(stdlib_fround_)) { EXPECT_TOKEN('('); bool negate = false; if (Check('-')) { negate = true; } double dvalue = 0.0; if (CheckForDouble(&dvalue)) { info->kind = VarKind::kLocal; info->type = AsmType::Float(); info->index = static_cast(param_count + locals->size()); locals->push_back(kWasmF32); if (negate) { dvalue = -dvalue; } current_function_builder_->EmitF32Const(dvalue); current_function_builder_->EmitSetLocal(info->index); } else if (CheckForUnsigned(&uvalue)) { if (uvalue > 0x7FFFFFFF) { FAIL("Numeric literal out of range"); } info->kind = VarKind::kLocal; info->type = AsmType::Float(); info->index = static_cast(param_count + locals->size()); locals->push_back(kWasmF32); int32_t value = static_cast(uvalue); if (negate) { value = -value; } float fvalue = static_cast(value); current_function_builder_->EmitF32Const(fvalue); current_function_builder_->EmitSetLocal(info->index); } else { FAIL("Expected variable initial value"); } EXPECT_TOKEN(')'); } else { FAIL("expected fround or const global"); } } else if (CheckForDouble(&dvalue)) { info->kind = VarKind::kLocal; info->type = AsmType::Double(); info->index = static_cast(param_count + locals->size()); locals->push_back(kWasmF64); current_function_builder_->EmitF64Const(dvalue); current_function_builder_->EmitSetLocal(info->index); } else if (CheckForUnsigned(&uvalue)) { info->kind = VarKind::kLocal; info->type = AsmType::Int(); info->index = static_cast(param_count + locals->size()); locals->push_back(kWasmI32); int32_t value = static_cast(uvalue); current_function_builder_->EmitI32Const(value); current_function_builder_->EmitSetLocal(info->index); } else { FAIL("Expected variable initial value"); } if (!Peek(',')) { break; } scanner_.EnterLocalScope(); EXPECT_TOKEN(','); scanner_.EnterGlobalScope(); } SkipSemicolon(); } } // 6.5 ValidateStatement void AsmJsParser::ValidateStatement() { call_coercion_ = nullptr; if (Peek('{')) { RECURSE(Block()); } else if (Peek(';')) { RECURSE(EmptyStatement()); } else if (Peek(TOK(if))) { RECURSE(IfStatement()); // clang-format off } else if (Peek(TOK(return))) { // clang-format on RECURSE(ReturnStatement()); } else if (IterationStatement()) { // Handled in IterationStatement. } else if (Peek(TOK(break))) { RECURSE(BreakStatement()); } else if (Peek(TOK(continue))) { RECURSE(ContinueStatement()); } else if (Peek(TOK(switch))) { RECURSE(SwitchStatement()); } else { RECURSE(ExpressionStatement()); } } // 6.5.1 Block void AsmJsParser::Block() { bool can_break_to_block = pending_label_ != 0; if (can_break_to_block) { Begin(pending_label_); } pending_label_ = 0; EXPECT_TOKEN('{'); while (!failed_ && !Peek('}')) { RECURSE(ValidateStatement()); } EXPECT_TOKEN('}'); if (can_break_to_block) { End(); } } // 6.5.2 ExpressionStatement void AsmJsParser::ExpressionStatement() { if (scanner_.IsGlobal() || scanner_.IsLocal()) { // NOTE: Both global or local identifiers can also be used as labels. scanner_.Next(); if (Peek(':')) { scanner_.Rewind(); RECURSE(LabelledStatement()); return; } scanner_.Rewind(); } AsmType* ret; RECURSE(ret = ValidateExpression()); if (!ret->IsA(AsmType::Void())) { current_function_builder_->Emit(kExprDrop); } SkipSemicolon(); } // 6.5.3 EmptyStatement void AsmJsParser::EmptyStatement() { EXPECT_TOKEN(';'); } // 6.5.4 IfStatement void AsmJsParser::IfStatement() { EXPECT_TOKEN(TOK(if)); EXPECT_TOKEN('('); RECURSE(Expression(AsmType::Int())); EXPECT_TOKEN(')'); current_function_builder_->EmitWithU8(kExprIf, kLocalVoid); BareBegin(); RECURSE(ValidateStatement()); if (Check(TOK(else))) { current_function_builder_->Emit(kExprElse); RECURSE(ValidateStatement()); } current_function_builder_->Emit(kExprEnd); BareEnd(); } // 6.5.5 ReturnStatement void AsmJsParser::ReturnStatement() { // clang-format off EXPECT_TOKEN(TOK(return)); // clang-format on if (!Peek(';') && !Peek('}')) { // TODO(bradnelson): See if this can be factored out. AsmType* ret; RECURSE(ret = Expression(return_type_)); if (ret->IsA(AsmType::Double())) { return_type_ = AsmType::Double(); } else if (ret->IsA(AsmType::Float())) { return_type_ = AsmType::Float(); } else if (ret->IsA(AsmType::Signed())) { return_type_ = AsmType::Signed(); } else { FAIL("Invalid return type"); } } else if (return_type_ == nullptr) { return_type_ = AsmType::Void(); } else if (!return_type_->IsA(AsmType::Void())) { FAIL("Invalid void return type"); } current_function_builder_->Emit(kExprReturn); SkipSemicolon(); } // 6.5.6 IterationStatement bool AsmJsParser::IterationStatement() { if (Peek(TOK(while))) { WhileStatement(); } else if (Peek(TOK(do))) { DoStatement(); } else if (Peek(TOK(for))) { ForStatement(); } else { return false; } return true; } // 6.5.6 IterationStatement - while void AsmJsParser::WhileStatement() { // a: block { Begin(pending_label_); // b: loop { Loop(pending_label_); pending_label_ = 0; EXPECT_TOKEN(TOK(while)); EXPECT_TOKEN('('); RECURSE(Expression(AsmType::Int())); EXPECT_TOKEN(')'); // if (!CONDITION) break a; current_function_builder_->Emit(kExprI32Eqz); current_function_builder_->EmitWithU8(kExprBrIf, 1); // BODY RECURSE(ValidateStatement()); // continue b; current_function_builder_->EmitWithU8(kExprBr, 0); End(); // } // } End(); } // 6.5.6 IterationStatement - do void AsmJsParser::DoStatement() { // a: block { Begin(pending_label_); // b: loop { Loop(); // c: block { // but treated like loop so continue works BareBegin(BlockKind::kLoop, pending_label_); current_function_builder_->EmitWithU8(kExprBlock, kLocalVoid); pending_label_ = 0; EXPECT_TOKEN(TOK(do)); // BODY RECURSE(ValidateStatement()); EXPECT_TOKEN(TOK(while)); End(); // } // end c EXPECT_TOKEN('('); RECURSE(Expression(AsmType::Int())); // if (!CONDITION) break a; current_function_builder_->Emit(kExprI32Eqz); current_function_builder_->EmitWithU8(kExprBrIf, 1); // continue b; current_function_builder_->EmitWithU8(kExprBr, 0); EXPECT_TOKEN(')'); // } // end b End(); // } // end a End(); SkipSemicolon(); } // 6.5.6 IterationStatement - for void AsmJsParser::ForStatement() { EXPECT_TOKEN(TOK(for)); EXPECT_TOKEN('('); if (!Peek(';')) { AsmType* ret; RECURSE(ret = Expression(nullptr)); if (!ret->IsA(AsmType::Void())) { current_function_builder_->Emit(kExprDrop); } } EXPECT_TOKEN(';'); // a: block { Begin(pending_label_); // b: loop { Loop(); // c: block { // but treated like loop so continue works BareBegin(BlockKind::kLoop, pending_label_); current_function_builder_->EmitWithU8(kExprBlock, kLocalVoid); pending_label_ = 0; if (!Peek(';')) { // if (!CONDITION) break a; RECURSE(Expression(AsmType::Int())); current_function_builder_->Emit(kExprI32Eqz); current_function_builder_->EmitWithU8(kExprBrIf, 2); } EXPECT_TOKEN(';'); // Race past INCREMENT size_t increment_position = scanner_.Position(); ScanToClosingParenthesis(); EXPECT_TOKEN(')'); // BODY RECURSE(ValidateStatement()); // } // end c End(); // INCREMENT size_t end_position = scanner_.Position(); scanner_.Seek(increment_position); if (!Peek(')')) { RECURSE(Expression(nullptr)); // NOTE: No explicit drop because below break is an implicit drop. } // continue b; current_function_builder_->EmitWithU8(kExprBr, 0); scanner_.Seek(end_position); // } // end b End(); // } // end a End(); } // 6.5.7 BreakStatement void AsmJsParser::BreakStatement() { EXPECT_TOKEN(TOK(break)); AsmJsScanner::token_t label_name = kTokenNone; if (scanner_.IsGlobal() || scanner_.IsLocal()) { // NOTE: Currently using globals/locals for labels too. label_name = Consume(); } int depth = FindBreakLabelDepth(label_name); if (depth < 0) { FAIL("Illegal break"); } current_function_builder_->Emit(kExprBr); current_function_builder_->EmitI32V(depth); SkipSemicolon(); } // 6.5.8 ContinueStatement void AsmJsParser::ContinueStatement() { EXPECT_TOKEN(TOK(continue)); AsmJsScanner::token_t label_name = kTokenNone; if (scanner_.IsGlobal() || scanner_.IsLocal()) { // NOTE: Currently using globals/locals for labels too. label_name = Consume(); } int depth = FindContinueLabelDepth(label_name); if (depth < 0) { FAIL("Illegal continue"); } current_function_builder_->EmitWithI32V(kExprBr, depth); SkipSemicolon(); } // 6.5.9 LabelledStatement void AsmJsParser::LabelledStatement() { DCHECK(scanner_.IsGlobal() || scanner_.IsLocal()); // NOTE: Currently using globals/locals for labels too. if (pending_label_ != 0) { FAIL("Double label unsupported"); } pending_label_ = scanner_.Token(); scanner_.Next(); EXPECT_TOKEN(':'); RECURSE(ValidateStatement()); } // 6.5.10 SwitchStatement void AsmJsParser::SwitchStatement() { EXPECT_TOKEN(TOK(switch)); EXPECT_TOKEN('('); AsmType* test; RECURSE(test = Expression(nullptr)); if (!test->IsA(AsmType::Signed())) { FAIL("Expected signed for switch value"); } EXPECT_TOKEN(')'); uint32_t tmp = TempVariable(0); current_function_builder_->EmitSetLocal(tmp); Begin(pending_label_); pending_label_ = 0; // TODO(bradnelson): Make less weird. CachedVector cases(cached_int_vectors_); GatherCases(&cases); EXPECT_TOKEN('{'); size_t count = cases.size() + 1; for (size_t i = 0; i < count; ++i) { BareBegin(BlockKind::kOther); current_function_builder_->EmitWithU8(kExprBlock, kLocalVoid); } int table_pos = 0; for (auto c : cases) { current_function_builder_->EmitGetLocal(tmp); current_function_builder_->EmitI32Const(c); current_function_builder_->Emit(kExprI32Eq); current_function_builder_->EmitWithI32V(kExprBrIf, table_pos++); } current_function_builder_->EmitWithI32V(kExprBr, table_pos++); while (!failed_ && Peek(TOK(case))) { current_function_builder_->Emit(kExprEnd); BareEnd(); RECURSE(ValidateCase()); } current_function_builder_->Emit(kExprEnd); BareEnd(); if (Peek(TOK(default))) { RECURSE(ValidateDefault()); } EXPECT_TOKEN('}'); End(); } // 6.6. ValidateCase void AsmJsParser::ValidateCase() { EXPECT_TOKEN(TOK(case)); bool negate = false; if (Check('-')) { negate = true; } uint32_t uvalue; if (!CheckForUnsigned(&uvalue)) { FAIL("Expected numeric literal"); } // TODO(bradnelson): Share negation plumbing. if ((negate && uvalue > 0x80000000) || (!negate && uvalue > 0x7FFFFFFF)) { FAIL("Numeric literal out of range"); } int32_t value = static_cast(uvalue); DCHECK_IMPLIES(negate && uvalue == 0x80000000, value == kMinInt); if (negate && value != kMinInt) { value = -value; } EXPECT_TOKEN(':'); while (!failed_ && !Peek('}') && !Peek(TOK(case)) && !Peek(TOK(default))) { RECURSE(ValidateStatement()); } } // 6.7 ValidateDefault void AsmJsParser::ValidateDefault() { EXPECT_TOKEN(TOK(default)); EXPECT_TOKEN(':'); while (!failed_ && !Peek('}')) { RECURSE(ValidateStatement()); } } // 6.8 ValidateExpression AsmType* AsmJsParser::ValidateExpression() { AsmType* ret; RECURSEn(ret = Expression(nullptr)); return ret; } // 6.8.1 Expression AsmType* AsmJsParser::Expression(AsmType* expected) { AsmType* a; for (;;) { RECURSEn(a = AssignmentExpression()); if (Peek(',')) { if (a->IsA(AsmType::None())) { FAILn("Expected actual type"); } if (!a->IsA(AsmType::Void())) { current_function_builder_->Emit(kExprDrop); } EXPECT_TOKENn(','); continue; } break; } if (expected != nullptr && !a->IsA(expected)) { FAILn("Unexpected type"); } return a; } // 6.8.2 NumericLiteral AsmType* AsmJsParser::NumericLiteral() { call_coercion_ = nullptr; double dvalue = 0.0; uint32_t uvalue = 0; if (CheckForDouble(&dvalue)) { current_function_builder_->EmitF64Const(dvalue); return AsmType::Double(); } else if (CheckForUnsigned(&uvalue)) { if (uvalue <= 0x7FFFFFFF) { current_function_builder_->EmitI32Const(static_cast(uvalue)); return AsmType::FixNum(); } else { current_function_builder_->EmitI32Const(static_cast(uvalue)); return AsmType::Unsigned(); } } else { FAILn("Expected numeric literal."); } } // 6.8.3 Identifier AsmType* AsmJsParser::Identifier() { call_coercion_ = nullptr; if (scanner_.IsLocal()) { VarInfo* info = GetVarInfo(Consume()); if (info->kind != VarKind::kLocal) { FAILn("Undefined local variable"); } current_function_builder_->EmitGetLocal(info->index); return info->type; } else if (scanner_.IsGlobal()) { VarInfo* info = GetVarInfo(Consume()); if (info->kind != VarKind::kGlobal) { FAILn("Undefined global variable"); } current_function_builder_->EmitWithI32V(kExprGetGlobal, VarIndex(info)); return info->type; } UNREACHABLE(); } // 6.8.4 CallExpression AsmType* AsmJsParser::CallExpression() { AsmType* ret; if (scanner_.IsGlobal() && GetVarInfo(scanner_.Token())->type->IsA(stdlib_fround_)) { ValidateFloatCoercion(); return AsmType::Float(); } else if (scanner_.IsGlobal() && GetVarInfo(scanner_.Token())->type->IsA(AsmType::Heap())) { RECURSEn(ret = MemberExpression()); } else if (Peek('(')) { RECURSEn(ret = ParenthesizedExpression()); } else if (PeekCall()) { RECURSEn(ret = ValidateCall()); } else if (scanner_.IsLocal() || scanner_.IsGlobal()) { RECURSEn(ret = Identifier()); } else { RECURSEn(ret = NumericLiteral()); } return ret; } // 6.8.5 MemberExpression AsmType* AsmJsParser::MemberExpression() { call_coercion_ = nullptr; RECURSEn(ValidateHeapAccess()); DCHECK_NOT_NULL(heap_access_type_); if (Peek('=')) { inside_heap_assignment_ = true; return heap_access_type_->StoreType(); } else { #define V(array_type, wasmload, wasmstore, type) \ if (heap_access_type_->IsA(AsmType::array_type())) { \ current_function_builder_->Emit(kExpr##type##AsmjsLoad##wasmload); \ return heap_access_type_->LoadType(); \ } STDLIB_ARRAY_TYPE_LIST(V) #undef V FAILn("Expected valid heap load"); } } // 6.8.6 AssignmentExpression AsmType* AsmJsParser::AssignmentExpression() { AsmType* ret; if (scanner_.IsGlobal() && GetVarInfo(scanner_.Token())->type->IsA(AsmType::Heap())) { RECURSEn(ret = ConditionalExpression()); if (Peek('=')) { if (!inside_heap_assignment_) { FAILn("Invalid assignment target"); } inside_heap_assignment_ = false; DCHECK_NOT_NULL(heap_access_type_); AsmType* heap_type = heap_access_type_; EXPECT_TOKENn('='); AsmType* value; RECURSEn(value = AssignmentExpression()); if (!value->IsA(ret)) { FAILn("Illegal type stored to heap view"); } if (heap_type->IsA(AsmType::Float32Array()) && value->IsA(AsmType::Double())) { // Assignment to a float32 heap can be used to convert doubles. current_function_builder_->Emit(kExprF32ConvertF64); } ret = value; #define V(array_type, wasmload, wasmstore, type) \ if (heap_type->IsA(AsmType::array_type())) { \ current_function_builder_->Emit(kExpr##type##AsmjsStore##wasmstore); \ return ret; \ } STDLIB_ARRAY_TYPE_LIST(V) #undef V } } else if (scanner_.IsLocal() || scanner_.IsGlobal()) { bool is_local = scanner_.IsLocal(); VarInfo* info = GetVarInfo(scanner_.Token()); USE(is_local); ret = info->type; scanner_.Next(); if (Check('=')) { // NOTE: Before this point, this might have been VarKind::kUnused even in // valid code, as it might be a label. if (info->kind == VarKind::kUnused) { FAILn("Undeclared assignment target"); } if (!info->mutable_variable) { FAILn("Expected mutable variable in assignment"); } DCHECK(is_local ? info->kind == VarKind::kLocal : info->kind == VarKind::kGlobal); AsmType* value; RECURSEn(value = AssignmentExpression()); if (!value->IsA(ret)) { FAILn("Type mismatch in assignment"); } if (info->kind == VarKind::kLocal) { current_function_builder_->EmitTeeLocal(info->index); } else if (info->kind == VarKind::kGlobal) { current_function_builder_->EmitWithU32V(kExprSetGlobal, VarIndex(info)); current_function_builder_->EmitWithU32V(kExprGetGlobal, VarIndex(info)); } else { UNREACHABLE(); } return ret; } scanner_.Rewind(); RECURSEn(ret = ConditionalExpression()); } else { RECURSEn(ret = ConditionalExpression()); } return ret; } // 6.8.7 UnaryExpression AsmType* AsmJsParser::UnaryExpression() { AsmType* ret; if (Check('-')) { uint32_t uvalue; if (CheckForUnsigned(&uvalue)) { // TODO(bradnelson): was supposed to be 0x7FFFFFFF, check errata. if (uvalue <= 0x80000000) { current_function_builder_->EmitI32Const(-static_cast(uvalue)); } else { FAILn("Integer numeric literal out of range."); } ret = AsmType::Signed(); } else { RECURSEn(ret = UnaryExpression()); if (ret->IsA(AsmType::Int())) { TemporaryVariableScope tmp(this); current_function_builder_->EmitSetLocal(tmp.get()); current_function_builder_->EmitI32Const(0); current_function_builder_->EmitGetLocal(tmp.get()); current_function_builder_->Emit(kExprI32Sub); ret = AsmType::Intish(); } else if (ret->IsA(AsmType::DoubleQ())) { current_function_builder_->Emit(kExprF64Neg); ret = AsmType::Double(); } else if (ret->IsA(AsmType::FloatQ())) { current_function_builder_->Emit(kExprF32Neg); ret = AsmType::Floatish(); } else { FAILn("expected int/double?/float?"); } } } else if (Peek('+')) { call_coercion_ = AsmType::Double(); call_coercion_position_ = scanner_.Position(); scanner_.Next(); // Done late for correct position. RECURSEn(ret = UnaryExpression()); // TODO(bradnelson): Generalize. if (ret->IsA(AsmType::Signed())) { current_function_builder_->Emit(kExprF64SConvertI32); ret = AsmType::Double(); } else if (ret->IsA(AsmType::Unsigned())) { current_function_builder_->Emit(kExprF64UConvertI32); ret = AsmType::Double(); } else if (ret->IsA(AsmType::DoubleQ())) { ret = AsmType::Double(); } else if (ret->IsA(AsmType::FloatQ())) { current_function_builder_->Emit(kExprF64ConvertF32); ret = AsmType::Double(); } else { FAILn("expected signed/unsigned/double?/float?"); } } else if (Check('!')) { RECURSEn(ret = UnaryExpression()); if (!ret->IsA(AsmType::Int())) { FAILn("expected int"); } current_function_builder_->Emit(kExprI32Eqz); } else if (Check('~')) { if (Check('~')) { RECURSEn(ret = UnaryExpression()); if (ret->IsA(AsmType::Double())) { current_function_builder_->Emit(kExprI32AsmjsSConvertF64); } else if (ret->IsA(AsmType::FloatQ())) { current_function_builder_->Emit(kExprI32AsmjsSConvertF32); } else { FAILn("expected double or float?"); } ret = AsmType::Signed(); } else { RECURSEn(ret = UnaryExpression()); if (!ret->IsA(AsmType::Intish())) { FAILn("operator ~ expects intish"); } current_function_builder_->EmitI32Const(0xFFFFFFFF); current_function_builder_->Emit(kExprI32Xor); ret = AsmType::Signed(); } } else { RECURSEn(ret = CallExpression()); } return ret; } // 6.8.8 MultiplicativeExpression AsmType* AsmJsParser::MultiplicativeExpression() { uint32_t uvalue; if (CheckForUnsignedBelow(0x100000, &uvalue)) { if (Check('*')) { AsmType* a; RECURSEn(a = UnaryExpression()); if (!a->IsA(AsmType::Int())) { FAILn("Expected int"); } int32_t value = static_cast(uvalue); current_function_builder_->EmitI32Const(value); current_function_builder_->Emit(kExprI32Mul); return AsmType::Intish(); } scanner_.Rewind(); } else if (Check('-')) { if (CheckForUnsignedBelow(0x100000, &uvalue)) { int32_t value = -static_cast(uvalue); current_function_builder_->EmitI32Const(value); if (Check('*')) { AsmType* a; RECURSEn(a = UnaryExpression()); if (!a->IsA(AsmType::Int())) { FAILn("Expected int"); } current_function_builder_->Emit(kExprI32Mul); return AsmType::Intish(); } return AsmType::Signed(); } scanner_.Rewind(); } AsmType* a; RECURSEn(a = UnaryExpression()); for (;;) { if (Check('*')) { uint32_t uvalue; if (Check('-')) { if (CheckForUnsigned(&uvalue)) { if (uvalue >= 0x100000) { FAILn("Constant multiple out of range"); } if (!a->IsA(AsmType::Int())) { FAILn("Integer multiply of expects int"); } int32_t value = -static_cast(uvalue); current_function_builder_->EmitI32Const(value); current_function_builder_->Emit(kExprI32Mul); return AsmType::Intish(); } scanner_.Rewind(); } else if (CheckForUnsigned(&uvalue)) { if (uvalue >= 0x100000) { FAILn("Constant multiple out of range"); } if (!a->IsA(AsmType::Int())) { FAILn("Integer multiply of expects int"); } int32_t value = static_cast(uvalue); current_function_builder_->EmitI32Const(value); current_function_builder_->Emit(kExprI32Mul); return AsmType::Intish(); } AsmType* b; RECURSEn(b = UnaryExpression()); if (a->IsA(AsmType::DoubleQ()) && b->IsA(AsmType::DoubleQ())) { current_function_builder_->Emit(kExprF64Mul); a = AsmType::Double(); } else if (a->IsA(AsmType::FloatQ()) && b->IsA(AsmType::FloatQ())) { current_function_builder_->Emit(kExprF32Mul); a = AsmType::Floatish(); } else { FAILn("expected doubles or floats"); } } else if (Check('/')) { AsmType* b; RECURSEn(b = UnaryExpression()); if (a->IsA(AsmType::DoubleQ()) && b->IsA(AsmType::DoubleQ())) { current_function_builder_->Emit(kExprF64Div); a = AsmType::Double(); } else if (a->IsA(AsmType::FloatQ()) && b->IsA(AsmType::FloatQ())) { current_function_builder_->Emit(kExprF32Div); a = AsmType::Floatish(); } else if (a->IsA(AsmType::Signed()) && b->IsA(AsmType::Signed())) { current_function_builder_->Emit(kExprI32AsmjsDivS); a = AsmType::Intish(); } else if (a->IsA(AsmType::Unsigned()) && b->IsA(AsmType::Unsigned())) { current_function_builder_->Emit(kExprI32AsmjsDivU); a = AsmType::Intish(); } else { FAILn("expected doubles or floats"); } } else if (Check('%')) { AsmType* b; RECURSEn(b = UnaryExpression()); if (a->IsA(AsmType::DoubleQ()) && b->IsA(AsmType::DoubleQ())) { current_function_builder_->Emit(kExprF64Mod); a = AsmType::Double(); } else if (a->IsA(AsmType::Signed()) && b->IsA(AsmType::Signed())) { current_function_builder_->Emit(kExprI32AsmjsRemS); a = AsmType::Intish(); } else if (a->IsA(AsmType::Unsigned()) && b->IsA(AsmType::Unsigned())) { current_function_builder_->Emit(kExprI32AsmjsRemU); a = AsmType::Intish(); } else { FAILn("expected doubles or floats"); } } else { break; } } return a; } // 6.8.9 AdditiveExpression AsmType* AsmJsParser::AdditiveExpression() { AsmType* a; RECURSEn(a = MultiplicativeExpression()); int n = 0; for (;;) { if (Check('+')) { AsmType* b; RECURSEn(b = MultiplicativeExpression()); if (a->IsA(AsmType::Double()) && b->IsA(AsmType::Double())) { current_function_builder_->Emit(kExprF64Add); a = AsmType::Double(); } else if (a->IsA(AsmType::FloatQ()) && b->IsA(AsmType::FloatQ())) { current_function_builder_->Emit(kExprF32Add); a = AsmType::Floatish(); } else if (a->IsA(AsmType::Int()) && b->IsA(AsmType::Int())) { current_function_builder_->Emit(kExprI32Add); a = AsmType::Intish(); n = 2; } else if (a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish())) { // TODO(bradnelson): b should really only be Int. // specialize intish to capture count. ++n; if (n > (1 << 20)) { FAILn("more than 2^20 additive values"); } current_function_builder_->Emit(kExprI32Add); } else { FAILn("illegal types for +"); } } else if (Check('-')) { AsmType* b; RECURSEn(b = MultiplicativeExpression()); if (a->IsA(AsmType::Double()) && b->IsA(AsmType::Double())) { current_function_builder_->Emit(kExprF64Sub); a = AsmType::Double(); } else if (a->IsA(AsmType::FloatQ()) && b->IsA(AsmType::FloatQ())) { current_function_builder_->Emit(kExprF32Sub); a = AsmType::Floatish(); } else if (a->IsA(AsmType::Int()) && b->IsA(AsmType::Int())) { current_function_builder_->Emit(kExprI32Sub); a = AsmType::Intish(); n = 2; } else if (a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish())) { // TODO(bradnelson): b should really only be Int. // specialize intish to capture count. ++n; if (n > (1 << 20)) { FAILn("more than 2^20 additive values"); } current_function_builder_->Emit(kExprI32Sub); } else { FAILn("illegal types for +"); } } else { break; } } return a; } // 6.8.10 ShiftExpression AsmType* AsmJsParser::ShiftExpression() { AsmType* a = nullptr; RECURSEn(a = AdditiveExpression()); heap_access_shift_position_ = kNoHeapAccessShift; // TODO(bradnelson): Implement backtracking to avoid emitting code // for the x >>> 0 case (similar to what's there for |0). for (;;) { switch (scanner_.Token()) { case TOK(SAR): { EXPECT_TOKENn(TOK(SAR)); heap_access_shift_position_ = kNoHeapAccessShift; // Remember position allowing this shift-expression to be used as part // of a heap access operation expecting `a >> n:NumericLiteral`. bool imm = false; size_t old_pos; size_t old_code; uint32_t shift_imm; if (a->IsA(AsmType::Intish()) && CheckForUnsigned(&shift_imm)) { old_pos = scanner_.Position(); old_code = current_function_builder_->GetPosition(); scanner_.Rewind(); imm = true; } AsmType* b = nullptr; RECURSEn(b = AdditiveExpression()); // Check for `a >> n:NumericLiteral` pattern. if (imm && old_pos == scanner_.Position()) { heap_access_shift_position_ = old_code; heap_access_shift_value_ = shift_imm; } if (!(a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish()))) { FAILn("Expected intish for operator >>."); } current_function_builder_->Emit(kExprI32ShrS); a = AsmType::Signed(); continue; } #define HANDLE_CASE(op, opcode, name, result) \ case TOK(op): { \ EXPECT_TOKENn(TOK(op)); \ heap_access_shift_position_ = kNoHeapAccessShift; \ AsmType* b = nullptr; \ RECURSEn(b = AdditiveExpression()); \ if (!(a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish()))) { \ FAILn("Expected intish for operator " #name "."); \ } \ current_function_builder_->Emit(kExpr##opcode); \ a = AsmType::result(); \ continue; \ } HANDLE_CASE(SHL, I32Shl, "<<", Signed); HANDLE_CASE(SHR, I32ShrU, ">>>", Unsigned); #undef HANDLE_CASE default: return a; } } } // 6.8.11 RelationalExpression AsmType* AsmJsParser::RelationalExpression() { AsmType* a = nullptr; RECURSEn(a = ShiftExpression()); for (;;) { switch (scanner_.Token()) { #define HANDLE_CASE(op, sop, uop, dop, fop, name) \ case op: { \ EXPECT_TOKENn(op); \ AsmType* b = nullptr; \ RECURSEn(b = ShiftExpression()); \ if (a->IsA(AsmType::Signed()) && b->IsA(AsmType::Signed())) { \ current_function_builder_->Emit(kExpr##sop); \ } else if (a->IsA(AsmType::Unsigned()) && b->IsA(AsmType::Unsigned())) { \ current_function_builder_->Emit(kExpr##uop); \ } else if (a->IsA(AsmType::Double()) && b->IsA(AsmType::Double())) { \ current_function_builder_->Emit(kExpr##dop); \ } else if (a->IsA(AsmType::Float()) && b->IsA(AsmType::Float())) { \ current_function_builder_->Emit(kExpr##fop); \ } else { \ FAILn("Expected signed, unsigned, double, or float for operator " #name \ "."); \ } \ a = AsmType::Int(); \ continue; \ } HANDLE_CASE('<', I32LtS, I32LtU, F64Lt, F32Lt, "<"); HANDLE_CASE(TOK(LE), I32LeS, I32LeU, F64Le, F32Le, "<="); HANDLE_CASE('>', I32GtS, I32GtU, F64Gt, F32Gt, ">"); HANDLE_CASE(TOK(GE), I32GeS, I32GeU, F64Ge, F32Ge, ">="); #undef HANDLE_CASE default: return a; } } } // 6.8.12 EqualityExpression AsmType* AsmJsParser::EqualityExpression() { AsmType* a = nullptr; RECURSEn(a = RelationalExpression()); for (;;) { switch (scanner_.Token()) { #define HANDLE_CASE(op, sop, uop, dop, fop, name) \ case op: { \ EXPECT_TOKENn(op); \ AsmType* b = nullptr; \ RECURSEn(b = RelationalExpression()); \ if (a->IsA(AsmType::Signed()) && b->IsA(AsmType::Signed())) { \ current_function_builder_->Emit(kExpr##sop); \ } else if (a->IsA(AsmType::Unsigned()) && b->IsA(AsmType::Unsigned())) { \ current_function_builder_->Emit(kExpr##uop); \ } else if (a->IsA(AsmType::Double()) && b->IsA(AsmType::Double())) { \ current_function_builder_->Emit(kExpr##dop); \ } else if (a->IsA(AsmType::Float()) && b->IsA(AsmType::Float())) { \ current_function_builder_->Emit(kExpr##fop); \ } else { \ FAILn("Expected signed, unsigned, double, or float for operator " #name \ "."); \ } \ a = AsmType::Int(); \ continue; \ } HANDLE_CASE(TOK(EQ), I32Eq, I32Eq, F64Eq, F32Eq, "=="); HANDLE_CASE(TOK(NE), I32Ne, I32Ne, F64Ne, F32Ne, "!="); #undef HANDLE_CASE default: return a; } } } // 6.8.13 BitwiseANDExpression AsmType* AsmJsParser::BitwiseANDExpression() { AsmType* a = nullptr; RECURSEn(a = EqualityExpression()); while (Check('&')) { AsmType* b = nullptr; RECURSEn(b = EqualityExpression()); if (a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish())) { current_function_builder_->Emit(kExprI32And); a = AsmType::Signed(); } else { FAILn("Expected intish for operator &."); } } return a; } // 6.8.14 BitwiseXORExpression AsmType* AsmJsParser::BitwiseXORExpression() { AsmType* a = nullptr; RECURSEn(a = BitwiseANDExpression()); while (Check('^')) { AsmType* b = nullptr; RECURSEn(b = BitwiseANDExpression()); if (a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish())) { current_function_builder_->Emit(kExprI32Xor); a = AsmType::Signed(); } else { FAILn("Expected intish for operator &."); } } return a; } // 6.8.15 BitwiseORExpression AsmType* AsmJsParser::BitwiseORExpression() { AsmType* a = nullptr; call_coercion_deferred_position_ = scanner_.Position(); RECURSEn(a = BitwiseXORExpression()); while (Check('|')) { AsmType* b = nullptr; // Remember whether the first operand to this OR-expression has requested // deferred validation of the |0 annotation. // NOTE: This has to happen here to work recursively. bool requires_zero = call_coercion_deferred_->IsExactly(AsmType::Signed()); call_coercion_deferred_ = nullptr; // TODO(bradnelson): Make it prettier. bool zero = false; size_t old_pos; size_t old_code; if (a->IsA(AsmType::Intish()) && CheckForZero()) { old_pos = scanner_.Position(); old_code = current_function_builder_->GetPosition(); scanner_.Rewind(); zero = true; } RECURSEn(b = BitwiseXORExpression()); // Handle |0 specially. if (zero && old_pos == scanner_.Position()) { current_function_builder_->DeleteCodeAfter(old_code); a = AsmType::Signed(); continue; } // Anything not matching |0 breaks the lookahead in {ValidateCall}. if (requires_zero) { FAILn("Expected |0 type annotation for call"); } if (a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish())) { current_function_builder_->Emit(kExprI32Ior); a = AsmType::Signed(); } else { FAILn("Expected intish for operator |."); } } DCHECK_NULL(call_coercion_deferred_); return a; } // 6.8.16 ConditionalExpression AsmType* AsmJsParser::ConditionalExpression() { AsmType* test = nullptr; RECURSEn(test = BitwiseORExpression()); if (Check('?')) { if (!test->IsA(AsmType::Int())) { FAILn("Expected int in condition of ternary operator."); } current_function_builder_->EmitWithU8(kExprIf, kLocalI32); size_t fixup = current_function_builder_->GetPosition() - 1; // Assumes encoding knowledge. AsmType* cons = nullptr; RECURSEn(cons = AssignmentExpression()); current_function_builder_->Emit(kExprElse); EXPECT_TOKENn(':'); AsmType* alt = nullptr; RECURSEn(alt = AssignmentExpression()); current_function_builder_->Emit(kExprEnd); if (cons->IsA(AsmType::Int()) && alt->IsA(AsmType::Int())) { current_function_builder_->FixupByte(fixup, kLocalI32); return AsmType::Int(); } else if (cons->IsA(AsmType::Double()) && alt->IsA(AsmType::Double())) { current_function_builder_->FixupByte(fixup, kLocalF64); return AsmType::Double(); } else if (cons->IsA(AsmType::Float()) && alt->IsA(AsmType::Float())) { current_function_builder_->FixupByte(fixup, kLocalF32); return AsmType::Float(); } else { FAILn("Type mismatch in ternary operator."); } } else { return test; } } // 6.8.17 ParenthesiedExpression AsmType* AsmJsParser::ParenthesizedExpression() { call_coercion_ = nullptr; AsmType* ret; EXPECT_TOKENn('('); RECURSEn(ret = Expression(nullptr)); EXPECT_TOKENn(')'); return ret; } // 6.9 ValidateCall AsmType* AsmJsParser::ValidateCall() { AsmType* return_type = call_coercion_; call_coercion_ = nullptr; size_t call_pos = scanner_.Position(); size_t to_number_pos = call_coercion_position_; bool allow_peek = (call_coercion_deferred_position_ == scanner_.Position()); AsmJsScanner::token_t function_name = Consume(); // Distinguish between ordinary function calls and function table calls. In // both cases we might be seeing the {function_name} for the first time and // hence allocate a {VarInfo} here, all subsequent uses of the same name then // need to match the information stored at this point. base::Optional tmp; if (Check('[')) { RECURSEn(EqualityExpression()); EXPECT_TOKENn('&'); uint32_t mask = 0; if (!CheckForUnsigned(&mask)) { FAILn("Expected mask literal"); } if (!base::bits::IsPowerOfTwo(mask + 1)) { FAILn("Expected power of 2 mask"); } current_function_builder_->EmitI32Const(mask); current_function_builder_->Emit(kExprI32And); EXPECT_TOKENn(']'); VarInfo* function_info = GetVarInfo(function_name); if (function_info->kind == VarKind::kUnused) { uint32_t index = module_builder_->AllocateIndirectFunctions(mask + 1); if (index == std::numeric_limits::max()) { FAILn("Exceeded maximum function table size"); } function_info->kind = VarKind::kTable; function_info->mask = mask; function_info->index = index; function_info->mutable_variable = false; } else { if (function_info->kind != VarKind::kTable) { FAILn("Expected call table"); } if (function_info->mask != mask) { FAILn("Mask size mismatch"); } } current_function_builder_->EmitI32Const(function_info->index); current_function_builder_->Emit(kExprI32Add); // We have to use a temporary for the correct order of evaluation. tmp.emplace(this); current_function_builder_->EmitSetLocal(tmp->get()); // The position of function table calls is after the table lookup. call_pos = scanner_.Position(); } else { VarInfo* function_info = GetVarInfo(function_name); if (function_info->kind == VarKind::kUnused) { function_info->kind = VarKind::kFunction; function_info->function_builder = module_builder_->AddFunction(); function_info->index = function_info->function_builder->func_index(); function_info->mutable_variable = false; } else { if (function_info->kind != VarKind::kFunction && function_info->kind < VarKind::kImportedFunction) { FAILn("Expected function as call target"); } } } // Parse argument list and gather types. CachedVector param_types(cached_asm_type_p_vectors_); CachedVector param_specific_types(cached_asm_type_p_vectors_); EXPECT_TOKENn('('); while (!failed_ && !Peek(')')) { AsmType* t; RECURSEn(t = AssignmentExpression()); param_specific_types.push_back(t); if (t->IsA(AsmType::Int())) { param_types.push_back(AsmType::Int()); } else if (t->IsA(AsmType::Float())) { param_types.push_back(AsmType::Float()); } else if (t->IsA(AsmType::Double())) { param_types.push_back(AsmType::Double()); } else { FAILn("Bad function argument type"); } if (!Peek(')')) { EXPECT_TOKENn(','); } } EXPECT_TOKENn(')'); // Reload {VarInfo} after parsing arguments as table might have grown. VarInfo* function_info = GetVarInfo(function_name); // We potentially use lookahead in order to determine the return type in case // it is not yet clear from the call context. Special care has to be taken to // ensure the non-contextual lookahead is valid. The following restrictions // substantiate the validity of the lookahead implemented below: // - All calls (except stdlib calls) require some sort of type annotation. // - The coercion to "signed" is part of the {BitwiseORExpression}, any // intermittent expressions like parenthesis in `(callsite(..))|0` are // syntactically not considered coercions. // - The coercion to "double" as part of the {UnaryExpression} has higher // precedence and wins in `+callsite(..)|0` cases. Only "float" return // types are overridden in `fround(callsite(..)|0)` expressions. // - Expected coercions to "signed" are flagged via {call_coercion_deferred} // and later on validated as part of {BitwiseORExpression} to ensure they // indeed apply to the current call expression. // - The deferred validation is only allowed if {BitwiseORExpression} did // promise to fulfill the request via {call_coercion_deferred_position}. if (allow_peek && Peek('|') && function_info->kind <= VarKind::kImportedFunction && (return_type == nullptr || return_type->IsA(AsmType::Float()))) { DCHECK_NULL(call_coercion_deferred_); call_coercion_deferred_ = AsmType::Signed(); to_number_pos = scanner_.Position(); return_type = AsmType::Signed(); } else if (return_type == nullptr) { to_number_pos = call_pos; // No conversion. return_type = AsmType::Void(); } // Compute function type and signature based on gathered types. AsmType* function_type = AsmType::Function(zone(), return_type); for (auto t : param_types) { function_type->AsFunctionType()->AddArgument(t); } FunctionSig* sig = ConvertSignature(return_type, param_types); uint32_t signature_index = module_builder_->AddSignature(sig); // Emit actual function invocation depending on the kind. At this point we // also determined the complete function type and can perform checking against // the expected type or update the expected type in case of first occurrence. if (function_info->kind == VarKind::kImportedFunction) { for (auto t : param_specific_types) { if (!t->IsA(AsmType::Extern())) { FAILn("Imported function args must be type extern"); } } if (return_type->IsA(AsmType::Float())) { FAILn("Imported function can't be called as float"); } DCHECK_NOT_NULL(function_info->import); // TODO(bradnelson): Factor out. uint32_t index; auto it = function_info->import->cache.find(*sig); if (it != function_info->import->cache.end()) { index = it->second; DCHECK(function_info->function_defined); } else { index = module_builder_->AddImport(function_info->import->function_name, sig); function_info->import->cache[*sig] = index; function_info->function_defined = true; } current_function_builder_->AddAsmWasmOffset(call_pos, to_number_pos); current_function_builder_->EmitWithU32V(kExprCallFunction, index); } else if (function_info->kind > VarKind::kImportedFunction) { AsmCallableType* callable = function_info->type->AsCallableType(); if (!callable) { FAILn("Expected callable function"); } // TODO(bradnelson): Refactor AsmType to not need this. if (callable->CanBeInvokedWith(return_type, param_specific_types)) { // Return type ok. } else if (callable->CanBeInvokedWith(AsmType::Float(), param_specific_types)) { return_type = AsmType::Float(); } else if (callable->CanBeInvokedWith(AsmType::Floatish(), param_specific_types)) { return_type = AsmType::Floatish(); } else if (callable->CanBeInvokedWith(AsmType::Double(), param_specific_types)) { return_type = AsmType::Double(); } else if (callable->CanBeInvokedWith(AsmType::Signed(), param_specific_types)) { return_type = AsmType::Signed(); } else if (callable->CanBeInvokedWith(AsmType::Unsigned(), param_specific_types)) { return_type = AsmType::Unsigned(); } else { FAILn("Function use doesn't match definition"); } switch (function_info->kind) { #define V(name, Name, op, sig) \ case VarKind::kMath##Name: \ current_function_builder_->Emit(op); \ break; STDLIB_MATH_FUNCTION_MONOMORPHIC_LIST(V) #undef V #define V(name, Name, op, sig) \ case VarKind::kMath##Name: \ if (param_specific_types[0]->IsA(AsmType::DoubleQ())) { \ current_function_builder_->Emit(kExprF64##Name); \ } else if (param_specific_types[0]->IsA(AsmType::FloatQ())) { \ current_function_builder_->Emit(kExprF32##Name); \ } else { \ UNREACHABLE(); \ } \ break; STDLIB_MATH_FUNCTION_CEIL_LIKE_LIST(V) #undef V case VarKind::kMathMin: case VarKind::kMathMax: if (param_specific_types[0]->IsA(AsmType::Double())) { for (size_t i = 1; i < param_specific_types.size(); ++i) { if (function_info->kind == VarKind::kMathMin) { current_function_builder_->Emit(kExprF64Min); } else { current_function_builder_->Emit(kExprF64Max); } } } else if (param_specific_types[0]->IsA(AsmType::Float())) { // NOTE: Not technically part of the asm.js spec, but Firefox // accepts it. for (size_t i = 1; i < param_specific_types.size(); ++i) { if (function_info->kind == VarKind::kMathMin) { current_function_builder_->Emit(kExprF32Min); } else { current_function_builder_->Emit(kExprF32Max); } } } else if (param_specific_types[0]->IsA(AsmType::Signed())) { TemporaryVariableScope tmp_x(this); TemporaryVariableScope tmp_y(this); for (size_t i = 1; i < param_specific_types.size(); ++i) { current_function_builder_->EmitSetLocal(tmp_x.get()); current_function_builder_->EmitTeeLocal(tmp_y.get()); current_function_builder_->EmitGetLocal(tmp_x.get()); if (function_info->kind == VarKind::kMathMin) { current_function_builder_->Emit(kExprI32GeS); } else { current_function_builder_->Emit(kExprI32LeS); } current_function_builder_->EmitWithU8(kExprIf, kLocalI32); current_function_builder_->EmitGetLocal(tmp_x.get()); current_function_builder_->Emit(kExprElse); current_function_builder_->EmitGetLocal(tmp_y.get()); current_function_builder_->Emit(kExprEnd); } } else { UNREACHABLE(); } break; case VarKind::kMathAbs: if (param_specific_types[0]->IsA(AsmType::Signed())) { TemporaryVariableScope tmp(this); current_function_builder_->EmitTeeLocal(tmp.get()); current_function_builder_->EmitGetLocal(tmp.get()); current_function_builder_->EmitI32Const(31); current_function_builder_->Emit(kExprI32ShrS); current_function_builder_->EmitTeeLocal(tmp.get()); current_function_builder_->Emit(kExprI32Xor); current_function_builder_->EmitGetLocal(tmp.get()); current_function_builder_->Emit(kExprI32Sub); } else if (param_specific_types[0]->IsA(AsmType::DoubleQ())) { current_function_builder_->Emit(kExprF64Abs); } else if (param_specific_types[0]->IsA(AsmType::FloatQ())) { current_function_builder_->Emit(kExprF32Abs); } else { UNREACHABLE(); } break; case VarKind::kMathFround: // NOTE: Handled in {AsmJsParser::CallExpression} specially and treated // as a coercion to "float" type. Cannot be reached as a call here. UNREACHABLE(); default: UNREACHABLE(); } } else { DCHECK(function_info->kind == VarKind::kFunction || function_info->kind == VarKind::kTable); if (function_info->type->IsA(AsmType::None())) { function_info->type = function_type; } else { AsmCallableType* callable = function_info->type->AsCallableType(); if (!callable || !callable->CanBeInvokedWith(return_type, param_specific_types)) { FAILn("Function use doesn't match definition"); } } if (function_info->kind == VarKind::kTable) { current_function_builder_->EmitGetLocal(tmp->get()); current_function_builder_->AddAsmWasmOffset(call_pos, to_number_pos); current_function_builder_->Emit(kExprCallIndirect); current_function_builder_->EmitU32V(signature_index); current_function_builder_->EmitU32V(0); // table index } else { current_function_builder_->AddAsmWasmOffset(call_pos, to_number_pos); current_function_builder_->Emit(kExprCallFunction); current_function_builder_->EmitDirectCallIndex(function_info->index); } } return return_type; } // 6.9 ValidateCall - helper bool AsmJsParser::PeekCall() { if (!scanner_.IsGlobal()) { return false; } if (GetVarInfo(scanner_.Token())->kind == VarKind::kFunction) { return true; } if (GetVarInfo(scanner_.Token())->kind >= VarKind::kImportedFunction) { return true; } if (GetVarInfo(scanner_.Token())->kind == VarKind::kUnused || GetVarInfo(scanner_.Token())->kind == VarKind::kTable) { scanner_.Next(); if (Peek('(') || Peek('[')) { scanner_.Rewind(); return true; } scanner_.Rewind(); } return false; } // 6.10 ValidateHeapAccess void AsmJsParser::ValidateHeapAccess() { VarInfo* info = GetVarInfo(Consume()); int32_t size = info->type->ElementSizeInBytes(); EXPECT_TOKEN('['); uint32_t offset; if (CheckForUnsigned(&offset)) { // TODO(bradnelson): Check more things. // TODO(mstarzinger): Clarify and explain where this limit is coming from, // as it is not mandated by the spec directly. if (offset > 0x7FFFFFFF || static_cast(offset) * static_cast(size) > 0x7FFFFFFF) { FAIL("Heap access out of range"); } if (Check(']')) { current_function_builder_->EmitI32Const( static_cast(offset * size)); // NOTE: This has to happen here to work recursively. heap_access_type_ = info->type; return; } else { scanner_.Rewind(); } } AsmType* index_type; if (info->type->IsA(AsmType::Int8Array()) || info->type->IsA(AsmType::Uint8Array())) { RECURSE(index_type = Expression(nullptr)); } else { RECURSE(index_type = ShiftExpression()); if (heap_access_shift_position_ == kNoHeapAccessShift) { FAIL("Expected shift of word size"); } if (heap_access_shift_value_ > 3) { FAIL("Expected valid heap access shift"); } if ((1 << heap_access_shift_value_) != size) { FAIL("Expected heap access shift to match heap view"); } // Delete the code of the actual shift operation. current_function_builder_->DeleteCodeAfter(heap_access_shift_position_); // Mask bottom bits to match asm.js behavior. current_function_builder_->EmitI32Const(~(size - 1)); current_function_builder_->Emit(kExprI32And); } if (!index_type->IsA(AsmType::Intish())) { FAIL("Expected intish index"); } EXPECT_TOKEN(']'); // NOTE: This has to happen here to work recursively. heap_access_type_ = info->type; } // 6.11 ValidateFloatCoercion void AsmJsParser::ValidateFloatCoercion() { if (!scanner_.IsGlobal() || !GetVarInfo(scanner_.Token())->type->IsA(stdlib_fround_)) { FAIL("Expected fround"); } scanner_.Next(); EXPECT_TOKEN('('); call_coercion_ = AsmType::Float(); // NOTE: The coercion position to float is not observable from JavaScript, // because imported functions are not allowed to have float return type. call_coercion_position_ = scanner_.Position(); AsmType* ret; RECURSE(ret = ValidateExpression()); if (ret->IsA(AsmType::Floatish())) { // Do nothing, as already a float. } else if (ret->IsA(AsmType::DoubleQ())) { current_function_builder_->Emit(kExprF32ConvertF64); } else if (ret->IsA(AsmType::Signed())) { current_function_builder_->Emit(kExprF32SConvertI32); } else if (ret->IsA(AsmType::Unsigned())) { current_function_builder_->Emit(kExprF32UConvertI32); } else { FAIL("Illegal conversion to float"); } EXPECT_TOKEN(')'); } void AsmJsParser::ScanToClosingParenthesis() { int depth = 0; for (;;) { if (Peek('(')) { ++depth; } else if (Peek(')')) { --depth; if (depth < 0) { break; } } else if (Peek(AsmJsScanner::kEndOfInput)) { break; } scanner_.Next(); } } void AsmJsParser::GatherCases(ZoneVector* cases) { size_t start = scanner_.Position(); int depth = 0; for (;;) { if (Peek('{')) { ++depth; } else if (Peek('}')) { --depth; if (depth <= 0) { break; } } else if (depth == 1 && Peek(TOK(case))) { scanner_.Next(); uint32_t uvalue; bool negate = false; if (Check('-')) negate = true; if (!CheckForUnsigned(&uvalue)) { break; } int32_t value = static_cast(uvalue); DCHECK_IMPLIES(negate && uvalue == 0x80000000, value == kMinInt); if (negate && value != kMinInt) { value = -value; } cases->push_back(value); } else if (Peek(AsmJsScanner::kEndOfInput) || Peek(AsmJsScanner::kParseError)) { break; } scanner_.Next(); } scanner_.Seek(start); } } // namespace wasm } // namespace internal } // namespace v8 #undef RECURSE