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