Commit 974f4a80 authored by jpp's avatar jpp Committed by Commit bot

V8. ASM-2-WASM. Validator V2.

This is a rewrite of the ASM validator. This one follows the spec instead of using the AST visitors.

BUG= https://bugs.chromium.org/p/v8/issues/detail?id=4203
TEST=cctest/asmjs/test-asm-typer
TEST=cctest/asmjs/test-typing-asm
LOG=N

Review-Url: https://codereview.chromium.org/2071343003
Cr-Commit-Position: refs/heads/master@{#37694}
parent cd95c600
......@@ -788,6 +788,8 @@ v8_source_set("v8_base") {
"src/arguments.h",
"src/asmjs/asm-js.cc",
"src/asmjs/asm-js.h",
"src/asmjs/asm-typer.cc",
"src/asmjs/asm-typer.h",
"src/asmjs/asm-types.cc",
"src/asmjs/asm-types.h",
"src/asmjs/asm-wasm-builder.cc",
......
# Keep in sync with test/cctest/asmjs/OWNERS.
set noparent
ahaas@chromium.org
......
// Copyright 2016 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-typer.h"
#include <limits>
#include <string>
#include "src/v8.h"
#include "src/asmjs/asm-types.h"
#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/base/bits.h"
#include "src/codegen.h"
#include "src/globals.h"
#include "src/type-cache.h"
#include "src/utils.h"
#define FAIL(node, msg) \
do { \
int line = node->position() == kNoSourcePosition \
? -1 \
: script_->GetLineNumber(node->position()); \
base::OS::SNPrintF(error_message_, sizeof(error_message_), \
"asm: line %d: %s\n", line + 1, msg); \
return AsmType::None(); \
} while (false)
#define RECURSE(call) \
do { \
if (GetCurrentStackPosition() < stack_limit_) { \
stack_overflow_ = true; \
FAIL(root_, "Stack overflow while parsing asm.js module."); \
} \
\
AsmType* result = (call); \
if (stack_overflow_) { \
return AsmType::None(); \
} \
\
if (result == AsmType::None()) { \
return AsmType::None(); \
} \
} while (false)
namespace v8 {
namespace internal {
namespace wasm {
using v8::internal::AstNode;
using v8::internal::GetCurrentStackPosition;
// ----------------------------------------------------------------------------
// Implementation of AsmTyper::FlattenedStatements
AsmTyper::FlattenedStatements::FlattenedStatements(Zone* zone,
ZoneList<Statement*>* s)
: context_stack_(zone) {
context_stack_.emplace_back(Context(s));
}
Statement* AsmTyper::FlattenedStatements::Next() {
for (;;) {
if (context_stack_.empty()) {
return nullptr;
}
Context* current = &context_stack_.back();
if (current->statements_->length() <= current->next_index_) {
context_stack_.pop_back();
continue;
}
Statement* current_statement =
current->statements_->at(current->next_index_++);
if (current_statement->IsBlock()) {
context_stack_.emplace_back(
Context(current_statement->AsBlock()->statements()));
continue;
}
return current_statement;
}
}
// ----------------------------------------------------------------------------
// Implementation of AsmTyper::VariableInfo
AsmTyper::VariableInfo* AsmTyper::VariableInfo::Clone(Zone* zone) const {
CHECK(standard_member_ != kNone);
auto* new_var_info = new (zone) VariableInfo(type_);
new_var_info->standard_member_ = standard_member_;
new_var_info->mutability_ = mutability_;
return new_var_info;
}
void AsmTyper::VariableInfo::FirstForwardUseIs(VariableProxy* var) {
DCHECK(first_forward_use_ == nullptr);
missing_definition_ = true;
first_forward_use_ = var;
}
// ----------------------------------------------------------------------------
// Implementation of AsmTyper
AsmTyper::AsmTyper(Isolate* isolate, Zone* zone, Script* script,
FunctionLiteral* root)
: isolate_(isolate),
zone_(zone),
script_(script),
root_(root),
forward_definitions_(zone),
stdlib_types_(zone),
stdlib_math_types_(zone),
global_scope_(ZoneHashMap::PointersMatch,
ZoneHashMap::kDefaultHashMapCapacity,
ZoneAllocationPolicy(zone)),
local_scope_(ZoneHashMap::PointersMatch,
ZoneHashMap::kDefaultHashMapCapacity,
ZoneAllocationPolicy(zone)),
stack_limit_(isolate->stack_guard()->real_climit()),
node_types_(zone_) {
InitializeStdlib();
module_info_.set_standard_member(kModule);
}
namespace {
bool ValidAsmIdentifier(Handle<String> name) {
static const char* kInvalidAsmNames[] = {"eval", "arguments"};
for (size_t ii = 0; ii < arraysize(kInvalidAsmNames); ++ii) {
if (strcmp(name->ToCString().get(), kInvalidAsmNames[ii]) == 0) {
return false;
}
}
return true;
}
} // namespace
void AsmTyper::InitializeStdlib() {
auto* d = AsmType::Double();
auto* dq = AsmType::DoubleQ();
auto* dq2d = AsmType::Function(zone_, d);
dq2d->AsFunctionType()->AddArgument(dq);
auto* dqdq2d = AsmType::Function(zone_, d);
dqdq2d->AsFunctionType()->AddArgument(dq);
dqdq2d->AsFunctionType()->AddArgument(dq);
auto* f = AsmType::Float();
auto* fq = AsmType::FloatQ();
auto* fq2f = AsmType::Function(zone_, f);
fq2f->AsFunctionType()->AddArgument(fq);
auto* s = AsmType::Signed();
auto* s2s = AsmType::Function(zone_, s);
s2s->AsFunctionType()->AddArgument(s);
auto* i = AsmType::Int();
auto* ii2s = AsmType::Function(zone_, s);
ii2s->AsFunctionType()->AddArgument(i);
ii2s->AsFunctionType()->AddArgument(i);
auto* minmax_d = AsmType::MinMaxType(zone_, d, d);
auto* minmax_i = AsmType::MinMaxType(zone_, s, i);
auto* minmax = AsmType::OverloadedFunction(zone_);
minmax->AsOverloadedFunctionType()->AddOverload(minmax_i);
minmax->AsOverloadedFunctionType()->AddOverload(minmax_d);
auto* fround = AsmType::FroundType(zone_);
auto* abs = AsmType::OverloadedFunction(zone_);
abs->AsOverloadedFunctionType()->AddOverload(s2s);
abs->AsOverloadedFunctionType()->AddOverload(dq2d);
abs->AsOverloadedFunctionType()->AddOverload(fq2f);
auto* ceil = AsmType::OverloadedFunction(zone_);
ceil->AsOverloadedFunctionType()->AddOverload(dq2d);
ceil->AsOverloadedFunctionType()->AddOverload(fq2f);
auto* floor = ceil;
auto* sqrt = ceil;
struct StandardMemberInitializer {
const char* name;
StandardMember standard_member;
AsmType* type;
};
const StandardMemberInitializer stdlib[] = {{"Infinity", kInfinity, d},
{"NaN", kNaN, d},
#define asm_TYPED_ARRAYS(V) \
V(Uint8) \
V(Int8) \
V(Uint16) \
V(Int16) \
V(Uint32) \
V(Int32) \
V(Float32) \
V(Float64)
#define asm_TYPED_ARRAY(TypeName) \
{#TypeName "Array", kNone, AsmType::TypeName##Array()},
asm_TYPED_ARRAYS(asm_TYPED_ARRAY)
#undef asm_TYPED_ARRAY
};
for (size_t ii = 0; ii < arraysize(stdlib); ++ii) {
stdlib_types_[stdlib[ii].name] = new (zone_) VariableInfo(stdlib[ii].type);
stdlib_types_[stdlib[ii].name]->set_standard_member(
stdlib[ii].standard_member);
stdlib_types_[stdlib[ii].name]->set_mutability(
VariableInfo::kImmutableGlobal);
}
const StandardMemberInitializer math[] = {
{"PI", kMathPI, d},
{"E", kMathE, d},
{"LN2", kMathLN2, d},
{"LN10", kMathLN10, d},
{"LOG2E", kMathLOG2E, d},
{"LOG10E", kMathLOG10E, d},
{"SQRT2", kMathSQRT2, d},
{"SQRT1_2", kMathSQRT1_2, d},
{"imul", kMathImul, ii2s},
{"abs", kMathAbs, abs},
{"ceil", kMathCeil, ceil},
{"floor", kMathFloor, floor},
{"fround", kMathFround, fround},
{"pow", kMathPow, dqdq2d},
{"exp", kMathExp, dq2d},
{"log", kMathLog, dq2d},
{"min", kMathMin, minmax},
{"max", kMathMax, minmax},
{"sqrt", kMathSqrt, sqrt},
{"cos", kMathCos, dq2d},
{"sin", kMathSin, dq2d},
{"tan", kMathTan, dq2d},
{"acos", kMathAcos, dq2d},
{"asin", kMathAsin, dq2d},
{"atan", kMathAtan, dq2d},
{"atan2", kMathAtan2, dqdq2d},
};
for (size_t ii = 0; ii < arraysize(math); ++ii) {
stdlib_math_types_[math[ii].name] = new (zone_) VariableInfo(math[ii].type);
stdlib_math_types_[math[ii].name]->set_standard_member(
math[ii].standard_member);
stdlib_math_types_[math[ii].name]->set_mutability(
VariableInfo::kImmutableGlobal);
}
}
// Used for 5.5 GlobalVariableTypeAnnotations
AsmTyper::VariableInfo* AsmTyper::ImportLookup(Property* import) {
auto* obj = import->obj();
auto* key = import->key()->AsLiteral();
ObjectTypeMap* stdlib = &stdlib_types_;
if (auto* obj_as_property = obj->AsProperty()) {
// This can only be stdlib.Math
auto* math_name = obj_as_property->key()->AsLiteral();
if (math_name == nullptr || !math_name->IsPropertyName()) {
return nullptr;
}
if (!math_name->AsPropertyName()->IsUtf8EqualTo(CStrVector("Math"))) {
return nullptr;
}
auto* stdlib_var_proxy = obj_as_property->obj()->AsVariableProxy();
if (stdlib_var_proxy == nullptr) {
return nullptr;
}
obj = stdlib_var_proxy;
stdlib = &stdlib_math_types_;
}
auto* obj_as_var_proxy = obj->AsVariableProxy();
if (obj_as_var_proxy == nullptr) {
return nullptr;
}
auto* obj_info = Lookup(obj_as_var_proxy->var());
if (obj_info == nullptr) {
return nullptr;
}
if (obj_info->IsFFI()) {
// For FFI we can't validate import->key, so assume this is OK.
return obj_info;
}
base::SmartArrayPointer<char> aname = key->AsPropertyName()->ToCString();
ObjectTypeMap::iterator i = stdlib->find(std::string(aname.get()));
if (i == stdlib->end()) {
return nullptr;
}
return i->second;
}
AsmTyper::VariableInfo* AsmTyper::Lookup(Variable* variable) {
ZoneHashMap* scope = in_function_ ? &local_scope_ : &global_scope_;
ZoneHashMap::Entry* entry =
scope->Lookup(variable, ComputePointerHash(variable));
if (entry == nullptr && in_function_) {
entry = global_scope_.Lookup(variable, ComputePointerHash(variable));
}
if (entry == nullptr && !module_name_.is_null() &&
module_name_->Equals(*variable->name())) {
return &module_info_;
}
return entry ? reinterpret_cast<VariableInfo*>(entry->value) : nullptr;
}
void AsmTyper::AddForwardReference(VariableProxy* proxy, VariableInfo* info) {
info->FirstForwardUseIs(proxy);
forward_definitions_.push_back(info);
}
bool AsmTyper::AddGlobal(Variable* variable, VariableInfo* info) {
// We can't DCHECK(!in_function_) because function may actually install global
// names (forward defined functions and function tables.)
DCHECK(info->mutability() != VariableInfo::kInvalidMutability);
DCHECK(info->IsGlobal());
DCHECK(ValidAsmIdentifier(variable->name()));
if (!module_name_.is_null() && module_name_->Equals(*variable->name())) {
return false;
}
ZoneHashMap::Entry* entry = global_scope_.LookupOrInsert(
variable, ComputePointerHash(variable), ZoneAllocationPolicy(zone_));
if (entry->value != nullptr) {
return false;
}
entry->value = info;
return true;
}
bool AsmTyper::AddLocal(Variable* variable, VariableInfo* info) {
DCHECK(in_function_);
DCHECK(info->mutability() != VariableInfo::kInvalidMutability);
DCHECK(!info->IsGlobal());
DCHECK(ValidAsmIdentifier(variable->name()));
ZoneHashMap::Entry* entry = local_scope_.LookupOrInsert(
variable, ComputePointerHash(variable), ZoneAllocationPolicy(zone_));
if (entry->value != nullptr) {
return false;
}
entry->value = info;
return true;
}
void AsmTyper::SetTypeOf(AstNode* node, AsmType* type) {
DCHECK_NE(type, AsmType::None());
auto** node_type = &node_types_[node];
DCHECK(*node_type == nullptr);
*node_type = type;
}
AsmType* AsmTyper::TypeOf(AstNode* node) const {
auto node_type_iter = node_types_.find(node);
if (node_type_iter == node_types_.end()) {
return AsmType::None();
}
return node_type_iter->second;
}
AsmTyper::StandardMember AsmTyper::VariableAsStandardMember(Variable* var) {
auto* var_info = Lookup(var);
if (var_info == nullptr) {
return kNone;
}
return var_info->standard_member();
}
bool AsmTyper::Validate() {
if (!AsmType::None()->IsExactly(ValidateModule(root_))) {
return true;
}
return false;
}
namespace {
bool IsUseAsmDirective(Statement* first_statement) {
ExpressionStatement* use_asm = first_statement->AsExpressionStatement();
if (use_asm == nullptr) {
return false;
}
Literal* use_asm_literal = use_asm->expression()->AsLiteral();
if (use_asm_literal == nullptr) {
return false;
}
return use_asm_literal->raw_value()->AsString()->IsOneByteEqualTo("use asm");
}
Assignment* ExtractInitializerExpression(Statement* statement) {
auto* expr_stmt = statement->AsExpressionStatement();
if (expr_stmt == nullptr) {
// Done with initializers.
return nullptr;
}
auto* assign = expr_stmt->expression()->AsAssignment();
if (assign == nullptr) {
// Done with initializers.
return nullptr;
}
if (assign->op() != Token::INIT) {
// Done with initializers.
return nullptr;
}
return assign;
}
} // namespace
// 6.1 ValidateModule
AsmType* AsmTyper::ValidateModule(FunctionLiteral* fun) {
Scope* scope = fun->scope();
if (!scope->is_function_scope()) FAIL(fun, "Not at function scope.");
if (!ValidAsmIdentifier(fun->name()))
FAIL(fun, "Invalid asm.js identifier in module name.");
module_name_ = fun->name();
// Allowed parameters: Stdlib, FFI, Mem
static const uint32_t MaxModuleParameters = 3;
if (scope->num_parameters() > MaxModuleParameters) {
FAIL(fun, "asm.js modules may not have more than three parameters.");
}
struct {
StandardMember standard_member;
AsmType* type;
} kModuleParamInfo[3] = {
{kStdlib, AsmType::None()},
{kFFI, AsmType::FFIType(zone_)},
{kHeap, AsmType::None()},
};
for (int ii = 0; ii < scope->num_parameters(); ++ii) {
Variable* param = scope->parameter(ii);
DCHECK(param);
if (!ValidAsmIdentifier(param->name())) {
FAIL(fun, "Invalid asm.js identifier in module parameter.");
}
auto* param_info = new (zone_) VariableInfo();
param_info->set_mutability(VariableInfo::kImmutableGlobal);
param_info->set_standard_member(kModuleParamInfo[ii].standard_member);
param_info->set_type(kModuleParamInfo[ii].type);
if (!AddGlobal(param, param_info)) {
FAIL(fun, "Redeclared identifier in module parameter.");
}
}
ZoneVector<Assignment*> function_pointer_tables(zone_);
FlattenedStatements iter(zone_, fun->body());
auto* use_asm_directive = iter.Next();
if (use_asm_directive == nullptr || !IsUseAsmDirective(use_asm_directive)) {
FAIL(fun, "Missing \"use asm\".");
}
ReturnStatement* module_return = nullptr;
// *VIOLATION* The spec states that globals should be followed by function
// declarations, which should be followed by function pointer tables, followed
// by the module export (return) statement. Our AST might be rearraged by the
// parser, so we can't rely on it being in source code order.
while (Statement* current = iter.Next()) {
if (auto* assign = ExtractInitializerExpression(current)) {
if (assign->value()->IsArrayLiteral()) {
// Save function tables for later validation.
function_pointer_tables.push_back(assign);
} else {
RECURSE(ValidateGlobalDeclaration(assign));
}
continue;
}
if (auto* current_as_return = current->AsReturnStatement()) {
if (module_return != nullptr) {
FAIL(fun, "Multiple export statements.");
}
module_return = current_as_return;
continue;
}
FAIL(current, "Invalid top-level statement in asm.js module.");
}
ZoneList<Declaration*>* decls = scope->declarations();
for (int ii = 0; ii < decls->length(); ++ii) {
Declaration* decl = decls->at(ii);
if (FunctionDeclaration* fun_decl = decl->AsFunctionDeclaration()) {
RECURSE(ValidateFunction(fun_decl));
continue;
}
}
for (auto* function_table : function_pointer_tables) {
RECURSE(ValidateFunctionTable(function_table));
}
for (int ii = 0; ii < decls->length(); ++ii) {
Declaration* decl = decls->at(ii);
if (decl->IsFunctionDeclaration()) {
continue;
}
VariableDeclaration* var_decl = decl->AsVariableDeclaration();
if (var_decl == nullptr) {
FAIL(decl, "Invalid asm.js declaration.");
}
auto* var_proxy = var_decl->proxy();
if (var_proxy == nullptr) {
FAIL(decl, "Invalid asm.js declaration.");
}
if (Lookup(var_proxy->var()) == nullptr) {
FAIL(decl, "Global variable missing initializer in asm.js module.");
}
}
// 6.2 ValidateExport
if (module_return == nullptr) {
FAIL(fun, "Missing asm.js module export.");
}
for (auto* forward_def : forward_definitions_) {
if (forward_def->missing_definition()) {
FAIL(forward_def->first_forward_use(),
"Missing definition for forward declared identifier.");
}
}
RECURSE(ValidateExport(module_return));
return AsmType::Int(); // Any type that is not AsmType::None();
}
namespace {
bool IsDoubleAnnotation(BinaryOperation* binop) {
// *VIOLATION* The parser replaces uses of +x with x*1.0.
if (binop->op() != Token::MUL) {
return false;
}
auto* right_as_literal = binop->right()->AsLiteral();
if (right_as_literal == nullptr) {
return false;
}
return right_as_literal->raw_value()->ContainsDot() &&
right_as_literal->raw_value()->AsNumber() == 1.0;
}
bool IsIntAnnotation(BinaryOperation* binop) {
if (binop->op() != Token::BIT_OR) {
return false;
}
auto* right_as_literal = binop->right()->AsLiteral();
if (right_as_literal == nullptr) {
return false;
}
return !right_as_literal->raw_value()->ContainsDot() &&
right_as_literal->raw_value()->AsNumber() == 0.0;
}
} // namespace
AsmType* AsmTyper::ValidateGlobalDeclaration(Assignment* assign) {
DCHECK(!assign->is_compound());
if (assign->is_compound()) {
FAIL(assign,
"Compound assignment not supported when declaring global variables.");
}
auto* target = assign->target();
if (!target->IsVariableProxy()) {
FAIL(target, "Module assignments may only assign to globals.");
}
auto* target_variable = target->AsVariableProxy()->var();
auto* target_info = Lookup(target_variable);
if (target_info != nullptr) {
FAIL(target, "Redefined global variable.");
}
auto* value = assign->value();
// Not all types of assignment are allowed by asm.js. See
// 5.5 Global Variable Type Annotations.
if (value->IsLiteral() || value->IsCall()) {
AsmType* type = nullptr;
RECURSE(type = VariableTypeAnnotations(value));
target_info = new (zone_) VariableInfo(type);
target_info->set_mutability(VariableInfo::kMutableGlobal);
} else if (value->IsProperty()) {
target_info = ImportLookup(value->AsProperty());
if (target_info == nullptr) {
FAIL(assign, "Invalid import.");
}
CHECK(target_info->mutability() == VariableInfo::kImmutableGlobal);
if (!target_info->IsFFI()) {
target_info = target_info->Clone(zone_);
} else {
// create a new target info that represents a foreign variable.
DCHECK(target_info->type()->AsFFIType() != nullptr);
target_info = new (zone_) VariableInfo(target_info->type());
target_info->set_mutability(VariableInfo::kImmutableGlobal);
}
} else if (value->IsBinaryOperation()) {
// This should either be:
//
// var <> = ffi.<>|0
//
// or
//
// var <> = +ffi.<>
auto* value_binop = value->AsBinaryOperation();
auto* left = value_binop->left();
AsmType* import_type = nullptr;
if (IsDoubleAnnotation(value_binop)) {
import_type = AsmType::Double();
} else if (IsIntAnnotation(value_binop)) {
import_type = AsmType::Int();
} else {
FAIL(value,
"Invalid initializer for foreign import - unrecognized annotation.");
}
if (!left->IsProperty()) {
FAIL(value,
"Invalid initializer for foreign import - must import member.");
}
target_info = ImportLookup(left->AsProperty());
if (target_info == nullptr) {
// TODO(jpp): this error message is innacurate: this may fail if the
// object lookup fails, or if the property lookup fails, or even if the
// import is bogus like a().c.
FAIL(value,
"Invalid initializer for foreign import - object lookup failed.");
}
CHECK(target_info->mutability() == VariableInfo::kImmutableGlobal);
if (!target_info->IsFFI()) {
FAIL(value,
"Invalid initializer for foreign import - object is not the ffi.");
}
// Create a new target info that represents the foreign import.
DCHECK(target_info->type()->AsFFIType() != nullptr);
target_info = new (zone_) VariableInfo(import_type);
target_info->set_mutability(VariableInfo::kMutableGlobal);
} else if (value->IsCallNew()) {
AsmType* type = nullptr;
RECURSE(type = NewHeapView(value->AsCallNew()));
target_info = new (zone_) VariableInfo(type);
target_info->set_mutability(VariableInfo::kImmutableGlobal);
}
if (target_info == nullptr) {
FAIL(assign, "Invalid global variable initializer.");
}
if (!ValidAsmIdentifier(target_variable->name())) {
FAIL(target, "Invalid asm.js identifier in global variable.");
}
if (!AddGlobal(target_variable, target_info)) {
FAIL(assign, "Redeclared global identifier.");
}
DCHECK(target_info->type() != AsmType::None());
return target_info->type();
}
// 6.2 ValidateExport
AsmType* AsmTyper::ExportType(VariableProxy* fun_export) {
auto* fun_info = Lookup(fun_export->var());
if (fun_info == nullptr) {
FAIL(fun_export, "Undefined identifier in asm.js module export.");
}
if (fun_info->standard_member() != kNone) {
FAIL(fun_export, "Module cannot export standard library functions.");
}
auto* type = fun_info->type();
if (type->AsFFIType() != nullptr) {
FAIL(fun_export, "Module cannot export foreign functions.");
}
if (type->AsFunctionTableType() != nullptr) {
FAIL(fun_export, "Module cannot export function tables.");
}
if (fun_info->type()->AsFunctionType() == nullptr) {
FAIL(fun_export, "Module export is not an asm.js function.");
}
return type;
}
AsmType* AsmTyper::ValidateExport(ReturnStatement* exports) {
// asm.js modules can export single functions, or multiple functions in an
// object literal.
if (auto* fun_export = exports->expression()->AsVariableProxy()) {
// Exporting single function.
AsmType* export_type;
RECURSE(export_type = ExportType(fun_export));
return export_type;
}
if (auto* obj_export = exports->expression()->AsObjectLiteral()) {
// Exporting object literal.
for (auto* prop : *obj_export->properties()) {
if (!prop->key()->IsLiteral()) {
FAIL(prop->key(),
"Only normal object properties may be used in the export object "
"literal.");
}
auto* export_obj = prop->value()->AsVariableProxy();
if (export_obj == nullptr) {
FAIL(prop->value(), "Exported value must be an asm.js function name.");
}
RECURSE(ExportType(export_obj));
}
return AsmType::Int();
}
FAIL(exports, "Unrecognized expression in asm.js module export expression.");
}
// 6.3 ValidateFunctionTable
AsmType* AsmTyper::ValidateFunctionTable(Assignment* assign) {
if (assign->is_compound()) {
FAIL(assign,
"Compound assignment not supported when declaring global variables.");
}
auto* target = assign->target();
if (!target->IsVariableProxy()) {
FAIL(target, "Module assignments may only assign to globals.");
}
auto* target_variable = target->AsVariableProxy()->var();
auto* value = assign->value()->AsArrayLiteral();
CHECK(value != nullptr);
ZoneList<Expression*>* pointers = value->values();
// The function table size must be n = 2 ** m, for m >= 0;
// TODO(jpp): should this be capped?
if (!base::bits::IsPowerOfTwo32(pointers->length())) {
FAIL(assign, "Invalid length for function pointer table.");
}
AsmType* table_element_type = nullptr;
AsmCallableType* callable_type = nullptr;
for (auto* initializer : *pointers) {
auto* var_proxy = initializer->AsVariableProxy();
if (var_proxy == nullptr) {
FAIL(initializer,
"Function pointer table initializer must be a function name.");
}
auto* var_info = Lookup(var_proxy->var());
if (var_info == nullptr) {
FAIL(var_proxy,
"Undefined identifier in function pointer table initializer.");
}
if (var_info->standard_member() != kNone) {
FAIL(initializer,
"Function pointer table must not be a member of the standard "
"library.");
}
auto* initializer_callable = var_info->type()->AsFunctionType();
if (initializer_callable == nullptr) {
FAIL(initializer,
"Function pointer table initializer must be an asm.js function.");
}
DCHECK(var_info->type()->AsFFIType() == nullptr);
DCHECK(var_info->type()->AsFunctionTableType() == nullptr);
if (callable_type == nullptr) {
table_element_type = var_info->type();
callable_type = initializer_callable;
} else if (callable_type->ValidateCall(initializer_callable->ReturnType(),
initializer_callable->Arguments()) ==
AsmType::None()) {
FAIL(initializer, "Type mismatch in function pointer table initializer.");
}
}
auto* target_info = Lookup(target_variable);
if (target_info == nullptr) {
// Function pointer tables are the last entities to be validates, so this is
// unlikely to happen: only unreferenced function tables will not already
// have an entry in the global scope.
target_info = new (zone_) VariableInfo(AsmType::FunctionTableType(
zone_, pointers->length(), table_element_type));
target_info->set_mutability(VariableInfo::kImmutableGlobal);
if (!ValidAsmIdentifier(target_variable->name())) {
FAIL(target, "Invalid asm.js identifier in function table name.");
}
if (!AddGlobal(target_variable, target_info)) {
DCHECK(false);
FAIL(assign, "Redeclared global identifier in function table name.");
}
return target_info->type();
}
auto* target_info_table = target_info->type()->AsFunctionTableType();
if (target_info_table == nullptr) {
FAIL(assign, "Identifier redefined as function pointer table.");
}
if (!target_info->missing_definition()) {
FAIL(assign, "Identifier redefined (function table name).");
}
if (target_info_table->length() != pointers->length()) {
FAIL(assign, "Function table size mismatch.");
}
auto* function_type = callable_type->AsFunctionType();
if (target_info_table->ValidateCall(function_type->ReturnType(),
function_type->Arguments()) ==
AsmType::None()) {
FAIL(assign, "Function table initializer does not match previous type.");
}
target_info->MarkDefined();
DCHECK(target_info->type() == AsmType::None());
SetTypeOf(value, target_info->type());
return target_info->type();
}
// 6.4 ValidateFunction
AsmType* AsmTyper::ValidateFunction(FunctionDeclaration* fun_decl) {
FunctionScope _(this);
// Extract parameter types.
auto* fun = fun_decl->fun();
auto* fun_decl_proxy = fun_decl->proxy();
if (fun_decl_proxy == nullptr) {
FAIL(fun_decl, "Anonymous functions are not support in asm.js.");
}
Statement* current;
FlattenedStatements iter(zone_, fun->body());
size_t annotated_parameters = 0;
// 5.3 Function type annotations
// * parameters
ZoneVector<AsmType*> parameter_types(zone_);
for (; (current = iter.Next()) != nullptr; ++annotated_parameters) {
auto* stmt = current->AsExpressionStatement();
if (stmt == nullptr) {
// Done with parameters.
break;
}
auto* expr = stmt->expression()->AsAssignment();
if (expr == nullptr || expr->is_compound()) {
// Done with parameters.
break;
}
auto* proxy = expr->target()->AsVariableProxy();
if (proxy == nullptr) {
// Done with parameters.
break;
}
auto* param = proxy->var();
if (param->location() != VariableLocation::PARAMETER ||
param->index() != annotated_parameters) {
// Done with parameters.
break;
}
AsmType* type;
RECURSE(type = ParameterTypeAnnotations(param, expr->value()));
DCHECK(type->IsParameterType());
auto* param_info = new (zone_) VariableInfo(type);
param_info->set_mutability(VariableInfo::kLocal);
if (!ValidAsmIdentifier(proxy->name())) {
FAIL(proxy, "Invalid asm.js identifier in parameter name.");
}
if (!AddLocal(param, param_info)) {
FAIL(proxy, "Redeclared parameter.");
}
parameter_types.push_back(type);
}
if (annotated_parameters != fun->parameter_count()) {
FAIL(fun_decl, "Incorrect parameter type annotations.");
}
// 5.3 Function type annotations
// * locals
for (; current; current = iter.Next()) {
auto* initializer = ExtractInitializerExpression(current);
if (initializer == nullptr) {
// Done with locals.
break;
}
auto* local = initializer->target()->AsVariableProxy();
if (local == nullptr) {
// Done with locals. It should never happen. Even if it does, the asm.js
// code should not declare any other locals after this point, so we assume
// this is OK. If any other variable declaration is found we report a
// validation error.
DCHECK(false);
break;
}
AsmType* type;
RECURSE(type = VariableTypeAnnotations(initializer->value()));
auto* local_info = new (zone_) VariableInfo(type);
local_info->set_mutability(VariableInfo::kLocal);
if (!ValidAsmIdentifier(local->name())) {
FAIL(local, "Invalid asm.js identifier in local variable.");
}
if (!AddLocal(local->var(), local_info)) {
FAIL(initializer, "Redeclared local.");
}
}
// 5.2 Return Type Annotations
// *VIOLATION* we peel blocks to find the last statement in the asm module
// because the parser may introduce synthetic blocks.
ZoneList<Statement*>* statements = fun->body();
do {
if (statements->length() == 0) {
return_type_ = AsmType::Void();
} else {
auto* last_statement = statements->last();
auto* as_block = last_statement->AsBlock();
if (as_block != nullptr) {
statements = as_block->statements();
} else {
// We don't check whether AsReturnStatement() below returns non-null --
// we leave that to the ReturnTypeAnnotations method.
RECURSE(return_type_ =
ReturnTypeAnnotations(last_statement->AsReturnStatement()));
}
}
} while (return_type_ == AsmType::None());
DCHECK(return_type_->IsReturnType());
for (auto* decl : *fun->scope()->declarations()) {
auto* var_decl = decl->AsVariableDeclaration();
if (var_decl == nullptr) {
FAIL(decl, "Functions may only define inner variables.");
}
auto* var_proxy = var_decl->proxy();
if (var_proxy == nullptr) {
FAIL(decl, "Invalid local declaration declaration.");
}
auto* var_info = Lookup(var_proxy->var());
if (var_info == nullptr || var_info->IsGlobal()) {
FAIL(decl, "Local variable missing initializer in asm.js module.");
}
}
for (; current; current = iter.Next()) {
AsmType* current_type;
RECURSE(current_type = ValidateStatement(current));
}
auto* fun_type = AsmType::Function(zone_, return_type_);
auto* fun_type_as_function = fun_type->AsFunctionType();
for (auto* param_type : parameter_types) {
fun_type_as_function->AddArgument(param_type);
}
auto* fun_var = fun_decl_proxy->var();
auto* fun_info = new (zone_) VariableInfo(fun_type);
fun_info->set_mutability(VariableInfo::kImmutableGlobal);
auto* old_fun_type = Lookup(fun_var);
if (old_fun_type == nullptr) {
if (!ValidAsmIdentifier(fun_var->name())) {
FAIL(fun_decl_proxy, "Invalid asm.js identifier in function name.");
}
if (!AddGlobal(fun_var, fun_info)) {
DCHECK(false);
FAIL(fun_decl, "Redeclared global identifier.");
}
return fun_type;
}
// Not necessarily an error -- fun_decl might have been used before being
// defined. If that's the case, then the type in the global environment must
// be the same as the type inferred by the parameter/return type annotations.
auto* old_fun_callable = old_fun_type->type()->AsCallableType();
if (old_fun_callable == nullptr) {
FAIL(fun_decl, "Identifier redefined as function.");
}
if (!old_fun_type->missing_definition()) {
FAIL(fun_decl, "Identifier redefined (function name).");
}
if (old_fun_callable->ValidateCall(fun_type_as_function->ReturnType(),
fun_type_as_function->Arguments()) ==
AsmType::None()) {
FAIL(fun_decl, "Signature mismatch when defining function.");
}
old_fun_type->MarkDefined();
SetTypeOf(fun, fun_type);
return fun_type;
}
// 6.5 ValidateStatement
AsmType* AsmTyper::ValidateStatement(Statement* statement) {
switch (statement->node_type()) {
default:
FAIL(statement, "Statement type invalid for asm.js.");
case AstNode::kBlock:
return ValidateBlockStatement(statement->AsBlock());
case AstNode::kExpressionStatement:
return ValidateExpressionStatement(statement->AsExpressionStatement());
case AstNode::kEmptyStatement:
return ValidateEmptyStatement(statement->AsEmptyStatement());
case AstNode::kIfStatement:
return ValidateIfStatement(statement->AsIfStatement());
case AstNode::kReturnStatement:
return ValidateReturnStatement(statement->AsReturnStatement());
case AstNode::kWhileStatement:
return ValidateWhileStatement(statement->AsWhileStatement());
case AstNode::kDoWhileStatement:
return ValidateDoWhileStatement(statement->AsDoWhileStatement());
case AstNode::kForStatement:
return ValidateForStatement(statement->AsForStatement());
case AstNode::kBreakStatement:
return ValidateBreakStatement(statement->AsBreakStatement());
case AstNode::kContinueStatement:
return ValidateContinueStatement(statement->AsContinueStatement());
case AstNode::kSwitchStatement:
return ValidateSwitchStatement(statement->AsSwitchStatement());
}
return AsmType::Void();
}
// 6.5.1 BlockStatement
AsmType* AsmTyper::ValidateBlockStatement(Block* block) {
FlattenedStatements iter(zone_, block->statements());
while (auto* current = iter.Next()) {
RECURSE(ValidateStatement(current));
}
return AsmType::Void();
}
// 6.5.2 ExpressionStatement
AsmType* AsmTyper::ValidateExpressionStatement(ExpressionStatement* expr) {
auto* expression = expr->expression();
if (auto* call = expression->AsCall()) {
RECURSE(ValidateCall(AsmType::Void(), call));
} else {
RECURSE(ValidateExpression(expression));
}
return AsmType::Void();
}
// 6.5.3 EmptyStatement
AsmType* AsmTyper::ValidateEmptyStatement(EmptyStatement* empty) {
return AsmType::Void();
}
// 6.5.4 IfStatement
AsmType* AsmTyper::ValidateIfStatement(IfStatement* if_stmt) {
AsmType* cond_type;
RECURSE(cond_type = ValidateExpression(if_stmt->condition()));
if (!cond_type->IsA(AsmType::Int())) {
FAIL(if_stmt->condition(), "If condition must be type int.");
}
RECURSE(ValidateStatement(if_stmt->then_statement()));
RECURSE(ValidateStatement(if_stmt->else_statement()));
return AsmType::Void();
}
// 6.5.5 ReturnStatement
AsmType* AsmTyper::ValidateReturnStatement(ReturnStatement* ret_stmt) {
AsmType* ret_expr_type = AsmType::Void();
if (auto* ret_expr = ret_stmt->expression()) {
RECURSE(ret_expr_type = ValidateExpression(ret_expr));
if (ret_expr_type == AsmType::Void()) {
// *VIOLATION* The parser modifies the source code so that expressionless
// returns will return undefined, so we need to allow that.
if (!ret_expr->IsUndefinedLiteral()) {
FAIL(ret_stmt, "Return statement expression can't be void.");
}
}
}
if (!ret_expr_type->IsA(return_type_)) {
FAIL(ret_stmt, "Type mismatch in return statement.");
}
return ret_expr_type;
}
// 6.5.6 IterationStatement
// 6.5.6.a WhileStatement
AsmType* AsmTyper::ValidateWhileStatement(WhileStatement* while_stmt) {
AsmType* cond_type;
RECURSE(cond_type = ValidateExpression(while_stmt->cond()));
if (!cond_type->IsA(AsmType::Int())) {
FAIL(while_stmt->cond(), "While condition must be type int.");
}
if (auto* body = while_stmt->body()) {
RECURSE(ValidateStatement(body));
}
return AsmType::Void();
}
// 6.5.6.b DoWhileStatement
AsmType* AsmTyper::ValidateDoWhileStatement(DoWhileStatement* do_while) {
AsmType* cond_type;
RECURSE(cond_type = ValidateExpression(do_while->cond()));
if (!cond_type->IsA(AsmType::Int())) {
FAIL(do_while->cond(), "Do {} While condition must be type int.");
}
if (auto* body = do_while->body()) {
RECURSE(ValidateStatement(body));
}
return AsmType::Void();
}
// 6.5.6.c ForStatement
AsmType* AsmTyper::ValidateForStatement(ForStatement* for_stmt) {
if (auto* init = for_stmt->init()) {
RECURSE(ValidateStatement(init));
}
if (auto* cond = for_stmt->cond()) {
AsmType* cond_type;
RECURSE(cond_type = ValidateExpression(cond));
if (!cond_type->IsA(AsmType::Int())) {
FAIL(cond, "For condition must be type int.");
}
}
if (auto* next = for_stmt->next()) {
RECURSE(ValidateStatement(next));
}
if (auto* body = for_stmt->body()) {
RECURSE(ValidateStatement(body));
}
return AsmType::Void();
}
// 6.5.7 BreakStatement
AsmType* AsmTyper::ValidateBreakStatement(BreakStatement* brk_stmt) {
return AsmType::Void();
}
// 6.5.8 ContinueStatement
AsmType* AsmTyper::ValidateContinueStatement(ContinueStatement* cont_stmt) {
return AsmType::Void();
}
// 6.5.9 LabelledStatement
// No need to handle these here -- see the AsmTyper's definition.
// 6.5.10 SwitchStatement
AsmType* AsmTyper::ValidateSwitchStatement(SwitchStatement* stmt) {
AsmType* cond_type;
RECURSE(cond_type = ValidateExpression(stmt->tag()));
if (!cond_type->IsA(AsmType::Signed())) {
FAIL(stmt, "Switch tag must be signed.");
}
bool has_default = false;
ZoneSet<int32_t> cases_seen(zone_);
for (auto* a_case : *stmt->cases()) {
if (a_case->is_default()) {
CHECK(!has_default);
RECURSE(ValidateDefault(a_case));
has_default = true;
}
int32_t case_lbl;
RECURSE(ValidateCase(a_case, &case_lbl));
auto case_lbl_pos = cases_seen.find(case_lbl);
if (case_lbl_pos != cases_seen.end() && *case_lbl_pos == case_lbl) {
FAIL(a_case, "Duplicated case label.");
}
cases_seen.insert(case_lbl);
}
if (!cases_seen.empty()) {
const int64_t max_lbl = *cases_seen.rbegin();
const int64_t min_lbl = *cases_seen.begin();
if (max_lbl - min_lbl > std::numeric_limits<int32_t>::max()) {
FAIL(stmt, "Out-of-bounds case label range.");
}
}
return AsmType::Void();
}
// 6.6 ValidateCase
namespace {
bool ExtractInt32CaseLabel(CaseClause* clause, int32_t* lbl) {
auto* lbl_expr = clause->label()->AsLiteral();
if (lbl_expr == nullptr) {
return false;
}
if (lbl_expr->raw_value()->ContainsDot()) {
return false;
}
return lbl_expr->value()->ToInt32(lbl);
}
} // namespace
AsmType* AsmTyper::ValidateCase(CaseClause* label, int32_t* case_lbl) {
if (!ExtractInt32CaseLabel(label, case_lbl)) {
FAIL(label, "Case label must be a 32-bit signed integer.");
}
FlattenedStatements iter(zone_, label->statements());
while (auto* current = iter.Next()) {
RECURSE(ValidateStatement(current));
}
return AsmType::Void();
}
// 6.7 ValidateDefault
AsmType* AsmTyper::ValidateDefault(CaseClause* label) {
FlattenedStatements iter(zone_, label->statements());
while (auto* current = iter.Next()) {
RECURSE(ValidateStatement(current));
}
return AsmType::Void();
}
// 6.8 ValidateExpression
AsmType* AsmTyper::ValidateExpression(Expression* expr) {
AsmType* expr_ty = AsmType::None();
switch (expr->node_type()) {
default:
FAIL(expr, "Invalid asm.js expression.");
case AstNode::kLiteral:
RECURSE(expr_ty = ValidateNumericLiteral(expr->AsLiteral()));
break;
case AstNode::kVariableProxy:
RECURSE(expr_ty = ValidateIdentifier(expr->AsVariableProxy()));
break;
case AstNode::kCall:
RECURSE(expr_ty = ValidateCallExpression(expr->AsCall()));
break;
case AstNode::kProperty:
RECURSE(expr_ty = ValidateMemberExpression(expr->AsProperty()));
break;
case AstNode::kAssignment:
RECURSE(expr_ty = ValidateAssignmentExpression(expr->AsAssignment()));
break;
case AstNode::kUnaryOperation:
RECURSE(expr_ty = ValidateUnaryExpression(expr->AsUnaryOperation()));
break;
case AstNode::kConditional:
RECURSE(expr_ty = ValidateConditionalExpression(expr->AsConditional()));
break;
case AstNode::kCompareOperation:
RECURSE(expr_ty = ValidateCompareOperation(expr->AsCompareOperation()));
break;
case AstNode::kBinaryOperation:
RECURSE(expr_ty = ValidateBinaryOperation(expr->AsBinaryOperation()));
break;
}
SetTypeOf(expr, expr_ty);
return expr_ty;
}
AsmType* AsmTyper::ValidateCompareOperation(CompareOperation* cmp) {
switch (cmp->op()) {
default:
FAIL(cmp, "Invalid asm.js comparison operator.");
case Token::LT:
case Token::LTE:
case Token::GT:
case Token::GTE:
return ValidateRelationalExpression(cmp);
case Token::EQ:
case Token::NE:
return ValidateEqualityExpression(cmp);
}
UNREACHABLE();
}
namespace {
bool IsNegate(BinaryOperation* binop) {
if (binop->op() != Token::BIT_XOR) {
return false;
}
auto* right_as_literal = binop->right()->AsLiteral();
if (right_as_literal == nullptr) {
return false;
}
return !right_as_literal->raw_value()->ContainsDot() &&
right_as_literal->raw_value()->AsNumber() == -1.0;
}
bool IsUnaryMinus(BinaryOperation* binop) {
// *VIOLATION* The parser replaces uses of +x with x*1.0.
if (binop->op() != Token::MUL) {
return false;
}
auto* right_as_literal = binop->right()->AsLiteral();
if (right_as_literal == nullptr) {
return false;
}
return right_as_literal->raw_value()->ContainsDot() &&
right_as_literal->raw_value()->AsNumber() == -1.0;
}
} // namespace
AsmType* AsmTyper::ValidateBinaryOperation(BinaryOperation* expr) {
#define UNOP_OVERLOAD(Src, Dest) \
do { \
if (left_type->IsA(AsmType::Src())) { \
return AsmType::Dest(); \
} \
} while (0)
switch (expr->op()) {
default:
FAIL(expr, "Invalid asm.js binary expression.");
case Token::COMMA:
return ValidateCommaExpression(expr);
case Token::MUL:
if (IsDoubleAnnotation(expr)) {
// *VIOLATION* We can't be 100% sure this really IS a unary + in the asm
// source so we have to be lenient, and treat this as a unary +.
if (auto* Call = expr->left()->AsCall()) {
return ValidateCall(AsmType::Double(), Call);
}
AsmType* left_type;
RECURSE(left_type = ValidateExpression(expr->left()));
UNOP_OVERLOAD(Signed, Double);
UNOP_OVERLOAD(Unsigned, Double);
UNOP_OVERLOAD(DoubleQ, Double);
UNOP_OVERLOAD(FloatQ, Double);
FAIL(expr, "Invalid type for conversion to double.");
}
if (IsUnaryMinus(expr)) {
// *VIOLATION* the parser converts -x to x * -1.0.
AsmType* left_type;
RECURSE(left_type = ValidateExpression(expr->left()));
UNOP_OVERLOAD(Int, Intish);
UNOP_OVERLOAD(DoubleQ, Double);
UNOP_OVERLOAD(FloatQ, Floatish);
FAIL(expr, "Invalid type for unary -.");
}
// FALTHROUGH
case Token::DIV:
case Token::MOD:
return ValidateMultiplicativeExpression(expr);
case Token::ADD:
case Token::SUB: {
static const uint32_t kInitialIntishCount = 0;
return ValidateAdditiveExpression(expr, kInitialIntishCount);
}
case Token::SAR:
case Token::SHL:
case Token::SHR:
return ValidateShiftExpression(expr);
case Token::BIT_AND:
return ValidateBitwiseANDExpression(expr);
case Token::BIT_XOR:
if (IsNegate(expr)) {
auto* left = expr->left();
auto* left_as_binop = left->AsBinaryOperation();
if (left_as_binop != nullptr && IsNegate(left_as_binop)) {
// This is the special ~~ operator.
AsmType* left_type;
RECURSE(left_type = ValidateExpression(left_as_binop->left()));
UNOP_OVERLOAD(Double, Signed);
UNOP_OVERLOAD(FloatQ, Signed);
FAIL(left_as_binop, "Invalid type for conversion to signed.");
}
AsmType* left_type;
RECURSE(left_type = ValidateExpression(left));
UNOP_OVERLOAD(Intish, Signed);
FAIL(left, "Invalid type for ~.");
}
return ValidateBitwiseXORExpression(expr);
case Token::BIT_OR:
return ValidateBitwiseORExpression(expr);
}
#undef UNOP_OVERLOAD
UNREACHABLE();
}
// 6.8.1 Expression
AsmType* AsmTyper::ValidateCommaExpression(BinaryOperation* comma) {
// The AST looks like:
// (expr COMMA (expr COMMA (expr COMMA (... ))))
auto* left = comma->left();
auto* left_as_binop = left->AsBinaryOperation();
if (left_as_binop && left_as_binop->op() == Token::COMMA) {
ValidateCommaExpression(left_as_binop);
} else if (auto* left_as_call = left->AsCall()) {
ValidateCall(AsmType::Void(), left_as_call);
} else {
ValidateExpression(left);
}
auto* right = comma->right();
auto* right_as_binop = right->AsBinaryOperation();
if (right_as_binop && right_as_binop->op() == Token::COMMA) {
return ValidateCommaExpression(right_as_binop);
} else {
return ValidateExpression(right);
}
UNREACHABLE();
}
// 6.8.2 NumericLiteral
AsmType* AsmTyper::ValidateNumericLiteral(Literal* literal) {
// *VIOLATION* asm.js does not allow the use of undefined, but our parser
// inserts them, so we have to handle them.
if (literal->IsUndefinedLiteral()) {
return AsmType::Void();
}
if (literal->raw_value()->ContainsDot()) {
return AsmType::Double();
}
uint32_t value;
if (!literal->value()->ToUint32(&value)) {
int32_t value;
if (!literal->value()->ToInt32(&value)) {
FAIL(literal, "Integer literal is out of range.");
}
// *VIOLATION* Not really a violation, but rather a different in the
// validation. The spec handles -NumericLiteral in ValidateUnaryExpression,
// but V8's AST represents the negative literals as Literals.
return AsmType::Signed();
}
static const uint32_t LargestFixNum = std::numeric_limits<int32_t>::max();
if (value <= LargestFixNum) {
return AsmType::FixNum();
}
return AsmType::Unsigned();
}
// 6.8.3 Identifier
AsmType* AsmTyper::ValidateIdentifier(VariableProxy* proxy) {
auto* proxy_info = Lookup(proxy->var());
if (proxy_info == nullptr) {
FAIL(proxy, "Undeclared identifier.");
}
if (proxy_info->type()->AsCallableType() != nullptr) {
FAIL(proxy, "Identifier may not be accessed by ordinary expressions.");
}
DCHECK(!proxy_info->type()->IsA(AsmType::None()));
return proxy_info->type();
}
// 6.8.4 CallExpression
AsmType* AsmTyper::ValidateCallExpression(Call* call) {
AsmType* return_type;
RECURSE(return_type = ValidateFloatCoercion(call));
if (return_type == nullptr) {
FAIL(call, "Unanotated call to a function must be a call to fround.");
}
return return_type;
}
// 6.8.5 MemberExpression
AsmType* AsmTyper::ValidateMemberExpression(Property* prop) {
AsmType* return_type;
RECURSE(return_type = ValidateHeapAccess(prop, LoadFromHeap));
return return_type;
}
// 6.8.6 AssignmentExpression
AsmType* AsmTyper::ValidateAssignmentExpression(Assignment* assignment) {
AsmType* value_type;
RECURSE(value_type = ValidateExpression(assignment->value()));
if (assignment->op() == Token::INIT) {
FAIL(assignment,
"Local variable declaration must be at the top of the function.");
}
if (auto* target_as_proxy = assignment->target()->AsVariableProxy()) {
auto* var = target_as_proxy->var();
auto* target_info = Lookup(var);
if (target_info == nullptr) {
if (var->mode() != TEMPORARY) {
FAIL(target_as_proxy, "Undeclared identifier.");
}
// Temporary variables are special: we add them to the local symbol table
// as we see them, with the exact type of the variable's initializer. This
// means that temporary variables might have nonsensical types (i.e.,
// intish, float?, fixnum, and not just the "canonical" types.)
auto* var_info = new (zone_) VariableInfo(value_type);
var_info->set_mutability(VariableInfo::kLocal);
if (!ValidAsmIdentifier(target_as_proxy->name())) {
FAIL(target_as_proxy,
"Invalid asm.js identifier in temporary variable.");
}
if (!AddLocal(var, var_info)) {
FAIL(assignment, "Failed to add temporary variable to symbol table.");
}
return value_type;
}
DCHECK(target_info->type() != AsmType::None());
if (!value_type->IsA(target_info->type())) {
FAIL(assignment, "Type mismatch in assignment.");
}
return value_type;
}
if (auto* target_as_property = assignment->target()->AsProperty()) {
AsmType* allowed_store_types;
RECURSE(allowed_store_types =
ValidateHeapAccess(target_as_property, StoreToHeap));
// TODO(jpp): Change FloatishDoubleQ and FloatQDoubleQ so that they are base
// classes for Floatish, DoubleQ, and FloatQ, and then invert this if so
// that it reads more naturally as
//
// if (!value_type->IsA(allowed_store_types))
if (allowed_store_types == AsmType::FloatishDoubleQ() ||
allowed_store_types == AsmType::FloatQDoubleQ()) {
if (!allowed_store_types->IsA(value_type)) {
FAIL(assignment, "Type mismatch in heap assignment.");
}
} else {
if (!value_type->IsA(allowed_store_types)) {
FAIL(assignment, "Type mismatch in heap assignment.");
}
}
return value_type;
}
FAIL(assignment, "Invalid asm.js assignment.");
}
// 6.8.7 UnaryExpression
AsmType* AsmTyper::ValidateUnaryExpression(UnaryOperation* unop) {
// *VIOLATION* -NumericLiteral is validated in ValidateLiteral.
// *VIOLATION* +UnaryExpression is validated in ValidateBinaryOperation.
// *VIOLATION* ~UnaryOperation is validated in ValidateBinaryOperation.
// *VIOLATION* ~~UnaryOperation is validated in ValidateBinaryOperation.
DCHECK(unop->op() != Token::BIT_NOT);
DCHECK(unop->op() != Token::ADD);
AsmType* exp_type;
RECURSE(exp_type = ValidateExpression(unop->expression()));
#define UNOP_OVERLOAD(Src, Dest) \
do { \
if (exp_type->IsA(AsmType::Src())) { \
return AsmType::Dest(); \
} \
} while (0)
// 8.1 Unary Operators
switch (unop->op()) {
default:
FAIL(unop, "Invalid unary operator.");
case Token::ADD:
// We can't test this because of the +x -> x * 1.0 transformation.
DCHECK(false);
UNOP_OVERLOAD(Signed, Double);
UNOP_OVERLOAD(Unsigned, Double);
UNOP_OVERLOAD(DoubleQ, Double);
UNOP_OVERLOAD(FloatQ, Double);
FAIL(unop, "Invalid type for unary +.");
case Token::SUB:
// We can't test this because of the -x -> x * -1.0 transformation.
DCHECK(false);
UNOP_OVERLOAD(Int, Intish);
UNOP_OVERLOAD(DoubleQ, Double);
UNOP_OVERLOAD(FloatQ, Floatish);
FAIL(unop, "Invalid type for unary -.");
case Token::BIT_NOT:
// We can't test this because of the ~x -> x ^ -1 transformation.
DCHECK(false);
UNOP_OVERLOAD(Intish, Signed);
FAIL(unop, "Invalid type for ~.");
case Token::NOT:
UNOP_OVERLOAD(Int, Int);
FAIL(unop, "Invalid type for !.");
}
#undef UNOP_OVERLOAD
UNREACHABLE();
}
// 6.8.8 MultiplicativeExpression
namespace {
bool IsIntishLiteralFactor(Expression* expr, int32_t* factor) {
auto* literal = expr->AsLiteral();
if (literal == nullptr) {
return false;
}
if (literal->raw_value()->ContainsDot()) {
return false;
}
if (!literal->value()->ToInt32(factor)) {
return false;
}
static const int32_t kIntishBound = 1 << 20;
return -kIntishBound < *factor && *factor < kIntishBound;
}
} // namespace
AsmType* AsmTyper::ValidateMultiplicativeExpression(BinaryOperation* binop) {
DCHECK(!IsDoubleAnnotation(binop));
auto* left = binop->left();
auto* right = binop->right();
bool intish_mul_failed = false;
if (binop->op() == Token::MUL) {
int32_t factor;
if (IsIntishLiteralFactor(left, &factor)) {
AsmType* right_type;
RECURSE(right_type = ValidateExpression(right));
if (right_type->IsA(AsmType::Int())) {
return AsmType::Intish();
}
// Can't fail here, because the rhs might contain a valid intish factor.
//
// The solution is to flag that there was an error, and later on -- when
// both lhs and rhs are evaluated -- complain.
intish_mul_failed = true;
}
if (IsIntishLiteralFactor(right, &factor)) {
AsmType* left_type;
RECURSE(left_type = ValidateExpression(left));
if (left_type->IsA(AsmType::Int())) {
// *VIOLATION* This will also (and correctly) handle -X, when X is an
// integer. Therefore, we don't need to handle this case within the if
// block below.
return AsmType::Intish();
}
intish_mul_failed = true;
if (factor == -1) {
// *VIOLATION* The frontend transforms -x into x * -1 (not -1.0, because
// consistency is overrated.)
if (left_type->IsA(AsmType::DoubleQ())) {
return AsmType::Double();
} else if (left_type->IsA(AsmType::FloatQ())) {
return AsmType::Floatish();
}
}
}
}
if (intish_mul_failed) {
FAIL(binop, "Invalid types for intish * (or unary -).");
}
AsmType* left_type;
AsmType* right_type;
RECURSE(left_type = ValidateExpression(left));
RECURSE(right_type = ValidateExpression(right));
#define BINOP_OVERLOAD(Src0, Src1, Dest) \
do { \
if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \
return AsmType::Dest(); \
} \
} while (0)
switch (binop->op()) {
default:
FAIL(binop, "Invalid multiplicative expression.");
case Token::MUL:
BINOP_OVERLOAD(DoubleQ, DoubleQ, Double);
BINOP_OVERLOAD(FloatQ, FloatQ, Floatish);
FAIL(binop, "Invalid operands for *.");
case Token::DIV:
BINOP_OVERLOAD(Signed, Signed, Intish);
BINOP_OVERLOAD(Unsigned, Unsigned, Intish);
BINOP_OVERLOAD(DoubleQ, DoubleQ, Double);
BINOP_OVERLOAD(FloatQ, FloatQ, Floatish);
FAIL(binop, "Invalid operands for /.");
case Token::MOD:
BINOP_OVERLOAD(Signed, Signed, Intish);
BINOP_OVERLOAD(Unsigned, Unsigned, Intish);
BINOP_OVERLOAD(DoubleQ, DoubleQ, Double);
FAIL(binop, "Invalid operands for %.");
}
#undef BINOP_OVERLOAD
UNREACHABLE();
}
// 6.8.9 AdditiveExpression
AsmType* AsmTyper::ValidateAdditiveExpression(BinaryOperation* binop,
uint32_t intish_count) {
static const uint32_t kMaxIntish = 1 << 20;
auto* left = binop->left();
auto* left_as_binop = left->AsBinaryOperation();
AsmType* left_type;
// TODO(jpp): maybe use an iterative approach instead of the recursion to
// ValidateAdditiveExpression.
if (left_as_binop != nullptr && (left_as_binop->op() == Token::ADD ||
left_as_binop->op() == Token::SUB)) {
RECURSE(left_type =
ValidateAdditiveExpression(left_as_binop, intish_count + 1));
} else {
RECURSE(left_type = ValidateExpression(left));
}
auto* right = binop->right();
auto* right_as_binop = right->AsBinaryOperation();
AsmType* right_type;
if (right_as_binop != nullptr && (right_as_binop->op() == Token::ADD ||
right_as_binop->op() == Token::SUB)) {
RECURSE(right_type =
ValidateAdditiveExpression(right_as_binop, intish_count + 1));
} else {
RECURSE(right_type = ValidateExpression(right));
}
if (left_type->IsA(AsmType::FloatQ()) && right_type->IsA(AsmType::FloatQ())) {
return AsmType::Floatish();
}
if (left_type->IsA(AsmType::Int()) && right_type->IsA(AsmType::Int())) {
if (intish_count == 0) {
return AsmType::Intish();
}
if (intish_count < kMaxIntish) {
return AsmType::Int();
}
FAIL(binop, "Too many uncoerced integer additive expressions.");
}
if (left_type->IsA(AsmType::Double()) && right_type->IsA(AsmType::Double())) {
return AsmType::Double();
}
if (binop->op() == Token::SUB) {
if (left_type->IsA(AsmType::DoubleQ()) &&
right_type->IsA(AsmType::DoubleQ())) {
return AsmType::Double();
}
}
FAIL(binop, "Invalid operands for additive expression.");
}
// 6.8.10 ShiftExpression
AsmType* AsmTyper::ValidateShiftExpression(BinaryOperation* binop) {
auto* left = binop->left();
auto* right = binop->right();
AsmType* left_type;
AsmType* right_type;
RECURSE(left_type = ValidateExpression(left));
RECURSE(right_type = ValidateExpression(right));
#define BINOP_OVERLOAD(Src0, Src1, Dest) \
do { \
if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \
return AsmType::Dest(); \
} \
} while (0)
switch (binop->op()) {
default:
FAIL(binop, "Invalid shift expression.");
case Token::SHL:
BINOP_OVERLOAD(Intish, Intish, Signed);
FAIL(binop, "Invalid operands for <<.");
case Token::SAR:
BINOP_OVERLOAD(Intish, Intish, Signed);
FAIL(binop, "Invalid operands for >>.");
case Token::SHR:
BINOP_OVERLOAD(Intish, Intish, Unsigned);
FAIL(binop, "Invalid operands for >>>.");
}
#undef BINOP_OVERLOAD
UNREACHABLE();
}
// 6.8.11 RelationalExpression
AsmType* AsmTyper::ValidateRelationalExpression(CompareOperation* cmpop) {
auto* left = cmpop->left();
auto* right = cmpop->right();
AsmType* left_type;
AsmType* right_type;
RECURSE(left_type = ValidateExpression(left));
RECURSE(right_type = ValidateExpression(right));
#define CMPOP_OVERLOAD(Src0, Src1, Dest) \
do { \
if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \
return AsmType::Dest(); \
} \
} while (0)
switch (cmpop->op()) {
default:
FAIL(cmpop, "Invalid relational expression.");
case Token::LT:
CMPOP_OVERLOAD(Signed, Signed, Int);
CMPOP_OVERLOAD(Unsigned, Unsigned, Int);
CMPOP_OVERLOAD(Float, Float, Int);
CMPOP_OVERLOAD(Double, Double, Int);
FAIL(cmpop, "Invalid operands for <.");
case Token::GT:
CMPOP_OVERLOAD(Signed, Signed, Int);
CMPOP_OVERLOAD(Unsigned, Unsigned, Int);
CMPOP_OVERLOAD(Float, Float, Int);
CMPOP_OVERLOAD(Double, Double, Int);
FAIL(cmpop, "Invalid operands for >.");
case Token::LTE:
CMPOP_OVERLOAD(Signed, Signed, Int);
CMPOP_OVERLOAD(Unsigned, Unsigned, Int);
CMPOP_OVERLOAD(Float, Float, Int);
CMPOP_OVERLOAD(Double, Double, Int);
FAIL(cmpop, "Invalid operands for <=.");
case Token::GTE:
CMPOP_OVERLOAD(Signed, Signed, Int);
CMPOP_OVERLOAD(Unsigned, Unsigned, Int);
CMPOP_OVERLOAD(Float, Float, Int);
CMPOP_OVERLOAD(Double, Double, Int);
FAIL(cmpop, "Invalid operands for >=.");
}
#undef CMPOP_OVERLOAD
UNREACHABLE();
}
// 6.8.12 EqualityExpression
AsmType* AsmTyper::ValidateEqualityExpression(CompareOperation* cmpop) {
auto* left = cmpop->left();
auto* right = cmpop->right();
AsmType* left_type;
AsmType* right_type;
RECURSE(left_type = ValidateExpression(left));
RECURSE(right_type = ValidateExpression(right));
#define CMPOP_OVERLOAD(Src0, Src1, Dest) \
do { \
if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \
return AsmType::Dest(); \
} \
} while (0)
switch (cmpop->op()) {
default:
FAIL(cmpop, "Invalid equality expression.");
case Token::EQ:
CMPOP_OVERLOAD(Signed, Signed, Int);
CMPOP_OVERLOAD(Unsigned, Unsigned, Int);
CMPOP_OVERLOAD(Float, Float, Int);
CMPOP_OVERLOAD(Double, Double, Int);
FAIL(cmpop, "Invalid operands for ==.");
case Token::NE:
CMPOP_OVERLOAD(Signed, Signed, Int);
CMPOP_OVERLOAD(Unsigned, Unsigned, Int);
CMPOP_OVERLOAD(Float, Float, Int);
CMPOP_OVERLOAD(Double, Double, Int);
FAIL(cmpop, "Invalid operands for !=.");
}
#undef CMPOP_OVERLOAD
UNREACHABLE();
}
// 6.8.13 BitwiseANDExpression
AsmType* AsmTyper::ValidateBitwiseANDExpression(BinaryOperation* binop) {
auto* left = binop->left();
auto* right = binop->right();
AsmType* left_type;
AsmType* right_type;
RECURSE(left_type = ValidateExpression(left));
RECURSE(right_type = ValidateExpression(right));
if (binop->op() != Token::BIT_AND) {
FAIL(binop, "Invalid & expression.");
}
#define BINOP_OVERLOAD(Src0, Src1, Dest) \
do { \
if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \
return AsmType::Dest(); \
} \
} while (0)
BINOP_OVERLOAD(Intish, Intish, Signed);
FAIL(binop, "Invalid operands for &.");
#undef BINOP_OVERLOAD
UNREACHABLE();
}
// 6.8.14 BitwiseXORExpression
AsmType* AsmTyper::ValidateBitwiseXORExpression(BinaryOperation* binop) {
auto* left = binop->left();
auto* right = binop->right();
AsmType* left_type;
AsmType* right_type;
RECURSE(left_type = ValidateExpression(left));
RECURSE(right_type = ValidateExpression(right));
if (binop->op() != Token::BIT_XOR) {
FAIL(binop, "Invalid ^ expression.");
}
#define BINOP_OVERLOAD(Src0, Src1, Dest) \
do { \
if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \
return AsmType::Dest(); \
} \
} while (0)
BINOP_OVERLOAD(Intish, Intish, Signed);
FAIL(binop, "Invalid operands for ^.");
#undef BINOP_OVERLOAD
UNREACHABLE();
}
// 6.8.15 BitwiseORExpression
AsmType* AsmTyper::ValidateBitwiseORExpression(BinaryOperation* binop) {
auto* left = binop->left();
if (IsIntAnnotation(binop)) {
if (auto* left_as_call = left->AsCall()) {
AsmType* type;
RECURSE(type = ValidateCall(AsmType::Signed(), left_as_call));
return type;
}
// TODO(jpp): at this point we know that binop is expr|0. We could sinply
//
// RECURSE(t = ValidateExpression(left));
// FAIL_IF(t->IsNotA(Intish));
// return Signed;
}
auto* right = binop->right();
AsmType* left_type;
AsmType* right_type;
RECURSE(left_type = ValidateExpression(left));
RECURSE(right_type = ValidateExpression(right));
if (binop->op() != Token::BIT_OR) {
FAIL(binop, "Invalid | expression.");
}
#define BINOP_OVERLOAD(Src0, Src1, Dest) \
do { \
if (left_type->IsA(AsmType::Src0()) && right_type->IsA(AsmType::Src1())) { \
return AsmType::Dest(); \
} \
} while (0)
BINOP_OVERLOAD(Intish, Intish, Signed);
FAIL(binop, "Invalid operands for |.");
#undef BINOP_OVERLOAD
UNREACHABLE();
}
// 6.8.16 ConditionalExpression
AsmType* AsmTyper::ValidateConditionalExpression(Conditional* cond) {
AsmType* cond_type;
RECURSE(cond_type = ValidateExpression(cond->condition()));
if (!cond_type->IsA(AsmType::Int())) {
FAIL(cond, "Ternary operation condition should be int.");
}
AsmType* then_type;
RECURSE(then_type = ValidateExpression(cond->then_expression()));
AsmType* else_type;
RECURSE(else_type = ValidateExpression(cond->else_expression()));
#define SUCCEED_IF_BOTH_ARE(type) \
do { \
if (then_type->IsA(AsmType::type())) { \
if (!else_type->IsA(AsmType::type())) { \
FAIL(cond, "Type mismatch for ternary operation result type."); \
} \
return AsmType::type(); \
} \
} while (0)
SUCCEED_IF_BOTH_ARE(Int);
SUCCEED_IF_BOTH_ARE(Float);
SUCCEED_IF_BOTH_ARE(Double);
#undef SUCCEED_IF_BOTH_ARE
FAIL(cond, "Ternary operator must return int, float, or double.");
}
// 6.9 ValidateCall
namespace {
bool ExtractIndirectCallMask(Expression* expr, uint32_t* value) {
auto* as_literal = expr->AsLiteral();
if (as_literal == nullptr) {
return false;
}
if (as_literal->raw_value()->ContainsDot()) {
return false;
}
if (!as_literal->value()->ToUint32(value)) {
return false;
}
return base::bits::IsPowerOfTwo32(1 + *value);
}
// TODO(jpp): Add a AsmType::ValidateCall is poorly designed. It can only handle
// function declarations, not invocations. CheckInvocationOf temporarily works
// around this limitation by converting each actual in actuals to a parameter
// type before invoking prototype->ValidateCall. This is the wrong behavior for
// FFIs (we need to pass Signed integers to FFIs, not Ints), so that case is
// handled separately.
bool CheckInvocationOf(AsmCallableType* prototype, AsmType* return_type,
ZoneVector<AsmType*>* actuals) {
if (auto* ffi = prototype->AsFFIType()) {
return ffi->ValidateCall(return_type, *actuals) != AsmType::None();
}
for (size_t ii = 0; ii < actuals->size(); ++ii) {
(*actuals)[ii] = (*actuals)[ii]->ToParameterType();
}
return prototype->ValidateCall(return_type, *actuals) != AsmType::None();
}
} // namespace
AsmType* AsmTyper::ValidateCall(AsmType* return_type, Call* call) {
AsmType* float_coercion_type;
RECURSE(float_coercion_type = ValidateFloatCoercion(call));
if (float_coercion_type == AsmType::Float()) {
return AsmType::Float();
}
// TODO(jpp): we should be able to reuse the args vector's storage space.
ZoneVector<AsmType*> args(zone_);
args.reserve(call->arguments()->length());
for (auto* arg : *call->arguments()) {
AsmType* arg_type;
RECURSE(arg_type = ValidateExpression(arg));
args.emplace_back(arg_type);
}
auto* call_expr = call->expression();
// identifier(Expression...)
if (auto* call_var_proxy = call_expr->AsVariableProxy()) {
auto* call_var_info = Lookup(call_var_proxy->var());
if (call_var_info == nullptr) {
// We can't fail here: the validator performs a single pass over the AST,
// so it is possible for some calls to be currently unresolved. We eagerly
// add the function to the table of globals.
auto* call_type = AsmType::Function(zone_, return_type)->AsFunctionType();
for (auto* arg : args) {
call_type->AddArgument(arg->ToParameterType());
}
auto* fun_info =
new (zone_) VariableInfo(reinterpret_cast<AsmType*>(call_type));
fun_info->set_mutability(VariableInfo::kImmutableGlobal);
AddForwardReference(call_var_proxy, fun_info);
if (!ValidAsmIdentifier(call_var_proxy->name())) {
FAIL(call_var_proxy,
"Invalid asm.js identifier in (forward) function name.");
}
if (!AddGlobal(call_var_proxy->var(), fun_info)) {
DCHECK(false);
FAIL(call, "Redeclared global identifier.");
}
return return_type;
}
auto* callee_type = call_var_info->type()->AsCallableType();
if (callee_type == nullptr) {
FAIL(call, "Calling something that's not a function.");
}
if (callee_type->AsFFIType() != nullptr &&
return_type == AsmType::Float()) {
FAIL(call, "Foreign functions can't return float.");
}
if (!CheckInvocationOf(callee_type, return_type, &args)) {
FAIL(call, "Function invocation does not match function type.");
}
return return_type;
}
// identifier[expr & n](Expression...)
if (auto* call_property = call_expr->AsProperty()) {
auto* index = call_property->key()->AsBinaryOperation();
if (index == nullptr || index->op() != Token::BIT_AND) {
FAIL(call_property->key(),
"Indirect call index must be in the expr & mask form.");
}
auto* left = index->left();
auto* right = index->right();
uint32_t mask;
if (!ExtractIndirectCallMask(right, &mask)) {
if (!ExtractIndirectCallMask(left, &mask)) {
FAIL(right, "Invalid indirect call mask.");
} else {
left = right;
}
}
const uint32_t table_length = mask + 1;
AsmType* left_type;
RECURSE(left_type = ValidateExpression(left));
if (!left_type->IsA(AsmType::Intish())) {
FAIL(left, "Indirect call index should be an intish.");
}
auto* name_var = call_property->obj()->AsVariableProxy();
if (name_var == nullptr) {
FAIL(call_property, "Invalid call.");
}
auto* name_info = Lookup(name_var->var());
if (name_info == nullptr) {
// We can't fail here -- just like above.
auto* call_type = AsmType::Function(zone_, return_type)->AsFunctionType();
for (auto* arg : args) {
call_type->AddArgument(arg->ToParameterType());
}
auto* table_type = AsmType::FunctionTableType(
zone_, table_length, reinterpret_cast<AsmType*>(call_type));
auto* fun_info =
new (zone_) VariableInfo(reinterpret_cast<AsmType*>(table_type));
fun_info->set_mutability(VariableInfo::kImmutableGlobal);
AddForwardReference(name_var, fun_info);
if (!ValidAsmIdentifier(name_var->name())) {
FAIL(name_var,
"Invalid asm.js identifier in (forward) function table name.");
}
if (!AddGlobal(name_var->var(), fun_info)) {
DCHECK(false);
FAIL(call, "Redeclared global identifier.");
}
return return_type;
}
auto* previous_type = name_info->type()->AsFunctionTableType();
if (previous_type == nullptr) {
FAIL(call, "Identifier does not name a function table.");
}
if (table_length != previous_type->length()) {
FAIL(call, "Function table size does not match expected size.");
}
auto* previous_type_signature =
previous_type->signature()->AsFunctionType();
DCHECK(previous_type_signature != nullptr);
if (!CheckInvocationOf(previous_type_signature, return_type, &args)) {
FAIL(call,
"Function pointer table signature does not match previous "
"signature.");
}
return return_type;
}
FAIL(call, "Invalid call.");
}
// 6.10 ValidateHeapAccess
namespace {
bool ExtractHeapAccessShift(Expression* expr, uint32_t* value) {
auto* as_literal = expr->AsLiteral();
if (as_literal == nullptr) {
return false;
}
if (as_literal->raw_value()->ContainsDot()) {
return false;
}
return as_literal->value()->ToUint32(value);
}
} // namespace
AsmType* AsmTyper::ValidateHeapAccess(Property* heap,
HeapAccessType access_type) {
auto* obj = heap->obj()->AsVariableProxy();
if (obj == nullptr) {
FAIL(heap, "Invalid heap access.");
}
auto* obj_info = Lookup(obj->var());
if (obj_info == nullptr) {
FAIL(heap, "Undeclared identifier in heap access.");
}
auto* obj_type = obj_info->type();
if (!obj_type->IsA(AsmType::Heap())) {
FAIL(heap, "Identifier does not represent a heap view.");
}
if (auto* key_as_literal = heap->key()->AsLiteral()) {
if (key_as_literal->raw_value()->ContainsDot()) {
FAIL(key_as_literal, "Heap access index must be intish.");
}
uint32_t _;
if (!key_as_literal->value()->ToUint32(&_)) {
FAIL(key_as_literal,
"Heap access index must be a 32-bit unsigned integer.");
}
if (access_type == LoadFromHeap) {
return obj_type->LoadType();
}
return obj_type->StoreType();
}
if (auto* key_as_binop = heap->key()->AsBinaryOperation()) {
uint32_t shift;
if (key_as_binop->op() == Token::SAR &&
ExtractHeapAccessShift(key_as_binop->right(), &shift) &&
(1 << shift) == obj_type->ElementSizeInBytes()) {
AsmType* type;
RECURSE(type = ValidateExpression(key_as_binop->left()));
if (type->IsA(AsmType::Intish())) {
if (access_type == LoadFromHeap) {
return obj_type->LoadType();
}
return obj_type->StoreType();
}
// TODO(jpp): it may be the case that, if type is not an Intish, we could
// fail here instead of letting the validator try using the "leniency"
// rule (i.e., allow unshifted indexes for heap views of 8-bit integers.
}
}
if (obj_type->ElementSizeInBytes() == 1) {
// Leniency: if this is a byte array, we don't require the shift operation
// to be present.
AsmType* index_type;
RECURSE(index_type = ValidateExpression(heap->key()));
if (!index_type->IsA(AsmType::Int())) {
FAIL(heap, "Invalid heap access index for byte array.");
}
if (access_type == LoadFromHeap) {
return obj_type->LoadType();
}
return obj_type->StoreType();
}
FAIL(heap, "Invalid heap access index.");
}
// 6.11 ValidateFloatCoercion
bool AsmTyper::IsCallToFround(Call* call) {
if (call->arguments()->length() != 1) {
return false;
}
auto* call_var_proxy = call->expression()->AsVariableProxy();
if (call_var_proxy == nullptr) {
return false;
}
auto* call_var_info = Lookup(call_var_proxy->var());
if (call_var_info == nullptr) {
return false;
}
return call_var_info->standard_member() == kMathFround;
}
AsmType* AsmTyper::ValidateFloatCoercion(Call* call) {
if (!IsCallToFround(call)) {
return nullptr;
}
auto* arg = call->arguments()->at(0);
// call is a fround() node. From now, there can be two possible outcomes:
// 1. fround is used as a return type annotation.
if (auto* arg_as_call = arg->AsCall()) {
RECURSE(ValidateCall(AsmType::Float(), arg_as_call));
return AsmType::Float();
}
// 2. fround is used for converting to float.
AsmType* arg_type;
RECURSE(arg_type = ValidateExpression(arg));
if (arg_type->IsA(AsmType::Floatish()) || arg_type->IsA(AsmType::DoubleQ()) ||
arg_type->IsA(AsmType::Signed()) || arg_type->IsA(AsmType::Unsigned())) {
return AsmType::Float();
}
FAIL(call, "Invalid argument type to fround.");
}
// 5.1 ParameterTypeAnnotations
AsmType* AsmTyper::ParameterTypeAnnotations(Variable* parameter,
Expression* annotation) {
if (auto* binop = annotation->AsBinaryOperation()) {
// Must be:
// * x|0
// * x*1 (*VIOLATION* i.e.,, +x)
auto* left = binop->left()->AsVariableProxy();
if (left == nullptr) {
FAIL(
binop->left(),
"Invalid parameter type annotation - should annotate an identifier.");
}
if (left->var() != parameter) {
FAIL(binop->left(),
"Invalid parameter type annotation - should annotate a parameter.");
}
if (IsDoubleAnnotation(binop)) {
return AsmType::Double();
}
if (IsIntAnnotation(binop)) {
return AsmType::Int();
}
FAIL(binop, "Invalid parameter type annotation.");
}
auto* call = annotation->AsCall();
if (call == nullptr) {
FAIL(
annotation,
"Invalid float parameter type annotation - must be fround(parameter).");
}
if (!IsCallToFround(call)) {
FAIL(annotation,
"Invalid float parameter type annotation - must be call to fround.");
}
auto* src_expr = call->arguments()->at(0)->AsVariableProxy();
if (src_expr == nullptr) {
FAIL(annotation,
"Invalid float parameter type annotation - argument to fround is not "
"an identifier.");
}
if (src_expr->var() != parameter) {
FAIL(annotation,
"Invalid float parameter type annotation - argument to fround is not "
"a parameter.");
}
return AsmType::Float();
}
// 5.2 ReturnTypeAnnotations
AsmType* AsmTyper::ReturnTypeAnnotations(ReturnStatement* statement) {
if (statement == nullptr) {
return AsmType::Void();
}
auto* ret_expr = statement->expression();
if (ret_expr == nullptr) {
return AsmType::Void();
}
if (auto* binop = ret_expr->AsBinaryOperation()) {
if (IsDoubleAnnotation(binop)) {
return AsmType::Double();
} else if (IsIntAnnotation(binop)) {
return AsmType::Signed();
}
FAIL(statement, "Invalid return type annotation.");
}
if (auto* call = ret_expr->AsCall()) {
if (IsCallToFround(call)) {
return AsmType::Float();
}
FAIL(statement, "Invalid function call in return statement.");
}
if (auto* literal = ret_expr->AsLiteral()) {
int32_t _;
if (literal->raw_value()->ContainsDot()) {
return AsmType::Double();
} else if (literal->value()->ToInt32(&_)) {
return AsmType::Signed();
} else if (literal->IsUndefinedLiteral()) {
// *VIOLATION* The parser changes
//
// return;
//
// into
//
// return undefined
return AsmType::Void();
}
FAIL(statement, "Invalid literal in return statement.");
}
FAIL(statement, "Invalid return type expression.");
}
// 5.4 VariableTypeAnnotations
// Also used for 5.5 GlobalVariableTypeAnnotations
AsmType* AsmTyper::VariableTypeAnnotations(Expression* initializer) {
if (auto* literal = initializer->AsLiteral()) {
if (literal->raw_value()->ContainsDot()) {
return AsmType::Double();
}
int32_t i32;
uint32_t u32;
if (literal->value()->ToInt32(&i32) || literal->value()->ToUint32(&u32)) {
return AsmType::Int();
}
FAIL(initializer, "Invalid type annotation - forbidden literal.");
}
auto* call = initializer->AsCall();
DCHECK(call != nullptr);
if (call == nullptr) {
FAIL(initializer,
"Invalid variable initialization - it should be a literal, or "
"fround(literal).");
}
if (!IsCallToFround(call)) {
FAIL(initializer,
"Invalid float coercion - expected call fround(literal).");
}
auto* src_expr = call->arguments()->at(0)->AsLiteral();
if (src_expr == nullptr) {
FAIL(initializer,
"Invalid float type annotation - expected literal argument for call "
"to fround.");
}
if (!src_expr->raw_value()->ContainsDot()) {
FAIL(initializer,
"Invalid float type annotation - expected literal argument to be a "
"floating point literal.");
}
return AsmType::Float();
}
// 5.5 GlobalVariableTypeAnnotations
AsmType* AsmTyper::NewHeapView(CallNew* new_heap_view) {
auto* heap_type = new_heap_view->expression()->AsProperty();
if (heap_type == nullptr) {
FAIL(new_heap_view, "Invalid type after new.");
}
auto* heap_view_info = ImportLookup(heap_type);
if (heap_view_info == nullptr) {
FAIL(new_heap_view, "Unknown stdlib member in heap view declaration.");
}
if (!heap_view_info->type()->IsA(AsmType::Heap())) {
FAIL(new_heap_view, "Type is not a heap view type.");
}
if (new_heap_view->arguments()->length() != 1) {
FAIL(new_heap_view, "Invalid number of arguments when creating heap view.");
}
auto* heap = new_heap_view->arguments()->at(0);
auto* heap_var_proxy = heap->AsVariableProxy();
if (heap_var_proxy == nullptr) {
FAIL(heap,
"Heap view creation parameter should be the module's heap parameter.");
}
auto* heap_var_info = Lookup(heap_var_proxy->var());
if (heap_var_info == nullptr) {
FAIL(heap, "Undeclared identifier instead of heap parameter.");
}
if (!heap_var_info->IsHeap()) {
FAIL(heap,
"Heap view creation parameter should be the module's heap parameter.");
}
DCHECK(heap_view_info->type()->IsA(AsmType::Heap()));
return heap_view_info->type();
}
bool IsValidAsm(Isolate* isolate, Zone* zone, Script* script,
FunctionLiteral* root, std::string* error_message) {
error_message->clear();
AsmTyper typer(isolate, zone, script, root);
if (typer.Validate()) {
return true;
}
*error_message = typer.error_message();
return false;
}
} // namespace wasm
} // namespace internal
} // namespace v8
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_ASMJS_ASM_TYPER_H_
#define SRC_ASMJS_ASM_TYPER_H_
#include <cstdint>
#include <string>
#include "src/allocation.h"
#include "src/asmjs/asm-types.h"
#include "src/ast/ast-type-bounds.h"
#include "src/ast/ast.h"
#include "src/effects.h"
#include "src/type-info.h"
#include "src/types.h"
#include "src/zone-containers.h"
#include "src/zone.h"
namespace v8 {
namespace internal {
namespace wasm {
class AsmTyperHarnessBuilder;
class AsmTyper final {
public:
enum StandardMember {
kHeap = -4,
kFFI = -3,
kStdlib = -2,
kModule = -1,
kNone = 0,
kInfinity,
kNaN,
kMathAcos,
kMathAsin,
kMathAtan,
kMathCos,
kMathSin,
kMathTan,
kMathExp,
kMathLog,
kMathCeil,
kMathFloor,
kMathSqrt,
kMathAbs,
kMathMin,
kMathMax,
kMathAtan2,
kMathPow,
kMathImul,
kMathFround,
kMathE,
kMathLN10,
kMathLN2,
kMathLOG2E,
kMathLOG10E,
kMathPI,
kMathSQRT1_2,
kMathSQRT2,
};
~AsmTyper() = default;
AsmTyper(Isolate* isolate, Zone* zone, Script* script, FunctionLiteral* root);
bool Validate();
const char* error_message() const { return error_message_; }
AsmType* TypeOf(AstNode* node) const;
StandardMember VariableAsStandardMember(Variable* var);
private:
friend class v8::internal::wasm::AsmTyperHarnessBuilder;
class VariableInfo : public ZoneObject {
public:
enum Mutability {
kInvalidMutability,
kLocal,
kMutableGlobal,
kImmutableGlobal,
};
VariableInfo() = default;
explicit VariableInfo(AsmType* t) : type_(t) {}
VariableInfo* Clone(Zone* zone) const;
bool IsMutable() const {
return mutability_ == kLocal || mutability_ == kMutableGlobal;
}
bool IsGlobal() const {
return mutability_ == kImmutableGlobal || mutability_ == kMutableGlobal;
}
bool IsStdlib() const { return standard_member_ == kStdlib; }
bool IsFFI() const { return standard_member_ == kFFI; }
bool IsHeap() const { return standard_member_ == kHeap; }
void MarkDefined() { missing_definition_ = false; }
void FirstForwardUseIs(VariableProxy* var);
StandardMember standard_member() const { return standard_member_; }
void set_standard_member(StandardMember standard_member) {
standard_member_ = standard_member;
}
AsmType* type() const { return type_; }
void set_type(AsmType* type) { type_ = type; }
Mutability mutability() const { return mutability_; }
void set_mutability(Mutability mutability) { mutability_ = mutability; }
bool missing_definition() const { return missing_definition_; }
VariableProxy* first_forward_use() const { return first_forward_use_; }
private:
AsmType* type_ = AsmType::None();
StandardMember standard_member_ = kNone;
Mutability mutability_ = kInvalidMutability;
// missing_definition_ is set to true for forward definition - i.e., use
// before definition.
bool missing_definition_ = false;
// first_forward_use_ holds the AST node that first referenced this
// VariableInfo. Used for error messages.
VariableProxy* first_forward_use_ = nullptr;
};
// RAII-style manager for the in_function_ member variable.
struct FunctionScope {
explicit FunctionScope(AsmTyper* typer) : typer_(typer) {
DCHECK(!typer_->in_function_);
typer_->in_function_ = true;
typer_->local_scope_.Clear();
typer_->return_type_ = AsmType::None();
}
~FunctionScope() {
DCHECK(typer_->in_function_);
typer_->in_function_ = false;
}
AsmTyper* typer_;
};
// FlattenedStatements is an iterator class for ZoneList<Statement*> that
// flattens the Block construct in the AST. This is here because we need it in
// the tests.
class FlattenedStatements {
public:
explicit FlattenedStatements(Zone* zone, ZoneList<Statement*>* s);
Statement* Next();
private:
struct Context {
explicit Context(ZoneList<Statement*>* s) : statements_(s) {}
ZoneList<Statement*>* statements_;
int next_index_ = 0;
};
ZoneVector<Context> context_stack_;
DISALLOW_IMPLICIT_CONSTRUCTORS(FlattenedStatements);
};
using ObjectTypeMap = ZoneMap<std::string, VariableInfo*>;
void InitializeStdlib();
void SetTypeOf(AstNode* node, AsmType* type);
void AddForwardReference(VariableProxy* proxy, VariableInfo* info);
bool AddGlobal(Variable* global, VariableInfo* info);
bool AddLocal(Variable* global, VariableInfo* info);
// Used for 5.5 GlobalVariableTypeAnnotations
VariableInfo* ImportLookup(Property* expr);
// 3.3 Environment Lookup
// NOTE: In the spec, the lookup function's prototype is
//
// Lookup(Delta, Gamma, x)
//
// Delta is the global_scope_ member, and Gamma, local_scope_.
VariableInfo* Lookup(Variable* variable);
// All of the ValidateXXX methods below return AsmType::None() in case of
// validation failure.
// 6.1 ValidateModule
AsmType* ValidateModule(FunctionLiteral* fun);
AsmType* ValidateGlobalDeclaration(Assignment* assign);
// 6.2 ValidateExport
AsmType* ExportType(VariableProxy* fun_export);
AsmType* ValidateExport(ReturnStatement* exports);
// 6.3 ValidateFunctionTable
AsmType* ValidateFunctionTable(Assignment* assign);
// 6.4 ValidateFunction
AsmType* ValidateFunction(FunctionDeclaration* fun_decl);
// 6.5 ValidateStatement
AsmType* ValidateStatement(Statement* statement);
// 6.5.1 BlockStatement
AsmType* ValidateBlockStatement(Block* block);
// 6.5.2 ExpressionStatement
AsmType* ValidateExpressionStatement(ExpressionStatement* expr);
// 6.5.3 EmptyStatement
AsmType* ValidateEmptyStatement(EmptyStatement* empty);
// 6.5.4 IfStatement
AsmType* ValidateIfStatement(IfStatement* if_stmt);
// 6.5.5 ReturnStatement
AsmType* ValidateReturnStatement(ReturnStatement* ret_stmt);
// 6.5.6 IterationStatement
// 6.5.6.a WhileStatement
AsmType* ValidateWhileStatement(WhileStatement* while_stmt);
// 6.5.6.b DoWhileStatement
AsmType* ValidateDoWhileStatement(DoWhileStatement* do_while);
// 6.5.6.c ForStatement
AsmType* ValidateForStatement(ForStatement* for_stmt);
// 6.5.7 BreakStatement
AsmType* ValidateBreakStatement(BreakStatement* brk_stmt);
// 6.5.8 ContinueStatement
AsmType* ValidateContinueStatement(ContinueStatement* cont_stmt);
// 6.5.9 LabelledStatement
// NOTE: we don't need to handle these: Labelled statements are
// BreakableStatements in our AST, but BreakableStatement is not a concrete
// class -- and we're handling all of BreakableStatement's subclasses.
// 6.5.10 SwitchStatement
AsmType* ValidateSwitchStatement(SwitchStatement* stmt);
// 6.6 ValidateCase
AsmType* ValidateCase(CaseClause* label, int32_t* case_lbl);
// 6.7 ValidateDefault
AsmType* ValidateDefault(CaseClause* label);
// 6.8 ValidateExpression
AsmType* ValidateExpression(Expression* expr);
AsmType* ValidateCompareOperation(CompareOperation* cmp);
AsmType* ValidateBinaryOperation(BinaryOperation* binop);
// 6.8.1 Expression
AsmType* ValidateCommaExpression(BinaryOperation* comma);
// 6.8.2 NumericLiteral
AsmType* ValidateNumericLiteral(Literal* literal);
// 6.8.3 Identifier
AsmType* ValidateIdentifier(VariableProxy* proxy);
// 6.8.4 CallExpression
AsmType* ValidateCallExpression(Call* call);
// 6.8.5 MemberExpression
AsmType* ValidateMemberExpression(Property* prop);
// 6.8.6 AssignmentExpression
AsmType* ValidateAssignmentExpression(Assignment* assignment);
// 6.8.7 UnaryExpression
AsmType* ValidateUnaryExpression(UnaryOperation* unop);
// 6.8.8 MultiplicativeExpression
AsmType* ValidateMultiplicativeExpression(BinaryOperation* binop);
// 6.8.9 AdditiveExpression
AsmType* ValidateAdditiveExpression(BinaryOperation* binop,
uint32_t intish_count);
// 6.8.10 ShiftExpression
AsmType* ValidateShiftExpression(BinaryOperation* binop);
// 6.8.11 RelationalExpression
AsmType* ValidateRelationalExpression(CompareOperation* cmpop);
// 6.8.12 EqualityExpression
AsmType* ValidateEqualityExpression(CompareOperation* cmpop);
// 6.8.13 BitwiseANDExpression
AsmType* ValidateBitwiseANDExpression(BinaryOperation* binop);
// 6.8.14 BitwiseXORExpression
AsmType* ValidateBitwiseXORExpression(BinaryOperation* binop);
// 6.8.15 BitwiseORExpression
AsmType* ValidateBitwiseORExpression(BinaryOperation* binop);
// 6.8.16 ConditionalExpression
AsmType* ValidateConditionalExpression(Conditional* cond);
// 6.9 ValidateCall
AsmType* ValidateCall(AsmType* return_type, Call* call);
// 6.10 ValidateHeapAccess
enum HeapAccessType { LoadFromHeap, StoreToHeap };
AsmType* ValidateHeapAccess(Property* heap, HeapAccessType access_type);
// 6.11 ValidateFloatCoercion
bool IsCallToFround(Call* call);
AsmType* ValidateFloatCoercion(Call* call);
// 5.1 ParameterTypeAnnotations
AsmType* ParameterTypeAnnotations(Variable* parameter,
Expression* annotation);
// 5.2 ReturnTypeAnnotations
AsmType* ReturnTypeAnnotations(ReturnStatement* statement);
// 5.4 VariableTypeAnnotations
AsmType* VariableTypeAnnotations(Expression* initializer);
// 5.5 GlobalVariableTypeAnnotations
AsmType* ImportExpression(Property* import);
AsmType* NewHeapView(CallNew* new_heap_view);
Isolate* isolate_;
Zone* zone_;
Script* script_;
FunctionLiteral* root_;
bool in_function_ = false;
AsmType* return_type_ = nullptr;
ZoneVector<VariableInfo*> forward_definitions_;
ObjectTypeMap stdlib_types_;
ObjectTypeMap stdlib_math_types_;
// The ASM module name. This member is used to prevent globals from redefining
// the module name.
VariableInfo module_info_;
Handle<String> module_name_;
// 3 Environments
ZoneHashMap global_scope_; // 3.1 Global environment
ZoneHashMap local_scope_; // 3.2 Variable environment
std::uintptr_t stack_limit_;
bool stack_overflow_ = false;
ZoneMap<AstNode*, AsmType*> node_types_;
static const int kErrorMessageLimit = 100;
char error_message_[kErrorMessageLimit];
DISALLOW_IMPLICIT_CONSTRUCTORS(AsmTyper);
};
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // SRC_ASMJS_ASM_TYPER_H_
......@@ -49,7 +49,9 @@ class AsmFunctionTableType;
V(Float32Array, "Float32Array", 20, kAsmHeap) \
V(Float64Array, "Float64Array", 21, kAsmHeap) \
/* Pseudo-types used in representing heap access for fp types.*/ \
/* TODO(jpp): FloatishDoubleQ should be a base type.*/ \
V(FloatishDoubleQ, "floatish|double?", 22, kAsmFloatish | kAsmDoubleQ) \
/* TODO(jpp): FloatQDoubleQ should be a base type.*/ \
V(FloatQDoubleQ, "float?|double?", 23, kAsmFloatQ | kAsmDoubleQ) \
/* None is used to represent errors in the type checker. */ \
V(None, "<none>", 31, 0)
......@@ -132,6 +134,9 @@ class AsmFunctionType : public AsmCallableType {
virtual bool IsMinMaxType() const { return false; }
virtual bool IsFroundType() const { return false; }
AsmType* ValidateCall(AsmType* return_type,
const ZoneVector<AsmType*>& args) override;
protected:
AsmFunctionType(Zone* zone, AsmType* return_type)
: return_type_(return_type), args_(zone) {}
......@@ -140,8 +145,6 @@ class AsmFunctionType : public AsmCallableType {
friend AsmType;
std::string Name() override;
AsmType* ValidateCall(AsmType* return_type,
const ZoneVector<AsmType*>& args) override;
AsmType* return_type_;
ZoneVector<AsmType*> args_;
......@@ -197,6 +200,7 @@ class AsmFunctionTableType : public AsmCallableType {
const ZoneVector<AsmType*>& args) override;
size_t length() const { return length_; }
AsmType* signature() { return signature_; }
private:
friend class AsmType;
......@@ -341,4 +345,4 @@ class AsmType {
} // namespace internal
} // namespace v8
#endif // SRC_WASM_ASM_TYPES_H_
#endif // SRC_ASMJS_ASM_TYPES_H_
......@@ -420,6 +420,8 @@
'arguments.h',
'asmjs/asm-js.cc',
'asmjs/asm-js.h',
'asmjs/asm-typer.cc',
'asmjs/asm-typer.h',
'asmjs/asm-types.cc',
'asmjs/asm-types.h',
'asmjs/asm-wasm-builder.cc',
......
......@@ -18,5 +18,3 @@ per-file *-x87*=chunyang.dai@intel.com
per-file *-x87*=weiliang.lin@intel.com
per-file expression-type-collector*=aseemgarg@chromium.org
per-file expression-type-collector*=bradnelson@chromium.org
per-file test-asm-validator.cc=aseemgarg@chromium.org
per-file test-asm-validator.cc=bradnelson@chromium.org
# Keep in sync with src/asmjs/OWNERS.
set noparent
ahaas@chromium.org
bradnelson@chromium.org
jpp@chromium.org
mtrofin@chromium.org
rossberg@chromium.org
titzer@chromium.org
// Copyright 2016 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 <cstring>
#include <functional>
#include <iostream>
#include <memory>
#include "src/asmjs/asm-typer.h"
#include "src/asmjs/asm-types.h"
#include "src/ast/ast-value-factory.h"
#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/base/platform/platform.h"
#include "src/parsing/parser.h"
#include "src/v8.h"
#include "test/cctest/cctest.h"
using namespace v8::internal;
namespace iw = v8::internal::wasm;
namespace v8 {
namespace internal {
namespace wasm {
namespace {
enum ValidationType {
ValidateModule,
ValidateGlobals,
ValidateFunctionTables,
ValidateExport,
ValidateFunction,
ValidateStatement,
ValidateExpression,
};
} // namespace
class AsmTyperHarnessBuilder {
public:
AsmTyperHarnessBuilder(const char* source, ValidationType type)
: source_(source),
validation_type_(type),
handles_(),
zone_(handles_.main_zone()),
isolate_(CcTest::i_isolate()),
ast_value_factory_(zone_, isolate_->heap()->HashSeed()),
factory_(isolate_->factory()),
source_code_(
factory_->NewStringFromUtf8(CStrVector(source)).ToHandleChecked()),
script_(factory_->NewScript(source_code_)) {
ParseInfo info(zone_, script_);
info.set_global();
info.set_lazy(false);
info.set_allow_lazy_parsing(false);
info.set_toplevel(true);
info.set_ast_value_factory(&ast_value_factory_);
info.set_ast_value_factory_owned(false);
Parser parser(&info);
if (!Compiler::ParseAndAnalyze(&info)) {
std::cerr << "Failed to parse:\n" << source_ << "\n";
CHECK(false);
}
outer_scope_ = info.script_scope();
module_ =
info.scope()->declarations()->at(0)->AsFunctionDeclaration()->fun();
typer_.reset(new AsmTyper(isolate_, zone_, *script_, module_));
if (validation_type_ == ValidateStatement ||
validation_type_ == ValidateExpression) {
fun_scope_.reset(new AsmTyper::FunctionScope(typer_.get()));
auto* decls = module_->scope()->declarations();
for (int ii = 0; ii < decls->length(); ++ii) {
Declaration* decl = decls->at(ii);
if (FunctionDeclaration* fun_decl = decl->AsFunctionDeclaration()) {
fun_decl_ = fun_decl;
break;
}
}
CHECK_NOT_NULL(fun_decl_);
}
}
struct VariableName {
VariableName(const char* name, VariableMode mode)
: name_(name), mode_(mode) {}
VariableName(const VariableName&) = default;
VariableName& operator=(const VariableName&) = default;
const char* name_;
const VariableMode mode_;
};
AsmTyperHarnessBuilder* WithLocal(VariableName var_name, AsmType* type) {
CHECK(validation_type_ == ValidateStatement ||
validation_type_ == ValidateExpression);
auto* var = DeclareVariable(var_name);
auto* var_info = new (zone_) AsmTyper::VariableInfo(type);
var_info->set_mutability(AsmTyper::VariableInfo::kLocal);
CHECK(typer_->AddLocal(var, var_info));
return this;
}
AsmTyperHarnessBuilder* WithGlobal(VariableName var_name, AsmType* type) {
auto* var = DeclareVariable(var_name);
auto* var_info = new (zone_) AsmTyper::VariableInfo(type);
var_info->set_mutability(AsmTyper::VariableInfo::kMutableGlobal);
CHECK(typer_->AddGlobal(var, var_info));
return this;
}
AsmTyperHarnessBuilder* WithGlobal(
VariableName var_name, std::function<AsmType*(Zone*)> type_creator) {
return WithGlobal(var_name, type_creator(zone_));
}
AsmTyperHarnessBuilder* WithUndefinedGlobal(
VariableName var_name, std::function<AsmType*(Zone*)> type_creator) {
auto* type = type_creator(zone_);
CHECK(type->AsFunctionType() != nullptr ||
type->AsFunctionTableType() != nullptr);
WithGlobal(var_name, type);
auto* var_info = typer_->Lookup(DeclareVariable(var_name));
CHECK(var_info);
var_info->FirstForwardUseIs(nullptr);
return this;
}
AsmTyperHarnessBuilder* WithImport(VariableName var_name,
AsmTyper::StandardMember standard_member) {
auto* var = DeclareVariable(var_name);
AsmTyper::VariableInfo* var_info = nullptr;
auto* stdlib_map = &typer_->stdlib_math_types_;
switch (standard_member) {
case AsmTyper::kHeap:
case AsmTyper::kStdlib:
case AsmTyper::kModule:
case AsmTyper::kNone:
CHECK(false);
case AsmTyper::kFFI:
stdlib_map = nullptr;
var_info = new (zone_) AsmTyper::VariableInfo(AsmType::FFIType(zone_));
var_info->set_mutability(AsmTyper::VariableInfo::kImmutableGlobal);
break;
case AsmTyper::kInfinity:
case AsmTyper::kNaN:
stdlib_map = &typer_->stdlib_types_;
default:
break;
}
if (var_info == nullptr) {
for (auto iter : *stdlib_map) {
if (iter.second->standard_member() == standard_member) {
var_info = iter.second;
break;
}
}
CHECK(var_info != nullptr);
var_info = var_info->Clone(zone_);
}
CHECK(typer_->AddGlobal(var, var_info));
return this;
}
AsmTyperHarnessBuilder* WithReturnType(AsmType* type) {
CHECK(type->IsReturnType());
CHECK(typer_->return_type_ == AsmType::None());
typer_->return_type_ = type;
return this;
}
AsmTyperHarnessBuilder* WithStdlib(VariableName var_name) {
auto* var = DeclareVariable(var_name);
auto* var_info = new (zone_) AsmTyper::VariableInfo();
var_info->set_mutability(AsmTyper::VariableInfo::kImmutableGlobal);
var_info->set_standard_member(AsmTyper::kStdlib);
CHECK(typer_->AddGlobal(var, var_info));
return this;
}
AsmTyperHarnessBuilder* WithHeap(VariableName var_name) {
auto* var = DeclareVariable(var_name);
auto* var_info = new (zone_) AsmTyper::VariableInfo();
var_info->set_mutability(AsmTyper::VariableInfo::kImmutableGlobal);
var_info->set_standard_member(AsmTyper::kHeap);
CHECK(typer_->AddGlobal(var, var_info));
return this;
}
AsmTyperHarnessBuilder* WithFFI(VariableName var_name) {
auto* var = DeclareVariable(var_name);
auto* var_info =
new (zone_) AsmTyper::VariableInfo(AsmType::FFIType(zone_));
var_info->set_mutability(AsmTyper::VariableInfo::kImmutableGlobal);
var_info->set_standard_member(AsmTyper::kFFI);
CHECK(typer_->AddGlobal(var, var_info));
return this;
}
bool Succeeds() {
CHECK(validation_type_ == ValidateModule ||
validation_type_ == ValidateGlobals ||
validation_type_ == ValidateFunctionTables ||
validation_type_ == ValidateExport ||
validation_type_ == ValidateFunction ||
validation_type_ == ValidateStatement);
if (validation_type_ == ValidateStatement) {
CHECK(typer_->return_type_ != AsmType::None());
if (ValidateAllStatements(fun_decl_)) {
return true;
}
} else if (typer_->Validate()) {
return true;
}
std::cerr << "Asm validation failed: " << typer_->error_message() << "\n";
return false;
}
bool SucceedsWithExactType(AsmType* type) {
CHECK(validation_type_ == ValidateExpression);
auto* validated_as = ValidateExpressionStatment(fun_decl_);
if (validated_as == AsmType::None()) {
std::cerr << "Validation failure: " << typer_->error_message() << "\n";
return false;
} else if (validated_as != type) {
std::cerr << "Validation succeeded with wrong type "
<< validated_as->Name() << " (vs. " << type->Name() << ").\n";
return false;
}
return true;
}
bool FailsWithMessage(const char* error_message) {
CHECK(validation_type_ == ValidateModule ||
validation_type_ == ValidateGlobals ||
validation_type_ == ValidateFunctionTables ||
validation_type_ == ValidateExport ||
validation_type_ == ValidateFunction ||
validation_type_ == ValidateStatement ||
validation_type_ == ValidateExpression);
bool success;
if (validation_type_ == ValidateStatement) {
CHECK(typer_->return_type_ != AsmType::None());
success = ValidateAllStatements(fun_decl_);
} else if (validation_type_ == ValidateExpression) {
success = ValidateExpressionStatment(fun_decl_) != AsmType::None();
} else {
success = typer_->Validate();
}
if (success) {
std::cerr << "Asm validation succeeded\n";
return false;
}
if (std::strstr(typer_->error_message(), error_message) == nullptr) {
std::cerr << "Asm validation failed with the wrong error message:\n"
"Expected to contain '"
<< error_message << "'\n"
" Actually is '"
<< typer_->error_message() << "'\n";
return false;
}
return true;
}
private:
Variable* DeclareVariable(VariableName var_name) {
auto* name_ast_string = ast_value_factory_.GetOneByteString(var_name.name_);
return var_name.mode_ == DYNAMIC_GLOBAL
? outer_scope_->DeclareDynamicGlobal(name_ast_string)
: module_->scope()->DeclareLocal(name_ast_string, VAR,
kCreatedInitialized,
Variable::NORMAL);
}
bool ValidateAllStatements(FunctionDeclaration* fun_decl) {
AsmTyper::FlattenedStatements iter(zone_, fun_decl->fun()->body());
while (auto* curr = iter.Next()) {
if (typer_->ValidateStatement(curr) == AsmType::None()) {
return false;
}
}
return true;
}
AsmType* ValidateExpressionStatment(FunctionDeclaration* fun_decl) {
AsmTyper::FlattenedStatements iter(zone_, fun_decl->fun()->body());
AsmType* ret = AsmType::None();
bool last_was_expression_statement = false;
while (auto* curr = iter.Next()) {
if (auto* expr_stmt = curr->AsExpressionStatement()) {
last_was_expression_statement = true;
if ((ret = typer_->ValidateExpression(expr_stmt->expression())) ==
AsmType::None()) {
break;
}
} else {
ret = AsmType::None();
last_was_expression_statement = true;
if (typer_->ValidateStatement(curr) == AsmType::None()) {
break;
}
}
}
CHECK(last_was_expression_statement || ret == AsmType::None());
return ret;
}
std::string source_;
ValidationType validation_type_;
HandleAndZoneScope handles_;
Zone* zone_;
Isolate* isolate_;
AstValueFactory ast_value_factory_;
Factory* factory_;
Handle<String> source_code_;
Handle<Script> script_;
Scope* outer_scope_;
FunctionLiteral* module_;
FunctionDeclaration* fun_decl_;
std::unique_ptr<AsmTyper> typer_;
std::unique_ptr<AsmTyper::FunctionScope> fun_scope_;
};
} // namespace wasm
} // namespace internal
} // namespace v8
namespace {
struct ValidationInput {
ValidationInput(const std::string& source, iw::ValidationType type)
: source_(source), type_(type) {}
const std::string source_;
const iw::ValidationType type_;
};
std::unique_ptr<iw::AsmTyperHarnessBuilder> ValidationOf(
ValidationInput input) {
return std::unique_ptr<iw::AsmTyperHarnessBuilder>(
new iw::AsmTyperHarnessBuilder(input.source_.c_str(), input.type_));
}
ValidationInput Module(const char* source) {
return ValidationInput(source, iw::ValidateModule);
}
std::string WrapInFunction(const char* source, bool needs_use_asm) {
if (needs_use_asm) {
return std::string(
"function foo() {\n"
" 'use asm';\n"
" ") +
source +
"\n"
"}";
}
return std::string(
"function bar() {\n"
" ") +
source +
"\n"
"}\n"
"return {b: bar};\n";
}
ValidationInput Globals(const char* source) {
static const bool kNeedsUseAsm = true;
return ValidationInput(WrapInFunction(source, kNeedsUseAsm),
iw::ValidateGlobals);
}
ValidationInput FunctionTables(const char* source) {
static const bool kNeedsUseAsm = true;
return ValidationInput(WrapInFunction(source, kNeedsUseAsm),
iw::ValidateFunctionTables);
}
ValidationInput Export(const char* source) {
static const bool kNeedsUseAsm = true;
return ValidationInput(WrapInFunction(source, kNeedsUseAsm),
iw::ValidateExport);
}
ValidationInput Function(const char* source) {
static const bool kNeedsUseAsm = true;
return ValidationInput(WrapInFunction(source, kNeedsUseAsm),
iw::ValidateFunction);
}
ValidationInput Statement(const char* source) {
static const bool kDoesNotNeedUseAsm = false;
static const bool kNeedsUseAsm = true;
return ValidationInput(
WrapInFunction(WrapInFunction(source, kDoesNotNeedUseAsm).c_str(),
kNeedsUseAsm),
iw::ValidateStatement);
}
ValidationInput Expression(const char* source) {
static const bool kDoesNotNeedUseAsm = false;
static const bool kNeedsUseAsm = true;
return ValidationInput(
WrapInFunction(WrapInFunction(source, kDoesNotNeedUseAsm).c_str(),
kNeedsUseAsm),
iw::ValidateExpression);
}
iw::AsmTyperHarnessBuilder::VariableName Var(const char* name) {
return iw::AsmTyperHarnessBuilder::VariableName(name, VAR);
}
iw::AsmTyperHarnessBuilder::VariableName DynamicGlobal(const char* name) {
return iw::AsmTyperHarnessBuilder::VariableName(name, DYNAMIC_GLOBAL);
}
TEST(MissingUseAsmDirective) {
v8::V8::Initialize();
// We can't test the empty input ("") because the AsmTyperHarnessBuilder will
// CHECK if there's no function in the top-level scope.
const char* kTests[] = {"function module(){}",
"function module(){ use_asm; }",
"function module(){ \"use asm \"; }",
"function module(){ \" use asm \"; }",
"function module(){ \"use Asm\"; }"};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const char* module = kTests[ii];
if (!ValidationOf(Module(module))
->FailsWithMessage("Missing \"use asm\"")) {
std::cerr << "Test:\n" << module;
CHECK(false);
}
}
}
TEST(InvalidModuleSignature) {
v8::V8::Initialize();
const struct {
const char* module;
const char* error_message;
} kTests[] = {
{"function eval(){ \"use asm\"; }",
"Invalid asm.js identifier in module name"},
{"function arguments(){ \"use asm\"; }",
"Invalid asm.js identifier in module name"},
{"function module(eval){ \"use asm\"; }",
"Invalid asm.js identifier in module parameter"},
{"function module(arguments){ \"use asm\"; }",
"Invalid asm.js identifier in module parameter"},
{"function module(stdlib, eval){ \"use asm\"; }",
"Invalid asm.js identifier in module parameter"},
{"function module(stdlib, arguments){ \"use asm\"; }",
"Invalid asm.js identifier in module parameter"},
{"function module(stdlib, foreign, eval){ \"use asm\"; }",
"Invalid asm.js identifier in module parameter"},
{"function module(stdlib, foreign, arguments){ \"use asm\"; }",
"Invalid asm.js identifier in module parameter"},
{"function module(stdlib, foreign, heap, eval){ \"use asm\"; }",
"asm.js modules may not have more than three parameters"},
{"function module(stdlib, foreign, heap, arguments){ \"use asm\"; }",
"asm.js modules may not have more than three parameters"},
{"function module(module){ \"use asm\"; }",
"Redeclared identifier in module parameter"},
{"function module(stdlib, module){ \"use asm\"; }",
"Redeclared identifier in module parameter"},
{"function module(stdlib, stdlib){ \"use asm\"; }",
"Redeclared identifier in module parameter"},
{"function module(stdlib, foreign, module){ \"use asm\"; }",
"Redeclared identifier in module parameter"},
{"function module(stdlib, foreign, stdlib){ \"use asm\"; }",
"Redeclared identifier in module parameter"},
{"function module(stdlib, foreign, foreign){ \"use asm\"; }",
"Redeclared identifier in module parameter"},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Module(test->module))
->FailsWithMessage(test->error_message)) {
std::cerr << "Test:\n" << test->module;
CHECK(false);
}
}
}
TEST(ErrorsInGlobalVariableDefinition) {
const struct {
const char* decl;
const char* error_message;
} kTests[] = {
{"var v;", "Global variable missing initializer"},
{"var v = uninitialized;", "Invalid global variable initializer"},
{"var v = 'use asm';", "type annotation - forbidden literal"},
{"var v = 4294967296;", " - forbidden literal"},
{"var v = not_fround;", "Invalid global variable initializer"},
{"var v = not_fround(1);", "expected call fround(literal)"},
{"var v = __fround__(1.0);", "expected call fround(literal)"},
{"var v = fround(1.0, 1.0);", "expected call fround(literal)"},
{"var v = fround(not_fround);", "literal argument for call to fround"},
{"var v = fround(1);", "literal argument to be a floating point"},
{"var v = stdlib.nan", "Invalid import"},
{"var v = stdlib.Math.nan", "Invalid import"},
{"var v = stdlib.Mathh.E", "Invalid import"},
{"var v = stdlib.Math", "Invalid import"},
{"var v = Stdlib.Math.E", "Invalid import"},
{"var v = stdlib.Math.E[0]", "Invalid import"},
{"var v = stdlibb.NaN", "Invalid import"},
{"var v = ffi.NaN[0]", "Invalid import"},
{"var v = heap.NaN[0]", "Invalid import"},
{"var v = ffi.foo * 2.0;", "unrecognized annotation"},
{"var v = ffi.foo|1;", "unrecognized annotation"},
{"var v = ffi()|0;", "must import member"},
{"var v = +ffi();", "must import member"},
{"var v = ffi().a|0;", "object lookup failed"},
{"var v = +ffi().a;", "object lookup failed"},
{"var v = sstdlib.a|0;", "object lookup failed"},
{"var v = +sstdlib.a;", "object lookup failed"},
{"var v = stdlib.NaN|0;", "object is not the ffi"},
{"var v = +stdlib.NaN;", "object is not the ffi"},
{"var v = new f()", "Invalid type after new"},
{"var v = new stdli.Uint8Array(heap)", "Unknown stdlib member in heap"},
{"var v = new stdlib.dd(heap)", "Unknown stdlib member in heap"},
{"var v = new stdlib.Math.fround(heap)", "Type is not a heap view type"},
{"var v = new stdlib.Uint8Array(a, b)", "Invalid number of arguments"},
{"var v = new stdlib.Uint8Array(heap())", "should be the module's heap"},
{"var v = new stdlib.Uint8Array(heap_)", "instead of heap parameter"},
{"var v = new stdlib.Uint8Array(ffi)", "should be the module's heap"},
{"var eval = 0;", "in global variable"},
{"var eval = 0.0;", "in global variable"},
{"var eval = fround(0.0);", "in global variable"},
{"var eval = +ffi.a;", "in global variable"},
{"var eval = ffi.a|0;", "in global variable"},
{"var eval = ffi.a;", "in global variable"},
{"var eval = new stdlib.Uint8Array(heap);", "in global variable"},
{"var arguments = 0;", "in global variable"},
{"var arguments = 0.0;", "in global variable"},
{"var arguments = fround(0.0);", "in global variable"},
{"var arguments = +ffi.a;", "in global variable"},
{"var arguments = ffi.a|0;", "in global variable"},
{"var arguments = ffi.a;", "in global variable"},
{"var arguments = new stdlib.Uint8Array(heap);", "in global variable"},
{"var a = 0, a = 0.0;", "Redefined global variable"},
{"var a = 0; var a = 0;", "Redefined global variable"},
{"var a = 0, b = 0; var a = 0;", "Redefined global variable"},
{"var a = 0, b = 0; var b = 0, a = 0.0;", "Redefined global variable"},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Globals(test->decl))
->WithStdlib(DynamicGlobal("stdlib"))
->WithFFI(DynamicGlobal("ffi"))
->WithHeap(DynamicGlobal("heap"))
->WithGlobal(DynamicGlobal("not_fround"), iw::AsmType::Int())
->WithImport(DynamicGlobal("fround"), iw::AsmTyper::kMathFround)
->FailsWithMessage(test->error_message)) {
std::cerr << "Test:\n" << test->decl;
CHECK(false);
}
}
}
TEST(ErrorsInFunctionTableDefinition) {
const struct {
const char* tables;
const char* error_message;
} kTests[] = {
{"var a = [a, a, a];", "Invalid length for function pointer table"},
{"var a = [d2s0()];", "must be a function name"},
{"var a = [d2s44];", "Undefined identifier in function pointer"},
{"var a = [fround];", "not be a member of the standard library"},
{"var a = [imul];", "not be a member of the standard library"},
{"var a = [ffi_import];", "must be an asm.js function"},
{"var a = [dI];", "must be an asm.js function"},
{"var a = [d2s0, d2s1, d2s0, f2s0];", "mismatch in function pointer"},
{"var eval = [d2s0, d2s1];", "asm.js identifier in function table name"},
{"var arguments = [d2s0, d2s1];", "asm.js identifier in function table"},
{"var foo = [d2s0, d2s1];",
"Identifier redefined as function pointer table"},
{"var I = [d2s0, d2s1];",
"Identifier redefined as function pointer table"},
{"var d2s = [d2f0, d2f1];", "redefined as function pointer table"},
{"var d2s_t = [d2s0];", "Function table size mismatch"},
{"var d2s_t = [d2f0, d2f1];", "initializer does not match previous"},
};
auto d2s = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Signed());
ret->AsFunctionType()->AddArgument(iw::AsmType::Double());
return ret;
};
auto d2s_tbl = [](Zone* zone) -> iw::AsmType* {
auto* d2s = iw::AsmType::Function(zone, iw::AsmType::Signed());
d2s->AsFunctionType()->AddArgument(iw::AsmType::Double());
auto* ret = iw::AsmType::FunctionTableType(zone, 2, d2s);
return ret;
};
auto f2s = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Signed());
ret->AsFunctionType()->AddArgument(iw::AsmType::Float());
return ret;
};
auto d2f = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Float());
ret->AsFunctionType()->AddArgument(iw::AsmType::Double());
return ret;
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(FunctionTables(test->tables))
->WithImport(DynamicGlobal("ffi_import"), iw::AsmTyper::kFFI)
->WithImport(DynamicGlobal("imul"), iw::AsmTyper::kMathImul)
->WithImport(DynamicGlobal("E"), iw::AsmTyper::kMathE)
->WithImport(DynamicGlobal("fround"), iw::AsmTyper::kMathFround)
->WithImport(DynamicGlobal("floor"), iw::AsmTyper::kMathFround)
->WithGlobal(DynamicGlobal("d2s0"), d2s)
->WithGlobal(DynamicGlobal("d2s1"), d2s)
->WithGlobal(DynamicGlobal("f2s0"), f2s)
->WithGlobal(DynamicGlobal("f2s1"), f2s)
->WithGlobal(DynamicGlobal("d2f0"), d2f)
->WithGlobal(DynamicGlobal("d2f1"), d2f)
->WithGlobal(DynamicGlobal("dI"), iw::AsmType::Int())
->WithGlobal(Var("I"), iw::AsmType::Int())
->WithUndefinedGlobal(Var("d2s"), d2s)
->WithUndefinedGlobal(Var("d2s_t"), d2s_tbl)
->FailsWithMessage(test->error_message)) {
std::cerr << "Test:\n" << test->tables;
CHECK(false);
}
}
}
TEST(ErrorsInModuleExport) {
const struct {
const char* module_export;
const char* error_message;
} kTests[] = {
{"", "Missing asm.js module export"},
{"return;", "Unrecognized expression in asm.js module export expression"},
{"return f;", "Undefined identifier in asm.js module export"},
{"return f();", "Unrecognized expression in asm.js module export"},
{"return d2s_tbl;", "cannot export function tables"},
{"return min;", "cannot export standard library functions"},
{"return ffi;", "cannot export foreign functions"},
{"return I;", "is not an asm.js function"},
{"return {'a': d2s_tbl}", "cannot export function tables"},
{"return {'a': min}", "cannot export standard library functions"},
{"return {'a': ffi}", "cannot export foreign functions"},
{"return {'a': f()}", "must be an asm.js function name"},
{"return {'a': f}", "Undefined identifier in asm.js module export"},
{"function v() { a(); } return {b: d2s}", "Missing definition for forw"},
{"return {b: d2s, 'a': d2s_tbl}", "cannot export function tables"},
{"return {b: d2s, 'a': min}", "cannot export standard library"},
{"return {b: d2s, 'a': ffi}", "cannot export foreign functions"},
{"return {b: d2s, 'a': f()}", "must be an asm.js function name"},
{"return {b: d2s, 'a': f}", "Undefined identifier in asm.js module"},
};
auto d2s_tbl = [](Zone* zone) -> iw::AsmType* {
auto* d2s = iw::AsmType::Function(zone, iw::AsmType::Signed());
d2s->AsFunctionType()->AddArgument(iw::AsmType::Double());
auto* ret = iw::AsmType::FunctionTableType(zone, 2, d2s);
return ret;
};
auto d2s = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Signed());
ret->AsFunctionType()->AddArgument(iw::AsmType::Double());
return ret;
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Export(test->module_export))
->WithGlobal(DynamicGlobal("d2s_tbl"), d2s_tbl)
->WithGlobal(DynamicGlobal("d2s"), d2s)
->WithImport(DynamicGlobal("min"), iw::AsmTyper::kMathMin)
->WithImport(DynamicGlobal("ffi"), iw::AsmTyper::kFFI)
->WithGlobal(DynamicGlobal("I"), iw::AsmType::Int())
->FailsWithMessage(test->error_message)) {
std::cerr << "Test:\n" << test->module_export;
CHECK(false);
}
}
}
TEST(ErrorsInFunction) {
auto d2s = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Signed());
ret->AsFunctionType()->AddArgument(iw::AsmType::Double());
return ret;
};
const struct {
const char* function;
const char* error_message;
} kTests[] = {
{"function f(eval) {"
" eval = eval|0;"
"}\n",
"Invalid asm.js identifier in parameter name"},
{"function f(arguments) {"
" arguments = arguments|0;"
"}\n",
"Invalid asm.js identifier in parameter name"},
// The following error should actually be a "redeclared local," but the
// AST "hides" the first parameter from us, so the parameter type checking
// will fail because the validator will think that the a = a|0 is
// annotating the second parameter.
{"function f(a, a) {\n"
" a = a|0;\n"
" a = +a;\n"
"}\n",
"Incorrect parameter type annotations"},
{"function f(b, a) {\n"
" if (0) return;\n"
" b = +b;\n"
" a = a|0;\n"
"}\n",
"Incorrect parameter type annotations"},
{"function f(b, a) {\n"
" f();\n"
" b = +b;\n"
" a = a|0;\n"
"}\n",
"Incorrect parameter type annotations"},
{"function f(b, a) {\n"
" f.a = 0;\n"
" b = +b;\n"
" a = a|0;\n"
"}\n",
"Incorrect parameter type annotations"},
{"function f(b, a) {\n"
" a = a|0;\n"
" b = +b;\n"
"}\n",
"Incorrect parameter type annotations"},
{"function f(b, a) {\n"
" b = +b;\n"
" a = a|0;\n"
" var eval = 0;\n"
"}\n",
"Invalid asm.js identifier in local variable"},
{"function f(b, a) {\n"
" b = +b;\n"
" a = a|0;\n"
" var b = 0;\n"
"}\n",
"Redeclared local"},
{"function f(b, a) {\n"
" b = +b;\n"
" a = a|0;\n"
" var c = 0, c = 1.0;\n"
"}\n",
"Redeclared local"},
{"function f(b, a) {\n"
" b = +b;\n"
" a = a|0;\n"
" var c = 0; var c = 1.0;\n"
"}\n",
"Redeclared local"},
{"function f(b, a) {\n"
" b = +b;\n"
" a = a|0;\n"
" f();\n"
" var c = 0;\n"
"}\n",
"Local variable missing initializer in asm.js module"},
{"function f() {\n"
" function ff() {}\n"
"}\n",
"Functions may only define inner variables"},
{"function f() {\n"
" return a+1;\n"
"}\n",
"Invalid return type annotation"},
{"function f() {\n"
" return ~~x;\n"
"}\n",
"Invalid return type annotation"},
{"function f() {\n"
" return d();\n"
"}\n",
"Invalid function call in return statement"},
{"function f() {\n"
" return 'use asm';\n"
"}\n",
"Invalid literal in return statement"},
{"function f() {\n"
" return 2147483648;\n"
"}\n",
"Invalid literal in return statement"},
{"function f() {\n"
" return stdlib.Math.E;"
"}\n",
"Invalid return type expression"},
{"function f() {\n"
" return E[0];"
"}\n",
"Invalid return type expression"},
{"function I() {}\n", "Identifier redefined as function"},
{"function foo() {}\n", "Identifier redefined as function"},
{"function d2s() {}\n", "Identifier redefined (function name)"},
{"function d2s(x) {\n"
" x = x|0;\n"
" return -1;\n"
"}\n",
"Identifier redefined (function name)"},
{"function d2s(x) {\n"
" x = +x;\n"
" return -1.0;\n"
"}\n",
"Identifier redefined (function name)"},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Function(test->function))
->WithGlobal(Var("I"), iw::AsmType::Int())
->WithGlobal(Var("d2s"), d2s)
->FailsWithMessage(test->error_message)) {
std::cerr << "Test:\n" << test->function;
CHECK(false);
}
}
}
TEST(ErrorsInStatement) {
const struct {
const char* statement;
const char* error_message;
} kTests[] = {
{"if (fround(1));", "If condition must be type int"},
{"return;", "Type mismatch in return statement"},
{"return +1.0;", "Type mismatch in return statement"},
{"return +d()", "Type mismatch in return statement"},
{"while (fround(1));", "While condition must be type int"},
{"do {} while (fround(1));", "Do {} While condition must be type int"},
{"for (;fround(1););", "For condition must be type int"},
{"switch(flocal){ case 0: return 0; }", "Switch tag must be signed"},
{"switch(slocal){ case 1: case 1: return 0; }", "Duplicated case label"},
{"switch(slocal){ case 1: case 0: break; case 1: return 0; }",
"Duplicated case label"},
{"switch(slocal){ case 1.0: return 0; }",
"Case label must be a 32-bit signed integer"},
{"switch(slocal){ case 1.0: return 0; }",
"Case label must be a 32-bit signed integer"},
{"switch(slocal){ case -100000: case 2147483647: return 0; }",
"Out-of-bounds case"},
{"switch(slocal){ case 2147483648: return 0; }",
"Case label must be a 32-bit signed"},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Statement(test->statement))
->WithReturnType(iw::AsmType::Signed())
->WithImport(DynamicGlobal("fround"), iw::AsmTyper::kMathFround)
->WithLocal(DynamicGlobal("flocal"), iw::AsmType::Float())
->WithLocal(DynamicGlobal("slocal"), iw::AsmType::Signed())
->FailsWithMessage(test->error_message)) {
std::cerr << "Test:\n" << test->statement;
CHECK(false);
}
}
}
TEST(ErrorsInExpression) {
auto d2d = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Double());
ret->AsFunctionType()->AddArgument(iw::AsmType::Double());
return ret;
};
auto d2s_tbl = [](Zone* zone) -> iw::AsmType* {
auto* d2s = iw::AsmType::Function(zone, iw::AsmType::Signed());
d2s->AsFunctionType()->AddArgument(iw::AsmType::Double());
auto* ret = iw::AsmType::FunctionTableType(zone, 2, d2s);
return ret;
};
const struct {
const char* expression;
const char* error_message;
} kTests[] = {
{"noy_a_function();", "Unanotated call to a function must be a call to"},
{"a = 0;", "Undeclared identifier"},
{"ilocal = +1.0", "Type mismatch in assignment"},
{"!dlocal", "Invalid type for !"},
{"2 * dlocal", "Invalid types for intish *"},
{"dlocal * 2", "Invalid types for intish *"},
{"1048577 * ilocal", "Invalid operands for *"},
{"1048577 / ilocal", "Invalid operands for /"},
{"1048577 % dlocal", "Invalid operands for %"},
{"1048577 * dlocal", "Invalid operands for *"},
{"1048577 / dlocal", "Invalid operands for /"},
{"1048577 % ilocal", "Invalid operands for %"},
{"ilocal * dlocal", "Invalid operands for *"},
{"ilocal / dlocal", "Invalid operands for /"},
{"ilocal % dlocal", "Invalid operands for %"},
{"1048577 + dlocal", "Invalid operands for additive expression"},
{"1048577 - dlocal", "Invalid operands for additive expression"},
{"ilocal + dlocal", "Invalid operands for additive expression"},
{"ilocal - dlocal", "Invalid operands for additive expression"},
{"1048577 << dlocal", "Invalid operands for <<"},
{"1048577 >> dlocal", "Invalid operands for >>"},
{"1048577 >>> dlocal", "Invalid operands for >>"},
{"ilocal << dlocal", "Invalid operands for <<"},
{"ilocal >> dlocal", "Invalid operands for >>"},
{"ilocal >>> dlocal", "Invalid operands for >>>"},
{"1048577 < dlocal", "Invalid operands for <"},
{"ilocal < dlocal", "Invalid operands for <"},
{"1048577 > dlocal", "Invalid operands for >"},
{"ilocal > dlocal", "Invalid operands for >"},
{"1048577 <= dlocal", "Invalid operands for <="},
{"ilocal <= dlocal", "Invalid operands for <="},
{"1048577 >= dlocal", "Invalid operands for >="},
{"ilocal >= dlocal", "Invalid operands for >="},
{"1048577 == dlocal", "Invalid operands for =="},
{"ilocal == dlocal", "Invalid operands for =="},
/* NOTE: the parser converts a == b to !(a == b). */
{"1048577 != dlocal", "Invalid operands for =="},
{"ilocal != dlocal", "Invalid operands for =="},
{"dlocal & dlocal", "Invalid operands for &"},
{"1048577 & dlocal", "Invalid operands for &"},
{"ilocal & dlocal", "Invalid operands for &"},
{"dlocal | dlocal2", "Invalid operands for |"},
{"1048577 | dlocal", "Invalid operands for |"},
{"ilocal | dlocal", "Invalid operands for |"},
{"dlocal ^ dlocal2", "Invalid operands for ^"},
{"1048577 ^ dlocal", "Invalid operands for ^"},
{"ilocal ^ dlocal", "Invalid operands for ^"},
{"dlocal ? 0 : 1", "Ternary operation condition should be int"},
{"ilocal ? dlocal : 1", "Type mismatch for ternary operation result"},
{"ilocal ? 1 : dlocal", "Type mismatch for ternary operation result"},
{"eval(10)|0", "Invalid asm.js identifier in (forward) function"},
{"arguments(10)|0", "Invalid asm.js identifier in (forward) function"},
{"not_a_function(10)|0", "Calling something that's not a function"},
{"fround(FFI())", "Foreign functions can't return float"},
{"FFI(fround(0))|0", "Function invocation does not match function type"},
{"FFI(2147483648)|0", "Function invocation does not match function type"},
{"d2d(2.0)|0", "Function invocation does not match function type"},
{"+d2d(2)", "Function invocation does not match function type"},
{"eval[ilocal & 3]()|0", "Invalid asm.js identifier in (forward)"},
{"arguments[ilocal & 3]()|0", "Invalid asm.js identifier in (forward)"},
{"not_a_function[ilocal & 3]()|0", "Identifier does not name a function"},
{"d2s_tbl[ilocal & 3](0.0)|0", "Function table size does not match"},
{"+d2s_tbl[ilocal & 1](0.0)", "does not match previous signature"},
{"d2s_tbl[ilocal & 1](0)|0", "does not match previous signature"},
{"a.b()|0", "Indirect call index must be in the expr & mask form"},
{"HEAP32[0][0] = 0", "Invalid heap access"},
{"heap32[0] = 0", "Undeclared identifier in heap access"},
{"not_a_function[0] = 0", "Identifier does not represent a heap view"},
{"HEAP32[0.0] = 0", "Heap access index must be intish"},
{"HEAP32[-1] = 0", "Heap access index must be a 32-bit unsigned integer"},
{"HEAP32[ilocal >> 1] = 0", "Invalid heap access index"},
// *VIOLATION* the following is invalid, but because of desugaring it is
// accepted.
// {"HEAP32[0 >> 1] = 0", "Invalid heap access index"},
{"HEAP8[fround(0.0)] = 0", "Invalid heap access index for byte array"},
{"HEAP8[iish] = 0", "Invalid heap access index for byte array"},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithLocal(DynamicGlobal("iish"), iw::AsmType::Intish())
->WithLocal(DynamicGlobal("ilocal"), iw::AsmType::Int())
->WithLocal(DynamicGlobal("dlocal"), iw::AsmType::Double())
->WithLocal(DynamicGlobal("dlocal2"), iw::AsmType::Double())
->WithLocal(DynamicGlobal("not_a_function"), iw::AsmType::Int())
->WithImport(DynamicGlobal("fround"), iw::AsmTyper::kMathFround)
->WithImport(DynamicGlobal("FFI"), iw::AsmTyper::kFFI)
->WithGlobal(DynamicGlobal("d2d"), d2d)
->WithGlobal(DynamicGlobal("d2s_tbl"), d2s_tbl)
->WithGlobal(DynamicGlobal("HEAP32"), iw::AsmType::Int32Array())
->WithGlobal(DynamicGlobal("HEAP8"), iw::AsmType::Int8Array())
->FailsWithMessage(test->error_message)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateNumericLiteral) {
const struct {
const char* expression;
iw::AsmType* expected_type;
} kTests[] = {
{"0", iw::AsmType::FixNum()},
{"-1", iw::AsmType::Signed()},
{"2147483648", iw::AsmType::Unsigned()},
{"0.0", iw::AsmType::Double()},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->SucceedsWithExactType(test->expected_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateIdentifier) {
const struct {
const char* expression;
iw::AsmType* expected_type;
} kTests[] = {{"afixnum", iw::AsmType::FixNum()},
{"adouble", iw::AsmType::Double()},
{"afloat", iw::AsmType::Float()},
{"anextern", iw::AsmType::Extern()},
{"avoid", iw::AsmType::Void()}};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithLocal(DynamicGlobal(test->expression), test->expected_type)
->WithGlobal(DynamicGlobal(test->expression),
iw::AsmType::Floatish())
->SucceedsWithExactType(test->expected_type)) {
std::cerr << "Test (local identifiers):\n" << test->expression;
CHECK(false);
}
}
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithGlobal(DynamicGlobal(test->expression), test->expected_type)
->SucceedsWithExactType(test->expected_type)) {
std::cerr << "Test (global identifiers):\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateCallExpression) {
auto v2f = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Float());
return ret;
};
const struct {
const char* expression;
} kTests[] = {
{"a_float_function()"},
{"fround(0)"},
{"slocal"},
{"ulocal"},
{"dqlocal"},
{"fishlocal"},
};
char full_test[200];
static const size_t kFullTestSize = arraysize(full_test);
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
CHECK(v8::base::OS::SNPrintF(full_test, kFullTestSize, "fround(%s)",
test->expression) < kFullTestSize);
if (!ValidationOf(Expression(full_test))
->WithImport(DynamicGlobal("fround"), iw::AsmTyper::kMathFround)
->WithGlobal(DynamicGlobal("a_float_function"), v2f)
->WithLocal(DynamicGlobal("slocal"), iw::AsmType::Signed())
->WithLocal(DynamicGlobal("ulocal"), iw::AsmType::Unsigned())
->WithLocal(DynamicGlobal("dqlocal"), iw::AsmType::DoubleQ())
->WithLocal(DynamicGlobal("fishlocal"), iw::AsmType::Floatish())
->SucceedsWithExactType(iw::AsmType::Float())) {
std::cerr << "Test:\n" << full_test;
CHECK(false);
}
}
const struct {
const char* expression;
const char* error_message;
} kFailureTests[] = {
{"vlocal", "Invalid argument type to fround"},
{"ilocal", "Invalid argument type to fround"},
{"a_double_function()", "Function invocation does not match"},
};
auto v2d = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Double());
return ret;
};
for (size_t ii = 0; ii < arraysize(kFailureTests); ++ii) {
const auto* test = kFailureTests + ii;
CHECK(v8::base::OS::SNPrintF(full_test, kFullTestSize, "fround(%s)",
test->expression) < kFullTestSize);
if (!ValidationOf(Expression(full_test))
->WithImport(DynamicGlobal("fround"), iw::AsmTyper::kMathFround)
->WithLocal(DynamicGlobal("ilocal"), iw::AsmType::Int())
->WithLocal(DynamicGlobal("vlocal"), iw::AsmType::Void())
->WithGlobal(DynamicGlobal("a_double_function"), v2d)
->FailsWithMessage(test->error_message)) {
std::cerr << "Test:\n" << full_test;
CHECK(false);
}
}
}
TEST(ValidateMemberExpression) {
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
{"I8[i]", iw::AsmType::Intish()}, // Legacy: no shift for 8-bit view.
{"I8[iish >> 0]", iw::AsmType::Intish()},
{"I8[0]", iw::AsmType::Intish()},
{"I8[2147483648]", iw::AsmType::Intish()},
{"U8[iish >> 0]", iw::AsmType::Intish()},
{"U8[i]", iw::AsmType::Intish()}, // Legacy: no shift for 8-bit view.
{"U8[0]", iw::AsmType::Intish()},
{"U8[2147483648]", iw::AsmType::Intish()},
{"I16[iish >> 1]", iw::AsmType::Intish()},
{"I16[0]", iw::AsmType::Intish()},
{"I16[2147483648]", iw::AsmType::Intish()}, // bug: must be pre-shifted.
{"U16[iish >> 1]", iw::AsmType::Intish()},
{"U16[0]", iw::AsmType::Intish()},
{"U16[2147483648]", iw::AsmType::Intish()}, // bug: must be pre-shifted.
{"I32[iish >> 2]", iw::AsmType::Intish()},
{"I32[0]", iw::AsmType::Intish()},
{"I32[2147483648]", iw::AsmType::Intish()}, // bug: must be pre-shifted.
{"U32[iish >> 2]", iw::AsmType::Intish()},
{"U32[0]", iw::AsmType::Intish()},
{"U32[2147483648]", iw::AsmType::Intish()}, // bug: must be pre-shifted.
{"F32[iish >> 2]", iw::AsmType::FloatQ()},
{"F32[0]", iw::AsmType::FloatQ()},
{"F32[2147483648]", iw::AsmType::FloatQ()}, // bug: must be pre-shifted.
{"F64[iish >> 3]", iw::AsmType::DoubleQ()},
{"F64[0]", iw::AsmType::DoubleQ()},
{"F64[2147483648]", iw::AsmType::DoubleQ()}, // bug: must be pre-shifted.
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithGlobal(DynamicGlobal("I8"), iw::AsmType::Int8Array())
->WithGlobal(DynamicGlobal("U8"), iw::AsmType::Uint8Array())
->WithGlobal(DynamicGlobal("I16"), iw::AsmType::Int16Array())
->WithGlobal(DynamicGlobal("U16"), iw::AsmType::Uint16Array())
->WithGlobal(DynamicGlobal("I32"), iw::AsmType::Int32Array())
->WithGlobal(DynamicGlobal("U32"), iw::AsmType::Uint32Array())
->WithGlobal(DynamicGlobal("F32"), iw::AsmType::Float32Array())
->WithGlobal(DynamicGlobal("F64"), iw::AsmType::Float64Array())
->WithLocal(DynamicGlobal("iish"), iw::AsmType::Intish())
->WithLocal(DynamicGlobal("i"), iw::AsmType::Int())
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateAssignmentExpression) {
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
// -----------------------------------------------------------------------
// Array assignments.
// Storing signed to int heap view.
{"I8[1024] = -1024", iw::AsmType::Signed()},
{"I8[1024 >> 0] = -1024", iw::AsmType::Signed()},
{"I8[0] = -1024", iw::AsmType::Signed()},
{"I8[2147483648] = -1024", iw::AsmType::Signed()},
{"U8[1024 >> 0] = -1024", iw::AsmType::Signed()},
{"U8[0] = -1024", iw::AsmType::Signed()},
{"U8[2147483648] = -1024", iw::AsmType::Signed()},
{"I16[1024 >> 1] = -1024", iw::AsmType::Signed()},
{"I16[0] = -1024", iw::AsmType::Signed()},
{"I16[2147483648] = -1024", iw::AsmType::Signed()}, // not pre-shifted.
{"U16[1024 >> 1] = -1024", iw::AsmType::Signed()},
{"U16[0] = -1024", iw::AsmType::Signed()},
{"U16[2147483648] = -1024", iw::AsmType::Signed()}, // not pre-shifted.
{"I32[1024 >> 2] = -1024", iw::AsmType::Signed()},
{"I32[0] = -1024", iw::AsmType::Signed()},
{"I32[2147483648] = -1024", iw::AsmType::Signed()}, // not pre-shifted.
{"U32[1024 >> 2] = -1024", iw::AsmType::Signed()},
{"U32[0] = -1024", iw::AsmType::Signed()},
{"U32[2147483648] = -1024", iw::AsmType::Signed()}, // not pre-shifted.
// Sroting fixnum to int heap view.
{"I8[1024] = 1024", iw::AsmType::FixNum()},
{"I8[1024 >> 0] = 1024", iw::AsmType::FixNum()},
{"I8[0] = 1024", iw::AsmType::FixNum()},
{"I8[2147483648] = 1024", iw::AsmType::FixNum()},
{"U8[1024 >> 0] = 1024", iw::AsmType::FixNum()},
{"U8[0] = 1024", iw::AsmType::FixNum()},
{"U8[2147483648] = 1024", iw::AsmType::FixNum()},
{"I16[1024 >> 1] = 1024", iw::AsmType::FixNum()},
{"I16[0] = 1024", iw::AsmType::FixNum()},
{"I16[2147483648] = 1024", iw::AsmType::FixNum()}, // not pre-shifted.
{"U16[1024 >> 1] = 1024", iw::AsmType::FixNum()},
{"U16[0] = 1024", iw::AsmType::FixNum()},
{"U16[2147483648] = 1024", iw::AsmType::FixNum()}, // not pre-shifted.
{"I32[1024 >> 2] = 1024", iw::AsmType::FixNum()},
{"I32[0] = 1024", iw::AsmType::FixNum()},
{"I32[2147483648] = 1024", iw::AsmType::FixNum()}, // not pre-shifted.
{"U32[1024 >> 2] = 1024", iw::AsmType::FixNum()},
{"U32[0] = 1024", iw::AsmType::FixNum()},
{"U32[2147483648] = 1024", iw::AsmType::FixNum()}, // not pre-shifted.
// Storing int to int heap view.
{"I8[ilocal] = ilocal", iw::AsmType::Int()},
{"I8[ilocal >> 0] = ilocal", iw::AsmType::Int()},
{"I8[0] = ilocal", iw::AsmType::Int()},
{"I8[2147483648] = ilocal", iw::AsmType::Int()},
{"U8[ilocal >> 0] = ilocal", iw::AsmType::Int()},
{"U8[0] = ilocal", iw::AsmType::Int()},
{"U8[2147483648] = ilocal", iw::AsmType::Int()},
{"I16[ilocal >> 1] = ilocal", iw::AsmType::Int()},
{"I16[0] = ilocal", iw::AsmType::Int()},
{"I16[2147483648] = ilocal", iw::AsmType::Int()}, // not pre-shifted.
{"U16[ilocal >> 1] = ilocal", iw::AsmType::Int()},
{"U16[0] = ilocal", iw::AsmType::Int()},
{"U16[2147483648] = ilocal", iw::AsmType::Int()}, // not pre-shifted.
{"I32[ilocal >> 2] = ilocal", iw::AsmType::Int()},
{"I32[0] = ilocal", iw::AsmType::Int()},
{"I32[2147483648] = ilocal", iw::AsmType::Int()}, // not pre-shifted.
{"U32[ilocal >> 2] = ilocal", iw::AsmType::Int()},
{"U32[0] = ilocal", iw::AsmType::Int()},
{"U32[2147483648] = ilocal", iw::AsmType::Int()}, // not pre-shifted.
// Storing intish to int heap view.
{"I8[ilocal] = iish", iw::AsmType::Intish()},
{"I8[iish >> 0] = iish", iw::AsmType::Intish()},
{"I8[0] = iish", iw::AsmType::Intish()},
{"I8[2147483648] = iish", iw::AsmType::Intish()},
{"U8[iish >> 0] = iish", iw::AsmType::Intish()},
{"U8[0] = iish", iw::AsmType::Intish()},
{"U8[2147483648] = iish", iw::AsmType::Intish()},
{"I16[iish >> 1] = iish", iw::AsmType::Intish()},
{"I16[0] = iish", iw::AsmType::Intish()},
{"I16[2147483648] = iish", iw::AsmType::Intish()}, // not pre-shifted.
{"U16[iish >> 1] = iish", iw::AsmType::Intish()},
{"U16[0] = iish", iw::AsmType::Intish()},
{"U16[2147483648] = iish", iw::AsmType::Intish()}, // not pre-shifted.
{"I32[iish >> 2] = iish", iw::AsmType::Intish()},
{"I32[0] = iish", iw::AsmType::Intish()},
{"I32[2147483648] = iish", iw::AsmType::Intish()}, // not pre-shifted.
{"U32[iish >> 2] = iish", iw::AsmType::Intish()},
{"U32[0] = iish", iw::AsmType::Intish()},
{"U32[2147483648] = iish", iw::AsmType::Intish()}, // not pre-shifted.
// Storing floatish to f32 heap view.
{"F32[iish >> 2] = fish", iw::AsmType::Floatish()},
{"F32[0] = fish", iw::AsmType::Floatish()},
{"F32[2147483648] = fish ", iw::AsmType::Floatish()}, // not pre-shifted.
// Storing double? to f32 heap view.
{"F32[iish >> 2] = dq", iw::AsmType::DoubleQ()},
{"F32[0] = dq", iw::AsmType::DoubleQ()},
{"F32[2147483648] = dq", iw::AsmType::DoubleQ()}, // not pre-shifted.
// Storing float? to f64 heap view.
{"F64[iish >> 3] = fq", iw::AsmType::FloatQ()},
{"F64[0] = fq", iw::AsmType::FloatQ()},
{"F64[2147483648] = fq", iw::AsmType::FloatQ()}, // not pre-shifted.
// Storing double? to f64 heap view.
{"F64[iish >> 3] = dq", iw::AsmType::DoubleQ()},
{"F64[0] = dq", iw::AsmType::DoubleQ()},
{"F64[2147483648] = dq", iw::AsmType::DoubleQ()}, // not pre-shifted.
// -----------------------------------------------------------------------
// Scalar assignments.
{"ilocal = 1024", iw::AsmType::FixNum()},
{"ilocal = -1024", iw::AsmType::Signed()},
{"ilocal = 2147483648", iw::AsmType::Unsigned()},
{"ilocal = iglobal", iw::AsmType::Int()},
{"iglobal = 1024", iw::AsmType::FixNum()},
{"iglobal = -1024", iw::AsmType::Signed()},
{"iglobal = 2147483648", iw::AsmType::Unsigned()},
{"iglobal = ilocal", iw::AsmType::Int()},
{"dlocal = 0.0", iw::AsmType::Double()},
{"dlocal = +make_double()", iw::AsmType::Double()},
{"dglobal = 0.0", iw::AsmType::Double()},
{"dglobal = +make_double()", iw::AsmType::Double()},
{"flocal = fround(0)", iw::AsmType::Float()},
{"flocal = fround(make_float())", iw::AsmType::Float()},
{"fglobal = fround(0)", iw::AsmType::Float()},
{"fglobal = fround(make_float())", iw::AsmType::Float()},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithImport(DynamicGlobal("fround"), iw::AsmTyper::kMathFround)
->WithLocal(DynamicGlobal("fq"), iw::AsmType::FloatQ())
->WithLocal(DynamicGlobal("dq"), iw::AsmType::DoubleQ())
->WithLocal(DynamicGlobal("fish"), iw::AsmType::Floatish())
->WithLocal(DynamicGlobal("iish"), iw::AsmType::Intish())
->WithGlobal(DynamicGlobal("iglobal"), iw::AsmType::Int())
->WithGlobal(DynamicGlobal("dglobal"), iw::AsmType::Double())
->WithGlobal(DynamicGlobal("fglobal"), iw::AsmType::Float())
->WithLocal(DynamicGlobal("ilocal"), iw::AsmType::Int())
->WithLocal(DynamicGlobal("dlocal"), iw::AsmType::Double())
->WithLocal(DynamicGlobal("flocal"), iw::AsmType::Float())
->WithGlobal(DynamicGlobal("I8"), iw::AsmType::Int8Array())
->WithGlobal(DynamicGlobal("U8"), iw::AsmType::Uint8Array())
->WithGlobal(DynamicGlobal("I16"), iw::AsmType::Int16Array())
->WithGlobal(DynamicGlobal("U16"), iw::AsmType::Uint16Array())
->WithGlobal(DynamicGlobal("I32"), iw::AsmType::Int32Array())
->WithGlobal(DynamicGlobal("U32"), iw::AsmType::Uint32Array())
->WithGlobal(DynamicGlobal("F32"), iw::AsmType::Float32Array())
->WithGlobal(DynamicGlobal("F64"), iw::AsmType::Float64Array())
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateUnaryExpression) {
auto v2d = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Double());
return ret;
};
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
{"-2147483648", iw::AsmType::Signed()},
{"-1024", iw::AsmType::Signed()},
{"-1", iw::AsmType::Signed()},
{"-2147483648.0", iw::AsmType::Double()},
{"+make_double()", iw::AsmType::Double()},
{"+dbl()", iw::AsmType::Double()},
{"make_double() * 1.0", iw::AsmType::Double()}, // Violation.
{"~~fq", iw::AsmType::Signed()},
{"~~dglobal", iw::AsmType::Signed()},
{"+slocal", iw::AsmType::Double()},
{"slocal * 1.0", iw::AsmType::Double()}, // Violation.
{"+ulocal", iw::AsmType::Double()},
{"ulocal * 1.0", iw::AsmType::Double()}, // Violation.
{"+dq", iw::AsmType::Double()},
{"dq * 1.0", iw::AsmType::Double()}, // Violation.
{"+fq", iw::AsmType::Double()},
{"fq * 1.0", iw::AsmType::Double()}, // Violation.
{"-ilocal", iw::AsmType::Intish()},
{"ilocal * -1", iw::AsmType::Intish()}, // Violation.
{"-dq", iw::AsmType::Double()},
{"dq * -1", iw::AsmType::Double()}, // Violation.
{"-fq", iw::AsmType::Floatish()},
{"fq * -1", iw::AsmType::Floatish()}, // Violation.
{"~iish", iw::AsmType::Signed()},
{"iish ^ -1", iw::AsmType::Signed()}, // Violation, but OK.
{"!ilocal", iw::AsmType::Int()},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithLocal(DynamicGlobal("fq"), iw::AsmType::FloatQ())
->WithLocal(DynamicGlobal("dq"), iw::AsmType::DoubleQ())
->WithLocal(DynamicGlobal("iish"), iw::AsmType::Intish())
->WithLocal(DynamicGlobal("slocal"), iw::AsmType::Signed())
->WithLocal(DynamicGlobal("ulocal"), iw::AsmType::Unsigned())
->WithLocal(DynamicGlobal("ilocal"), iw::AsmType::Int())
->WithGlobal(DynamicGlobal("dglobal"), iw::AsmType::Double())
->WithGlobal(DynamicGlobal("dbl"), v2d)
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateMultiplicativeExpression) {
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
{"dq * dq", iw::AsmType::Double()},
{"fq * fq", iw::AsmType::Floatish()},
{"slocal / slocal", iw::AsmType::Intish()},
{"ulocal / ulocal", iw::AsmType::Intish()},
{"dq / dq", iw::AsmType::Double()},
{"fq / fq", iw::AsmType::Floatish()},
{"slocal % slocal", iw::AsmType::Intish()},
{"ulocal % ulocal", iw::AsmType::Intish()},
{"dq % dq", iw::AsmType::Double()},
{"-1048575 * ilocal", iw::AsmType::Intish()},
{"ilocal * -1048575", iw::AsmType::Intish()},
{"1048575 * ilocal", iw::AsmType::Intish()},
{"ilocal * 1048575", iw::AsmType::Intish()},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithLocal(DynamicGlobal("fq"), iw::AsmType::FloatQ())
->WithLocal(DynamicGlobal("dq"), iw::AsmType::DoubleQ())
->WithLocal(DynamicGlobal("slocal"), iw::AsmType::Signed())
->WithLocal(DynamicGlobal("ulocal"), iw::AsmType::Unsigned())
->WithLocal(DynamicGlobal("ilocal"), iw::AsmType::Int())
->WithGlobal(DynamicGlobal("dglobal"), iw::AsmType::Double())
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateAdditiveExpression) {
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
{"dlocal + dlocal", iw::AsmType::Double()},
{"fq + fq", iw::AsmType::Floatish()},
{"dq - dq", iw::AsmType::Double()},
{"fq - fq", iw::AsmType::Floatish()},
{"ilocal + 1", iw::AsmType::Intish()},
{"ilocal - 1", iw::AsmType::Intish()},
{"slocal + ilocal + 1", iw::AsmType::Intish()},
{"slocal - ilocal + 1", iw::AsmType::Intish()},
{"ulocal + ilocal + 1", iw::AsmType::Intish()},
{"ulocal - ilocal + 1", iw::AsmType::Intish()},
{"ulocal + slocal + ilocal + 1", iw::AsmType::Intish()},
{"ulocal + slocal - ilocal + 1", iw::AsmType::Intish()},
{"ulocal - slocal + ilocal + 1", iw::AsmType::Intish()},
{"ulocal - slocal - ilocal + 1", iw::AsmType::Intish()},
{"1 + 1", iw::AsmType::FixNum()}, // Violation: intish.
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithLocal(DynamicGlobal("fq"), iw::AsmType::FloatQ())
->WithLocal(DynamicGlobal("dq"), iw::AsmType::DoubleQ())
->WithLocal(DynamicGlobal("iish"), iw::AsmType::Intish())
->WithLocal(DynamicGlobal("dlocal"), iw::AsmType::Double())
->WithLocal(DynamicGlobal("slocal"), iw::AsmType::Signed())
->WithLocal(DynamicGlobal("ulocal"), iw::AsmType::Unsigned())
->WithLocal(DynamicGlobal("ilocal"), iw::AsmType::Int())
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateShiftExpression) {
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
{"iish << iish", iw::AsmType::Signed()},
{"iish >> iish", iw::AsmType::Signed()},
{"iish >>> iish", iw::AsmType::Unsigned()},
{"1 << 0", iw::AsmType::FixNum()}, // Violation: signed.
{"1 >> 0", iw::AsmType::FixNum()}, // Violation: signed.
{"4294967295 >>> 0", iw::AsmType::Unsigned()},
{"-1 >>> 0", iw::AsmType::Unsigned()},
{"2147483647 >>> 0", iw::AsmType::FixNum()}, // Violation: unsigned.
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithLocal(DynamicGlobal("iish"), iw::AsmType::Intish())
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateComparisonExpression) {
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
// -----------------------------------------------------------------------
// Non const <op> Non const
{"s0 == s1", iw::AsmType::Int()},
{"u0 == u1", iw::AsmType::Int()},
{"f0 == f1", iw::AsmType::Int()},
{"d0 == d1", iw::AsmType::Int()},
{"s0 != s1", iw::AsmType::Int()},
{"u0 != u1", iw::AsmType::Int()},
{"f0 != f1", iw::AsmType::Int()},
{"d0 != d1", iw::AsmType::Int()},
{"s0 < s1", iw::AsmType::Int()},
{"u0 < u1", iw::AsmType::Int()},
{"f0 < f1", iw::AsmType::Int()},
{"d0 < d1", iw::AsmType::Int()},
{"s0 <= s1", iw::AsmType::Int()},
{"u0 <= u1", iw::AsmType::Int()},
{"f0 <= f1", iw::AsmType::Int()},
{"d0 <= d1", iw::AsmType::Int()},
{"s0 > s1", iw::AsmType::Int()},
{"u0 > u1", iw::AsmType::Int()},
{"f0 > f1", iw::AsmType::Int()},
{"d0 > d1", iw::AsmType::Int()},
{"s0 >= s1", iw::AsmType::Int()},
{"u0 >= u1", iw::AsmType::Int()},
{"f0 >= f1", iw::AsmType::Int()},
{"d0 >= d1", iw::AsmType::Int()},
// -----------------------------------------------------------------------
// Non const <op> Const
{"s0 == -1025", iw::AsmType::Int()},
{"u0 == 123456789", iw::AsmType::Int()},
{"f0 == fround(123456.78)", iw::AsmType::Int()},
{"d0 == 9876543.201", iw::AsmType::Int()},
{"s0 != -1025", iw::AsmType::Int()},
{"u0 != 123456789", iw::AsmType::Int()},
{"f0 != fround(123456.78)", iw::AsmType::Int()},
{"d0 != 9876543.201", iw::AsmType::Int()},
{"s0 < -1025", iw::AsmType::Int()},
{"u0 < 123456789", iw::AsmType::Int()},
{"f0 < fround(123456.78)", iw::AsmType::Int()},
{"d0 < 9876543.201", iw::AsmType::Int()},
{"s0 <= -1025", iw::AsmType::Int()},
{"u0 <= 123456789", iw::AsmType::Int()},
{"f0 <= fround(123456.78)", iw::AsmType::Int()},
{"d0 <= 9876543.201", iw::AsmType::Int()},
{"s0 > -1025", iw::AsmType::Int()},
{"u0 > 123456789", iw::AsmType::Int()},
{"f0 > fround(123456.78)", iw::AsmType::Int()},
{"d0 > 9876543.201", iw::AsmType::Int()},
{"s0 >= -1025", iw::AsmType::Int()},
{"u0 >= 123456789", iw::AsmType::Int()},
{"f0 >= fround(123456.78)", iw::AsmType::Int()},
{"d0 >= 9876543.201", iw::AsmType::Int()},
// -----------------------------------------------------------------------
// Const <op> Non const
{"-1025 == s0", iw::AsmType::Int()},
{"123456789 == u0", iw::AsmType::Int()},
{"fround(123456.78) == f0", iw::AsmType::Int()},
{"9876543.201 == d0", iw::AsmType::Int()},
{"-1025 != s0", iw::AsmType::Int()},
{"123456789 != u0", iw::AsmType::Int()},
{"fround(123456.78) != f0", iw::AsmType::Int()},
{"9876543.201 != d0", iw::AsmType::Int()},
{"-1025 < s0", iw::AsmType::Int()},
{"123456789 < u0", iw::AsmType::Int()},
{"fround(123456.78) < f0", iw::AsmType::Int()},
{"9876543.201 < d0", iw::AsmType::Int()},
{"-1025 <= s0", iw::AsmType::Int()},
{"123456789 <= u0", iw::AsmType::Int()},
{"fround(123456.78) <= f0", iw::AsmType::Int()},
{"9876543.201 <= d0", iw::AsmType::Int()},
{"-1025 > s0", iw::AsmType::Int()},
{"123456789 > u0", iw::AsmType::Int()},
{"fround(123456.78) > f0", iw::AsmType::Int()},
{"9876543.201 > d0", iw::AsmType::Int()},
{"-1025 >= s0", iw::AsmType::Int()},
{"123456789 >= u0", iw::AsmType::Int()},
{"fround(123456.78) >= f0", iw::AsmType::Int()},
{"9876543.201 >= d0", iw::AsmType::Int()},
// TODO(jpp): maybe add Const <op> Const.
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithImport(DynamicGlobal("fround"), iw::AsmTyper::kMathFround)
->WithLocal(DynamicGlobal("u0"), iw::AsmType::Unsigned())
->WithLocal(DynamicGlobal("u1"), iw::AsmType::Unsigned())
->WithLocal(DynamicGlobal("s0"), iw::AsmType::Signed())
->WithLocal(DynamicGlobal("s1"), iw::AsmType::Signed())
->WithLocal(DynamicGlobal("f0"), iw::AsmType::Float())
->WithLocal(DynamicGlobal("f1"), iw::AsmType::Float())
->WithLocal(DynamicGlobal("d0"), iw::AsmType::Double())
->WithLocal(DynamicGlobal("d1"), iw::AsmType::Double())
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateBitwiseExpression) {
auto v2s = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Signed());
return ret;
};
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
{"iish0 & iish1", iw::AsmType::Signed()},
{"iish0 | iish1", iw::AsmType::Signed()},
{"iish0 ^ iish1", iw::AsmType::Signed()},
{"iish0 & -1", iw::AsmType::Signed()},
{"iish0 | -1", iw::AsmType::Signed()},
{"iish0 ^ -1", iw::AsmType::Signed()},
{"2147483648 & iish1", iw::AsmType::Signed()},
{"2147483648 | iish1", iw::AsmType::Signed()},
{"2147483648 ^ iish1", iw::AsmType::Signed()},
{"2147483648 & 0", iw::AsmType::FixNum()}, // Violation: signed.
{"2147483648 | 0", iw::AsmType::Signed()},
{"2147483648 ^ 0", iw::AsmType::Signed()},
{"2134651 & 123", iw::AsmType::FixNum()}, // Violation: signed.
{"2134651 | 123", iw::AsmType::FixNum()}, // Violation: signed.
{"2134651 ^ 123", iw::AsmType::FixNum()}, // Violation: signed.
{"make_signed()|0", iw::AsmType::Signed()},
{"signed()|0", iw::AsmType::Signed()},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithLocal(DynamicGlobal("iish1"), iw::AsmType::Intish())
->WithLocal(DynamicGlobal("iish0"), iw::AsmType::Intish())
->WithGlobal(DynamicGlobal("signed"), v2s)
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateConditionalExpression) {
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
{"i0 ? i0 : i1", iw::AsmType::Int()},
{"i0 ? f0 : f1", iw::AsmType::Float()},
{"i0 ? d0 : d1", iw::AsmType::Double()},
{"0 ? -1 : 2147483648", iw::AsmType::Int()},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithLocal(DynamicGlobal("i0"), iw::AsmType::Int())
->WithLocal(DynamicGlobal("i1"), iw::AsmType::Int())
->WithLocal(DynamicGlobal("f0"), iw::AsmType::Float())
->WithLocal(DynamicGlobal("f1"), iw::AsmType::Float())
->WithLocal(DynamicGlobal("d0"), iw::AsmType::Double())
->WithLocal(DynamicGlobal("d1"), iw::AsmType::Double())
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
TEST(ValidateCall) {
auto v2f = [](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, iw::AsmType::Float());
return ret;
};
// ifd2_ is a helper function that returns a lambda for creating a function
// type that accepts an int, a float, and a double. ret_type_factory is a
// pointer to an AsmType*() function, and (*ret_type_factory)() returns the
// desired return type. For example,
//
// ifd2_(&iw::AsmType::Float)
//
// returns an AsmType representing an asm.j function with the following
// signature:
//
// float(int, float, double)
auto ifd2_ = [](iw::AsmType* (
*ret_type_factory)()) -> std::function<iw::AsmType*(Zone*)> {
return [ret_type_factory](Zone* zone) -> iw::AsmType* {
auto* ret = iw::AsmType::Function(zone, (*ret_type_factory)());
ret->AsFunctionType()->AddArgument(iw::AsmType::Int());
ret->AsFunctionType()->AddArgument(iw::AsmType::Float());
ret->AsFunctionType()->AddArgument(iw::AsmType::Double());
return ret;
};
};
auto ifd2f = ifd2_(&iw::AsmType::Float);
auto ifd2d = ifd2_(&iw::AsmType::Double);
auto ifd2i = ifd2_(&iw::AsmType::Signed);
// Just like ifd2_, but this one returns a type representing a function table.
auto tbl_ifd2_ = [](size_t tbl_size, iw::AsmType* (*ret_type_factory)())
-> std::function<iw::AsmType*(Zone*)> {
return [tbl_size, ret_type_factory](Zone* zone) -> iw::AsmType* {
auto* signature = iw::AsmType::Function(zone, (*ret_type_factory)());
signature->AsFunctionType()->AddArgument(iw::AsmType::Int());
signature->AsFunctionType()->AddArgument(iw::AsmType::Float());
signature->AsFunctionType()->AddArgument(iw::AsmType::Double());
auto* ret = iw::AsmType::FunctionTableType(zone, tbl_size, signature);
return ret;
};
};
auto ifd2f_tbl = tbl_ifd2_(32, &iw::AsmType::Float);
auto ifd2d_tbl = tbl_ifd2_(64, &iw::AsmType::Double);
auto ifd2i_tbl = tbl_ifd2_(4096, &iw::AsmType::Signed);
const struct {
const char* expression;
iw::AsmType* load_type;
} kTests[] = {
// -----------------------------------------------------------------------
// Functions.
{"fround(v2f())", iw::AsmType::Float()},
{"fround(fish)", iw::AsmType::Float()},
{"fround(dq)", iw::AsmType::Float()},
{"fround(s)", iw::AsmType::Float()},
{"fround(u)", iw::AsmType::Float()},
{"ffi()|0", iw::AsmType::Signed()},
{"ffi(1.0)|0", iw::AsmType::Signed()},
{"ffi(1.0, 2.0)|0", iw::AsmType::Signed()},
{"ffi(1.0, 2.0, 3)|0", iw::AsmType::Signed()},
{"ffi(1.0, 2.0, 3, 4)|0", iw::AsmType::Signed()},
{"+ffi()", iw::AsmType::Double()},
{"+ffi(1.0)", iw::AsmType::Double()},
{"+ffi(1.0, 2.0)", iw::AsmType::Double()},
{"+ffi(1.0, 2.0, 3)", iw::AsmType::Double()},
{"+ffi(1.0, 2.0, 3, 4)", iw::AsmType::Double()},
{"fround(ifd2f(1, fround(1), 1.0))", iw::AsmType::Float()},
{"+ifd2d(1, fround(1), 1.0)", iw::AsmType::Double()},
{"ifd2i(1, fround(1), 1.0)|0", iw::AsmType::Signed()},
// -----------------------------------------------------------------------
// Function tables.
{"fround(ifd2f_tbl[iish & 31](1, fround(1), 1.0))", iw::AsmType::Float()},
{"+ifd2d_tbl[iish & 63](1, fround(1), 1.0)", iw::AsmType::Double()},
{"ifd2i_tbl[iish & 4095](1, fround(1), 1.0)|0", iw::AsmType::Signed()},
};
for (size_t ii = 0; ii < arraysize(kTests); ++ii) {
const auto* test = kTests + ii;
if (!ValidationOf(Expression(test->expression))
->WithImport(DynamicGlobal("fround"), iw::AsmTyper::kMathFround)
->WithImport(DynamicGlobal("ffi"), iw::AsmTyper::kFFI)
->WithLocal(DynamicGlobal("fish"), iw::AsmType::Floatish())
->WithLocal(DynamicGlobal("dq"), iw::AsmType::DoubleQ())
->WithLocal(DynamicGlobal("s"), iw::AsmType::Signed())
->WithLocal(DynamicGlobal("u"), iw::AsmType::Unsigned())
->WithLocal(DynamicGlobal("iish"), iw::AsmType::Intish())
->WithGlobal(DynamicGlobal("v2f"), v2f)
->WithGlobal(DynamicGlobal("ifd2f_tbl"), ifd2f_tbl)
->WithGlobal(DynamicGlobal("ifd2d_tbl"), ifd2d_tbl)
->WithGlobal(DynamicGlobal("ifd2i_tbl"), ifd2i_tbl)
->SucceedsWithExactType(test->load_type)) {
std::cerr << "Test:\n" << test->expression;
CHECK(false);
}
}
}
} // namespace
......@@ -81,7 +81,6 @@ std::string Validate(Zone* zone, const char* source,
} // namespace
TEST(ValidateMinimum) {
const char test_function[] =
"function GeometricMean(stdlib, foreign, buffer) {\n"
......@@ -307,7 +306,6 @@ TEST(ValidateMinimum) {
CHECK_TYPES_END
}
TEST(MissingUseAsm) {
const char test_function[] =
"function foo() {\n"
......@@ -322,7 +320,6 @@ TEST(MissingUseAsm) {
Validate(zone, test_function, &types));
}
TEST(WrongUseAsm) {
const char test_function[] =
"function foo() {\n"
......@@ -338,7 +335,6 @@ TEST(WrongUseAsm) {
Validate(zone, test_function, &types));
}
TEST(MissingReturnExports) {
const char test_function[] =
"function foo() {\n"
......@@ -414,7 +410,6 @@ TEST(MissingReturnExports) {
} \
}
#define CHECK_VAR_SHORTCUT(name, type) \
CHECK_EXPR(Assignment, type) { \
CHECK_VAR(name, type); \
......@@ -424,7 +419,6 @@ TEST(MissingReturnExports) {
} \
}
#define CHECK_VAR_NEW_SHORTCUT(name, type) \
CHECK_EXPR(Assignment, type) { \
CHECK_VAR(name, type); \
......@@ -437,7 +431,6 @@ TEST(MissingReturnExports) {
} \
}
namespace {
void CheckStdlibShortcuts1(Zone* zone, ZoneVector<ExpressionTypeEntry>& types,
......@@ -470,7 +463,6 @@ void CheckStdlibShortcuts1(Zone* zone, ZoneVector<ExpressionTypeEntry>& types,
CHECK_VAR_MATH_SHORTCUT(fround, FUNC_N2F_TYPE);
}
void CheckStdlibShortcuts2(Zone* zone, ZoneVector<ExpressionTypeEntry>& types,
size_t& index, int& depth, TypeCache& cache) {
// var exp = stdlib.Math.*; (D * 12)
......@@ -495,7 +487,6 @@ void CheckStdlibShortcuts2(Zone* zone, ZoneVector<ExpressionTypeEntry>& types,
} // namespace
#define CHECK_FUNC_TYPES_BEGIN(func) \
HARNESS_PREAMBLE() \
func "\n" HARNESS_POSTAMBLE(); \
......@@ -526,12 +517,10 @@ void CheckStdlibShortcuts2(Zone* zone, ZoneVector<ExpressionTypeEntry>& types,
} \
CHECK_TYPES_END
#define CHECK_FUNC_TYPES_END \
CHECK_FUNC_TYPES_END_1(); \
CHECK_FUNC_TYPES_END_2();
#define CHECK_FUNC_ERROR(func, message) \
HARNESS_PREAMBLE() \
func "\n" HARNESS_POSTAMBLE(); \
......@@ -542,7 +531,6 @@ void CheckStdlibShortcuts2(Zone* zone, ZoneVector<ExpressionTypeEntry>& types,
ZoneVector<ExpressionTypeEntry> types(zone); \
CHECK_EQ(message, Validate(zone, test_function, &types));
TEST(BareHarness) {
CHECK_FUNC_TYPES_BEGIN("function foo() {}") {
CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) {}
......@@ -550,7 +538,6 @@ TEST(BareHarness) {
CHECK_FUNC_TYPES_END
}
TEST(ReturnVoid) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { return; }\n"
......@@ -568,7 +555,6 @@ TEST(ReturnVoid) {
CHECK_FUNC_TYPES_END
}
TEST(EmptyBody) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { }\n"
......@@ -583,7 +569,6 @@ TEST(EmptyBody) {
CHECK_FUNC_TYPES_END
}
TEST(DoesNothing) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1.0; }\n"
......@@ -603,7 +588,6 @@ TEST(DoesNothing) {
CHECK_FUNC_TYPES_END
}
TEST(ReturnInt32Literal) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { return 1; }\n"
......@@ -621,7 +605,6 @@ TEST(ReturnInt32Literal) {
CHECK_FUNC_TYPES_END
}
TEST(ReturnFloat64Literal) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { return 1.0; }\n"
......@@ -639,7 +622,6 @@ TEST(ReturnFloat64Literal) {
CHECK_FUNC_TYPES_END
}
TEST(ReturnFloat32Literal) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { return fround(1.0); }\n"
......@@ -658,7 +640,6 @@ TEST(ReturnFloat32Literal) {
CHECK_FUNC_TYPES_END
}
TEST(ReturnFloat64Var) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1.0; return +x; }\n"
......@@ -684,7 +665,6 @@ TEST(ReturnFloat64Var) {
CHECK_FUNC_TYPES_END
}
TEST(Addition2) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = 2; return (x+y)|0; }\n"
......@@ -711,7 +691,6 @@ TEST(Addition2) {
CHECK_FUNC_TYPES_END
}
#define TEST_COMPARE_OP(name, op) \
TEST(name) { \
CHECK_FUNC_TYPES_BEGIN("function bar() { return (0 " op \
......@@ -731,14 +710,12 @@ TEST(Addition2) {
CHECK_FUNC_TYPES_END \
}
TEST_COMPARE_OP(EqOperator, "==")
TEST_COMPARE_OP(LtOperator, "<")
TEST_COMPARE_OP(LteOperator, "<=")
TEST_COMPARE_OP(GtOperator, ">")
TEST_COMPARE_OP(GteOperator, ">=")
TEST(NeqOperator) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { return (0 != 0)|0; }\n"
......@@ -759,7 +736,6 @@ TEST(NeqOperator) {
CHECK_FUNC_TYPES_END
}
TEST(NotOperator) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 0; return (!x)|0; }\n"
......@@ -781,7 +757,6 @@ TEST(NotOperator) {
CHECK_FUNC_TYPES_END
}
TEST(InvertOperator) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 0; return (~x)|0; }\n"
......@@ -804,7 +779,6 @@ TEST(InvertOperator) {
CHECK_FUNC_TYPES_END
}
TEST(InvertConversion) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 0.0; return (~~x)|0; }\n"
......@@ -830,7 +804,6 @@ TEST(InvertConversion) {
CHECK_FUNC_TYPES_END
}
TEST(Ternary) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = 1; return (x?y:5)|0; }\n"
......@@ -858,7 +831,6 @@ TEST(Ternary) {
CHECK_FUNC_TYPES_END
}
#define TEST_INT_BIN_OP(name, op) \
TEST(name) { \
CHECK_FUNC_TYPES_BEGIN("function bar() { var x = 0; return (x " op \
......@@ -882,12 +854,10 @@ TEST(Ternary) {
CHECK_FUNC_TYPES_END \
}
TEST_INT_BIN_OP(AndOperator, "&")
TEST_INT_BIN_OP(OrOperator, "|")
TEST_INT_BIN_OP(XorOperator, "^")
TEST(SignedCompare) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = 1; return ((x|0) < (y|0))|0; }\n"
......@@ -920,7 +890,6 @@ TEST(SignedCompare) {
CHECK_FUNC_TYPES_END
}
TEST(SignedCompareConst) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = 1; return ((x|0) < (1<<31))|0; }\n"
......@@ -950,7 +919,6 @@ TEST(SignedCompareConst) {
CHECK_FUNC_TYPES_END
}
TEST(UnsignedCompare) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = 1; return ((x>>>0) < (y>>>0))|0; }\n"
......@@ -983,7 +951,6 @@ TEST(UnsignedCompare) {
CHECK_FUNC_TYPES_END
}
TEST(UnsignedCompareConst0) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = 1; return ((x>>>0) < (0>>>0))|0; }\n"
......@@ -1013,7 +980,6 @@ TEST(UnsignedCompareConst0) {
CHECK_FUNC_TYPES_END
}
TEST(UnsignedCompareConst1) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = 1; return ((x>>>0) < "
......@@ -1044,7 +1010,6 @@ TEST(UnsignedCompareConst1) {
CHECK_FUNC_TYPES_END
}
TEST(UnsignedDivide) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = 1; return ((x>>>0) / (y>>>0))|0; }\n"
......@@ -1077,7 +1042,6 @@ TEST(UnsignedDivide) {
CHECK_FUNC_TYPES_END
}
TEST(UnsignedFromFloat64) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1.0; return (x>>>0)|0; }\n"
......@@ -1085,7 +1049,6 @@ TEST(UnsignedFromFloat64) {
"asm: line 1: left bitwise operand expected to be an integer\n");
}
TEST(AndFloat64) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1.0; return (x&0)|0; }\n"
......@@ -1093,7 +1056,6 @@ TEST(AndFloat64) {
"asm: line 1: left bitwise operand expected to be an integer\n");
}
TEST(TypeMismatchAddInt32Float64) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1.0; var y = 0; return (x + y)|0; }\n"
......@@ -1101,7 +1063,6 @@ TEST(TypeMismatchAddInt32Float64) {
"asm: line 1: ill-typed arithmetic operation\n");
}
TEST(TypeMismatchSubInt32Float64) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1.0; var y = 0; return (x - y)|0; }\n"
......@@ -1109,7 +1070,6 @@ TEST(TypeMismatchSubInt32Float64) {
"asm: line 1: ill-typed arithmetic operation\n");
}
TEST(TypeMismatchDivInt32Float64) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1.0; var y = 0; return (x / y)|0; }\n"
......@@ -1117,7 +1077,6 @@ TEST(TypeMismatchDivInt32Float64) {
"asm: line 1: ill-typed arithmetic operation\n");
}
TEST(TypeMismatchModInt32Float64) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1.0; var y = 0; return (x % y)|0; }\n"
......@@ -1125,7 +1084,6 @@ TEST(TypeMismatchModInt32Float64) {
"asm: line 1: ill-typed arithmetic operation\n");
}
TEST(ModFloat32) {
CHECK_FUNC_ERROR(
"function bar() { var x = fround(1.0); return (x % x)|0; }\n"
......@@ -1133,7 +1091,6 @@ TEST(ModFloat32) {
"asm: line 1: ill-typed arithmetic operation\n");
}
TEST(TernaryMismatchInt32Float64) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; var y = 0.0; return (1 ? x : y)|0; }\n"
......@@ -1142,7 +1099,6 @@ TEST(TernaryMismatchInt32Float64) {
"and be int, float, or double\n");
}
TEST(TernaryMismatchIntish) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; var y = 0; return (1 ? x + x : y)|0; }\n"
......@@ -1151,7 +1107,6 @@ TEST(TernaryMismatchIntish) {
"and be int, float, or double\n");
}
TEST(TernaryMismatchInt32Float32) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; var y = 2.0; return (x?fround(y):x)|0; }\n"
......@@ -1160,7 +1115,6 @@ TEST(TernaryMismatchInt32Float32) {
"and be int, float, or double\n");
}
TEST(TernaryBadCondition) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; var y = 2.0; return (y?x:1)|0; }\n"
......@@ -1308,7 +1262,6 @@ TEST(Addition4) {
CHECK_FUNC_TYPES_END
}
TEST(Multiplication2) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; var y = 2; return (x*y)|0; }\n"
......@@ -1316,7 +1269,6 @@ TEST(Multiplication2) {
"asm: line 1: multiply must be by an integer literal\n");
}
TEST(Division4) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; var y = 2; return (x/y/x/y)|0; }\n"
......@@ -1357,7 +1309,6 @@ TEST(DivIntMismatch) {
"must match and be signed or unsigned\n");
}
TEST(CompareToStringLeft) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; return ('hi' > x)|0; }\n"
......@@ -1366,7 +1317,6 @@ TEST(CompareToStringLeft) {
"and be signed, unsigned, float, or double\n");
}
TEST(CompareToStringRight) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; return (x < 'hi')|0; }\n"
......@@ -1375,7 +1325,6 @@ TEST(CompareToStringRight) {
"and be signed, unsigned, float, or double\n");
}
TEST(CompareMismatchInt32Float64) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; var y = 2.0; return (x < y)|0; }\n"
......@@ -1384,7 +1333,6 @@ TEST(CompareMismatchInt32Float64) {
"and be signed, unsigned, float, or double\n");
}
TEST(CompareMismatchInt32Uint32) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; var y = 2; return ((x|0) < (y>>>0))|0; }\n"
......@@ -1393,7 +1341,6 @@ TEST(CompareMismatchInt32Uint32) {
"and be signed, unsigned, float, or double\n");
}
TEST(CompareMismatchInt32Float32) {
CHECK_FUNC_ERROR(
"function bar() { var x = 1; var y = 2.0; return (x < fround(y))|0; }\n"
......@@ -1438,7 +1385,6 @@ TEST(Float64ToInt32) {
CHECK_FUNC_TYPES_END
}
TEST(Load1) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = i8[x>>0]|0; }\n"
......@@ -1467,7 +1413,6 @@ TEST(Load1) {
CHECK_FUNC_TYPES_END
}
TEST(LoadDouble) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; var y = 0.0; y = +f64[x>>3]; }\n"
......@@ -1500,7 +1445,6 @@ TEST(LoadDouble) {
CHECK_FUNC_TYPES_END
}
TEST(Store1) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 1; i8[x>>0] = 0; }\n"
......@@ -1526,7 +1470,6 @@ TEST(Store1) {
CHECK_FUNC_TYPES_END
}
TEST(StoreFloat) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = fround(1.0); "
......@@ -1653,7 +1596,6 @@ TEST(Load1Constant) {
CHECK_FUNC_TYPES_END
}
TEST(FunctionTables) {
CHECK_FUNC_TYPES_BEGIN(
"function func1(x) { x = x | 0; return (x * 5) | 0; }\n"
......@@ -1738,7 +1680,6 @@ TEST(FunctionTables) {
CHECK_FUNC_TYPES_END_2();
}
TEST(BadFunctionTable) {
CHECK_FUNC_ERROR(
"function func1(x) { x = x | 0; return (x * 5) | 0; }\n"
......@@ -1749,7 +1690,6 @@ TEST(BadFunctionTable) {
"asm: line 2: array component expected to be a function\n");
}
TEST(MissingParameterTypes) {
CHECK_FUNC_ERROR(
"function bar(x) { var y = 1; }\n"
......@@ -1757,7 +1697,6 @@ TEST(MissingParameterTypes) {
"asm: line 1: missing parameter type annotations\n");
}
TEST(InvalidTypeAnnotationBinaryOpDiv) {
CHECK_FUNC_ERROR(
"function bar(x) { x = x / 4; }\n"
......@@ -1765,7 +1704,6 @@ TEST(InvalidTypeAnnotationBinaryOpDiv) {
"asm: line 1: invalid type annotation on binary op\n");
}
TEST(InvalidTypeAnnotationBinaryOpMul) {
CHECK_FUNC_ERROR(
"function bar(x) { x = x * 4.0; }\n"
......@@ -1773,7 +1711,6 @@ TEST(InvalidTypeAnnotationBinaryOpMul) {
"asm: line 1: invalid type annotation on binary op\n");
}
TEST(InvalidArgumentCount) {
CHECK_FUNC_ERROR(
"function bar(x) { return fround(4, 5); }\n"
......@@ -1781,7 +1718,6 @@ TEST(InvalidArgumentCount) {
"asm: line 1: invalid argument count calling function\n");
}
TEST(InvalidTypeAnnotationArity) {
CHECK_FUNC_ERROR(
"function bar(x) { x = max(x); }\n"
......@@ -1789,7 +1725,6 @@ TEST(InvalidTypeAnnotationArity) {
"asm: line 1: only fround allowed on expression annotations\n");
}
TEST(InvalidTypeAnnotationOnlyFround) {
CHECK_FUNC_ERROR(
"function bar(x) { x = sin(x); }\n"
......@@ -1797,7 +1732,6 @@ TEST(InvalidTypeAnnotationOnlyFround) {
"asm: line 1: only fround allowed on expression annotations\n");
}
TEST(InvalidTypeAnnotation) {
CHECK_FUNC_ERROR(
"function bar(x) { x = (x+x)(x); }\n"
......@@ -1805,7 +1739,6 @@ TEST(InvalidTypeAnnotation) {
"asm: line 1: invalid type annotation\n");
}
TEST(WithStatement) {
CHECK_FUNC_ERROR(
"function bar() { var x = 0; with (x) { x = x + 1; } }\n"
......@@ -1813,7 +1746,6 @@ TEST(WithStatement) {
"asm: line 1: bad with statement\n");
}
TEST(NestedFunction) {
CHECK_FUNC_ERROR(
"function bar() { function x() { return 1; } }\n"
......@@ -1821,7 +1753,6 @@ TEST(NestedFunction) {
"asm: line 1: function declared inside another\n");
}
TEST(UnboundVariable) {
CHECK_FUNC_ERROR(
"function bar() { var x = y; }\n"
......@@ -1829,7 +1760,6 @@ TEST(UnboundVariable) {
"asm: line 1: unbound variable\n");
}
TEST(EqStrict) {
CHECK_FUNC_ERROR(
"function bar() { return (0 === 0)|0; }\n"
......@@ -1837,7 +1767,6 @@ TEST(EqStrict) {
"asm: line 1: illegal comparison operator\n");
}
TEST(NeStrict) {
CHECK_FUNC_ERROR(
"function bar() { return (0 !== 0)|0; }\n"
......@@ -1845,7 +1774,6 @@ TEST(NeStrict) {
"asm: line 1: illegal comparison operator\n");
}
TEST(InstanceOf) {
CHECK_FUNC_ERROR(
"function bar() { return (0 instanceof 0)|0; }\n"
......@@ -1853,7 +1781,6 @@ TEST(InstanceOf) {
"asm: line 1: illegal comparison operator\n");
}
TEST(InOperator) {
CHECK_FUNC_ERROR(
"function bar() { return (0 in 0)|0; }\n"
......@@ -1861,7 +1788,6 @@ TEST(InOperator) {
"asm: line 1: illegal comparison operator\n");
}
TEST(LogicalAndOperator) {
CHECK_FUNC_ERROR(
"function bar() { return (0 && 0)|0; }\n"
......@@ -1869,7 +1795,6 @@ TEST(LogicalAndOperator) {
"asm: line 1: illegal logical operator\n");
}
TEST(LogicalOrOperator) {
CHECK_FUNC_ERROR(
"function bar() { return (0 || 0)|0; }\n"
......@@ -1891,7 +1816,6 @@ TEST(BadLiteral) {
"asm: line 1: illegal literal\n");
}
TEST(MismatchedReturnTypeLiteral) {
CHECK_FUNC_ERROR(
"function bar() { if(1) { return 1; } return 1.0; }\n"
......@@ -1899,7 +1823,6 @@ TEST(MismatchedReturnTypeLiteral) {
"asm: line 1: return type does not match function signature\n");
}
TEST(MismatchedReturnTypeExpression) {
CHECK_FUNC_ERROR(
"function bar() {\n"
......@@ -1908,7 +1831,6 @@ TEST(MismatchedReturnTypeExpression) {
"asm: line 2: return type does not match function signature\n");
}
TEST(AssignToFloatishToF64) {
CHECK_FUNC_ERROR(
"function bar() { var v = fround(1.0); f64[0] = v + fround(1.0); }\n"
......@@ -1916,7 +1838,6 @@ TEST(AssignToFloatishToF64) {
"asm: line 1: floatish assignment to double array\n");
}
TEST(ForeignFunction) {
CHECK_FUNC_TYPES_BEGIN(
"var baz = foreign.baz;\n"
......@@ -1986,7 +1907,6 @@ TEST(BadExports) {
Validate(zone, test_function, &types));
}
TEST(NestedHeapAssignment) {
CHECK_FUNC_ERROR(
"function bar() { var x = 0; i16[x = 1] = 2; }\n"
......@@ -2001,7 +1921,6 @@ TEST(BadOperatorHeapAssignment) {
"asm: line 1: expected >> in heap access\n");
}
TEST(BadArrayAssignment) {
CHECK_FUNC_ERROR(
"function bar() { i8[0] = 0.0; }\n"
......@@ -2009,7 +1928,6 @@ TEST(BadArrayAssignment) {
"asm: line 1: illegal type in assignment\n");
}
TEST(BadStandardFunctionCallOutside) {
CHECK_FUNC_ERROR(
"var s0 = sin(0);\n"
......@@ -2018,7 +1936,6 @@ TEST(BadStandardFunctionCallOutside) {
"asm: line 1: illegal variable reference in module body\n");
}
TEST(BadFunctionCallOutside) {
CHECK_FUNC_ERROR(
"function bar() { return 0.0; }\n"
......@@ -2064,7 +1981,6 @@ TEST(NestedVariableAssignment) {
CHECK_FUNC_TYPES_END
}
TEST(NestedAssignmentInHeap) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = 0; i8[(x = 1) >> 0] = 2; }\n"
......@@ -2093,7 +2009,6 @@ TEST(NestedAssignmentInHeap) {
CHECK_FUNC_TYPES_END
}
TEST(NegativeDouble) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = -123.2; }\n"
......@@ -2109,7 +2024,6 @@ TEST(NegativeDouble) {
CHECK_FUNC_TYPES_END
}
TEST(NegativeInteger) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = -123; }\n"
......@@ -2125,7 +2039,6 @@ TEST(NegativeInteger) {
CHECK_FUNC_TYPES_END
}
TEST(AbsFunction) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = -123.0; x = abs(x); }\n"
......@@ -2148,7 +2061,6 @@ TEST(AbsFunction) {
CHECK_FUNC_TYPES_END
}
TEST(CeilFloat) {
CHECK_FUNC_TYPES_BEGIN(
"function bar() { var x = fround(3.1); x = ceil(x); }\n"
......@@ -2246,7 +2158,6 @@ TEST(TypeConsistency) {
CHECK(!cache.kAsmDouble->Is(cache.kAsmFloat));
}
TEST(SwitchTest) {
CHECK_FUNC_TYPES_BEGIN(
"function switcher(x) {\n"
......@@ -2292,7 +2203,6 @@ TEST(SwitchTest) {
CHECK_FUNC_TYPES_END
}
TEST(BadSwitchRange) {
CHECK_FUNC_ERROR(
"function bar() { switch (1) { case -1: case 0x7fffffff: } }\n"
......@@ -2300,7 +2210,6 @@ TEST(BadSwitchRange) {
"asm: line 1: case range too large\n");
}
TEST(DuplicateSwitchCase) {
CHECK_FUNC_ERROR(
"function bar() { switch (1) { case 0: case 0: } }\n"
......@@ -2308,7 +2217,6 @@ TEST(DuplicateSwitchCase) {
"asm: line 1: duplicate case value\n");
}
TEST(BadSwitchOrder) {
CHECK_FUNC_ERROR(
"function bar() { switch (1) { default: case 0: } }\n"
......
......@@ -32,6 +32,8 @@
'v8_code': 1,
'generated_file': '<(SHARED_INTERMEDIATE_DIR)/resources.cc',
'cctest_sources': [ ### gcmole(all) ###
'asmjs/test-asm-typer.cc',
'asmjs/test-typing-asm.cc',
'compiler/c-signature.h',
'compiler/codegen-tester.cc',
'compiler/codegen-tester.h',
......@@ -115,7 +117,6 @@
'test-array-list.cc',
'test-ast.cc',
'test-ast-expression-visitor.cc',
'test-asm-validator.cc',
'test-atomicops.cc',
'test-bignum.cc',
'test-bignum-dtoa.cc',
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment