|  | // 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 |