| // 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 <math.h> |
| #include <string.h> |
| |
| #include <algorithm> |
| |
| #include "src/asmjs/asm-js.h" |
| #include "src/asmjs/asm-types.h" |
| #include "src/base/optional.h" |
| #include "src/base/overflowing-math.h" |
| #include "src/flags/flags.h" |
| #include "src/numbers/conversions-inl.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<int>(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<int>(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_(zone->New<WasmModuleBuilder>(zone)), |
| stack_limit_(stack_limit), |
| block_stack_(zone), |
| 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<AsmType*>& 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) { |
| const bool is_global = AsmJsScanner::IsGlobal(token); |
| DCHECK(is_global || AsmJsScanner::IsLocal(token)); |
| Vector<VarInfo>& var_info = is_global ? global_var_info_ : local_var_info_; |
| size_t old_capacity = var_info.size(); |
| size_t index = is_global ? AsmJsScanner::GlobalIndex(token) |
| : AsmJsScanner::LocalIndex(token); |
| if (is_global && index + 1 > num_globals_) num_globals_ = index + 1; |
| if (index + 1 > old_capacity) { |
| size_t new_size = std::max(2 * old_capacity, index + 1); |
| Vector<VarInfo> new_info{zone_->NewArray<VarInfo>(new_size), new_size}; |
| std::uninitialized_fill(new_info.begin(), new_info.end(), VarInfo{}); |
| std::copy(var_info.begin(), var_info.end(), new_info.begin()); |
| var_info = new_info; |
| } |
| return &var_info[index]; |
| } |
| |
| uint32_t AsmJsParser::VarIndex(VarInfo* info) { |
| DCHECK_EQ(info->kind, VarKind::kGlobal); |
| return info->index + static_cast<uint32_t>(global_imports_.size()); |
| } |
| |
| void AsmJsParser::AddGlobalImport(Vector<const char> name, AsmType* type, |
| ValueType vtype, bool mutable_variable, |
| VarInfo* info) { |
| // Allocate a separate variable for the import. |
| // TODO(asmjs): 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, |
| WasmInitExpr init) { |
| info->kind = VarKind::kGlobal; |
| info->type = type; |
| info->index = module_builder_->AddGlobal(vtype, true, std::move(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<const char> AsmJsParser::CopyCurrentIdentifierString() { |
| const std::string& str = scanner_.GetIdentifierString(); |
| char* buffer = zone()->NewArray<char>(str.size()); |
| str.copy(buffer, str.size()); |
| return Vector<const char>(buffer, static_cast<int>(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, kVoidCode); |
| } |
| |
| 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, kVoidCode); |
| } |
| |
| 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) { |
| // A 'continue' statement targets ... |
| // - The innermost {kLoop} block if no label is given. |
| // - The matching {kLoop} block (when a label is provided). |
| 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) { |
| // A 'break' statement targets ... |
| // - The innermost {kRegular} block if no label is given. |
| // - The matching {kRegular} or {kNamed} block (when a label is provided). |
| if ((it->kind == BlockKind::kRegular && |
| (label == kTokenNone || it->label == label)) || |
| (it->kind == BlockKind::kNamed && it->label == label)) { |
| return count; |
| } |
| } |
| return -1; |
| } |
| |
| // 6.1 ValidateModule |
| void AsmJsParser::ValidateModule() { |
| RECURSE(ValidateModuleParameters()); |
| EXPECT_TOKEN('{'); |
| EXPECT_TOKEN(TOK(UseAsm)); |
| RECURSE(SkipSemicolon()); |
| RECURSE(ValidateModuleVars()); |
| while (Peek(TOK(function))) { |
| RECURSE(ValidateFunction()); |
| } |
| while (Peek(TOK(var))) { |
| RECURSE(ValidateFunctionTable()); |
| } |
| RECURSE(ValidateExport()); |
| RECURSE(SkipSemicolon()); |
| EXPECT_TOKEN('}'); |
| |
| // Check that all functions were eventually defined. |
| for (auto& info : global_var_info_.SubVector(0, num_globals_)) { |
| 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, |
| false /* mutability */); |
| start->EmitWithI32V(kExprGlobalGet, import_index); |
| start->EmitWithI32V(kExprGlobalSet, 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<int32_t>(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"); |
| } |
| if (uvalue == 0) { |
| // '-0' is treated as float. |
| DeclareGlobal(info, mutable_variable, AsmType::Float(), kWasmF32, |
| WasmInitExpr(-0.f)); |
| } else { |
| DeclareGlobal(info, mutable_variable, |
| mutable_variable ? AsmType::Int() : AsmType::Signed(), |
| kWasmI32, WasmInitExpr(-static_cast<int32_t>(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(DoubleToFloat32(dvalue))); |
| } else if (CheckForUnsigned(&uvalue)) { |
| dvalue = uvalue; |
| if (negate) { |
| dvalue = -dvalue; |
| } |
| DeclareGlobal(info, mutable_variable, AsmType::Float(), kWasmF32, |
| WasmInitExpr(static_cast<float>(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<const char> name = CopyCurrentIdentifierString(); |
| AddGlobalImport(name, AsmType::Double(), kWasmF64, mutable_variable, info); |
| scanner_.Next(); |
| } else { |
| EXPECT_TOKEN(foreign_name_); |
| EXPECT_TOKEN('.'); |
| Vector<const char> 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 = zone()->New<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<double>::infinity())); |
| stdlib_uses_.Add(StandardMember::kInfinity); |
| } else if (Check(TOK(NaN))) { |
| DeclareGlobal(info, false, AsmType::Double(), kWasmF64, |
| WasmInitExpr(std::numeric_limits<double>::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<const char> 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<uint64_t>(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<uint32_t>(table_info->index + count), info->index); |
| } |
| ++count; |
| if (Check(',')) { |
| if (!Peek(']')) { |
| continue; |
| } |
| } |
| break; |
| } |
| EXPECT_TOKEN(']'); |
| if (table_info->kind == VarKind::kTable && |
| count != static_cast<uint64_t>(table_info->mask) + 1) { |
| FAIL("Function table size does not match uses"); |
| } |
| SkipSemicolon(); |
| } |
| |
| // 6.4 ValidateFunction |
| void AsmJsParser::ValidateFunction() { |
| // Remember position of the 'function' token as start position. |
| size_t function_start_position = scanner_.Position(); |
| |
| EXPECT_TOKEN(TOK(function)); |
| if (!scanner_.IsGlobal()) { |
| FAIL("Expected function name"); |
| } |
| |
| Vector<const char> 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( |
| function_start_position); |
| |
| CachedVector<AsmType*> 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<ValueType> locals(&cached_valuetype_vectors_); |
| ValidateFunctionLocals(params.size(), &locals); |
| |
| function_temp_locals_offset_ = static_cast<uint32_t>( |
| 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()); |
| } |
| |
| size_t function_end_position = scanner_.Position() + 1; |
| |
| 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); |
| |
| // Emit function end position as the last position for this function. |
| current_function_builder_->AddAsmWasmOffset(function_end_position, |
| function_end_position); |
| |
| if (current_function_builder_->GetPosition() > kV8MaxWasmFunctionSize) { |
| FAIL("Size of function body exceeds internal limit"); |
| } |
| // 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(); |
| std::fill(local_var_info_.begin(), local_var_info_.end(), VarInfo{}); |
| } |
| |
| // 6.4 ValidateFunction |
| void AsmJsParser::ValidateFunctionParams(ZoneVector<AsmType*>* 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<AsmJsScanner::token_t> 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<uint32_t>(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<uint32_t>(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<uint32_t>(params->size()); |
| params->push_back(AsmType::Float()); |
| } |
| SkipSemicolon(); |
| } |
| } |
| |
| // 6.4 ValidateFunction - locals |
| void AsmJsParser::ValidateFunctionLocals(size_t param_count, |
| ZoneVector<ValueType>* 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<uint32_t>(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<uint32_t>(param_count + locals->size()); |
| locals->push_back(kWasmI32); |
| int32_t value = -static_cast<int32_t>(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<uint32_t>(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(kExprGlobalGet, |
| 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<uint32_t>(param_count + locals->size()); |
| locals->push_back(kWasmF32); |
| if (negate) { |
| dvalue = -dvalue; |
| } |
| float fvalue = DoubleToFloat32(dvalue); |
| current_function_builder_->EmitF32Const(fvalue); |
| 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<uint32_t>(param_count + locals->size()); |
| locals->push_back(kWasmF32); |
| int32_t value = static_cast<int32_t>(uvalue); |
| if (negate) { |
| value = -value; |
| } |
| float fvalue = static_cast<float>(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<uint32_t>(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<uint32_t>(param_count + locals->size()); |
| locals->push_back(kWasmI32); |
| int32_t value = static_cast<int32_t>(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) { |
| BareBegin(BlockKind::kNamed, pending_label_); |
| current_function_builder_->EmitWithU8(kExprBlock, kVoidCode); |
| } |
| 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(')'); |
| BareBegin(BlockKind::kOther); |
| current_function_builder_->EmitWithU8(kExprIf, kVoidCode); |
| 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, kVoidCode); |
| 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, kVoidCode); |
| 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<int32_t> 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, kVoidCode); |
| } |
| 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<int32_t>(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<int32_t>(uvalue)); |
| return AsmType::FixNum(); |
| } else { |
| current_function_builder_->EmitI32Const(static_cast<int32_t>(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(kExprGlobalGet, 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"); |
| } |
| ret = value; |
| if (heap_type->IsA(AsmType::Float32Array()) && |
| value->IsA(AsmType::DoubleQ())) { |
| // Assignment to a float32 heap can be used to convert doubles. |
| current_function_builder_->Emit(kExprF32ConvertF64); |
| ret = AsmType::FloatQ(); |
| } |
| if (heap_type->IsA(AsmType::Float64Array()) && |
| value->IsA(AsmType::FloatQ())) { |
| // Assignment to a float64 heap can be used to convert floats. |
| current_function_builder_->Emit(kExprF64ConvertF32); |
| ret = AsmType::DoubleQ(); |
| } |
| #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(kExprGlobalSet, VarIndex(info)); |
| current_function_builder_->EmitWithU32V(kExprGlobalGet, 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)) { |
| if (uvalue == 0) { |
| current_function_builder_->EmitF64Const(-0.0); |
| ret = AsmType::Double(); |
| } else if (uvalue <= 0x80000000) { |
| // TODO(bradnelson): was supposed to be 0x7FFFFFFF, check errata. |
| current_function_builder_->EmitI32Const( |
| base::NegateWithWraparound(static_cast<int32_t>(uvalue))); |
| ret = AsmType::Signed(); |
| } else { |
| FAILn("Integer numeric literal out of range."); |
| } |
| } 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() { |
| AsmType* a; |
| 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<int32_t>(uvalue); |
| current_function_builder_->EmitI32Const(value); |
| current_function_builder_->Emit(kExprI32Mul); |
| return AsmType::Intish(); |
| } else { |
| scanner_.Rewind(); |
| RECURSEn(a = UnaryExpression()); |
| } |
| } else if (Check('-')) { |
| if (!PeekForZero() && CheckForUnsignedBelow(0x100000, &uvalue)) { |
| int32_t value = -static_cast<int32_t>(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(); |
| } |
| a = AsmType::Signed(); |
| } else { |
| scanner_.Rewind(); |
| RECURSEn(a = UnaryExpression()); |
| } |
| } else { |
| RECURSEn(a = UnaryExpression()); |
| } |
| for (;;) { |
| if (Check('*')) { |
| uint32_t uvalue; |
| if (Check('-')) { |
| if (!PeekForZero() && 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<int32_t>(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<int32_t>(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 = |
| AsmType::IsExactly(call_coercion_deferred_, 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, kI32Code); |
| 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, kI32Code); |
| return AsmType::Int(); |
| } else if (cons->IsA(AsmType::Double()) && alt->IsA(AsmType::Double())) { |
| current_function_builder_->FixupByte(fixup, kF64Code); |
| return AsmType::Double(); |
| } else if (cons->IsA(AsmType::Float()) && alt->IsA(AsmType::Float())) { |
| current_function_builder_->FixupByte(fixup, kF32Code); |
| 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<TemporaryVariableScope> tmp; |
| if (Check('[')) { |
| AsmType* index = nullptr; |
| RECURSEn(index = EqualityExpression()); |
| if (!index->IsA(AsmType::Intish())) { |
| FAILn("Expected intish index"); |
| } |
| 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<uint32_t>::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<AsmType*> param_types(&cached_asm_type_p_vectors_); |
| CachedVector<AsmType*> 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, kI32Code); |
| 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(asmjs): Clarify and explain where this limit is coming from, |
| // as it is not mandated by the spec directly. |
| if (offset > 0x7FFFFFFF || |
| static_cast<uint64_t>(offset) * static_cast<uint64_t>(size) > |
| 0x7FFFFFFF) { |
| FAIL("Heap access out of range"); |
| } |
| if (Check(']')) { |
| current_function_builder_->EmitI32Const( |
| static_cast<uint32_t>(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 = AssignmentExpression()); |
| 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<int32_t>* 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<int32_t>(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 |