diff options
author | Michaël Zasso <targos@protonmail.com> | 2017-06-06 10:28:14 +0200 |
---|---|---|
committer | Michaël Zasso <targos@protonmail.com> | 2017-06-07 10:33:31 +0200 |
commit | 3dc8c3bed4cf3a77607edbb0b015e33f8b60fc09 (patch) | |
tree | 9dee56e142638b34f1eccbd0ad88c3bce5377c29 /deps/v8/src/asmjs | |
parent | 91a1bbe3055a660194ca4d403795aa0c03e9d056 (diff) | |
download | android-node-v8-3dc8c3bed4cf3a77607edbb0b015e33f8b60fc09.tar.gz android-node-v8-3dc8c3bed4cf3a77607edbb0b015e33f8b60fc09.tar.bz2 android-node-v8-3dc8c3bed4cf3a77607edbb0b015e33f8b60fc09.zip |
deps: update V8 to 5.9.211.32
PR-URL: https://github.com/nodejs/node/pull/13263
Reviewed-By: Gibson Fahnestock <gibfahn@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com>
Reviewed-By: Myles Borins <myles.borins@gmail.com>
Diffstat (limited to 'deps/v8/src/asmjs')
-rw-r--r-- | deps/v8/src/asmjs/asm-js.cc | 138 | ||||
-rw-r--r-- | deps/v8/src/asmjs/asm-names.h | 110 | ||||
-rw-r--r-- | deps/v8/src/asmjs/asm-parser.cc | 2449 | ||||
-rw-r--r-- | deps/v8/src/asmjs/asm-parser.h | 316 | ||||
-rw-r--r-- | deps/v8/src/asmjs/asm-scanner.cc | 431 | ||||
-rw-r--r-- | deps/v8/src/asmjs/asm-scanner.h | 165 | ||||
-rw-r--r-- | deps/v8/src/asmjs/asm-wasm-builder.cc | 12 |
7 files changed, 3574 insertions, 47 deletions
diff --git a/deps/v8/src/asmjs/asm-js.cc b/deps/v8/src/asmjs/asm-js.cc index 95d1e8a64f..fb46026a98 100644 --- a/deps/v8/src/asmjs/asm-js.cc +++ b/deps/v8/src/asmjs/asm-js.cc @@ -6,6 +6,7 @@ #include "src/api-natives.h" #include "src/api.h" +#include "src/asmjs/asm-parser.h" #include "src/asmjs/asm-typer.h" #include "src/asmjs/asm-wasm-builder.h" #include "src/assert-scope.h" @@ -164,47 +165,90 @@ bool IsStdlibMemberValid(i::Isolate* isolate, Handle<JSReceiver> stdlib, } // namespace MaybeHandle<FixedArray> AsmJs::CompileAsmViaWasm(CompilationInfo* info) { - ErrorThrower thrower(info->isolate(), "Asm.js -> WebAssembly conversion"); + wasm::ZoneBuffer* module = nullptr; + wasm::ZoneBuffer* asm_offsets = nullptr; + Handle<FixedArray> uses_array; + Handle<FixedArray> foreign_globals; base::ElapsedTimer asm_wasm_timer; asm_wasm_timer.Start(); wasm::AsmWasmBuilder builder(info); - Handle<FixedArray> foreign_globals; - auto asm_wasm_result = builder.Run(&foreign_globals); - if (!asm_wasm_result.success) { - DCHECK(!info->isolate()->has_pending_exception()); - if (!FLAG_suppress_asm_messages) { - MessageHandler::ReportMessage(info->isolate(), - builder.typer()->message_location(), - builder.typer()->error_message()); + if (FLAG_fast_validate_asm) { + wasm::AsmJsParser parser(info->isolate(), info->zone(), info->script(), + info->literal()->start_position(), + info->literal()->end_position()); + if (!parser.Run()) { + DCHECK(!info->isolate()->has_pending_exception()); + if (!FLAG_suppress_asm_messages) { + MessageLocation location(info->script(), parser.failure_location(), + parser.failure_location()); + Handle<String> message = + info->isolate() + ->factory() + ->NewStringFromUtf8(CStrVector(parser.failure_message())) + .ToHandleChecked(); + Handle<JSMessageObject> error_message = + MessageHandler::MakeMessageObject( + info->isolate(), MessageTemplate::kAsmJsInvalid, &location, + message, Handle<JSArray>::null()); + error_message->set_error_level(v8::Isolate::kMessageWarning); + MessageHandler::ReportMessage(info->isolate(), &location, + error_message); + } + return MaybeHandle<FixedArray>(); + } + Zone* zone = info->zone(); + module = new (zone) wasm::ZoneBuffer(zone); + parser.module_builder()->WriteTo(*module); + asm_offsets = new (zone) wasm::ZoneBuffer(zone); + parser.module_builder()->WriteAsmJsOffsetTable(*asm_offsets); + // TODO(bradnelson): Remove foreign_globals plumbing (as we don't need it + // for the new parser). + foreign_globals = info->isolate()->factory()->NewFixedArray(0); + uses_array = info->isolate()->factory()->NewFixedArray( + static_cast<int>(parser.stdlib_uses()->size())); + int count = 0; + for (auto i : *parser.stdlib_uses()) { + uses_array->set(count++, Smi::FromInt(i)); + } + } else { + auto asm_wasm_result = builder.Run(&foreign_globals); + if (!asm_wasm_result.success) { + DCHECK(!info->isolate()->has_pending_exception()); + if (!FLAG_suppress_asm_messages) { + MessageHandler::ReportMessage(info->isolate(), + builder.typer()->message_location(), + builder.typer()->error_message()); + } + return MaybeHandle<FixedArray>(); + } + module = asm_wasm_result.module_bytes; + asm_offsets = asm_wasm_result.asm_offset_table; + wasm::AsmTyper::StdlibSet uses = builder.typer()->StdlibUses(); + uses_array = info->isolate()->factory()->NewFixedArray( + static_cast<int>(uses.size())); + int count = 0; + for (auto i : uses) { + uses_array->set(count++, Smi::FromInt(i)); } - return MaybeHandle<FixedArray>(); } - double asm_wasm_time = asm_wasm_timer.Elapsed().InMillisecondsF(); - wasm::ZoneBuffer* module = asm_wasm_result.module_bytes; - wasm::ZoneBuffer* asm_offsets = asm_wasm_result.asm_offset_table; + double asm_wasm_time = asm_wasm_timer.Elapsed().InMillisecondsF(); Vector<const byte> asm_offsets_vec(asm_offsets->begin(), static_cast<int>(asm_offsets->size())); base::ElapsedTimer compile_timer; compile_timer.Start(); + ErrorThrower thrower(info->isolate(), "Asm.js -> WebAssembly conversion"); MaybeHandle<JSObject> compiled = SyncCompileTranslatedAsmJs( info->isolate(), &thrower, wasm::ModuleWireBytes(module->begin(), module->end()), info->script(), asm_offsets_vec); DCHECK(!compiled.is_null()); + DCHECK(!thrower.error()); double compile_time = compile_timer.Elapsed().InMillisecondsF(); DCHECK_GE(module->end(), module->begin()); uintptr_t wasm_size = module->end() - module->begin(); - wasm::AsmTyper::StdlibSet uses = builder.typer()->StdlibUses(); - Handle<FixedArray> uses_array = - info->isolate()->factory()->NewFixedArray(static_cast<int>(uses.size())); - int count = 0; - for (auto i : uses) { - uses_array->set(count++, Smi::FromInt(i)); - } - Handle<FixedArray> result = info->isolate()->factory()->NewFixedArray(kWasmDataEntryCount); result->set(kWasmDataCompiledModule, *compiled.ToHandleChecked()); @@ -264,8 +308,6 @@ MaybeHandle<Object> AsmJs::InstantiateAsmWasm(i::Isolate* isolate, i::Handle<i::FixedArray> foreign_globals( i::FixedArray::cast(wasm_data->get(kWasmDataForeignGlobals))); - ErrorThrower thrower(isolate, "Asm.js -> WebAssembly instantiation"); - // Create the ffi object for foreign functions {"": foreign}. Handle<JSObject> ffi_object; if (!foreign.is_null()) { @@ -276,40 +318,46 @@ MaybeHandle<Object> AsmJs::InstantiateAsmWasm(i::Isolate* isolate, foreign, NONE); } + ErrorThrower thrower(isolate, "Asm.js -> WebAssembly instantiation"); i::MaybeHandle<i::Object> maybe_module_object = i::wasm::SyncInstantiate(isolate, &thrower, module, ffi_object, memory); if (maybe_module_object.is_null()) { + thrower.Reify(); // Ensure exceptions do not propagate. return MaybeHandle<Object>(); } + DCHECK(!thrower.error()); i::Handle<i::Object> module_object = maybe_module_object.ToHandleChecked(); - i::Handle<i::Name> init_name(isolate->factory()->InternalizeUtf8String( - wasm::AsmWasmBuilder::foreign_init_name)); - i::Handle<i::Object> init = - i::Object::GetProperty(module_object, init_name).ToHandleChecked(); + if (!FLAG_fast_validate_asm) { + i::Handle<i::Name> init_name(isolate->factory()->InternalizeUtf8String( + wasm::AsmWasmBuilder::foreign_init_name)); + i::Handle<i::Object> init = + i::Object::GetProperty(module_object, init_name).ToHandleChecked(); - i::Handle<i::Object> undefined(isolate->heap()->undefined_value(), isolate); - i::Handle<i::Object>* foreign_args_array = - new i::Handle<i::Object>[foreign_globals->length()]; - for (int j = 0; j < foreign_globals->length(); j++) { - if (!foreign.is_null()) { - i::MaybeHandle<i::Name> name = i::Object::ToName( - isolate, i::Handle<i::Object>(foreign_globals->get(j), isolate)); - if (!name.is_null()) { - i::MaybeHandle<i::Object> val = - i::Object::GetProperty(foreign, name.ToHandleChecked()); - if (!val.is_null()) { - foreign_args_array[j] = val.ToHandleChecked(); - continue; + i::Handle<i::Object> undefined(isolate->heap()->undefined_value(), isolate); + i::Handle<i::Object>* foreign_args_array = + new i::Handle<i::Object>[foreign_globals->length()]; + for (int j = 0; j < foreign_globals->length(); j++) { + if (!foreign.is_null()) { + i::MaybeHandle<i::Name> name = i::Object::ToName( + isolate, i::Handle<i::Object>(foreign_globals->get(j), isolate)); + if (!name.is_null()) { + i::MaybeHandle<i::Object> val = + i::Object::GetProperty(foreign, name.ToHandleChecked()); + if (!val.is_null()) { + foreign_args_array[j] = val.ToHandleChecked(); + continue; + } } } + foreign_args_array[j] = undefined; } - foreign_args_array[j] = undefined; + i::MaybeHandle<i::Object> retval = + i::Execution::Call(isolate, init, undefined, foreign_globals->length(), + foreign_args_array); + delete[] foreign_args_array; + DCHECK(!retval.is_null()); } - i::MaybeHandle<i::Object> retval = i::Execution::Call( - isolate, init, undefined, foreign_globals->length(), foreign_args_array); - delete[] foreign_args_array; - DCHECK(!retval.is_null()); i::Handle<i::Name> single_function_name( isolate->factory()->InternalizeUtf8String( diff --git a/deps/v8/src/asmjs/asm-names.h b/deps/v8/src/asmjs/asm-names.h new file mode 100644 index 0000000000..1cc151dc4c --- /dev/null +++ b/deps/v8/src/asmjs/asm-names.h @@ -0,0 +1,110 @@ +// 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. + +#ifndef V8_ASMJS_ASM_NAMES_H_ +#define V8_ASMJS_ASM_NAMES_H_ + +#define STDLIB_MATH_VALUE_LIST(V) \ + V(E) \ + V(LN10) \ + V(LN2) \ + V(LOG2E) \ + V(LOG10E) \ + V(PI) \ + V(SQRT1_2) \ + V(SQRT2) + +// V(stdlib.Math.<name>, Name, wasm-opcode, asm-js-type) +#define STDLIB_MATH_FUNCTION_MONOMORPHIC_LIST(V) \ + V(acos, Acos, kExprF64Acos, dq2d) \ + V(asin, Asin, kExprF64Asin, dq2d) \ + V(atan, Atan, kExprF64Atan, dq2d) \ + V(cos, Cos, kExprF64Cos, dq2d) \ + V(sin, Sin, kExprF64Sin, dq2d) \ + V(tan, Tan, kExprF64Tan, dq2d) \ + V(exp, Exp, kExprF64Exp, dq2d) \ + V(log, Log, kExprF64Log, dq2d) \ + V(atan2, Atan2, kExprF64Atan2, dqdq2d) \ + V(pow, Pow, kExprF64Pow, dqdq2d) \ + V(imul, Imul, kExprI32Mul, ii2s) \ + V(clz32, Clz32, kExprI32Clz, i2s) + +// V(stdlib.Math.<name>, Name, unused, asm-js-type) +#define STDLIB_MATH_FUNCTION_CEIL_LIKE_LIST(V) \ + V(ceil, Ceil, x, ceil_like) \ + V(floor, Floor, x, ceil_like) \ + V(sqrt, Sqrt, x, ceil_like) + +// V(stdlib.Math.<name>, Name, unused, asm-js-type) +#define STDLIB_MATH_FUNCTION_LIST(V) \ + V(min, Min, x, minmax) \ + V(max, Max, x, minmax) \ + V(abs, Abs, x, abs) \ + V(fround, Fround, x, fround) \ + STDLIB_MATH_FUNCTION_MONOMORPHIC_LIST(V) \ + STDLIB_MATH_FUNCTION_CEIL_LIKE_LIST(V) + +// V(stdlib.<name>, wasm-load-type, wasm-store-type, wasm-type) +#define STDLIB_ARRAY_TYPE_LIST(V) \ + V(Int8Array, Mem8S, Mem8, I32) \ + V(Uint8Array, Mem8U, Mem8, I32) \ + V(Int16Array, Mem16S, Mem16, I32) \ + V(Uint16Array, Mem16U, Mem16, I32) \ + V(Int32Array, Mem, Mem, I32) \ + V(Uint32Array, Mem, Mem, I32) \ + V(Float32Array, Mem, Mem, F32) \ + V(Float64Array, Mem, Mem, F64) + +#define STDLIB_OTHER_LIST(V) \ + V(Infinity) \ + V(NaN) \ + V(Math) + +// clang-format off (for return) +#define KEYWORD_NAME_LIST(V) \ + V(arguments) \ + V(break) \ + V(case) \ + V(const) \ + V(continue) \ + V(default) \ + V(do) \ + V(else) \ + V(eval) \ + V(for) \ + V(function) \ + V(if) \ + V(new) \ + V(return ) \ + V(switch) \ + V(var) \ + V(while) +// clang-format on + +// V(token-string, token-name) +#define LONG_SYMBOL_NAME_LIST(V) \ + V("<=", LE) \ + V(">=", GE) \ + V("==", EQ) \ + V("!=", NE) \ + V("<<", SHL) \ + V(">>", SAR) \ + V(">>>", SHR) \ + V("'use asm'", UseAsm) + +// clang-format off +#define SIMPLE_SINGLE_TOKEN_LIST(V) \ + V('+') V('-') V('*') V('%') V('~') V('^') V('&') V('|') V('(') V(')') \ + V('[') V(']') V('{') V('}') V(':') V(';') V(',') V('?') +// clang-format on + +// V(name, value, string-name) +#define SPECIAL_TOKEN_LIST(V) \ + V(kUninitialized, 0, "{uninitalized}") \ + V(kEndOfInput, -1, "{end of input}") \ + V(kParseError, -2, "{parse error}") \ + V(kUnsigned, -3, "{unsigned value}") \ + V(kDouble, -4, "{double value}") + +#endif diff --git a/deps/v8/src/asmjs/asm-parser.cc b/deps/v8/src/asmjs/asm-parser.cc new file mode 100644 index 0000000000..c18f7d1bf2 --- /dev/null +++ b/deps/v8/src/asmjs/asm-parser.cc @@ -0,0 +1,2449 @@ +// 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" + +// Required to get M_E etc. for MSVC. +// References from STDLIB_MATH_VALUE_LIST in asm-names.h +#if defined(_WIN32) +#define _USE_MATH_DEFINES +#endif +#include <math.h> +#include <string.h> + +#include <algorithm> + +#include "src/asmjs/asm-types.h" +#include "src/objects-inl.h" +#include "src/objects.h" +#include "src/parsing/scanner-character-streams.h" +#include "src/wasm/wasm-macro-gen.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_ = scanner_.GetPosition(); \ + 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_ = scanner_.GetPosition(); \ + return ret; +#endif + +#define FAIL(msg) FAIL_AND_RETURN(, msg) +#define FAILn(msg) FAIL_AND_RETURN(nullptr, msg) +#define FAILf(msg) FAIL_AND_RETURN(false, 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 EXPECT_TOKENf(token) EXPECT_TOKEN_OR_RETURN(false, 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 RECURSEf(call) RECURSE_OR_RETURN(false, call) + +#define TOK(name) AsmJsScanner::kToken_##name + +AsmJsParser::AsmJsParser(Isolate* isolate, Zone* zone, Handle<Script> script, + int start, int end) + : zone_(zone), + module_builder_(new (zone) WasmModuleBuilder(zone)), + return_type_(nullptr), + stack_limit_(isolate->stack_guard()->real_climit()), + global_var_info_(zone), + local_var_info_(zone), + failed_(false), + failure_location_(start), + stdlib_name_(kTokenNone), + foreign_name_(kTokenNone), + heap_name_(kTokenNone), + inside_heap_assignment_(false), + heap_access_type_(nullptr), + block_stack_(zone), + pending_label_(0), + global_imports_(zone) { + InitializeStdlibTypes(); + Handle<String> source(String::cast(script->source()), isolate); + std::unique_ptr<Utf16CharacterStream> stream( + ScannerStream::For(source, start, end)); + scanner_.SetStream(std::move(stream)); +} + +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* fq = AsmType::FloatQ(); + stdlib_fq2f_ = AsmType::Function(zone(), f); + stdlib_fq2f_->AsFunctionType()->AddArgument(fq); + + auto* s = AsmType::Signed(); + auto* s2s = AsmType::Function(zone(), s); + s2s->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); + + auto* minmax_d = AsmType::MinMaxType(zone(), d, d); + // *VIOLATION* The float variant is not part of the spec, but firefox accepts + // it. + auto* minmax_f = AsmType::MinMaxType(zone(), f, f); + auto* minmax_i = AsmType::MinMaxType(zone(), s, i); + stdlib_minmax_ = AsmType::OverloadedFunction(zone()); + stdlib_minmax_->AsOverloadedFunctionType()->AddOverload(minmax_i); + stdlib_minmax_->AsOverloadedFunctionType()->AddOverload(minmax_f); + stdlib_minmax_->AsOverloadedFunctionType()->AddOverload(minmax_d); + + stdlib_abs_ = AsmType::OverloadedFunction(zone()); + stdlib_abs_->AsOverloadedFunctionType()->AddOverload(s2s); + stdlib_abs_->AsOverloadedFunctionType()->AddOverload(stdlib_dq2d_); + stdlib_abs_->AsOverloadedFunctionType()->AddOverload(stdlib_fq2f_); + + stdlib_ceil_like_ = AsmType::OverloadedFunction(zone()); + stdlib_ceil_like_->AsOverloadedFunctionType()->AddOverload(stdlib_dq2d_); + stdlib_ceil_like_->AsOverloadedFunctionType()->AddOverload(stdlib_fq2f_); + + stdlib_fround_ = AsmType::FroundType(zone()); +} + +FunctionSig* AsmJsParser::ConvertSignature( + AsmType* return_type, const std::vector<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 { + return nullptr; + } + } + 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 { + return 0; + } + } + 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_; +}; + +AsmJsParser::VarInfo::VarInfo() + : type(AsmType::None()), + function_builder(nullptr), + import(nullptr), + mask(-1), + index(0), + kind(VarKind::kUnused), + mutable_variable(true), + function_defined(false) {} + +wasm::AsmJsParser::VarInfo* AsmJsParser::GetVarInfo( + AsmJsScanner::token_t token) { + if (AsmJsScanner::IsGlobal(token)) { + size_t old = global_var_info_.size(); + size_t index = AsmJsScanner::GlobalIndex(token); + size_t sz = std::max(old, index + 1); + if (sz != old) { + global_var_info_.resize(sz); + } + return &global_var_info_[index]; + } else if (AsmJsScanner::IsLocal(token)) { + size_t old = local_var_info_.size(); + size_t index = AsmJsScanner::LocalIndex(token); + size_t sz = std::max(old, index + 1); + if (sz != old) { + local_var_info_.resize(sz); + } + return &local_var_info_[index]; + } + UNREACHABLE(); + return nullptr; +} + +uint32_t AsmJsParser::VarIndex(VarInfo* info) { + if (info->import != nullptr) { + return info->index; + } else { + return info->index + static_cast<uint32_t>(global_imports_.size()); + } +} + +void AsmJsParser::AddGlobalImport(std::string name, AsmType* type, + ValueType vtype, bool mutable_variable, + VarInfo* info) { + // TODO(bradnelson): Refactor memory management here. + // AsmModuleBuilder should really own import names. + char* name_data = zone()->NewArray<char>(name.size()); + memcpy(name_data, name.data(), name.size()); + if (mutable_variable) { + // Allocate a separate variable for the import. + DeclareGlobal(info, true, type, vtype); + // Record the need to initialize the global from the import. + global_imports_.push_back({name_data, name.size(), 0, info->index, true}); + } else { + // Just use the import directly. + global_imports_.push_back({name_data, name.size(), 0, info->index, false}); + } + GlobalImport& gi = global_imports_.back(); + // TODO(bradnelson): Reuse parse buffer memory / make wasm-module-builder + // managed the memory for the import name (currently have to keep our + // own memory for it). + gi.import_index = module_builder_->AddGlobalImport( + name_data, static_cast<int>(name.size()), vtype); + if (!mutable_variable) { + info->DeclareGlobalImport(type, gi.import_index); + } +} + +void AsmJsParser::VarInfo::DeclareGlobalImport(AsmType* type, uint32_t index) { + kind = VarKind::kGlobal; + this->type = type; + this->index = index; + mutable_variable = false; +} + +void AsmJsParser::VarInfo::DeclareStdlibFunc(VarKind kind, AsmType* type) { + this->kind = kind; + this->type = type; + index = 0; // unused + mutable_variable = false; +} + +void AsmJsParser::DeclareGlobal(VarInfo* info, bool mutable_variable, + AsmType* type, ValueType vtype, + const WasmInitExpr& init) { + info->kind = VarKind::kGlobal; + info->type = type; + info->index = module_builder_->AddGlobal(vtype, false, true, init); + info->mutable_variable = mutable_variable; +} + +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; +} + +void AsmJsParser::SkipSemicolon() { + if (Check(';')) { + // Had a semicolon. + } else if (!Peek('}') && !scanner_.IsPrecededByNewline()) { + FAIL("Expected ;"); + } +} + +void AsmJsParser::Begin(AsmJsScanner::token_t label) { + BareBegin(BlockKind::kRegular, label); + current_function_builder_->EmitWithU8(kExprBlock, kLocalVoid); +} + +void AsmJsParser::Loop(AsmJsScanner::token_t label) { + BareBegin(BlockKind::kLoop, label); + current_function_builder_->EmitWithU8(kExprLoop, kLocalVoid); +} + +void AsmJsParser::End() { + BareEnd(); + current_function_builder_->Emit(kExprEnd); +} + +void AsmJsParser::BareBegin(BlockKind kind, AsmJsScanner::token_t label) { + BlockInfo info; + info.kind = kind; + info.label = label; + block_stack_.push_back(info); +} + +void AsmJsParser::BareEnd() { + DCHECK(block_stack_.size() > 0); + block_stack_.pop_back(); +} + +int AsmJsParser::FindContinueLabelDepth(AsmJsScanner::token_t label) { + int count = 0; + for (auto it = block_stack_.rbegin(); it != block_stack_.rend(); + ++it, ++count) { + if (it->kind == BlockKind::kLoop && + (label == kTokenNone || it->label == label)) { + return count; + } + } + return -1; +} + +int AsmJsParser::FindBreakLabelDepth(AsmJsScanner::token_t label) { + int count = 0; + for (auto it = block_stack_.rbegin(); it != block_stack_.rend(); + ++it, ++count) { + if (it->kind == BlockKind::kRegular && + (label == kTokenNone || it->label == label)) { + return count; + } + } + return -1; +} + +// 6.1 ValidateModule +void AsmJsParser::ValidateModule() { + RECURSE(ValidateModuleParameters()); + EXPECT_TOKEN('{'); + EXPECT_TOKEN(TOK(UseAsm)); + SkipSemicolon(); + RECURSE(ValidateModuleVars()); + while (Peek(TOK(function))) { + RECURSE(ValidateFunction()); + } + while (Peek(TOK(var))) { + RECURSE(ValidateFunctionTable()); + } + RECURSE(ValidateExport()); + + // Check that all functions were eventually defined. + for (auto& info : global_var_info_) { + if (info.kind == VarKind::kFunction && !info.function_defined) { + FAIL("Undefined function"); + } + if (info.kind == VarKind::kTable && !info.function_defined) { + FAIL("Undefined function table"); + } + } + + // Add start function to init things. + WasmFunctionBuilder* start = module_builder_->AddFunction(); + module_builder_->MarkStartFunction(start); + for (auto global_import : global_imports_) { + if (global_import.needs_init) { + start->EmitWithVarInt(kExprGetGlobal, global_import.import_index); + start->EmitWithVarInt(kExprSetGlobal, + static_cast<uint32_t>(global_import.global_index + + global_imports_.size())); + } + } + 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; + uint64_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"); + } + 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 (ValidateModuleVarImport(info, mutable_variable)) { + // Handled inside. + } 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; + uint64_t uvalue = 0; + if (CheckForDouble(&dvalue)) { + if (negate) { + dvalue = -dvalue; + } + DeclareGlobal(info, mutable_variable, AsmType::Float(), kWasmF32, + WasmInitExpr(static_cast<float>(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 +bool AsmJsParser::ValidateModuleVarImport(VarInfo* info, + bool mutable_variable) { + if (Check('+')) { + EXPECT_TOKENf(foreign_name_); + EXPECT_TOKENf('.'); + AddGlobalImport(scanner_.GetIdentifierString(), AsmType::Double(), kWasmF64, + mutable_variable, info); + scanner_.Next(); + return true; + } else if (Check(foreign_name_)) { + EXPECT_TOKENf('.'); + std::string import_name = scanner_.GetIdentifierString(); + scanner_.Next(); + if (Check('|')) { + if (!CheckForZero()) { + FAILf("Expected |0 type annotation for foreign integer import"); + } + AddGlobalImport(import_name, AsmType::Int(), kWasmI32, mutable_variable, + info); + return true; + } + info->kind = VarKind::kImportedFunction; + function_import_info_.resize(function_import_info_.size() + 1); + info->import = &function_import_info_.back(); + // TODO(bradnelson): Refactor memory management here. + // AsmModuleBuilder should really own import names. + info->import->function_name = zone()->NewArray<char>(import_name.size()); + memcpy(info->import->function_name, import_name.data(), import_name.size()); + info->import->function_name_size = import_name.size(); + return true; + } + return 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): \ + info->DeclareStdlibFunc(VarKind::kSpecial, AsmType::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) \ + case TOK(name): \ + DeclareGlobal(info, false, AsmType::Double(), kWasmF64, \ + WasmInitExpr(M_##name)); \ + stdlib_uses_.insert(AsmTyper::kMath##name); \ + break; + STDLIB_MATH_VALUE_LIST(V) +#undef V +#define V(name, Name, op, sig) \ + case TOK(name): \ + info->DeclareStdlibFunc(VarKind::kMath##Name, stdlib_##sig##_); \ + stdlib_uses_.insert(AsmTyper::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_.insert(AsmTyper::kInfinity); + } else if (Check(TOK(NaN))) { + DeclareGlobal(info, false, AsmType::Double(), kWasmF64, + WasmInitExpr(std::numeric_limits<double>::quiet_NaN())); + stdlib_uses_.insert(AsmTyper::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 (;;) { + std::string name = scanner_.GetIdentifierString(); + 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"); + } + info->function_builder->ExportAs( + {name.c_str(), static_cast<int>(name.size())}); + 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"); + } + const char* single_function_name = "__single_function__"; + info->function_builder->ExportAs(CStrVector(single_function_name)); + } +} + +// 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) { + DCHECK_GE(table_info->mask, 0); + 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() { + EXPECT_TOKEN(TOK(function)); + if (!scanner_.IsGlobal()) { + FAIL("Expected function name"); + } + + std::string function_name_raw = scanner_.GetIdentifierString(); + 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(); + } 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; + // TODO(bradnelson): Cleanup memory management here. + // WasmModuleBuilder should own these. + char* function_name_chr = zone()->NewArray<char>(function_name_raw.size()); + memcpy(function_name_chr, function_name_raw.data(), function_name_raw.size()); + function_info->function_builder->SetName( + {function_name_chr, static_cast<int>(function_name_raw.size())}); + current_function_builder_ = function_info->function_builder; + return_type_ = nullptr; + + // Record start of the function, used as position for the stack check. + int start_position = static_cast<int>(scanner_.Position()); + current_function_builder_->SetAsmFunctionStartPosition(start_position); + + std::vector<AsmType*> params; + ValidateFunctionParams(¶ms); + std::vector<ValueType> locals; + 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; + + while (!failed_ && !Peek('}')) { + RECURSE(ValidateStatement()); + } + EXPECT_TOKEN('}'); + + if (return_type_ == nullptr) { + return_type_ = AsmType::Void(); + } + + // 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); + if (sig == nullptr) { + FAIL("Invalid function signature in declaration"); + } + 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); + } + + // End function + current_function_builder_->Emit(kExprEnd); + + // Record (or validate) function type. + AsmType* function_type = AsmType::Function(zone(), return_type_); + for (auto t : params) { + function_type->AsFunctionType()->AddArgument(t); + } + function_info = GetVarInfo(function_name); + if (function_info->type->IsA(AsmType::None())) { + DCHECK(function_info->kind == VarKind::kFunction); + function_info->type = function_type; + } else if (!function_type->IsA(function_info->type)) { + // TODO(bradnelson): Should IsExactly be used here? + FAIL("Function definition doesn't match use"); + } + + scanner_.ResetLocals(); + local_var_info_.clear(); +} + +// 6.4 ValidateFunction +void AsmJsParser::ValidateFunctionParams(std::vector<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('('); + std::vector<AsmJsScanner::token_t> function_parameters; + 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 (!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, std::vector<ValueType>* locals) { + // 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; + uint64_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); + byte code[] = {WASM_F64(-dvalue)}; + current_function_builder_->EmitCode(code, sizeof(code)); + 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_->EmitWithVarInt(kExprGetGlobal, + VarIndex(sinfo)); + current_function_builder_->EmitSetLocal(info->index); + } else if (sinfo->type->IsA(stdlib_fround_)) { + EXPECT_TOKEN('('); + bool negate = false; + if (Check('-')) { + negate = true; + } + double dvalue = 0.0; + if (CheckForDouble(&dvalue)) { + info->kind = VarKind::kLocal; + info->type = AsmType::Float(); + info->index = static_cast<uint32_t>(param_count + locals->size()); + locals->push_back(kWasmF32); + if (negate) { + dvalue = -dvalue; + } + byte code[] = {WASM_F32(dvalue)}; + current_function_builder_->EmitCode(code, sizeof(code)); + 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; + } + double fvalue = static_cast<double>(value); + byte code[] = {WASM_F32(fvalue)}; + current_function_builder_->EmitCode(code, sizeof(code)); + 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); + byte code[] = {WASM_F64(dvalue)}; + current_function_builder_->EmitCode(code, sizeof(code)); + 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(); + } +} + +// ValidateStatement +void AsmJsParser::ValidateStatement() { + call_coercion_ = nullptr; + if (Peek('{')) { + RECURSE(Block()); + } else if (Peek(';')) { + RECURSE(EmptyStatement()); + } else if (Peek(TOK(if))) { + RECURSE(IfStatement()); + // clang-format off + } else if (Peek(TOK(return))) { + // clang-format on + RECURSE(ReturnStatement()); + } else if (IterationStatement()) { + // Handled in IterationStatement. + } else if (Peek(TOK(break))) { + RECURSE(BreakStatement()); + } else if (Peek(TOK(continue))) { + RECURSE(ContinueStatement()); + } else if (Peek(TOK(switch))) { + RECURSE(SwitchStatement()); + } else { + RECURSE(ExpressionStatement()); + } +} + +// 6.5.1 Block +void AsmJsParser::Block() { + bool can_break_to_block = pending_label_ != 0; + if (can_break_to_block) { + Begin(pending_label_); + } + pending_label_ = 0; + EXPECT_TOKEN('{'); + while (!failed_ && !Peek('}')) { + RECURSE(ValidateStatement()); + } + EXPECT_TOKEN('}'); + if (can_break_to_block) { + End(); + } +} + +// 6.5.2 ExpressionStatement +void AsmJsParser::ExpressionStatement() { + if (scanner_.IsGlobal() || scanner_.IsLocal()) { + // NOTE: Both global or local identifiers can also be used as labels. + scanner_.Next(); + if (Peek(':')) { + scanner_.Rewind(); + RECURSE(LabelledStatement()); + return; + } + scanner_.Rewind(); + } + AsmType* ret; + RECURSE(ret = ValidateExpression()); + if (!ret->IsA(AsmType::Void())) { + current_function_builder_->Emit(kExprDrop); + } + SkipSemicolon(); +} + +// 6.5.3 EmptyStatement +void AsmJsParser::EmptyStatement() { EXPECT_TOKEN(';'); } + +// 6.5.4 IfStatement +void AsmJsParser::IfStatement() { + EXPECT_TOKEN(TOK(if)); + EXPECT_TOKEN('('); + RECURSE(Expression(AsmType::Int())); + EXPECT_TOKEN(')'); + current_function_builder_->EmitWithU8(kExprIf, kLocalVoid); + BareBegin(); + RECURSE(ValidateStatement()); + if (Check(TOK(else))) { + current_function_builder_->Emit(kExprElse); + RECURSE(ValidateStatement()); + } + current_function_builder_->Emit(kExprEnd); + BareEnd(); +} + +// 6.5.5 ReturnStatement +void AsmJsParser::ReturnStatement() { + // clang-format off + EXPECT_TOKEN(TOK(return )); + // clang-format on + if (!Peek(';') && !Peek('}')) { + // TODO(bradnelson): See if this can be factored out. + AsmType* ret; + RECURSE(ret = Expression(return_type_)); + if (ret->IsA(AsmType::Double())) { + return_type_ = AsmType::Double(); + } else if (ret->IsA(AsmType::Float())) { + return_type_ = AsmType::Float(); + } else if (ret->IsA(AsmType::Signed())) { + return_type_ = AsmType::Signed(); + } else { + FAIL("Invalid return type"); + } + } else { + return_type_ = AsmType::Void(); + } + current_function_builder_->Emit(kExprReturn); + SkipSemicolon(); +} + +// 6.5.6 IterationStatement +bool AsmJsParser::IterationStatement() { + if (Peek(TOK(while))) { + WhileStatement(); + } else if (Peek(TOK(do))) { + DoStatement(); + } else if (Peek(TOK(for))) { + ForStatement(); + } else { + return false; + } + return true; +} + +// 6.5.6 IterationStatement - while +void AsmJsParser::WhileStatement() { + // a: block { + Begin(pending_label_); + // b: loop { + Loop(pending_label_); + pending_label_ = 0; + EXPECT_TOKEN(TOK(while)); + EXPECT_TOKEN('('); + RECURSE(Expression(AsmType::Int())); + EXPECT_TOKEN(')'); + // if (!CONDITION) break a; + current_function_builder_->Emit(kExprI32Eqz); + current_function_builder_->EmitWithU8(kExprBrIf, 1); + // BODY + RECURSE(ValidateStatement()); + // continue b; + current_function_builder_->EmitWithU8(kExprBr, 0); + End(); + // } + // } + End(); +} + +// 6.5.6 IterationStatement - do +void AsmJsParser::DoStatement() { + // a: block { + Begin(pending_label_); + // b: loop { + Loop(); + // c: block { // but treated like loop so continue works + BareBegin(BlockKind::kLoop, pending_label_); + current_function_builder_->EmitWithU8(kExprBlock, kLocalVoid); + pending_label_ = 0; + EXPECT_TOKEN(TOK(do)); + // BODY + RECURSE(ValidateStatement()); + EXPECT_TOKEN(TOK(while)); + End(); + // } + 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(); + // } + End(); + SkipSemicolon(); +} + +// 6.5.6 IterationStatement - for +void AsmJsParser::ForStatement() { + EXPECT_TOKEN(TOK(for)); + EXPECT_TOKEN('('); + if (!Peek(';')) { + Expression(nullptr); + } + EXPECT_TOKEN(';'); + // a: block { + Begin(pending_label_); + // b: loop { + Loop(pending_label_); + pending_label_ = 0; + if (!Peek(';')) { + // if (CONDITION) break a; + RECURSE(Expression(AsmType::Int())); + current_function_builder_->Emit(kExprI32Eqz); + current_function_builder_->EmitWithU8(kExprBrIf, 1); + } + EXPECT_TOKEN(';'); + // Stash away INCREMENT + size_t increment_position = current_function_builder_->GetPosition(); + if (!Peek(')')) { + RECURSE(Expression(nullptr)); + } + std::vector<byte> increment_code; + current_function_builder_->StashCode(&increment_code, increment_position); + EXPECT_TOKEN(')'); + // BODY + RECURSE(ValidateStatement()); + // INCREMENT + current_function_builder_->EmitCode( + increment_code.data(), static_cast<uint32_t>(increment_code.size())); + current_function_builder_->EmitWithU8(kExprBr, 0); + // } + End(); + // } + 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_->EmitVarInt(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_->Emit(kExprBr); + current_function_builder_->EmitVarInt(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. + std::vector<int32_t> cases; + GatherCases(&cases); // Skips { implicitly. + size_t count = cases.size() + 1; + for (size_t i = 0; i < count; ++i) { + BareBegin(BlockKind::kOther); + current_function_builder_->EmitWithU8(kExprBlock, kLocalVoid); + } + int table_pos = 0; + for (auto c : cases) { + current_function_builder_->EmitGetLocal(tmp); + current_function_builder_->EmitI32Const(c); + current_function_builder_->Emit(kExprI32Eq); + current_function_builder_->EmitWithVarInt(kExprBrIf, table_pos++); + } + current_function_builder_->EmitWithVarInt(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; + } + uint64_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); + if (negate) { + 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; + uint64_t uvalue = 0; + if (CheckForDouble(&dvalue)) { + byte code[] = {WASM_F64(dvalue)}; + current_function_builder_->EmitCode(code, sizeof(code)); + return AsmType::Double(); + } else if (CheckForUnsigned(&uvalue)) { + if (uvalue <= 0x7fffffff) { + current_function_builder_->EmitI32Const(static_cast<int32_t>(uvalue)); + return AsmType::FixNum(); + } else if (uvalue <= 0xffffffff) { + current_function_builder_->EmitI32Const(static_cast<int32_t>(uvalue)); + return AsmType::Unsigned(); + } else { + FAILn("Integer numeric literal out of range."); + } + } 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_->EmitWithVarInt(kExprGetGlobal, VarIndex(info)); + return info->type; + } + UNREACHABLE(); + return nullptr; +} + +// 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; + ValidateHeapAccess(); + 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; + AsmType* heap_type = heap_access_type_; + EXPECT_TOKENn('='); + AsmType* value; + RECURSEn(value = AssignmentExpression()); + if (!value->IsA(ret)) { + FAILn("Illegal type stored to heap view"); + } + if (heap_type->IsA(AsmType::Float32Array()) && + value->IsA(AsmType::Double())) { + // Assignment to a float32 heap can be used to convert doubles. + current_function_builder_->Emit(kExprF32ConvertF64); + } + ret = value; +#define V(array_type, wasmload, wasmstore, type) \ + if (heap_type->IsA(AsmType::array_type())) { \ + current_function_builder_->Emit(kExpr##type##AsmjsStore##wasmstore); \ + return ret; \ + } + STDLIB_ARRAY_TYPE_LIST(V) +#undef V + } + } else if (scanner_.IsLocal() || scanner_.IsGlobal()) { + bool is_local = scanner_.IsLocal(); + VarInfo* info = GetVarInfo(scanner_.Token()); + USE(is_local); + ret = info->type; + scanner_.Next(); + if (Check('=')) { + // NOTE: Before this point, this might have been VarKind::kUnused even in + // valid code, as it might be a label. + if (info->kind == VarKind::kUnused) { + FAILn("Undeclared assignment target"); + } + 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_->EmitWithVarUint(kExprSetGlobal, + VarIndex(info)); + current_function_builder_->EmitWithVarUint(kExprGetGlobal, + VarIndex(info)); + } else { + UNREACHABLE(); + } + return ret; + } + scanner_.Rewind(); + RECURSEn(ret = ConditionalExpression()); + } else { + RECURSEn(ret = ConditionalExpression()); + } + return ret; +} + +// 6.8.7 UnaryExpression +AsmType* AsmJsParser::UnaryExpression() { + AsmType* ret; + if (Check('-')) { + uint64_t uvalue; + if (CheckForUnsigned(&uvalue)) { + // TODO(bradnelson): was supposed to be 0x7fffffff, check errata. + if (uvalue <= 0x80000000) { + current_function_builder_->EmitI32Const(-static_cast<int32_t>(uvalue)); + } else { + FAILn("Integer numeric literal out of range."); + } + ret = AsmType::Signed(); + } else { + RECURSEn(ret = UnaryExpression()); + if (ret->IsA(AsmType::Int())) { + TemporaryVariableScope tmp(this); + current_function_builder_->EmitSetLocal(tmp.get()); + current_function_builder_->EmitI32Const(0); + current_function_builder_->EmitGetLocal(tmp.get()); + current_function_builder_->Emit(kExprI32Sub); + ret = AsmType::Intish(); + } else if (ret->IsA(AsmType::DoubleQ())) { + current_function_builder_->Emit(kExprF64Neg); + ret = AsmType::Double(); + } else if (ret->IsA(AsmType::FloatQ())) { + current_function_builder_->Emit(kExprF32Neg); + ret = AsmType::Floatish(); + } else { + FAILn("expected int/double?/float?"); + } + } + } else if (Peek('+')) { + call_coercion_ = AsmType::Double(); + call_coercion_position_ = scanner_.Position(); + scanner_.Next(); // Done late for correct position. + RECURSEn(ret = UnaryExpression()); + // TODO(bradnelson): Generalize. + if (ret->IsA(AsmType::Signed())) { + current_function_builder_->Emit(kExprF64SConvertI32); + ret = AsmType::Double(); + } else if (ret->IsA(AsmType::Unsigned())) { + current_function_builder_->Emit(kExprF64UConvertI32); + ret = AsmType::Double(); + } else if (ret->IsA(AsmType::DoubleQ())) { + ret = AsmType::Double(); + } else if (ret->IsA(AsmType::FloatQ())) { + current_function_builder_->Emit(kExprF64ConvertF32); + ret = AsmType::Double(); + } else { + FAILn("expected signed/unsigned/double?/float?"); + } + } else if (Check('!')) { + RECURSEn(ret = UnaryExpression()); + if (!ret->IsA(AsmType::Int())) { + FAILn("expected int"); + } + current_function_builder_->Emit(kExprI32Eqz); + } else if (Check('~')) { + if (Check('~')) { + RECURSEn(ret = UnaryExpression()); + if (ret->IsA(AsmType::Double())) { + current_function_builder_->Emit(kExprI32AsmjsSConvertF64); + } else if (ret->IsA(AsmType::FloatQ())) { + current_function_builder_->Emit(kExprI32AsmjsSConvertF32); + } else { + FAILn("expected double or float?"); + } + ret = AsmType::Signed(); + } else { + RECURSEn(ret = UnaryExpression()); + if (!ret->IsA(AsmType::Intish())) { + FAILn("operator ~ expects intish"); + } + current_function_builder_->EmitI32Const(0xffffffff); + current_function_builder_->Emit(kExprI32Xor); + ret = AsmType::Signed(); + } + } else { + RECURSEn(ret = CallExpression()); + } + return ret; +} + +// 6.8.8 MultaplicativeExpression +AsmType* AsmJsParser::MultiplicativeExpression() { + uint64_t uvalue; + if (CheckForUnsignedBelow(0x100000, &uvalue)) { + if (Check('*')) { + AsmType* a; + RECURSEn(a = UnaryExpression()); + if (!a->IsA(AsmType::Int())) { + FAILn("Expected int"); + } + current_function_builder_->EmitI32Const(static_cast<int32_t>(uvalue)); + current_function_builder_->Emit(kExprI32Mul); + return AsmType::Intish(); + } + scanner_.Rewind(); + } else if (Check('-')) { + if (CheckForUnsignedBelow(0x100000, &uvalue)) { + current_function_builder_->EmitI32Const(-static_cast<int32_t>(uvalue)); + if (Check('*')) { + AsmType* a; + RECURSEn(a = UnaryExpression()); + if (!a->IsA(AsmType::Int())) { + FAILn("Expected int"); + } + current_function_builder_->Emit(kExprI32Mul); + return AsmType::Intish(); + } + return AsmType::Signed(); + } + scanner_.Rewind(); + } + AsmType* a; + RECURSEn(a = UnaryExpression()); + for (;;) { + if (Check('*')) { + uint64_t uvalue; + if (Check('-')) { + if (CheckForUnsigned(&uvalue)) { + if (uvalue >= 0x100000) { + FAILn("Constant multiple out of range"); + } + if (!a->IsA(AsmType::Int())) { + FAILn("Integer multiply of expects int"); + } + current_function_builder_->EmitI32Const(static_cast<int32_t>(uvalue)); + 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"); + } + current_function_builder_->EmitI32Const(static_cast<int32_t>(uvalue)); + 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 = MultiplicativeExpression()); + 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 = MultiplicativeExpression()); + 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()); + for (;;) { + switch (scanner_.Token()) { +// TODO(bradnelson): Implement backtracking to avoid emitting code +// for the x >>> 0 case (similar to what's there for |0). +#define HANDLE_CASE(op, opcode, name, result) \ + case TOK(op): { \ + EXPECT_TOKENn(TOK(op)); \ + 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(SAR, I32ShrS, ">>", 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; + RECURSEn(a = BitwiseXORExpression()); + while (Check('|')) { + // TODO(bradnelson): Make it prettier. + AsmType* b = nullptr; + bool zero = false; + int old_pos; + size_t old_code; + if (CheckForZero()) { + old_pos = scanner_.GetPosition(); + old_code = current_function_builder_->GetPosition(); + scanner_.Rewind(); + zero = true; + } + RECURSEn(b = BitwiseXORExpression()); + // Handle |0 specially. + if (zero && old_pos == scanner_.GetPosition()) { + current_function_builder_->StashCode(nullptr, old_code); + a = AsmType::Signed(); + continue; + } + if (a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish())) { + current_function_builder_->Emit(kExprI32Ior); + a = AsmType::Signed(); + } else { + FAILn("Expected intish for operator |."); + } + } + return a; +} + +// 6.8.16 ConditionalExpression +AsmType* AsmJsParser::ConditionalExpression() { + AsmType* test = nullptr; + RECURSEn(test = BitwiseORExpression()); + if (Check('?')) { + if (!test->IsA(AsmType::Int())) { + FAILn("Expected int in condition of ternary operator."); + } + current_function_builder_->EmitWithU8(kExprIf, kLocalI32); + size_t fixup = current_function_builder_->GetPosition() - + 1; // Assumes encoding knowledge. + AsmType* cons = nullptr; + RECURSEn(cons = AssignmentExpression()); + current_function_builder_->Emit(kExprElse); + EXPECT_TOKENn(':'); + AsmType* alt = nullptr; + RECURSEn(alt = AssignmentExpression()); + current_function_builder_->Emit(kExprEnd); + if (cons->IsA(AsmType::Int()) && alt->IsA(AsmType::Int())) { + current_function_builder_->FixupByte(fixup, kLocalI32); + return AsmType::Int(); + } else if (cons->IsA(AsmType::Double()) && alt->IsA(AsmType::Double())) { + current_function_builder_->FixupByte(fixup, kLocalF64); + return AsmType::Double(); + } else if (cons->IsA(AsmType::Float()) && alt->IsA(AsmType::Float())) { + current_function_builder_->FixupByte(fixup, kLocalF32); + return AsmType::Float(); + } else { + FAILn("Type mismatch in ternary operator."); + } + } else { + return test; + } +} + +// 6.8.17 ParenthesiedExpression +AsmType* AsmJsParser::ParenthesizedExpression() { + call_coercion_ = nullptr; + AsmType* ret; + EXPECT_TOKENn('('); + RECURSEn(ret = Expression(nullptr)); + EXPECT_TOKENn(')'); + return ret; +} + +// 6.9 ValidateCall +AsmType* AsmJsParser::ValidateCall() { + AsmType* return_type = call_coercion_; + call_coercion_ = nullptr; + int call_pos = static_cast<int>(scanner_.Position()); + int to_number_pos = static_cast<int>(call_coercion_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. + // TODO(mstarzinger): Consider using Chromiums base::Optional instead. + std::unique_ptr<TemporaryVariableScope> tmp; + if (Check('[')) { + RECURSEn(EqualityExpression()); + EXPECT_TOKENn('&'); + uint64_t mask = 0; + if (!CheckForUnsigned(&mask)) { + FAILn("Expected mask literal"); + } + if (mask > 0x7fffffff) { + FAILn("Expected power of 2 mask"); + } + if (!base::bits::IsPowerOfTwo32(static_cast<uint32_t>(1 + mask))) { + FAILn("Expected power of 2 mask"); + } + current_function_builder_->EmitI32Const(static_cast<uint32_t>(mask)); + current_function_builder_->Emit(kExprI32And); + EXPECT_TOKENn(']'); + VarInfo* function_info = GetVarInfo(function_name); + if (function_info->kind == VarKind::kUnused) { + function_info->kind = VarKind::kTable; + function_info->mask = static_cast<int32_t>(mask); + function_info->index = module_builder_->AllocateIndirectFunctions( + static_cast<uint32_t>(mask + 1)); + } else { + if (function_info->kind != VarKind::kTable) { + FAILn("Expected call table"); + } + if (function_info->mask != static_cast<int32_t>(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.reset(new TemporaryVariableScope(this)); + current_function_builder_->EmitSetLocal(tmp.get()->get()); + // The position of function table calls is after the table lookup. + call_pos = static_cast<int>(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(); + } else { + if (function_info->kind != VarKind::kFunction && + function_info->kind < VarKind::kImportedFunction) { + FAILn("Expected function as call target"); + } + } + } + + // Parse argument list and gather types. + std::vector<AsmType*> param_types; + ZoneVector<AsmType*> param_specific_types(zone()); + 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 { + std::string a = t->Name(); + FAILn("Bad function argument type"); + } + if (!Peek(')')) { + EXPECT_TOKENn(','); + } + } + EXPECT_TOKENn(')'); + + // We potentially use lookahead in order to determine the return type in case + // it is not yet clear from the call context. + // TODO(mstarzinger,6183): Several issues with look-ahead are known. Fix! + // TODO(bradnelson): clarify how this binds, and why only float? + if (Peek('|') && + (return_type == nullptr || return_type->IsA(AsmType::Float()))) { + to_number_pos = static_cast<int>(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); + if (sig == nullptr) { + FAILn("Invalid function signature"); + } + 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. + // Reload {VarInfo} as table might have grown. + VarInfo* function_info = GetVarInfo(function_name); + 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(function_info->import != nullptr); + // TODO(bradnelson): Factor out. + uint32_t cache_index = function_info->import->cache.FindOrInsert(sig); + uint32_t index; + if (cache_index >= function_info->import->cache_index.size()) { + index = module_builder_->AddImport( + function_info->import->function_name, + static_cast<uint32_t>(function_info->import->function_name_size), + sig); + function_info->import->cache_index.push_back(index); + } else { + index = function_info->import->cache_index[cache_index]; + } + current_function_builder_->AddAsmWasmOffset(call_pos, to_number_pos); + current_function_builder_->Emit(kExprCallFunction); + current_function_builder_->EmitVarUint(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 (return_type->IsA(AsmType::Void()) && + callable->CanBeInvokedWith(AsmType::Float(), + param_specific_types)) { + return_type = AsmType::Float(); + } else if (return_type->IsA(AsmType::Void()) && + callable->CanBeInvokedWith(AsmType::Double(), + param_specific_types)) { + return_type = AsmType::Double(); + } else if (return_type->IsA(AsmType::Void()) && + callable->CanBeInvokedWith(AsmType::Signed(), + param_specific_types)) { + return_type = AsmType::Signed(); + } 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::Int())) { + TemporaryVariableScope tmp_x(this); + TemporaryVariableScope tmp_y(this); + for (size_t i = 1; i < param_specific_types.size(); ++i) { + current_function_builder_->EmitSetLocal(tmp_x.get()); + current_function_builder_->EmitTeeLocal(tmp_y.get()); + current_function_builder_->EmitGetLocal(tmp_x.get()); + if (function_info->kind == VarKind::kMathMin) { + current_function_builder_->Emit(kExprI32GeS); + } else { + current_function_builder_->Emit(kExprI32LeS); + } + current_function_builder_->EmitWithU8(kExprIf, kLocalI32); + current_function_builder_->EmitGetLocal(tmp_x.get()); + current_function_builder_->Emit(kExprElse); + current_function_builder_->EmitGetLocal(tmp_y.get()); + current_function_builder_->Emit(kExprEnd); + } + } else { + UNREACHABLE(); + } + break; + + case VarKind::kMathAbs: + if (param_specific_types[0]->IsA(AsmType::Signed())) { + TemporaryVariableScope tmp(this); + current_function_builder_->EmitTeeLocal(tmp.get()); + current_function_builder_->Emit(kExprI32Clz); + current_function_builder_->EmitWithU8(kExprIf, kLocalI32); + current_function_builder_->EmitGetLocal(tmp.get()); + current_function_builder_->Emit(kExprElse); + current_function_builder_->EmitI32Const(0); + current_function_builder_->EmitGetLocal(tmp.get()); + current_function_builder_->Emit(kExprI32Sub); + current_function_builder_->Emit(kExprEnd); + } 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: + if (param_specific_types[0]->IsA(AsmType::DoubleQ())) { + current_function_builder_->Emit(kExprF32ConvertF64); + } else { + DCHECK(param_specific_types[0]->IsA(AsmType::FloatQ())); + } + break; + + 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()->get()); + current_function_builder_->AddAsmWasmOffset(call_pos, to_number_pos); + current_function_builder_->Emit(kExprCallIndirect); + current_function_builder_->EmitVarUint(signature_index); + current_function_builder_->EmitVarUint(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('['); + uint64_t offset; + if (CheckForUnsigned(&offset)) { + // TODO(bradnelson): Check more things. + if (offset > 0x7fffffff || offset * 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 = AdditiveExpression()); + EXPECT_TOKEN(TOK(SAR)); + uint64_t shift; + if (!CheckForUnsigned(&shift)) { + FAIL("Expected shift of word size"); + } + if (shift > 3) { + FAIL("Expected valid heap access shift"); + } + if ((1 << shift) != size) { + FAIL("Expected heap access shift to match heap view"); + } + // Mask bottom bits to match asm.js behavior. + current_function_builder_->EmitI32Const(~(size - 1)); + current_function_builder_->Emit(kExprI32And); + } + if (!index_type->IsA(AsmType::Intish())) { + FAIL("Expected intish index"); + } + EXPECT_TOKEN(']'); + // NOTE: This has to happen here to work recursively. + heap_access_type_ = info->type; +} + +// 6.11 ValidateFloatCoercion +void AsmJsParser::ValidateFloatCoercion() { + if (!scanner_.IsGlobal() || + !GetVarInfo(scanner_.Token())->type->IsA(stdlib_fround_)) { + FAIL("Expected fround"); + } + scanner_.Next(); + EXPECT_TOKEN('('); + call_coercion_ = AsmType::Float(); + // NOTE: The coercion position to float is not observable from JavaScript, + // because imported functions are not allowed to have float return type. + call_coercion_position_ = scanner_.Position(); + AsmType* ret; + RECURSE(ret = ValidateExpression()); + if (ret->IsA(AsmType::Floatish())) { + // Do nothing, as already a float. + } else if (ret->IsA(AsmType::DoubleQ())) { + current_function_builder_->Emit(kExprF32ConvertF64); + } else if (ret->IsA(AsmType::Signed())) { + current_function_builder_->Emit(kExprF32SConvertI32); + } else if (ret->IsA(AsmType::Unsigned())) { + current_function_builder_->Emit(kExprF32UConvertI32); + } else { + FAIL("Illegal conversion to float"); + } + EXPECT_TOKEN(')'); +} + +void AsmJsParser::GatherCases(std::vector<int32_t>* cases) { + int start = scanner_.GetPosition(); + int depth = 0; + for (;;) { + if (Peek('{')) { + ++depth; + } else if (Peek('}')) { + --depth; + if (depth <= 0) { + break; + } + } else if (depth == 1 && Peek(TOK(case))) { + scanner_.Next(); + int32_t value; + uint64_t uvalue; + if (Check('-')) { + if (!CheckForUnsigned(&uvalue)) { + break; + } + value = -static_cast<int32_t>(uvalue); + } else { + if (!CheckForUnsigned(&uvalue)) { + break; + } + value = static_cast<int32_t>(uvalue); + } + cases->push_back(value); + } else if (Peek(AsmJsScanner::kEndOfInput)) { + break; + } + scanner_.Next(); + } + scanner_.Seek(start); +} + +} // namespace wasm +} // namespace internal +} // namespace v8 diff --git a/deps/v8/src/asmjs/asm-parser.h b/deps/v8/src/asmjs/asm-parser.h new file mode 100644 index 0000000000..c7f6a66352 --- /dev/null +++ b/deps/v8/src/asmjs/asm-parser.h @@ -0,0 +1,316 @@ +// 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. + +#ifndef V8_ASMJS_ASM_PARSER_H_ +#define V8_ASMJS_ASM_PARSER_H_ + +#include <list> +#include <string> +#include <vector> + +#include "src/asmjs/asm-scanner.h" +#include "src/asmjs/asm-typer.h" +#include "src/asmjs/asm-types.h" +#include "src/wasm/signature-map.h" +#include "src/wasm/wasm-module-builder.h" +#include "src/zone/zone-containers.h" + +namespace v8 { +namespace internal { +namespace wasm { + +// A custom parser + validator + wasm converter for asm.js: +// http://asmjs.org/spec/latest/ +// This parser intentionally avoids the portion of JavaScript parsing +// that are not required to determine if code is valid asm.js code. +// * It is mostly one pass. +// * It bails out on unexpected input. +// * It assumes strict ordering insofar as permitted by asm.js validation rules. +// * It relies on a custom scanner that provides de-duped identifiers in two +// scopes (local + module wide). +class AsmJsParser { + public: + explicit AsmJsParser(Isolate* isolate, Zone* zone, Handle<Script> script, + int start, int end); + bool Run(); + const char* failure_message() const { return failure_message_.c_str(); } + int failure_location() const { return failure_location_; } + WasmModuleBuilder* module_builder() { return module_builder_; } + const AsmTyper::StdlibSet* stdlib_uses() const { return &stdlib_uses_; } + + private: + // clang-format off + enum class VarKind { + kUnused, + kLocal, + kGlobal, + kSpecial, + kFunction, + kTable, + kImportedFunction, +#define V(_unused0, Name, _unused1, _unused2) kMath##Name, + STDLIB_MATH_FUNCTION_LIST(V) +#undef V +#define V(Name) kMath##Name, + STDLIB_MATH_VALUE_LIST(V) +#undef V + }; + // clang-format on + + struct FunctionImportInfo { + char* function_name; + size_t function_name_size; + SignatureMap cache; + std::vector<uint32_t> cache_index; + }; + + struct VarInfo { + AsmType* type; + WasmFunctionBuilder* function_builder; + FunctionImportInfo* import; + int32_t mask; + uint32_t index; + VarKind kind; + bool mutable_variable; + bool function_defined; + + VarInfo(); + void DeclareGlobalImport(AsmType* type, uint32_t index); + void DeclareStdlibFunc(VarKind kind, AsmType* type); + }; + + struct GlobalImport { + char* import_name; + size_t import_name_size; + uint32_t import_index; + uint32_t global_index; + bool needs_init; + }; + + enum class BlockKind { kRegular, kLoop, kOther }; + + struct BlockInfo { + BlockKind kind; + AsmJsScanner::token_t label; + }; + + // Helper class to make {TempVariable} safe for nesting. + class TemporaryVariableScope; + + Zone* zone_; + AsmJsScanner scanner_; + WasmModuleBuilder* module_builder_; + WasmFunctionBuilder* current_function_builder_; + AsmType* return_type_; + std::uintptr_t stack_limit_; + AsmTyper::StdlibSet stdlib_uses_; + std::list<FunctionImportInfo> function_import_info_; + ZoneVector<VarInfo> global_var_info_; + ZoneVector<VarInfo> local_var_info_; + + int function_temp_locals_offset_; + int function_temp_locals_used_; + int function_temp_locals_depth_; + + // Error Handling related + bool failed_; + std::string failure_message_; + int failure_location_; + + // Module Related. + AsmJsScanner::token_t stdlib_name_; + AsmJsScanner::token_t foreign_name_; + AsmJsScanner::token_t heap_name_; + + static const AsmJsScanner::token_t kTokenNone = 0; + + // Track if parsing a heap assignment. + bool inside_heap_assignment_; + AsmType* heap_access_type_; + + ZoneVector<BlockInfo> block_stack_; + + // Types used for stdlib function and their set up. + AsmType* stdlib_dq2d_; + AsmType* stdlib_dqdq2d_; + AsmType* stdlib_fq2f_; + AsmType* stdlib_i2s_; + AsmType* stdlib_ii2s_; + AsmType* stdlib_minmax_; + AsmType* stdlib_abs_; + AsmType* stdlib_ceil_like_; + AsmType* stdlib_fround_; + + // When making calls, the return type is needed to lookup signatures. + // For +callsite(..) or fround(callsite(..)) use this value to pass + // along the coercion. + AsmType* call_coercion_; + + // The source position associated with the above {call_coercion}. + size_t call_coercion_position_; + + // Used to track the last label we've seen so it can be matched to later + // statements it's attached to. + AsmJsScanner::token_t pending_label_; + + // Global imports. + // NOTE: Holds the strings referenced in wasm-module-builder for imports. + ZoneLinkedList<GlobalImport> global_imports_; + + Zone* zone() { return zone_; } + + inline bool Peek(AsmJsScanner::token_t token) { + return scanner_.Token() == token; + } + + inline bool Check(AsmJsScanner::token_t token) { + if (scanner_.Token() == token) { + scanner_.Next(); + return true; + } else { + return false; + } + } + + inline bool CheckForZero() { + if (scanner_.IsUnsigned() && scanner_.AsUnsigned() == 0) { + scanner_.Next(); + return true; + } else { + return false; + } + } + + inline bool CheckForDouble(double* value) { + if (scanner_.IsDouble()) { + *value = scanner_.AsDouble(); + scanner_.Next(); + return true; + } else { + return false; + } + } + + inline bool CheckForUnsigned(uint64_t* value) { + if (scanner_.IsUnsigned()) { + *value = scanner_.AsUnsigned(); + scanner_.Next(); + return true; + } else { + return false; + } + } + + inline bool CheckForUnsignedBelow(uint64_t limit, uint64_t* value) { + if (scanner_.IsUnsigned() && scanner_.AsUnsigned() < limit) { + *value = scanner_.AsUnsigned(); + scanner_.Next(); + return true; + } else { + return false; + } + } + + inline AsmJsScanner::token_t Consume() { + AsmJsScanner::token_t ret = scanner_.Token(); + scanner_.Next(); + return ret; + } + + void SkipSemicolon(); + + VarInfo* GetVarInfo(AsmJsScanner::token_t token); + uint32_t VarIndex(VarInfo* info); + void DeclareGlobal(VarInfo* info, bool mutable_variable, AsmType* type, + ValueType vtype, + const WasmInitExpr& init = WasmInitExpr()); + + // Allocates a temporary local variable. The given {index} is absolute within + // the function body, consider using {TemporaryVariableScope} when nesting. + uint32_t TempVariable(int index); + + void AddGlobalImport(std::string name, AsmType* type, ValueType vtype, + bool mutable_variable, VarInfo* info); + + // Use to set up block stack layers (including synthetic ones for if-else). + // Begin/Loop/End below are implemented with these plus code generation. + void BareBegin(BlockKind kind = BlockKind::kOther, + AsmJsScanner::token_t label = 0); + void BareEnd(); + int FindContinueLabelDepth(AsmJsScanner::token_t label); + int FindBreakLabelDepth(AsmJsScanner::token_t label); + + // Use to set up actual wasm blocks/loops. + void Begin(AsmJsScanner::token_t label = 0); + void Loop(AsmJsScanner::token_t label = 0); + void End(); + + void InitializeStdlibTypes(); + + FunctionSig* ConvertSignature(AsmType* return_type, + const std::vector<AsmType*>& params); + + // 6.1 ValidateModule + void ValidateModule(); + void ValidateModuleParameters(); + void ValidateModuleVars(); + void ValidateModuleVar(bool mutable_variable); + bool ValidateModuleVarImport(VarInfo* info, bool mutable_variable); + void ValidateModuleVarStdlib(VarInfo* info); + void ValidateModuleVarNewStdlib(VarInfo* info); + void ValidateModuleVarFromGlobal(VarInfo* info, bool mutable_variable); + + void ValidateExport(); // 6.2 ValidateExport + void ValidateFunctionTable(); // 6.3 ValidateFunctionTable + void ValidateFunction(); // 6.4 ValidateFunction + void ValidateFunctionParams(std::vector<AsmType*>* params); + void ValidateFunctionLocals(size_t param_count, + std::vector<ValueType>* locals); + void ValidateStatement(); // ValidateStatement + void Block(); // 6.5.1 Block + void ExpressionStatement(); // 6.5.2 ExpressionStatement + void EmptyStatement(); // 6.5.3 EmptyStatement + void IfStatement(); // 6.5.4 IfStatement + void ReturnStatement(); // 6.5.5 ReturnStatement + bool IterationStatement(); // 6.5.6 IterationStatement + void WhileStatement(); // 6.5.6 IterationStatement - while + void DoStatement(); // 6.5.6 IterationStatement - do + void ForStatement(); // 6.5.6 IterationStatement - for + void BreakStatement(); // 6.5.7 BreakStatement + void ContinueStatement(); // 6.5.8 ContinueStatement + void LabelledStatement(); // 6.5.9 LabelledStatement + void SwitchStatement(); // 6.5.10 SwitchStatement + void ValidateCase(); // 6.6. ValidateCase + void ValidateDefault(); // 6.7 ValidateDefault + AsmType* ValidateExpression(); // 6.8 ValidateExpression + AsmType* Expression(AsmType* expect); // 6.8.1 Expression + AsmType* NumericLiteral(); // 6.8.2 NumericLiteral + AsmType* Identifier(); // 6.8.3 Identifier + AsmType* CallExpression(); // 6.8.4 CallExpression + AsmType* MemberExpression(); // 6.8.5 MemberExpression + AsmType* AssignmentExpression(); // 6.8.6 AssignmentExpression + AsmType* UnaryExpression(); // 6.8.7 UnaryExpression + AsmType* MultiplicativeExpression(); // 6.8.8 MultaplicativeExpression + AsmType* AdditiveExpression(); // 6.8.9 AdditiveExpression + AsmType* ShiftExpression(); // 6.8.10 ShiftExpression + AsmType* RelationalExpression(); // 6.8.11 RelationalExpression + AsmType* EqualityExpression(); // 6.8.12 EqualityExpression + AsmType* BitwiseANDExpression(); // 6.8.13 BitwiseANDExpression + AsmType* BitwiseXORExpression(); // 6.8.14 BitwiseXORExpression + AsmType* BitwiseORExpression(); // 6.8.15 BitwiseORExpression + AsmType* ConditionalExpression(); // 6.8.16 ConditionalExpression + AsmType* ParenthesizedExpression(); // 6.8.17 ParenthesiedExpression + AsmType* ValidateCall(); // 6.9 ValidateCall + bool PeekCall(); // 6.9 ValidateCall - helper + void ValidateHeapAccess(); // 6.10 ValidateHeapAccess + void ValidateFloatCoercion(); // 6.11 ValidateFloatCoercion + + void GatherCases(std::vector<int32_t>* cases); +}; + +} // namespace wasm +} // namespace internal +} // namespace v8 + +#endif // V8_ASMJS_ASM_PARSER_H_ diff --git a/deps/v8/src/asmjs/asm-scanner.cc b/deps/v8/src/asmjs/asm-scanner.cc new file mode 100644 index 0000000000..5f272652f4 --- /dev/null +++ b/deps/v8/src/asmjs/asm-scanner.cc @@ -0,0 +1,431 @@ +// 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-scanner.h" + +#include "src/conversions.h" +#include "src/flags.h" +#include "src/parsing/scanner.h" +#include "src/unicode-cache.h" + +namespace v8 { +namespace internal { + +namespace { +// Cap number of identifiers to ensure we can assign both global and +// local ones a token id in the range of an int32_t. +static const int kMaxIdentifierCount = 0xf000000; +}; + +AsmJsScanner::AsmJsScanner() + : token_(kUninitialized), + preceding_token_(kUninitialized), + next_token_(kUninitialized), + position_(0), + preceding_position_(0), + next_position_(0), + rewind_(false), + in_local_scope_(false), + global_count_(0), + double_value_(0.0), + unsigned_value_(0), + preceded_by_newline_(false) { +#define V(name, _junk1, _junk2, _junk3) property_names_[#name] = kToken_##name; + STDLIB_MATH_FUNCTION_LIST(V) + STDLIB_ARRAY_TYPE_LIST(V) +#undef V +#define V(name) property_names_[#name] = kToken_##name; + STDLIB_MATH_VALUE_LIST(V) + STDLIB_OTHER_LIST(V) +#undef V +#define V(name) global_names_[#name] = kToken_##name; + KEYWORD_NAME_LIST(V) +#undef V +} + +void AsmJsScanner::SetStream(std::unique_ptr<Utf16CharacterStream> stream) { + stream_ = std::move(stream); + Next(); +} + +void AsmJsScanner::Next() { + if (rewind_) { + preceding_token_ = token_; + preceding_position_ = position_; + token_ = next_token_; + position_ = next_position_; + next_token_ = kUninitialized; + next_position_ = 0; + rewind_ = false; + return; + } + + if (token_ == kEndOfInput || token_ == kParseError) { + return; + } + +#if DEBUG + if (FLAG_trace_asm_scanner) { + if (Token() == kDouble) { + PrintF("%lf ", AsDouble()); + } else if (Token() == kUnsigned) { + PrintF("%" PRIu64 " ", AsUnsigned()); + } else { + std::string name = Name(Token()); + PrintF("%s ", name.c_str()); + } + } +#endif + + preceded_by_newline_ = false; + preceding_token_ = token_; + preceding_position_ = position_; + + for (;;) { + position_ = stream_->pos(); + uc32 ch = stream_->Advance(); + switch (ch) { + case ' ': + case '\t': + case '\r': + // Ignore whitespace. + break; + + case '\n': + // Track when we've passed a newline for optional semicolon support, + // but keep scanning. + preceded_by_newline_ = true; + break; + + case kEndOfInput: + token_ = kEndOfInput; + return; + + case '\'': + case '"': + ConsumeString(ch); + return; + + case '/': + ch = stream_->Advance(); + if (ch == '/') { + ConsumeCPPComment(); + } else if (ch == '*') { + if (!ConsumeCComment()) { + token_ = kParseError; + return; + } + } else { + stream_->Back(); + token_ = '/'; + return; + } + // Breaks out of switch, but loops again (i.e. the case when we parsed + // a comment, but need to continue to look for the next token). + break; + + case '<': + case '>': + case '=': + case '!': + ConsumeCompareOrShift(ch); + return; + +#define V(single_char_token) case single_char_token: + SIMPLE_SINGLE_TOKEN_LIST(V) +#undef V + // Use fixed token IDs for ASCII. + token_ = ch; + return; + + default: + if (IsIdentifierStart(ch)) { + ConsumeIdentifier(ch); + } else if (IsNumberStart(ch)) { + ConsumeNumber(ch); + } else { + // TODO(bradnelson): Support unicode (probably via UnicodeCache). + token_ = kParseError; + } + return; + } + } +} + +void AsmJsScanner::Rewind() { + DCHECK_NE(kUninitialized, preceding_token_); + // TODO(bradnelson): Currently rewinding needs to leave in place the + // preceding newline state (in case a |0 ends a line). + // This is weird and stateful, fix me. + DCHECK(!rewind_); + next_token_ = token_; + next_position_ = position_; + token_ = preceding_token_; + position_ = preceding_position_; + preceding_token_ = kUninitialized; + preceding_position_ = 0; + rewind_ = true; + identifier_string_.clear(); +} + +void AsmJsScanner::ResetLocals() { local_names_.clear(); } + +#if DEBUG +// Only used for debugging. +std::string AsmJsScanner::Name(token_t token) const { + if (token >= 32 && token < 127) { + return std::string(1, static_cast<char>(token)); + } + for (auto& i : local_names_) { + if (i.second == token) { + return i.first; + } + } + for (auto& i : global_names_) { + if (i.second == token) { + return i.first; + } + } + for (auto& i : property_names_) { + if (i.second == token) { + return i.first; + } + } + switch (token) { +#define V(rawname, name) \ + case kToken_##name: \ + return rawname; + LONG_SYMBOL_NAME_LIST(V) +#undef V +#define V(name, value, string_name) \ + case name: \ + return string_name; + SPECIAL_TOKEN_LIST(V) + default: + break; + } + UNREACHABLE(); + return "{unreachable}"; +} +#endif + +int AsmJsScanner::GetPosition() const { + DCHECK(!rewind_); + return static_cast<int>(stream_->pos()); +} + +void AsmJsScanner::Seek(int pos) { + stream_->Seek(pos); + preceding_token_ = kUninitialized; + token_ = kUninitialized; + next_token_ = kUninitialized; + preceding_position_ = 0; + position_ = 0; + next_position_ = 0; + rewind_ = false; + Next(); +} + +void AsmJsScanner::ConsumeIdentifier(uc32 ch) { + // Consume characters while still part of the identifier. + identifier_string_.clear(); + while (IsIdentifierPart(ch)) { + identifier_string_ += ch; + ch = stream_->Advance(); + } + // Go back one for next time. + stream_->Back(); + + // Decode what the identifier means. + if (preceding_token_ == '.') { + auto i = property_names_.find(identifier_string_); + if (i != property_names_.end()) { + token_ = i->second; + return; + } + } else { + { + auto i = local_names_.find(identifier_string_); + if (i != local_names_.end()) { + token_ = i->second; + return; + } + } + if (!in_local_scope_) { + auto i = global_names_.find(identifier_string_); + if (i != global_names_.end()) { + token_ = i->second; + return; + } + } + } + if (preceding_token_ == '.') { + CHECK(global_count_ < kMaxIdentifierCount); + token_ = kGlobalsStart + global_count_++; + property_names_[identifier_string_] = token_; + } else if (in_local_scope_) { + CHECK(local_names_.size() < kMaxIdentifierCount); + token_ = kLocalsStart - static_cast<token_t>(local_names_.size()); + local_names_[identifier_string_] = token_; + } else { + CHECK(global_count_ < kMaxIdentifierCount); + token_ = kGlobalsStart + global_count_++; + global_names_[identifier_string_] = token_; + } +} + +void AsmJsScanner::ConsumeNumber(uc32 ch) { + std::string number; + number = ch; + bool has_dot = ch == '.'; + for (;;) { + ch = stream_->Advance(); + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || + (ch >= 'A' && ch <= 'F') || ch == '.' || ch == 'b' || ch == 'o' || + ch == 'x' || + ((ch == '-' || ch == '+') && (number[number.size() - 1] == 'e' || + number[number.size() - 1] == 'E'))) { + // TODO(bradnelson): Test weird cases ending in -. + if (ch == '.') { + has_dot = true; + } + number.push_back(ch); + } else { + break; + } + } + stream_->Back(); + // Special case the most common number. + if (number.size() == 1 && number[0] == '0') { + unsigned_value_ = 0; + token_ = kUnsigned; + return; + } + // Pick out dot. + if (number.size() == 1 && number[0] == '.') { + token_ = '.'; + return; + } + // Decode numbers. + UnicodeCache cache; + double_value_ = StringToDouble( + &cache, + Vector<uint8_t>( + const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(number.data())), + static_cast<int>(number.size())), + ALLOW_HEX | ALLOW_OCTAL | ALLOW_BINARY | ALLOW_IMPLICIT_OCTAL); + if (std::isnan(double_value_)) { + // Check if string to number conversion didn't consume all the characters. + // This happens if the character filter let through something invalid + // like: 0123ef for example. + // TODO(bradnelson): Check if this happens often enough to be a perf + // problem. + if (number[0] == '.') { + for (size_t k = 1; k < number.size(); ++k) { + stream_->Back(); + } + token_ = '.'; + return; + } + // Anything else that doesn't parse is an error. + token_ = kParseError; + return; + } + if (has_dot) { + token_ = kDouble; + } else { + unsigned_value_ = static_cast<uint32_t>(double_value_); + token_ = kUnsigned; + } +} + +bool AsmJsScanner::ConsumeCComment() { + for (;;) { + uc32 ch = stream_->Advance(); + while (ch == '*') { + ch = stream_->Advance(); + if (ch == '/') { + return true; + } + } + if (ch == kEndOfInput) { + return false; + } + } +} + +void AsmJsScanner::ConsumeCPPComment() { + for (;;) { + uc32 ch = stream_->Advance(); + if (ch == '\n' || ch == kEndOfInput) { + return; + } + } +} + +void AsmJsScanner::ConsumeString(uc32 quote) { + // Only string allowed is 'use asm' / "use asm". + const char* expected = "use asm"; + for (; *expected != '\0'; ++expected) { + if (stream_->Advance() != *expected) { + token_ = kParseError; + return; + } + } + if (stream_->Advance() != quote) { + token_ = kParseError; + return; + } + token_ = kToken_UseAsm; +} + +void AsmJsScanner::ConsumeCompareOrShift(uc32 ch) { + uc32 next_ch = stream_->Advance(); + if (next_ch == '=') { + switch (ch) { + case '<': + token_ = kToken_LE; + break; + case '>': + token_ = kToken_GE; + break; + case '=': + token_ = kToken_EQ; + break; + case '!': + token_ = kToken_NE; + break; + default: + UNREACHABLE(); + } + } else if (ch == '<' && next_ch == '<') { + token_ = kToken_SHL; + } else if (ch == '>' && next_ch == '>') { + if (stream_->Advance() == '>') { + token_ = kToken_SHR; + } else { + token_ = kToken_SAR; + stream_->Back(); + } + } else { + stream_->Back(); + token_ = ch; + } +} + +bool AsmJsScanner::IsIdentifierStart(uc32 ch) { + return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || + ch == '$'; +} + +bool AsmJsScanner::IsIdentifierPart(uc32 ch) { + return IsIdentifierStart(ch) || (ch >= '0' && ch <= '9'); +} + +bool AsmJsScanner::IsNumberStart(uc32 ch) { + return ch == '.' || (ch >= '0' && ch <= '9'); +} + +} // namespace internal +} // namespace v8 diff --git a/deps/v8/src/asmjs/asm-scanner.h b/deps/v8/src/asmjs/asm-scanner.h new file mode 100644 index 0000000000..4ac5370a1f --- /dev/null +++ b/deps/v8/src/asmjs/asm-scanner.h @@ -0,0 +1,165 @@ +// 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. + +#ifndef V8_ASMJS_ASM_SCANNER_H_ +#define V8_ASMJS_ASM_SCANNER_H_ + +#include <memory> +#include <string> +#include <unordered_map> + +#include "src/asmjs/asm-names.h" +#include "src/base/logging.h" +#include "src/globals.h" + +namespace v8 { +namespace internal { + +class Utf16CharacterStream; + +// A custom scanner to extract the token stream needed to parse valid +// asm.js: http://asmjs.org/spec/latest/ +// This scanner intentionally avoids the portion of JavaScript lexing +// that are not required to determine if code is valid asm.js code. +// * Strings are disallowed except for 'use asm'. +// * Only the subset of keywords needed to check asm.js invariants are +// included. +// * Identifiers are accumulated into local + global string tables +// (for performance). +class V8_EXPORT_PRIVATE AsmJsScanner { + public: + typedef int32_t token_t; + + AsmJsScanner(); + // Pick the stream to parse (must be called before anything else). + void SetStream(std::unique_ptr<Utf16CharacterStream> stream); + + // Get current token. + token_t Token() const { return token_; } + // Get position of current token. + size_t Position() const { return position_; } + // Advance to the next token. + void Next(); + // Back up by one token. + void Rewind(); + // Get raw string for current identifier. + const std::string& GetIdentifierString() const { + // Identifier strings don't work after a rewind. + DCHECK(!rewind_); + return identifier_string_; + } + // Check if we just passed a newline. + bool IsPrecededByNewline() const { + // Newline tracking doesn't work if you back up. + DCHECK(!rewind_); + return preceded_by_newline_; + } + +#if DEBUG + // Debug only method to go from a token back to its name. + // Slow, only use for debugging. + std::string Name(token_t token) const; +#endif + + // Get current position (to use with Seek). + int GetPosition() const; + // Restores old position (token after that position). + void Seek(int pos); + + // Select whether identifiers are resolved in global or local scope, + // and which scope new identifiers are added to. + void EnterLocalScope() { in_local_scope_ = true; } + void EnterGlobalScope() { in_local_scope_ = false; } + // Drop all current local identifiers. + void ResetLocals(); + + // Methods to check if a token is an identifier and which scope. + bool IsLocal() const { return IsLocal(Token()); } + bool IsGlobal() const { return IsGlobal(Token()); } + static bool IsLocal(token_t token) { return token <= kLocalsStart; } + static bool IsGlobal(token_t token) { return token >= kGlobalsStart; } + // Methods to find the index position of an identifier (count starting from + // 0 for each scope separately). + static size_t LocalIndex(token_t token) { + DCHECK(IsLocal(token)); + return -(token - kLocalsStart); + } + static size_t GlobalIndex(token_t token) { + DCHECK(IsGlobal(token)); + return token - kGlobalsStart; + } + + // Methods to check if the current token is an asm.js "number" (contains a + // dot) or an "unsigned" (a number without a dot). + bool IsUnsigned() const { return Token() == kUnsigned; } + uint64_t AsUnsigned() const { return unsigned_value_; } + bool IsDouble() const { return Token() == kDouble; } + double AsDouble() const { return double_value_; } + + // clang-format off + enum { + // [-10000-kMaxIdentifierCount, -10000) :: Local identifiers (counting + // backwards) + // [-10000 .. -1) :: Builtin tokens like keywords + // (also includes some special + // ones like end of input) + // 0 .. 255 :: Single char tokens + // 256 .. 256+kMaxIdentifierCount :: Global identifiers + kLocalsStart = -10000, +#define V(name, _junk1, _junk2, _junk3) kToken_##name, + STDLIB_MATH_FUNCTION_LIST(V) + STDLIB_ARRAY_TYPE_LIST(V) +#undef V +#define V(name) kToken_##name, + STDLIB_OTHER_LIST(V) + STDLIB_MATH_VALUE_LIST(V) + KEYWORD_NAME_LIST(V) +#undef V +#define V(rawname, name) kToken_##name, + LONG_SYMBOL_NAME_LIST(V) +#undef V +#define V(name, value, string_name) name = value, + SPECIAL_TOKEN_LIST(V) +#undef V + kGlobalsStart = 256, + }; + // clang-format on + + private: + std::unique_ptr<Utf16CharacterStream> stream_; + token_t token_; + token_t preceding_token_; + token_t next_token_; // Only set when in {rewind} state. + size_t position_; // Corresponds to {token} position. + size_t preceding_position_; // Corresponds to {preceding_token} position. + size_t next_position_; // Only set when in {rewind} state. + bool rewind_; + std::string identifier_string_; + bool in_local_scope_; + std::unordered_map<std::string, token_t> local_names_; + std::unordered_map<std::string, token_t> global_names_; + std::unordered_map<std::string, token_t> property_names_; + int global_count_; + double double_value_; + uint64_t unsigned_value_; + bool preceded_by_newline_; + + // Consume multiple characters. + void ConsumeIdentifier(uc32 ch); + void ConsumeNumber(uc32 ch); + bool ConsumeCComment(); + void ConsumeCPPComment(); + void ConsumeString(uc32 quote); + void ConsumeCompareOrShift(uc32 ch); + + // Classify character categories. + bool IsIdentifierStart(uc32 ch); + bool IsIdentifierPart(uc32 ch); + bool IsNumberStart(uc32 ch); +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_ASMJS_ASM_SCANNER_H_ diff --git a/deps/v8/src/asmjs/asm-wasm-builder.cc b/deps/v8/src/asmjs/asm-wasm-builder.cc index 891cba3ef9..a92e3ca1b4 100644 --- a/deps/v8/src/asmjs/asm-wasm-builder.cc +++ b/deps/v8/src/asmjs/asm-wasm-builder.cc @@ -91,6 +91,8 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> { FunctionSig::Builder b(zone(), 0, 0); init_function_ = builder_->AddFunction(b.Build()); builder_->MarkStartFunction(init_function_); + // Record start of the function, used as position for the stack check. + init_function_->SetAsmFunctionStartPosition(literal_->start_position()); } void BuildForeignInitFunction() { @@ -170,7 +172,7 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> { new_func_scope = new (info->zone()) DeclarationScope( info->zone(), decl->fun()->scope()->outer_scope(), FUNCTION_SCOPE); info->set_asm_function_scope(new_func_scope); - if (!Compiler::ParseAndAnalyze(info.get())) { + if (!Compiler::ParseAndAnalyze(info.get(), info_->isolate())) { decl->fun()->scope()->outer_scope()->RemoveInnerScope(new_func_scope); if (isolate_->has_pending_exception()) { isolate_->clear_pending_exception(); @@ -224,6 +226,7 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> { } RECURSE(Visit(stmt)); if (typer_failed_) break; + // Not stopping when a jump statement is found. } } @@ -300,6 +303,8 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> { void VisitGetIterator(GetIterator* expr) { UNREACHABLE(); } + void VisitImportCallExpression(ImportCallExpression* expr) { UNREACHABLE(); } + void VisitIfStatement(IfStatement* stmt) { DCHECK_EQ(kFuncScope, scope_); RECURSE(Visit(stmt->condition())); @@ -1066,7 +1071,7 @@ class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> { if (as_init) UnLoadInitFunction(); } - void VisitYield(Yield* expr) { UNREACHABLE(); } + void VisitSuspend(Suspend* expr) { UNREACHABLE(); } void VisitThrow(Throw* expr) { UNREACHABLE(); } @@ -2001,6 +2006,9 @@ AsmWasmBuilder::Result AsmWasmBuilder::Run(Handle<FixedArray>* foreign_args) { info_->parse_info()->ast_value_factory(), info_->script(), info_->literal(), &typer_); bool success = impl.Build(); + if (!success) { + return {nullptr, nullptr, success}; + } *foreign_args = impl.GetForeignArgs(); ZoneBuffer* module_buffer = new (zone) ZoneBuffer(zone); impl.builder_->WriteTo(*module_buffer); |