Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(19)

Unified Diff: src/asmjs/asm-parser.cc

Issue 2757693003: [wasm][asm.js] Asm.js -> wasm custom parser. (Closed)
Patch Set: Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: src/asmjs/asm-parser.cc
diff --git a/src/asmjs/asm-parser.cc b/src/asmjs/asm-parser.cc
new file mode 100644
index 0000000000000000000000000000000000000000..3dbe68993bc904ebc1705f64456d56f554c022fe
--- /dev/null
+++ b/src/asmjs/asm-parser.cc
@@ -0,0 +1,2354 @@
+// 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. in MSVC.
Michael Starzinger 2017/03/22 14:55:51 nit: Could we make this comment more explicit, tha
bradn 2017/03/24 03:58:18 Done.
+#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 {
+
+#define ASSTRING1(x) #x
+#define ASSTRING(x) ASSTRING1(x)
vogelheim 2017/03/17 18:01:29 What does this indirect definition accomplish?
bradn 2017/03/24 03:58:18 Sorry leftover, inlined.
+
+#ifdef DEBUG
+#define FAIL(msg) \
+ failed_ = true; \
+ failure_message_ = std::string(msg) + \
+ " token: " + scanner_.Name(scanner_.Token()) + \
+ " see: " + __FILE__ + ":" + ASSTRING(__LINE__); \
+ failure_location_ = scanner_.GetPosition(); \
+ return FAILURE_RETURN;
+#else
+#define FAIL(msg) \
+ failed_ = true; \
+ failure_message_ = msg; \
+ failure_location_ = scanner_.GetPosition(); \
+ return FAILURE_RETURN;
+#endif
+
+#ifdef DEBUG
+#define SKIP(token) \
marja 2017/03/17 13:49:04 Would it make sense to have these as functions ins
bradn 2017/03/24 03:58:17 It might be possible (and might be denser and ther
+ do { \
+ if (scanner_.Token() != token) { \
+ FAIL(std::string("expected token ") + scanner_.Name(token) + \
+ " but found " + scanner_.Name(scanner_.Token())); \
+ } \
+ scanner_.Next(); \
+ } while (false);
+#else
+#define SKIP(token) \
marja 2017/03/17 13:49:04 For naming consistency, SKIP could be called Expec
vogelheim 2017/03/17 17:31:13 +1 to Marja's comment. (I don't care so much abou
bradn 2017/03/24 03:58:18 Acknowledged.
bradn 2017/03/24 03:58:18 As I'm unable to switch this from being a macro at
+ do { \
+ if (scanner_.Token() != token) { \
+ FAIL("unexpected token"); \
+ } \
+ scanner_.Next(); \
+ } while (false);
+#endif
+
+#define RECURSE(call) \
+ do { \
+ DCHECK(GetCurrentStackPosition() >= stack_limit_); \
marja 2017/03/17 13:49:04 I like that the stack check is done when recursing
bradn 2017/03/24 03:58:17 Not keen on this pattern myself (got it from the a
+ call; \
+ if (GetCurrentStackPosition() < stack_limit_) { \
+ FAIL("Stack overflow while parsing asm.js module."); \
+ } \
+ if (failed_) return FAILURE_RETURN; \
+ } while (false);
+
+#define TOK(name) AsmJsScanner::kToken_##name
+
+AsmJsParser::AsmJsParser(Isolate* isolate, Zone* zone, Handle<Script> script,
+ int start, int end)
+ : zone_(zone),
+ builder_(new (zone) WasmModuleBuilder(zone)),
+ return_type_(nullptr),
+ stack_limit_(isolate->stack_guard()->real_climit()),
+ failed_(false),
+ failure_location_(start),
+ inside_heap_assignment_(false),
+ heap_assignment_location_(0),
+ pending_label_(0) {
+ 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();
+}
+
+#define FAILURE_RETURN
vogelheim 2017/03/17 18:01:29 Hrmpf. This (and lines 1249/1259 and 2214/2215) ef
Michael Starzinger 2017/03/22 14:55:51 +1, I would also prefer to just define two differe
bradn 2017/03/24 03:58:17 Also applies to SKIP->EXPECT_TOKEN. Done.
bradn 2017/03/24 03:58:18 Acknowledged.
+
+bool AsmJsParser::Run() {
+ ValidateModule();
+ return !failed_;
+}
+
+AsmJsParser::VarInfo::VarInfo()
+ : type(AsmType::None()),
+ index(0),
+ mask(0),
+ kind(VarKind::kUnused),
+ function_builder(nullptr),
+ mut(true),
+ import(nullptr) {}
+
+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 mut, VarInfo* info) {
marja 2017/03/17 13:49:04 What's "mut"?
marja 2017/03/20 14:28:47 Nevermind, it's actually "mut" in the spec too, so
bradn 2017/03/24 03:58:17 Yeah that was the other reason I used it, but chan
bradn 2017/03/24 03:58:18 mutable
+ if (mut) {
+ // 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, 0, info->index, true});
+ } else {
+ // Just use the import directly.
+ global_imports_.push_back({name, 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 = builder_->AddGlobalImport(
+ name.data(), static_cast<int>(name.size()), vtype);
+ if (!mut) {
+ info->DeclareGlobalImport(type, gi.import_index);
+ }
+}
+
+void AsmJsParser::VarInfo::DeclareGlobalImport(AsmType* type, uint32_t index) {
+ kind = VarKind::kGlobal;
+ this->type = type;
+ this->index = index;
+ mut = false;
+}
+
+void AsmJsParser::VarInfo::DeclareStdlibFunc(VarKind kind, AsmType* type) {
+ this->kind = kind;
+ this->type = type;
+ index = 0; // unused
+ mut = false;
+}
+
+void AsmJsParser::DeclareGlobal(VarInfo* info, bool mut, AsmType* type,
+ ValueType vtype, const WasmInitExpr& init) {
+ info->kind = VarKind::kGlobal;
+ info->type = type;
+ info->index = builder_->AddGlobal(vtype, false, true, init);
+ info->mut = mut;
+}
+
+int32_t AsmJsParser::TempVariable(int i) {
+ if (i + 1 > function_temp_locals_used_) {
+ function_temp_locals_used_ = i + 1;
+ }
+ return function_temp_locals_offset_ + i;
+}
+
+void AsmJsParser::SkipSemicolon() {
+ if (Peek(';')) {
+ SKIP(';');
+ } 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::FindLabelDepth(AsmJsScanner::token_t label, bool loop) {
+ int count = 0;
+ for (auto it = block_stack_.rbegin(); it != block_stack_.rend();
+ ++it, ++count) {
+ if (loop && it->kind != BlockKind::kLoop) {
+ continue;
+ }
+ if (!loop && it->kind != BlockKind::kRegular) {
marja 2017/03/21 13:31:44 I don't get this function... (esp. its usage, when
bradn 2017/03/24 03:58:18 Broke this apart into two methods for finding brea
+ continue;
+ }
+ if (label == 0 || it->label == label) {
marja 2017/03/23 12:16:55 Documenting offline disucssion: IMO this looks lik
bradn 2017/03/24 03:58:17 So I believe this works. The 'if' type blocks are
+ return count;
+ }
+ }
+ return -1;
+}
+
+// 6.1 ValidateModule
+void AsmJsParser::ValidateModule() {
+ RECURSE(ValidateModuleParameters());
marja 2017/03/20 14:28:47 Do we actually need to use RECURSE in constructs l
bradn 2017/03/24 03:58:18 RECURSE doubles as both a stack check and an early
+ SKIP('{');
+ SKIP(TOK(UseAsm));
+ SkipSemicolon();
+ RECURSE(ValidateModuleVars());
+ while (Peek(TOK(function))) {
+ RECURSE(ValidateFunction());
+ }
+ while (Peek(TOK(var))) {
+ RECURSE(ValidateFunctionTable());
+ }
+ RECURSE(ValidateExport());
+
+ // Add start function to init things.
+ WasmFunctionBuilder* start = builder_->AddFunction();
+ 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() {
+ SKIP('(');
+ stdlib_name_ = 0;
+ foreign_name_ = 0;
+ heap_name_ = 0;
+ if (!Peek(')')) {
+ if (!scanner_.IsGlobal()) {
+ FAIL("Expected stdlib parameter");
+ }
+ stdlib_name_ = Consume();
+ if (!Peek(')')) {
+ SKIP(',');
+ if (!scanner_.IsGlobal()) {
+ FAIL("Expected foreign parameter");
+ }
+ foreign_name_ = Consume();
+ if (!Peek(')')) {
+ SKIP(',');
+ if (!scanner_.IsGlobal()) {
+ FAIL("Expected heap parameter");
+ }
+ heap_name_ = Consume();
+ }
+ }
+ }
+ SKIP(')');
+}
+
+// 6.1 ValidateModule - variables
+void AsmJsParser::ValidateModuleVars() {
+ while (Peek(TOK(var)) || Peek(TOK(const))) {
+ bool mut = true;
+ if (Peek(TOK(var))) {
+ SKIP(TOK(var));
+ } else {
+ SKIP(TOK(const));
+ mut = false;
+ }
+ for (;;) {
+ RECURSE(ValidateModuleVar(mut));
+ if (Peek(',')) {
+ SKIP(',');
+ continue;
+ }
+ break;
+ }
+ SkipSemicolon();
+ }
+}
+
+// 6.1 ValidateModule - one variable
+void AsmJsParser::ValidateModuleVar(bool mut) {
marja 2017/03/20 14:28:47 Lost... which part of the spec do I need to read t
bradn 2017/03/24 03:58:18 Acknowledged.
+ if (!scanner_.IsGlobal()) {
+ FAIL("Expected identifier");
+ }
+ VarInfo* info = GetVarInfo(Consume());
+ if (info->kind != VarKind::kUnused) {
+ FAIL("Redefinition of variable");
+ }
+ SKIP('=');
+ if (scanner_.IsDouble()) {
marja 2017/03/20 14:28:47 One thing I'm confused about is that which part pr
bradn 2017/03/24 03:58:18 So all of that lives in the parser. It's not share
+ DeclareGlobal(info, mut, AsmType::Double(), kWasmF64,
+ WasmInitExpr(scanner_.AsDouble()));
+ scanner_.Next();
+ } else if (scanner_.IsUnsigned()) {
marja 2017/03/20 14:28:47 I wonder whether it would make sense to have one f
bradn 2017/03/24 03:58:19 Added something like this in parser for now.
+ uint64_t value = scanner_.AsUnsigned();
+ if (value > 0x7fffffff) {
+ FAIL("Numeric literal out of range");
+ }
+ DeclareGlobal(info, mut, AsmType::Int(), kWasmI32,
+ WasmInitExpr(static_cast<int32_t>(value)));
+ scanner_.Next();
+ } else if (Peek('-')) {
+ SKIP('-');
+ if (scanner_.IsDouble()) {
+ DeclareGlobal(info, mut, AsmType::Double(), kWasmF64,
+ WasmInitExpr(-scanner_.AsDouble()));
+ scanner_.Next();
+ } else if (scanner_.IsUnsigned()) {
+ uint64_t value = scanner_.AsUnsigned();
+ if (value > 0x7fffffff) {
+ FAIL("Numeric literal out of range");
+ }
+ DeclareGlobal(info, mut, AsmType::Int(), kWasmI32,
+ WasmInitExpr(-static_cast<int32_t>(value)));
+ scanner_.Next();
+ } else {
+ FAIL("Expected numeric literal");
+ }
+ } else if (Peek('+')) {
+ SKIP('+');
+ SKIP(foreign_name_);
+ SKIP('.');
+ AddGlobalImport(scanner_.GetIdentifierString(), AsmType::Double(), kWasmF64,
+ mut, info);
+ scanner_.Next();
+ } else if (Peek(TOK(new))) {
+ SKIP(TOK(new));
+ SKIP(stdlib_name_);
+ SKIP('.');
+ switch (scanner_.Token()) {
+#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;
+ }
+ scanner_.Next();
+ SKIP('(');
+ SKIP(heap_name_);
+ SKIP(')');
+ } else if (Peek(stdlib_name_)) {
+ SKIP(stdlib_name_);
+ SKIP('.');
+ RECURSE(ValidateModuleVarStdlib(info));
+ } else if (Peek(foreign_name_)) {
+ SKIP(foreign_name_);
+ SKIP('.');
+ std::string import_name = scanner_.GetIdentifierString();
+ scanner_.Next();
+ if (Peek('|')) {
+ SKIP('|');
+ if (!scanner_.IsUnsigned() || scanner_.AsUnsigned() != 0) {
marja 2017/03/20 14:57:57 Checking for "|0" sounds such a common use case th
bradn 2017/03/24 03:58:17 Added a helper for checking for 0 which fit 3 plac
+ FAIL("Expected 0 in global signature");
+ }
+ scanner_.Next();
+ AddGlobalImport(import_name, AsmType::Int(), kWasmI32, mut, info);
+ return;
+ }
+ info->kind = VarKind::kImportedFunction;
+ function_import_info_.resize(function_import_info_.size() + 1);
+ info->import = &function_import_info_.back();
+ info->import->name = import_name;
+ } else if (scanner_.IsGlobal()) {
+ if (!GetVarInfo(Consume())->type->IsA(stdlib_fround_)) {
+ FAIL("Expected fround");
+ }
+ SKIP('(');
+ bool negate = false;
+ if (Peek('-')) {
+ SKIP('-');
+ negate = true;
+ }
+ if (scanner_.IsDouble()) {
+ double value = scanner_.AsDouble();
+ if (negate) {
+ value = -value;
+ }
+ DeclareGlobal(info, mut, AsmType::Float(), kWasmF32,
+ WasmInitExpr(static_cast<float>(value)));
+ scanner_.Next();
+ } else if (scanner_.IsUnsigned()) {
+ double value = scanner_.AsUnsigned();
+ if (negate) {
+ value = -value;
+ }
+ DeclareGlobal(info, mut, AsmType::Float(), kWasmF32,
+ WasmInitExpr(static_cast<float>(value)));
+ scanner_.Next();
+ } else {
+ FAIL("Expected numeric literal");
+ }
+ SKIP(')');
+ } else {
+ FAIL("Bad variable declaration");
+ }
+}
marja 2017/03/20 14:28:47 This function is quite long, too.
bradn 2017/03/24 03:58:18 Chopped it up into a few parts. In general this is
+
+// 6.1 ValidateModule - one variable
+// 9 - Standard Library
+void AsmJsParser::ValidateModuleVarStdlib(VarInfo* info) {
+ if (Peek(TOK(Math))) {
+ SKIP(TOK(Math));
+ SKIP('.');
+ switch (Consume()) {
+#define V(name) \
+ case TOK(name): \
+ DeclareGlobal(info, false, AsmType::Double(), kWasmF64, \
+ WasmInitExpr(M_##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 (Peek(TOK(Infinity))) {
+ SKIP(TOK(Infinity));
+ DeclareGlobal(info, false, AsmType::Double(), kWasmF64,
+ WasmInitExpr(std::numeric_limits<double>::infinity()));
+ } else if (Peek(TOK(NaN))) {
+ SKIP(TOK(NaN));
+ DeclareGlobal(info, false, AsmType::Double(), kWasmF64,
+ WasmInitExpr(std::numeric_limits<double>::quiet_NaN()));
+ } else {
+ FAIL("Invalid member of stdlib");
+ }
+}
+
+// 6.2 ValidateExport
+void AsmJsParser::ValidateExport() {
+ // clang-format off
+ SKIP(TOK(return));
+ // clang format on
+ if (Peek('{')) {
marja 2017/03/17 13:49:04 Would it make sense to have Check() like in Parser
bradn 2017/03/24 03:58:17 Great idea. Done.
+ SKIP('{');
+ for (;;) {
+ std::string name = scanner_.GetIdentifierString();
+ if (!scanner_.IsGlobal() && !scanner_.IsLocal()) {
+ FAIL("Illegal export name");
+ }
+ Consume();
+ SKIP(':');
+ 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 (Peek(',')) {
+ SKIP(',');
+ if (!Peek('}')) {
+ continue;
+ }
+ }
+ break;
+ }
+ SKIP('}');
+ } 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() {
+ SKIP(TOK(var));
+ if (!scanner_.IsGlobal()) {
+ FAIL("Expected table name");
+ }
+ VarInfo* table_info = GetVarInfo(Consume());
+ if (table_info->kind != VarKind::kTable) {
+ FAIL("Unused function table");
marja 2017/03/20 14:57:57 Not sure when this error occurs or what it means.
bradn 2017/03/24 03:58:17 Meant a function table was defined but never used.
+ }
+ // TODO(bradnelson): Check for double use of export name.
+ SKIP('=');
+ SKIP('[');
+ uint64_t count = 0;
+ for (;;) {
+ if (!scanner_.IsGlobal()) {
+ FAIL("Expected function name");
+ }
+ VarInfo* info = GetVarInfo(Consume());
+ if (info->kind != VarKind::kFunction) {
+ FAIL("Expected function");
+ }
+ if (count >= table_info->mask + 1) {
marja 2017/03/20 14:57:58 It's non-trivial to see where the mask is coming f
bradn 2017/03/24 03:58:19 Done.
+ FAIL("Exceeded function table size");
+ }
+ builder_->SetIndirectFunction(
+ static_cast<uint32_t>(table_info->index + count), info->index);
+ ++count;
+ if (Peek(',')) {
+ SKIP(',');
+ if (!Peek(']')) {
+ continue;
+ }
+ }
+ break;
+ }
+ SKIP(']');
+ if (count != table_info->mask + 1) {
+ FAIL("Function table size does not match uses");
+ }
+ SkipSemicolon();
+}
+
+// 6.4 ValidateFunction
+void AsmJsParser::ValidateFunction() {
+ int start_position = scanner_.GetPosition();
+ SKIP(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) {
marja 2017/03/21 13:31:43 So redeclarations are ok? Which test tests that?
bradn 2017/03/24 03:58:17 Ooh, messed that up. They're allowed to be defined
+ function_info->kind = VarKind::kFunction;
+ function_info->function_builder = builder_->AddFunction();
marja 2017/03/21 13:31:44 This line got me confused for a moment; would it h
bradn 2017/03/24 03:58:17 Done.
+ function_info->function_builder->SetName(
+ {function_name_raw.c_str(),
+ static_cast<int>(function_name_raw.size())});
+ function_info->index = function_info->function_builder->func_index();
+ }
+ current_function_builder_ = function_info->function_builder;
+ return_type_ = nullptr;
+
+ // Record start of the function, used as position for the stack check.
+ current_function_builder_->SetAsmFunctionStartPosition(start_position);
+
+ scanner_.EnterLocalScope();
marja 2017/03/21 13:31:44 Continuing my previous comments about EnterLocalSc
bradn 2017/03/24 03:58:17 Agreed this is confusing and needs to be redone. A
+ SKIP('(');
+ std::vector<AsmJsScanner::token_t> function_parameters;
+ while (!failed_ && !Peek(')')) {
+ if (!scanner_.IsLocal()) {
+ FAIL("Expected parameter name");
+ }
+ function_parameters.push_back(Consume());
+ if (!Peek(')')) {
+ SKIP(',');
+ }
+ }
+ SKIP(')');
+ std::vector<AsmType*> params;
+ std::vector<ValueType> locals;
+ scanner_.EnterGlobalScope();
+ SKIP('{');
+ // 5.1 Parameter Type Annotations
+ for (auto p : function_parameters) {
+ SKIP(p);
+ SKIP('=');
+ VarInfo* info = GetVarInfo(p);
+ if (info->kind != VarKind::kUnused) {
+ FAIL("Duplicate parameter name");
+ }
+ if (Peek(p)) {
+ SKIP(p);
+ SKIP('|');
+ if (!Peek(AsmJsScanner::kUnsigned) || scanner_.AsUnsigned() != 0) {
+ FAIL("Bad integer parameter annotation.");
+ }
+ scanner_.Next();
+ info->kind = VarKind::kLocal;
+ info->type = AsmType::Int();
+ info->index = static_cast<uint32_t>(params.size());
+ params.push_back(AsmType::Int());
+ } else if (Peek('+')) {
+ SKIP('+');
+ SKIP(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");
+ }
+ SKIP('(');
+ SKIP(p);
+ SKIP(')');
+ info->kind = VarKind::kLocal;
+ info->type = AsmType::Float();
+ info->index = static_cast<uint32_t>(params.size());
+ params.push_back(AsmType::Float());
+ }
+ SkipSemicolon();
+ }
+ // Local Variables.
+ while (Peek(TOK(var))) {
+ scanner_.EnterLocalScope();
+ SKIP(TOK(var));
+ scanner_.EnterGlobalScope();
marja 2017/03/20 14:57:58 This is quite confusing construct! The reason see
bradn 2017/03/24 03:58:18 Agreed this is very messy. I had thought about def
+ 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.
+ SKIP('=');
+ if (Peek('-')) {
+ SKIP('-');
+ if (scanner_.IsDouble()) {
+ info->kind = VarKind::kLocal;
+ info->type = AsmType::Double();
+ info->index = static_cast<uint32_t>(params.size() + locals.size());
+ locals.push_back(kWasmF64);
+ double value = -scanner_.AsDouble();
+ byte code[] = {WASM_F64(value)};
+ current_function_builder_->EmitCode(code, sizeof(code));
+ current_function_builder_->EmitSetLocal(info->index);
+ scanner_.Next();
+ } else if (scanner_.IsUnsigned()) {
+ if (scanner_.AsUnsigned() > 0x7fffffff) {
+ FAIL("Numeric literal out of range");
+ }
+ info->kind = VarKind::kLocal;
+ info->type = AsmType::Int();
+ info->index = static_cast<uint32_t>(params.size() + locals.size());
+ locals.push_back(kWasmI32);
+ int32_t value = -static_cast<int32_t>(scanner_.AsUnsigned());
+ current_function_builder_->EmitI32Const(value);
+ current_function_builder_->EmitSetLocal(info->index);
+ scanner_.Next();
+ } else {
+ FAIL("Expected variable initial value");
+ }
+ } else if (scanner_.IsGlobal()) {
+ VarInfo* sinfo = GetVarInfo(Consume());
+ if (sinfo->kind == VarKind::kGlobal) {
+ if (sinfo->mut) {
+ FAIL("Initializing from global requires const variable");
+ }
+ info->kind = VarKind::kLocal;
+ info->type = sinfo->type;
+ info->index = static_cast<uint32_t>(params.size() + 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_)) {
+ SKIP('(');
+ bool negate = false;
+ if (Peek('-')) {
+ SKIP('-');
+ negate = true;
+ }
+ if (scanner_.IsDouble()) {
+ info->kind = VarKind::kLocal;
+ info->type = AsmType::Float();
+ info->index = static_cast<uint32_t>(params.size() + locals.size());
+ locals.push_back(kWasmF32);
+ double value = scanner_.AsDouble();
+ if (negate) {
+ value = -value;
+ }
+ byte code[] = {WASM_F32(value)};
+ current_function_builder_->EmitCode(code, sizeof(code));
+ current_function_builder_->EmitSetLocal(info->index);
+ scanner_.Next();
+ } else if (scanner_.IsUnsigned()) {
+ if (scanner_.AsUnsigned() > 0x7fffffff) {
+ FAIL("Numeric literal out of range");
+ }
+ info->kind = VarKind::kLocal;
+ info->type = AsmType::Float();
+ info->index = static_cast<uint32_t>(params.size() + locals.size());
+ locals.push_back(kWasmF32);
+ int32_t value = static_cast<int32_t>(scanner_.AsUnsigned());
+ 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);
+ scanner_.Next();
+ } else {
+ FAIL("Expected variable initial value");
+ }
+ SKIP(')');
+ } else {
+ FAIL("expected fround or const global");
+ }
+ } else if (scanner_.IsDouble()) {
+ info->kind = VarKind::kLocal;
+ info->type = AsmType::Double();
+ info->index = static_cast<uint32_t>(params.size() + locals.size());
+ locals.push_back(kWasmF64);
+ double value = scanner_.AsDouble();
+ byte code[] = {WASM_F64(value)};
+ current_function_builder_->EmitCode(code, sizeof(code));
+ current_function_builder_->EmitSetLocal(info->index);
+ scanner_.Next();
+ } else if (scanner_.IsUnsigned()) {
+ info->kind = VarKind::kLocal;
+ info->type = AsmType::Int();
+ info->index = static_cast<uint32_t>(params.size() + locals.size());
+ locals.push_back(kWasmI32);
+ int32_t value = static_cast<int32_t>(scanner_.AsUnsigned());
+ current_function_builder_->EmitI32Const(value);
+ current_function_builder_->EmitSetLocal(info->index);
+ scanner_.Next();
+ } else {
+ FAIL("Expected variable initial value");
+ }
+ if (!Peek(',')) {
+ break;
+ }
+ scanner_.EnterLocalScope();
marja 2017/03/20 14:57:57 Ditto... except that this is even more confusing a
bradn 2017/03/24 03:58:17 Same as above.
+ SKIP(',');
+ scanner_.EnterGlobalScope();
+ }
+ SkipSemicolon();
+ }
+
+ function_temp_locals_offset_ = static_cast<uint32_t>(
+ params.size() + locals.size());
+ function_temp_locals_used_ = 0;
+
+ while (!failed_ && !Peek('}')) {
+ RECURSE(ValidateStatement());
+ }
+ SKIP('}');
+
+ 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);
+
+ // Add in 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->kind == VarKind::kUnused) {
+ function_info->kind = VarKind::kFunction;
+ function_info->index = current_function_builder_->func_index();
+ function_info->type = function_type;
+ } else {
+ if (function_info->kind != VarKind::kFunction) {
+ FAIL("Function name collides with variable");
+ }
+ // TODO(bradnelson): Should IsExactly be used here?
+ if (!function_info->type->IsA(AsmType::None()) &&
+ !function_type->IsA(function_info->type)) {
+ FAIL("Function definition doesn't match use");
+ }
+ }
+
+ scanner_.ResetLocals();
+ local_var_info_.clear();
+}
marja 2017/03/20 14:28:47 This function is almost 300 lines; would it be pos
bradn 2017/03/24 03:58:18 Done.
+
+// 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 wrap_block = pending_label_ != 0;
marja 2017/03/20 14:57:57 What's "wrap_block"?
bradn 2017/03/24 03:58:17 Renamed can_break_to_block i.e. this is deciding i
+ if (wrap_block) {
+ Begin(pending_label_);
+ }
+ pending_label_ = 0;
+ SKIP('{');
+ while (!failed_ && !Peek('}')) {
+ RECURSE(ValidateStatement());
+ }
+ SKIP('}');
+ if (wrap_block) {
+ End();
+ }
+}
+
+// 6.5.2 ExpressionStatement
+void AsmJsParser::ExpressionStatement() {
+ if (scanner_.IsGlobal() || scanner_.IsLocal()) {
+ scanner_.Next();
+ if (Peek(':')) {
+ scanner_.Rewind();
marja 2017/03/20 14:57:57 Does this construct make labels appear in the loca
bradn 2017/03/24 03:58:18 Added a comment. Surprisingly asm.js code in the w
+ 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() { SKIP(';'); }
+
+// 6.5.4 IfStatement
+void AsmJsParser::IfStatement() {
+ SKIP(TOK(if));
+ SKIP('(');
+ RECURSE(Expression(AsmType::Int()));
+ SKIP(')');
+ current_function_builder_->EmitWithU8(kExprIf, kLocalVoid);
+ BareBegin();
+ RECURSE(ValidateStatement());
+ if (Peek(TOK(else))) {
+ SKIP(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
+ SKIP(TOK(return ));
+ // clang-format on
+ if (!Peek(';') && !Peek('}')) {
+ AsmType* ret;
+ RECURSE(ret = Expression(return_type_));
+ if (ret->IsA(AsmType::Double())) {
marja 2017/03/21 13:31:44 This kind of check (checking the type and setting
bradn 2017/03/24 03:58:18 A bunch of these are close, but no patterned jumpe
+ 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() {
+ Begin(pending_label_);
+ Loop(pending_label_);
marja 2017/03/21 13:31:43 I don't understand (based on looking at this for a
bradn 2017/03/24 03:58:18 Done.
+ pending_label_ = 0;
+ SKIP(TOK(while));
+ SKIP('(');
+ RECURSE(Expression(AsmType::Int()));
+ SKIP(')');
+ current_function_builder_->Emit(kExprI32Eqz);
marja 2017/03/21 13:31:43 Comment pls.
bradn 2017/03/24 03:58:18 Done.
+ current_function_builder_->EmitWithU8(kExprBrIf, 1);
+ RECURSE(ValidateStatement());
+ current_function_builder_->EmitWithU8(kExprBr, 0);
+ End();
+ End();
+}
+
+// 6.5.6 IterationStatement - do
+void AsmJsParser::DoStatement() {
+ Begin(pending_label_);
+ Loop();
+ BareBegin(BlockKind::kLoop, pending_label_);
+ current_function_builder_->EmitWithU8(kExprBlock, kLocalVoid);
+ pending_label_ = 0;
+ SKIP(TOK(do));
+ RECURSE(ValidateStatement());
+ SKIP(TOK(while));
+ current_function_builder_->Emit(kExprEnd);
+ BareEnd();
+ SKIP('(');
+ RECURSE(Expression(AsmType::Int()));
+ current_function_builder_->Emit(kExprI32Eqz);
marja 2017/03/21 13:31:43 Ditto.
bradn 2017/03/24 03:58:17 Done.
+ current_function_builder_->EmitWithU8(kExprBrIf, 1);
+ current_function_builder_->EmitWithU8(kExprBr, 0);
+ SKIP(')');
+ End();
+ End();
+ SkipSemicolon();
+}
+
+// 6.5.6 IterationStatement - for
+void AsmJsParser::ForStatement() {
+ SKIP(TOK(for));
+ SKIP('(');
+ if (!Peek(';')) {
+ Expression(nullptr);
+ }
+ SKIP(';');
+ Begin(pending_label_);
+ Loop(pending_label_);
+ pending_label_ = 0;
+ if (!Peek(';')) {
+ RECURSE(Expression(AsmType::Int()));
+ current_function_builder_->Emit(kExprI32Eqz);
+ current_function_builder_->EmitWithU8(kExprBrIf, 1);
+ }
+ SKIP(';');
+ 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);
+ SKIP(')');
+ RECURSE(ValidateStatement());
+ 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() {
+ SKIP(TOK(break));
+ int depth;
+ if (scanner_.IsGlobal() || scanner_.IsLocal()) {
marja 2017/03/21 13:31:43 Why can it be Global here? Can this construct appe
bradn 2017/03/24 03:58:17 Currently reusing global / local identifiers for l
+ depth = FindLabelDepth(scanner_.Token(), false);
+ scanner_.Next();
+ } else {
+ depth = FindLabelDepth(0, false);
+ }
+ if (depth < 0) {
+ FAIL("Illegal break");
+ }
+ current_function_builder_->Emit(kExprBr);
+ current_function_builder_->EmitVarInt(depth);
+ SkipSemicolon();
+}
+
+// 6.5.8 ContinueStatement
+void AsmJsParser::ContinueStatement() {
+ SKIP(TOK(continue));
+ int depth;
+ if (scanner_.IsGlobal() || scanner_.IsLocal()) {
+ depth = FindLabelDepth(scanner_.Token(), true);
+ scanner_.Next();
+ } else {
+ depth = FindLabelDepth(0, true);
marja 2017/03/21 13:31:44 Why is the "bool loop" true here and false in Brea
bradn 2017/03/24 03:58:18 Refactored for clarity, they share the same stack,
+ }
+ if (depth < 0) {
+ FAIL("Illegal continue");
+ }
+ current_function_builder_->Emit(kExprBr);
marja 2017/03/21 13:31:43 This sounds suspicious. BreakStatement and Continu
bradn 2017/03/24 03:58:18 Br is the only unconditional construct in wasm. It
+ current_function_builder_->EmitVarInt(depth);
+ SkipSemicolon();
+}
+
+// 6.5.9 LabelledStatement
+void AsmJsParser::LabelledStatement() {
+ DCHECK(scanner_.IsGlobal() || scanner_.IsLocal());
+ if (pending_label_ != 0) {
+ FAIL("Double label unsupported");
+ }
+ pending_label_ = scanner_.Token();
+ scanner_.Next();
+ SKIP(':');
+ RECURSE(ValidateStatement());
+}
+
+// 6.5.10 SwitchStatement
+void AsmJsParser::SwitchStatement() {
+ SKIP(TOK(switch));
+ SKIP('(');
+ AsmType* test;
+ RECURSE(test = Expression(nullptr));
+ if (!test->IsA(AsmType::Signed())) {
+ FAIL("Expected signed for switch value");
+ }
+ SKIP(')');
+ int32_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.
vogelheim 2017/03/17 18:01:29 Is the comment correct? GatherCases stores GetPosi
bradn 2017/03/24 03:58:18 The comment is correct, that is a little odd thoug
+ 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());
+ }
+ SKIP('}');
+ End();
+}
+
+// 6.6. ValidateCase
+void AsmJsParser::ValidateCase() {
+ SKIP(TOK(case));
+ bool negate = false;
+ if (Peek('-')) {
+ SKIP('-');
+ negate = true;
+ }
+ if (!scanner_.IsUnsigned()) {
+ FAIL("Expected numeric literal");
+ }
+ if ((negate && scanner_.AsUnsigned() > 0x80000000) ||
marja 2017/03/21 13:31:43 This logic is duplicated in several places -> can
bradn 2017/03/24 03:58:18 Should do. Don't see an easy pattern. Added a TODO
+ (!negate && scanner_.AsUnsigned() > 0x7fffffff)) {
+ FAIL("Numeric literal out of range");
+ }
+ int32_t value = static_cast<int32_t>(scanner_.AsUnsigned());
+ if (negate) {
+ value = -value;
+ }
+ scanner_.Next();
+ SKIP(':');
+ while (!failed_ && !Peek('}') && !Peek(TOK(case)) && !Peek(TOK(default))) {
+ RECURSE(ValidateStatement());
+ }
+}
+
+// 6.7 ValidateDefault
+void AsmJsParser::ValidateDefault() {
+ SKIP(TOK(default));
+ SKIP(':');
+ while (!failed_ && !Peek('}')) {
+ RECURSE(ValidateStatement());
+ }
+}
+
+#undef FAILURE_RETURN
+#define FAILURE_RETURN AsmType::None()
+
+// 6.8 ValidateExpression
+AsmType* AsmJsParser::ValidateExpression() {
+ AsmType* ret;
+ RECURSE(ret = Expression(nullptr));
+ return ret;
+}
+
+// 6.8.1 Expression
+AsmType* AsmJsParser::Expression(AsmType* expected) {
+ AsmType* a;
+ for (;;) {
+ RECURSE(a = AssignmentExpression());
+ if (Peek(',')) {
+ if (a->IsA(AsmType::None())) {
+ FAIL("Expected actual type");
+ }
+ if (!a->IsA(AsmType::Void())) {
+ current_function_builder_->Emit(kExprDrop);
+ }
+ SKIP(',');
+ continue;
+ }
+ break;
+ }
+ if (expected != nullptr && !a->IsA(expected)) {
+ FAIL("Unexpected type");
+ }
+ return a;
+}
+
+// 6.8.2 NumericLiteral
+AsmType* AsmJsParser::NumericLiteral() {
+ call_coercion_ = nullptr;
+ if (scanner_.Token() == AsmJsScanner::kDouble) {
+ double value = scanner_.AsDouble();
+ byte code[] = {WASM_F64(value)};
+ current_function_builder_->EmitCode(code, sizeof(code));
+ scanner_.Next();
+ return AsmType::Double();
+ } else if (scanner_.IsUnsigned()) {
+ uint64_t value = scanner_.AsUnsigned();
+ if (value <= 0x7fffffff) {
+ current_function_builder_->EmitI32Const(static_cast<int32_t>(value));
+ scanner_.Next();
+ return AsmType::FixNum();
+ } else if (value <= 0x100000000) {
vogelheim 2017/03/17 18:01:28 < instead <= ???
bradn 2017/03/24 03:58:18 Done.
+ current_function_builder_->EmitI32Const(static_cast<int32_t>(value));
+ scanner_.Next();
+ return AsmType::Unsigned();
+ } else {
+ FAIL("Integer numeric literal out of range.");
+ }
+ } else {
+ FAIL("Expected numeric literal.");
+ }
+}
+
+// 6.8.3 Identifier
+AsmType* AsmJsParser::Identifier() {
+ call_coercion_ = nullptr;
marja 2017/03/21 13:31:43 What's call_coercion_?
bradn 2017/03/24 03:58:17 Added comment in header.
+ if (scanner_.IsLocal()) {
+ VarInfo* info = GetVarInfo(Consume());
+ if (info->kind != VarKind::kLocal) {
+ FAIL("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) {
+ FAIL("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())) {
+ RECURSE(ret = MemberExpression());
+ } else if (Peek('(')) {
+ RECURSE(ret = ParenthesizedExpression());
+ } else if (PeekCall()) {
+ RECURSE(ret = ValidateCall());
+ } else if (scanner_.IsLocal() || scanner_.IsGlobal()) {
+ RECURSE(ret = Identifier());
+ } else {
+ RECURSE(ret = NumericLiteral());
+ }
+ return ret;
+}
+
+// 6.8.5 MemberExpression
+AsmType* AsmJsParser::MemberExpression() {
+ call_coercion_ = nullptr;
+ int access_location = scanner_.GetPosition();
+ ValidateHeapAccess();
+ if (Peek('=')) {
+ inside_heap_assignment_ = true;
+ heap_assignment_location_ = access_location;
+ 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
+ FAIL("Expected valid heap load");
+ }
+}
+
+// 6.8.6 AssignmentExpression
+AsmType* AsmJsParser::AssignmentExpression() {
+ AsmType* ret;
+ if (scanner_.IsGlobal() &&
+ GetVarInfo(scanner_.Token())->type->IsA(AsmType::Heap())) {
+ int access_location = scanner_.GetPosition();
+ RECURSE(ret = ConditionalExpression());
+ if (Peek('=')) {
+ if (!inside_heap_assignment_ ||
+ access_location != heap_assignment_location_) {
+ FAIL("Invalid assignment target");
+ }
+ inside_heap_assignment_ = false;
+ AsmType* heap_type = heap_access_type_;
+ SKIP('=');
+ AsmType* value;
+ RECURSE(value = AssignmentExpression());
+ if (!value->IsA(ret)) {
+ FAIL("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()) {
marja 2017/03/21 13:31:43 For cases like this, if the Scanner no longer know
bradn 2017/03/24 03:58:18 So this was a particularly tricky spot. The || was
+ VarInfo* info = GetVarInfo(scanner_.Token());
+ ret = info->type;
+ scanner_.Next();
+ if (Peek('=')) {
+ SKIP('=');
+ AsmType* value;
+ RECURSE(value = AssignmentExpression());
+ if (!value->IsA(ret)) {
+ FAIL("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();
+ RECURSE(ret = ConditionalExpression());
+ } else {
+ RECURSE(ret = ConditionalExpression());
+ }
+ return ret;
+}
+
+// 6.8.7 UnaryExpression
+AsmType* AsmJsParser::UnaryExpression() {
+ AsmType* ret;
+ if (Peek('-')) {
+ SKIP('-');
+ if (scanner_.IsUnsigned()) {
+ uint64_t value = scanner_.AsUnsigned();
+ // TODO(bradnelson): was supposed to be 0x7fffffff, check errata.
+ if (value <= 0x80000000) {
+ current_function_builder_->EmitI32Const(-static_cast<int32_t>(value));
+ scanner_.Next();
+ } else {
+ FAIL("Integer numeric literal out of range.");
+ }
+ ret = AsmType::Signed();
+ } else {
+ RECURSE(ret = UnaryExpression());
+ if (ret->IsA(AsmType::Int())) {
+ int32_t tmp = TempVariable(0);
+ current_function_builder_->EmitSetLocal(tmp);
+ current_function_builder_->EmitI32Const(0);
+ current_function_builder_->EmitGetLocal(tmp);
+ 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 {
+ FAIL("expected int/double?/float?");
+ }
+ }
+ } else if (Peek('+')) {
+ SKIP('+');
+ call_coercion_ = AsmType::Double();
+ RECURSE(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 {
+ FAIL("expected signed/unsigned/double?/float?");
+ }
+ } else if (Peek('!')) {
+ SKIP('!');
+ RECURSE(ret = UnaryExpression());
+ if (!ret->IsA(AsmType::Int())) {
+ FAIL("expected int");
+ }
+ current_function_builder_->Emit(kExprI32Eqz);
+ } else if (Peek('~')) {
+ SKIP('~');
+ if (Peek('~')) {
+ SKIP('~');
+ RECURSE(ret = UnaryExpression());
+ if (ret->IsA(AsmType::Double())) {
+ current_function_builder_->Emit(kExprI32AsmjsSConvertF64);
+ } else if (ret->IsA(AsmType::FloatQ())) {
+ current_function_builder_->Emit(kExprI32AsmjsSConvertF32);
+ } else {
+ FAIL("expected double or float?");
+ }
+ ret = AsmType::Signed();
+ } else {
+ RECURSE(ret = UnaryExpression());
+ if (!ret->IsA(AsmType::Intish())) {
+ FAIL("operator ~ expects intish");
+ }
+ current_function_builder_->EmitI32Const(0xffffffff);
+ current_function_builder_->Emit(kExprI32Xor);
+ ret = AsmType::Signed();
+ }
+ } else {
+ RECURSE(ret = CallExpression());
+ }
+ return ret;
+}
+
+// 6.8.8 MultaplicativeExpression
+AsmType* AsmJsParser::MultiplicativeExpression() {
+ if (scanner_.IsUnsigned() && scanner_.AsUnsigned() < 0x100000) {
+ int32_t value = static_cast<int32_t>(scanner_.AsUnsigned());
+ scanner_.Next();
+ if (Peek('*')) {
+ SKIP('*');
+ AsmType* a;
+ RECURSE(a = UnaryExpression());
+ if (!a->IsA(AsmType::Int())) {
+ FAIL("Expected int");
+ }
+ current_function_builder_->EmitI32Const(value);
+ current_function_builder_->Emit(kExprI32Mul);
+ return AsmType::Intish();
+ }
+ scanner_.Rewind();
+ } else if (Peek('-')) {
+ SKIP('-');
+ if (scanner_.IsUnsigned() && scanner_.AsUnsigned() < 0x100000) {
+ int32_t value = -static_cast<int32_t>(scanner_.AsUnsigned());
+ current_function_builder_->EmitI32Const(value);
+ scanner_.Next();
+ if (Peek('*')) {
+ SKIP('*');
+ AsmType* a;
+ RECURSE(a = UnaryExpression());
+ if (!a->IsA(AsmType::Int())) {
+ FAIL("Expected int");
+ }
+ current_function_builder_->Emit(kExprI32Mul);
+ return AsmType::Intish();
+ }
+ return AsmType::Signed();
+ }
+ scanner_.Rewind();
+ }
+ AsmType* a;
+ RECURSE(a = UnaryExpression());
+ for (;;) {
+ if (Peek('*')) {
+ SKIP('*');
+ if (Peek('-')) {
+ SKIP('-');
+ if (scanner_.IsUnsigned()) {
+ if (scanner_.AsUnsigned() >= 0x100000) {
+ FAIL("Constant multiple out of range");
+ }
+ if (!a->IsA(AsmType::Int())) {
+ FAIL("Integer multiply of expects int");
+ }
+ int32_t value = -static_cast<int32_t>(scanner_.AsUnsigned());
+ current_function_builder_->EmitI32Const(value);
+ scanner_.Next();
+ current_function_builder_->Emit(kExprI32Mul);
+ return AsmType::Intish();
+ }
+ scanner_.Rewind();
+ } else if (scanner_.IsUnsigned()) {
+ if (scanner_.AsUnsigned() >= 0x100000) {
+ FAIL("Constant multiple out of range");
+ }
+ if (!a->IsA(AsmType::Int())) {
+ FAIL("Integer multiply of expects int");
+ }
+ current_function_builder_->EmitI32Const(
+ static_cast<int32_t>(scanner_.AsUnsigned()));
+ scanner_.Next();
+ current_function_builder_->Emit(kExprI32Mul);
+ return AsmType::Intish();
+ }
+ AsmType* 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 {
+ FAIL("expected doubles or floats");
+ }
+ } else if (Peek('/')) {
+ SKIP('/');
+ AsmType* 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 {
+ FAIL("expected doubles or floats");
+ }
+ } else if (Peek('%')) {
+ SKIP('%');
+ AsmType* 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 {
+ FAIL("expected doubles or floats");
+ }
+ } else {
+ break;
+ }
+ }
+ return a;
+}
+
+// 6.8.9 AdditiveExpression
+AsmType* AsmJsParser::AdditiveExpression() {
+ AsmType* a = MultiplicativeExpression();
+ int n = 0;
+ for (;;) {
+ if (Peek('+')) {
+ SKIP('+');
+ AsmType* 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)) {
+ FAIL("more than 2^20 additive values");
+ }
+ current_function_builder_->Emit(kExprI32Add);
+ } else {
+ FAIL("illegal types for +");
+ }
+ } else if (Peek('-')) {
+ SKIP('-');
+ AsmType* 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)) {
+ FAIL("more than 2^20 additive values");
+ }
+ current_function_builder_->Emit(kExprI32Sub);
+ } else {
+ FAIL("illegal types for +");
+ }
+ } else {
+ break;
+ }
+ }
+ return a;
+}
+
+// 6.8.10 ShiftExpression
+AsmType* AsmJsParser::ShiftExpression() {
+ AsmType* a = nullptr;
+ RECURSE(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): { \
+ SKIP(TOK(op)); \
+ AsmType* b = nullptr; \
+ RECURSE(b = AdditiveExpression()); \
+ if (!(a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish()))) { \
+ FAIL("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;
+ RECURSE(a = ShiftExpression());
+ for (;;) {
+ switch (scanner_.Token()) {
+#define HANDLE_CASE(op, sop, uop, dop, fop, name) \
+ case op: { \
+ SKIP(op); \
+ AsmType* b = nullptr; \
+ RECURSE(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 { \
+ FAIL("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;
+ RECURSE(a = RelationalExpression());
+ for (;;) {
+ switch (scanner_.Token()) {
+#define HANDLE_CASE(op, sop, uop, dop, fop, name) \
+ case op: { \
+ SKIP(op); \
+ AsmType* b = nullptr; \
+ RECURSE(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 { \
+ FAIL("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;
+ RECURSE(a = EqualityExpression());
+ while (Peek('&')) {
+ SKIP('&');
+ AsmType* b = nullptr;
+ RECURSE(b = EqualityExpression());
+ if (a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish())) {
+ current_function_builder_->Emit(kExprI32And);
+ a = AsmType::Signed();
+ } else {
+ FAIL("Expected intish for operator &.");
+ }
+ }
+ return a;
+}
+
+// 6.8.14 BitwiseXORExpression
+AsmType* AsmJsParser::BitwiseXORExpression() {
+ AsmType* a = nullptr;
+ RECURSE(a = BitwiseANDExpression());
+ while (Peek('^')) {
+ SKIP('^');
+ AsmType* b = nullptr;
+ RECURSE(b = BitwiseANDExpression());
+ if (a->IsA(AsmType::Intish()) && b->IsA(AsmType::Intish())) {
+ current_function_builder_->Emit(kExprI32Xor);
+ a = AsmType::Signed();
+ } else {
+ FAIL("Expected intish for operator &.");
+ }
+ }
+ return a;
+}
+
+// 6.8.15 BitwiseORExpression
+AsmType* AsmJsParser::BitwiseORExpression() {
+ AsmType* a = nullptr;
+ RECURSE(a = BitwiseXORExpression());
+ // }
+ while (Peek('|')) {
+ SKIP('|');
+ // TODO(bradnelson): Make it prettier.
+ AsmType* b = nullptr;
+ bool zero = false;
+ int old_pos;
+ size_t old_code;
+ if (scanner_.IsUnsigned() && scanner_.AsUnsigned() == 0) {
+ scanner_.Next();
+ old_pos = scanner_.GetPosition();
+ old_code = current_function_builder_->GetPosition();
+ scanner_.Rewind();
+ zero = true;
+ }
+ RECURSE(b = BitwiseXORExpression());
+ // Handle |0 specially.
+ if (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 {
+ FAIL("Expected intish for operator |.");
+ }
+ }
+ return a;
+}
+
+// 6.8.16 ConditionalExpression
+AsmType* AsmJsParser::ConditionalExpression() {
+ AsmType* test = nullptr;
+ RECURSE(test = BitwiseORExpression());
+ if (Peek('?')) {
+ SKIP('?');
+ if (!test->IsA(AsmType::Int())) {
+ FAIL("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;
+ RECURSE(cons = AssignmentExpression());
+ current_function_builder_->Emit(kExprElse);
+ SKIP(':');
+ AsmType* alt = nullptr;
+ RECURSE(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 {
+ FAIL("Type mismatch in ternary operator.");
+ }
+ } else {
+ return test;
+ }
+}
+
+// 6.8.17 ParenthesiedExpression
+AsmType* AsmJsParser::ParenthesizedExpression() {
+ call_coercion_ = nullptr;
+ AsmType* ret;
+ SKIP('(');
+ RECURSE(ret = Expression(nullptr));
+ SKIP(')');
+ return ret;
+}
+
+// 6.9 ValidateCall
+AsmType* AsmJsParser::ValidateCall() {
+ AsmType* return_type = call_coercion_;
+ call_coercion_ = nullptr;
+ AsmJsScanner::token_t function_name = Consume();
+ int32_t tmp = TempVariable(0);
+ if (Peek('[')) {
+ SKIP('[');
+ RECURSE(EqualityExpression());
+ SKIP('&');
+ if (!scanner_.IsUnsigned()) {
+ FAIL("Expected mask literal");
+ }
+ uint64_t value = scanner_.AsUnsigned();
+ if (value > 0x7fffffff) {
+ FAIL("Expected power of 2 mask");
+ }
+ if (!base::bits::IsPowerOfTwo32(static_cast<uint32_t>(1 + value))) {
+ FAIL("Expected power of 2 mask");
+ }
+ scanner_.Next();
+ current_function_builder_->EmitI32Const(static_cast<uint32_t>(value));
+ current_function_builder_->Emit(kExprI32And);
+ SKIP(']');
+ VarInfo* function_info = GetVarInfo(function_name);
+ if (function_info->kind == VarKind::kUnused) {
+ function_info->kind = VarKind::kTable;
+ function_info->mask = value;
+ function_info->index =
+ builder_->AllocateIndirectFunctions(static_cast<uint32_t>(value + 1));
+ } else {
+ if (function_info->kind != VarKind::kTable) {
+ FAIL("Expected call table");
+ }
+ if (function_info->mask != value) {
+ FAIL("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.
+ current_function_builder_->EmitSetLocal(tmp);
+ }
+ std::vector<AsmType*> param_types;
+ ZoneVector<AsmType*> param_specific_types(zone());
+ SKIP('(');
+ while (!failed_ && !Peek(')')) {
+ AsmType* t;
+ RECURSE(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();
+ FAIL("Bad function argument type");
+ }
+ if (!Peek(')')) {
+ SKIP(',');
+ }
+ }
+ SKIP(')');
+ // TODO(bradnelson): clarify how this binds, and why only float?
+ if (Peek('|') &&
+ (return_type == nullptr || return_type->IsA(AsmType::Float()))) {
+ return_type = AsmType::Signed();
+ } else if (return_type == nullptr) {
+ return_type = AsmType::Void();
+ }
+ 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) {
+ FAIL("Invalid function signature");
+ }
+ uint32_t signature_index = builder_->AddSignature(sig);
+
+ // TODO(bradnelson): Fix this to use a less error prone pattern.
+ // Reload as table might have grown.
+ VarInfo* function_info = GetVarInfo(function_name);
+ if (function_info->kind == VarKind::kUnused) {
+ function_info->kind = VarKind::kFunction;
+ function_info->function_builder = builder_->AddFunction();
+ function_info->index = function_info->function_builder->func_index();
+ function_info->type = function_type;
+ // TODO(bradnelson): Figure out the right debug scanner offset and
+ // re-enable.
+ // current_function_builder_->AddAsmWasmOffset(scanner_.GetPosition(),
+ // scanner_.GetPosition());
+ current_function_builder_->Emit(kExprCallFunction);
+ current_function_builder_->EmitDirectCallIndex(function_info->index);
+ } else if (function_info->kind == VarKind::kImportedFunction) {
+ for (auto t : param_specific_types) {
+ if (!t->IsA(AsmType::Extern())) {
+ FAIL("Imported function args must be type extern");
+ }
+ }
+ if (return_type->IsA(AsmType::Float())) {
+ FAIL("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 = builder_->AddImport(
+ function_info->import->name.data(),
+ static_cast<uint32_t>(function_info->import->name.size()), sig);
+ function_info->import->cache_index.push_back(index);
+ } else {
+ index = function_info->import->cache_index[cache_index];
+ }
+ current_function_builder_->Emit(kExprCallFunction);
+ current_function_builder_->EmitVarUint(index);
+ } else if (function_info->type->IsA(AsmType::None())) {
+ function_info->type = function_type;
+ if (function_info->kind == VarKind::kTable) {
+ current_function_builder_->EmitGetLocal(tmp);
+ // TODO(bradnelson): Figure out the right debug scanner offset and
+ // re-enable.
+ // current_function_builder_->AddAsmWasmOffset(scanner_.GetPosition(),
+ // scanner_.GetPosition());
+ current_function_builder_->Emit(kExprCallIndirect);
+ current_function_builder_->EmitVarUint(signature_index);
+ current_function_builder_->EmitVarUint(0); // table index
+ } else {
+ // current_function_builder_->AddAsmWasmOffset(scanner_.GetPosition(),
+ // scanner_.GetPosition());
+ current_function_builder_->Emit(kExprCallFunction);
+ current_function_builder_->EmitDirectCallIndex(function_info->index);
+ }
+ } else if (function_info->kind > VarKind::kImportedFunction) {
+ AsmCallableType* callable = function_info->type->AsCallableType();
+ if (!callable) {
+ FAIL("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 {
+ FAIL("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())) {
+ int32_t tmp_x = TempVariable(0);
+ int32_t tmp_y = TempVariable(1);
+ for (size_t i = 1; i < param_specific_types.size(); ++i) {
+ current_function_builder_->EmitSetLocal(tmp_x);
+ current_function_builder_->EmitTeeLocal(tmp_y);
+ current_function_builder_->EmitGetLocal(tmp_x);
+ 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);
+ current_function_builder_->Emit(kExprElse);
+ current_function_builder_->EmitGetLocal(tmp_y);
+ current_function_builder_->Emit(kExprEnd);
+ }
+ } else {
+ UNREACHABLE();
+ }
+ break;
+
+ case VarKind::kMathAbs:
+ if (param_specific_types[0]->IsA(AsmType::Signed())) {
+ int32_t tmp = TempVariable(0);
+ current_function_builder_->EmitTeeLocal(tmp);
+ current_function_builder_->Emit(kExprI32Clz);
+ current_function_builder_->EmitWithU8(kExprIf, kLocalI32);
+ current_function_builder_->EmitGetLocal(tmp);
+ current_function_builder_->Emit(kExprElse);
+ current_function_builder_->EmitI32Const(0);
+ current_function_builder_->EmitGetLocal(tmp);
+ 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 {
+ if (function_info->kind != VarKind::kFunction &&
+ function_info->kind != VarKind::kTable) {
+ FAIL("Function name collides with variable");
+ }
+ AsmCallableType* callable = function_info->type->AsCallableType();
+ if (!callable ||
+ !callable->CanBeInvokedWith(return_type, param_specific_types)) {
+ FAIL("Function use doesn't match definition");
+ }
+ if (function_info->kind == VarKind::kTable) {
+ current_function_builder_->EmitGetLocal(tmp);
+ // TODO(bradnelson): Figure out the right debug scanner offset and
+ // re-enable.
+ // current_function_builder_->AddAsmWasmOffset(scanner_.GetPosition(),
+ // scanner_.GetPosition());
+ current_function_builder_->Emit(kExprCallIndirect);
+ current_function_builder_->EmitVarUint(signature_index);
+ current_function_builder_->EmitVarUint(0); // table index
+ } else {
+ // TODO(bradnelson): Figure out the right debug scanner offset and
+ // re-enable.
+ // current_function_builder_->AddAsmWasmOffset(scanner_.GetPosition(),
+ // scanner_.GetPosition());
+ current_function_builder_->Emit(kExprCallFunction);
+ current_function_builder_->EmitDirectCallIndex(function_info->index);
+ }
+ }
+
+ return return_type;
+}
+
+#undef FAILURE_RETURN
+#define FAILURE_RETURN
+
+// 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();
+ SKIP('[');
+ if (scanner_.IsUnsigned()) {
+ // TODO(bradnelson): Check more things.
+ uint64_t offset = scanner_.AsUnsigned();
+ if (offset > 0x7fffffff || offset * size > 0x7fffffff) {
+ FAIL("Heap access out of range");
+ }
+ scanner_.Next();
+ if (Peek(']')) {
+ SKIP(']');
+ 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());
+ SKIP(TOK(SAR));
+ if (!scanner_.IsUnsigned()) {
+ FAIL("Expected shift of word size");
+ }
+ if (scanner_.AsUnsigned() > 3) {
+ FAIL("Expected valid heap access shift");
+ }
+ if ((1 << scanner_.AsUnsigned()) != size) {
+ FAIL("Expected heap access shift to match heap view");
+ }
+ scanner_.Next();
+ // 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");
+ }
+ SKIP(']');
+ // 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();
+ SKIP('(');
+ call_coercion_ = AsmType::Float();
+ 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");
+ }
+ SKIP(')');
+}
+
+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;
+ if (Peek('-')) {
vogelheim 2017/03/17 18:01:29 This whole thing - in addition to being generally
bradn 2017/03/24 03:58:19 Not quite following your meaning. This probably do
+ SKIP('-');
+ if (!scanner_.IsUnsigned() || scanner_.AsUnsigned() > 0x80000000) {
+ break;
+ }
+ value = -static_cast<int32_t>(scanner_.AsUnsigned());
+ } else {
+ if (!scanner_.IsUnsigned() || scanner_.AsUnsigned() > 0x7fffffff) {
+ break;
+ }
+ value = static_cast<int32_t>(scanner_.AsUnsigned());
+ }
+ cases->push_back(value);
+ } else if (Peek(AsmJsScanner::kEndOfInput)) {
+ break;
+ }
+ scanner_.Next();
+ }
+ scanner_.Seek(start);
+}
+
+} // namespace wasm
+} // namespace internal
+} // namespace v8

Powered by Google App Engine
This is Rietveld 408576698