From 546d9a70ace27ccecbf4adaad44bdeee6de135b7 Mon Sep 17 00:00:00 2001 From: bradnelson Date: Tue, 1 Sep 2015 11:30:34 -0700 Subject: [PATCH] Add asm.js typer / validator. Walk asm.js module ASTs, attach concrete type information in preparation for generating a WASM module. cctest test coverage (mjsunit coming in later CL). Expressions, function tables, and foreign functions have coverage. Statement coverage to be expanded in a later CL. BUG= https://code.google.com/p/v8/issues/detail?id=4203 TEST=test-asm-validator R=rossberg@chromium.org,titzer@chromium.org LOG=N Review URL: https://codereview.chromium.org/1322773002 Cr-Commit-Position: refs/heads/master@{#30520} --- BUILD.gn | 2 + src/typing-asm.cc | 1070 +++++++++++++++++++++++++++++ src/typing-asm.h | 95 +++ test/cctest/cctest.gyp | 1 + test/cctest/test-asm-validator.cc | 918 +++++++++++++++++++++++++ tools/gyp/v8.gyp | 2 + 6 files changed, 2088 insertions(+) create mode 100644 src/typing-asm.cc create mode 100644 src/typing-asm.h create mode 100644 test/cctest/test-asm-validator.cc diff --git a/BUILD.gn b/BUILD.gn index 6d07d7700..2b8f46a96 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -1213,6 +1213,8 @@ source_set("v8_base") { "src/types-inl.h", "src/types.cc", "src/types.h", + "src/typing-asm.cc", + "src/typing-asm.h", "src/typing-reset.cc", "src/typing-reset.h", "src/typing.cc", diff --git a/src/typing-asm.cc b/src/typing-asm.cc new file mode 100644 index 000000000..a8d0fbc73 --- /dev/null +++ b/src/typing-asm.cc @@ -0,0 +1,1070 @@ +// Copyright 2015 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/v8.h" + +#include "src/typing-asm.h" + +#include "src/ast.h" +#include "src/codegen.h" +#include "src/scopes.h" +#include "src/zone-type-cache.h" + +namespace v8 { +namespace internal { +namespace { + +base::LazyInstance::type kCache = LAZY_INSTANCE_INITIALIZER; + +} // namespace + + +#define FAIL(node, msg) \ + do { \ + valid_ = false; \ + int line = node->position() == RelocInfo::kNoPosition \ + ? -1 \ + : script_->GetLineNumber(node->position()); \ + base::OS::SNPrintF(error_message_, sizeof(error_message_), \ + "asm: line %d: %s\n", line + 1, msg); \ + return; \ + } while (false) + + +#define RECURSE(call) \ + do { \ + DCHECK(!HasStackOverflow()); \ + call; \ + if (HasStackOverflow()) return; \ + if (!valid_) return; \ + } while (false) + + +AsmTyper::AsmTyper(Isolate* isolate, Zone* zone, Script* script, + FunctionLiteral* root) + : script_(script), + root_(root), + valid_(true), + stdlib_types_(zone), + stdlib_heap_types_(zone), + stdlib_math_types_(zone), + global_variable_type_(HashMap::PointersMatch, + ZoneHashMap::kDefaultHashMapCapacity, + ZoneAllocationPolicy(zone)), + local_variable_type_(HashMap::PointersMatch, + ZoneHashMap::kDefaultHashMapCapacity, + ZoneAllocationPolicy(zone)), + in_function_(false), + building_function_tables_(false), + cache_(kCache.Get()) { + InitializeAstVisitor(isolate, zone); + InitializeStdlib(); +} + + +bool AsmTyper::Validate() { + VisitAsmModule(root_); + return valid_ && !HasStackOverflow(); +} + + +void AsmTyper::VisitAsmModule(FunctionLiteral* fun) { + Scope* scope = fun->scope(); + if (!scope->is_function_scope()) FAIL(fun, "not at function scope"); + + // Module parameters. + for (int i = 0; i < scope->num_parameters(); ++i) { + Variable* param = scope->parameter(i); + DCHECK(GetType(param) == NULL); + SetType(param, Type::None(zone())); + } + + ZoneList* decls = scope->declarations(); + + // Set all globals to type Any. + VariableDeclaration* decl = scope->function(); + if (decl != NULL) SetType(decl->proxy()->var(), Type::None()); + RECURSE(VisitDeclarations(scope->declarations())); + + // Validate global variables. + RECURSE(VisitStatements(fun->body())); + + // Validate function annotations. + for (int i = 0; i < decls->length(); ++i) { + FunctionDeclaration* decl = decls->at(i)->AsFunctionDeclaration(); + if (decl != NULL) { + RECURSE(VisitFunctionAnnotation(decl->fun())); + Variable* var = decl->proxy()->var(); + DCHECK(GetType(var) == NULL); + SetType(var, computed_type_); + DCHECK(GetType(var) != NULL); + } + } + + // Build function tables. + building_function_tables_ = true; + RECURSE(VisitStatements(fun->body())); + building_function_tables_ = false; + + // Validate function bodies. + for (int i = 0; i < decls->length(); ++i) { + FunctionDeclaration* decl = decls->at(i)->AsFunctionDeclaration(); + if (decl != NULL) { + RECURSE( + VisitWithExpectation(decl->fun(), Type::Any(zone()), "UNREACHABLE")); + if (!computed_type_->IsFunction()) { + FAIL(decl->fun(), "function literal expected to be a function"); + } + } + } + + // Validate exports. + ReturnStatement* stmt = fun->body()->last()->AsReturnStatement(); + RECURSE(VisitWithExpectation(stmt->expression(), Type::Object(), + "expected object export")); +} + + +void AsmTyper::VisitVariableDeclaration(VariableDeclaration* decl) { + Variable* var = decl->proxy()->var(); + if (var->location() != VariableLocation::PARAMETER) { + if (GetType(var) == NULL) { + SetType(var, Type::Any(zone())); + } else { + DCHECK(!GetType(var)->IsFunction()); + } + } + DCHECK(GetType(var) != NULL); + intish_ = 0; +} + + +void AsmTyper::VisitFunctionDeclaration(FunctionDeclaration* decl) { + if (in_function_) { + FAIL(decl, "function declared inside another"); + } +} + + +void AsmTyper::VisitFunctionAnnotation(FunctionLiteral* fun) { + // Extract result type. + ZoneList* body = fun->body(); + Type* result_type = Type::Undefined(zone()); + if (body->length() > 0) { + ReturnStatement* stmt = body->last()->AsReturnStatement(); + if (stmt != NULL) { + RECURSE(VisitExpressionAnnotation(stmt->expression())); + result_type = computed_type_; + } + } + Type::FunctionType* type = + Type::Function(result_type, Type::Any(), fun->parameter_count(), zone()) + ->AsFunction(); + + // Extract parameter types. + bool good = true; + for (int i = 0; i < fun->parameter_count(); ++i) { + good = false; + if (i >= body->length()) break; + ExpressionStatement* stmt = body->at(i)->AsExpressionStatement(); + if (stmt == NULL) break; + Assignment* expr = stmt->expression()->AsAssignment(); + if (expr == NULL || expr->is_compound()) break; + VariableProxy* proxy = expr->target()->AsVariableProxy(); + if (proxy == NULL) break; + Variable* var = proxy->var(); + if (var->location() != VariableLocation::PARAMETER || var->index() != i) + break; + RECURSE(VisitExpressionAnnotation(expr->value())); + SetType(var, computed_type_); + type->InitParameter(i, computed_type_); + good = true; + } + if (!good) FAIL(fun, "missing parameter type annotations"); + + SetResult(fun, type); +} + + +void AsmTyper::VisitExpressionAnnotation(Expression* expr) { + // Normal +x or x|0 annotations. + BinaryOperation* bin = expr->AsBinaryOperation(); + if (bin != NULL) { + Literal* right = bin->right()->AsLiteral(); + if (right != NULL) { + switch (bin->op()) { + case Token::MUL: // We encode +x as 1*x + if (right->raw_value()->ContainsDot() && + right->raw_value()->AsNumber() == 1.0) { + SetResult(expr, cache_.kFloat64); + return; + } + break; + case Token::BIT_OR: + if (!right->raw_value()->ContainsDot() && + right->raw_value()->AsNumber() == 0.0) { + SetResult(expr, cache_.kInt32); + return; + } + break; + default: + break; + } + } + FAIL(expr, "invalid type annotation on binary op"); + } + + // Numbers or the undefined literal (for empty returns). + if (expr->IsLiteral()) { + RECURSE(VisitWithExpectation(expr, Type::Any(), "invalid literal")); + return; + } + + Call* call = expr->AsCall(); + if (call != NULL) { + if (call->expression()->IsVariableProxy()) { + RECURSE(VisitWithExpectation( + call->expression(), Type::Any(zone()), + "only fround allowed on expression annotations")); + if (!computed_type_->Is( + Type::Function(cache_.kFloat32, Type::Number(zone()), zone()))) { + FAIL(call->expression(), + "only fround allowed on expression annotations"); + } + if (call->arguments()->length() != 1) { + FAIL(call, "invalid argument count calling fround"); + } + SetResult(expr, cache_.kFloat32); + return; + } + } + + FAIL(expr, "invalid type annotation"); +} + + +void AsmTyper::VisitStatements(ZoneList* stmts) { + for (int i = 0; i < stmts->length(); ++i) { + Statement* stmt = stmts->at(i); + RECURSE(Visit(stmt)); + } +} + + +void AsmTyper::VisitBlock(Block* stmt) { + RECURSE(VisitStatements(stmt->statements())); +} + + +void AsmTyper::VisitExpressionStatement(ExpressionStatement* stmt) { + RECURSE(VisitWithExpectation(stmt->expression(), Type::Any(), + "expression statement expected to be any")); +} + + +void AsmTyper::VisitEmptyStatement(EmptyStatement* stmt) {} + + +void AsmTyper::VisitEmptyParentheses(EmptyParentheses* expr) { UNREACHABLE(); } + + +void AsmTyper::VisitIfStatement(IfStatement* stmt) { + if (!in_function_) { + FAIL(stmt, "if statement inside module body"); + } + RECURSE(VisitWithExpectation(stmt->condition(), cache_.kInt32, + "if condition expected to be integer")); + RECURSE(Visit(stmt->then_statement())); + RECURSE(Visit(stmt->else_statement())); +} + + +void AsmTyper::VisitContinueStatement(ContinueStatement* stmt) { + if (!in_function_) { + FAIL(stmt, "continue statement inside module body"); + } +} + + +void AsmTyper::VisitBreakStatement(BreakStatement* stmt) { + if (!in_function_) { + FAIL(stmt, "continue statement inside module body"); + } +} + + +void AsmTyper::VisitReturnStatement(ReturnStatement* stmt) { + // Handle module return statement in VisitAsmModule. + if (!in_function_) { + return; + } + RECURSE( + VisitWithExpectation(stmt->expression(), return_type_, + "return expression expected to have return type")); +} + + +void AsmTyper::VisitWithStatement(WithStatement* stmt) { + FAIL(stmt, "bad with statement"); +} + + +void AsmTyper::VisitSwitchStatement(SwitchStatement* stmt) { + if (!in_function_) { + FAIL(stmt, "switch statement inside module body"); + } + RECURSE(VisitWithExpectation(stmt->tag(), cache_.kInt32, + "switch expression non-integer")); + ZoneList* clauses = stmt->cases(); + for (int i = 0; i < clauses->length(); ++i) { + CaseClause* clause = clauses->at(i); + if (clause->is_default()) continue; + Expression* label = clause->label(); + RECURSE( + VisitWithExpectation(label, cache_.kInt32, "case label non-integer")); + if (!label->IsLiteral()) FAIL(label, "non-literal case label"); + Handle value = label->AsLiteral()->value(); + int32_t value32; + if (!value->ToInt32(&value32)) FAIL(label, "illegal case label value"); + // TODO(bradnelson): Detect duplicates. + ZoneList* stmts = clause->statements(); + RECURSE(VisitStatements(stmts)); + } +} + + +void AsmTyper::VisitCaseClause(CaseClause* clause) { UNREACHABLE(); } + + +void AsmTyper::VisitDoWhileStatement(DoWhileStatement* stmt) { + if (!in_function_) { + FAIL(stmt, "do statement inside module body"); + } + RECURSE(Visit(stmt->body())); + RECURSE(VisitWithExpectation(stmt->cond(), cache_.kInt32, + "do condition expected to be integer")); +} + + +void AsmTyper::VisitWhileStatement(WhileStatement* stmt) { + if (!in_function_) { + FAIL(stmt, "while statement inside module body"); + } + RECURSE(VisitWithExpectation(stmt->cond(), cache_.kInt32, + "while condition expected to be integer")); + RECURSE(Visit(stmt->body())); +} + + +void AsmTyper::VisitForStatement(ForStatement* stmt) { + if (!in_function_) { + FAIL(stmt, "for statement inside module body"); + } + if (stmt->init() != NULL) { + RECURSE(Visit(stmt->init())); + } + if (stmt->cond() != NULL) { + RECURSE(VisitWithExpectation(stmt->cond(), cache_.kInt32, + "for condition expected to be integer")); + } + if (stmt->next() != NULL) { + RECURSE(Visit(stmt->next())); + } + RECURSE(Visit(stmt->body())); +} + + +void AsmTyper::VisitForInStatement(ForInStatement* stmt) { + FAIL(stmt, "for-in statement encountered"); +} + + +void AsmTyper::VisitForOfStatement(ForOfStatement* stmt) { + FAIL(stmt, "for-of statement encountered"); +} + + +void AsmTyper::VisitTryCatchStatement(TryCatchStatement* stmt) { + FAIL(stmt, "try statement encountered"); +} + + +void AsmTyper::VisitTryFinallyStatement(TryFinallyStatement* stmt) { + FAIL(stmt, "try statement encountered"); +} + + +void AsmTyper::VisitDebuggerStatement(DebuggerStatement* stmt) { + FAIL(stmt, "debugger statement encountered"); +} + + +void AsmTyper::VisitFunctionLiteral(FunctionLiteral* expr) { + Scope* scope = expr->scope(); + DCHECK(scope->is_function_scope()); + if (in_function_) { + FAIL(expr, "invalid nested function"); + } + + if (!expr->bounds().upper->IsFunction()) { + FAIL(expr, "invalid function literal"); + } + + Type::FunctionType* type = expr->bounds().upper->AsFunction(); + Type* save_return_type = return_type_; + return_type_ = type->Result(); + in_function_ = true; + local_variable_type_.Clear(); + RECURSE(VisitDeclarations(scope->declarations())); + RECURSE(VisitStatements(expr->body())); + in_function_ = false; + return_type_ = save_return_type; + IntersectResult(expr, type); +} + + +void AsmTyper::VisitNativeFunctionLiteral(NativeFunctionLiteral* expr) { + FAIL(expr, "function info literal encountered"); +} + + +void AsmTyper::VisitConditional(Conditional* expr) { + RECURSE(VisitWithExpectation(expr->condition(), cache_.kInt32, + "condition expected to be integer")); + RECURSE(VisitWithExpectation( + expr->then_expression(), expected_type_, + "conditional then branch type mismatch with enclosing expression")); + Type* then_type = computed_type_; + RECURSE(VisitWithExpectation( + expr->else_expression(), expected_type_, + "conditional else branch type mismatch with enclosing expression")); + Type* else_type = computed_type_; + Type* type = Type::Intersect(then_type, else_type, zone()); + if (!(type->Is(cache_.kInt32) || type->Is(cache_.kFloat64))) { + FAIL(expr, "ill-typed conditional"); + } + IntersectResult(expr, type); +} + + +void AsmTyper::VisitVariableProxy(VariableProxy* expr) { + Variable* var = expr->var(); + if (GetType(var) == NULL) { + FAIL(expr, "unbound variable"); + } + Type* type = Type::Intersect(GetType(var), expected_type_, zone()); + if (type->Is(cache_.kInt32)) { + type = cache_.kInt32; + } + SetType(var, type); + intish_ = 0; + IntersectResult(expr, type); +} + + +void AsmTyper::VisitLiteral(Literal* expr) { + intish_ = 0; + Handle value = expr->value(); + if (value->IsNumber()) { + int32_t i; + uint32_t u; + if (expr->raw_value()->ContainsDot()) { + IntersectResult(expr, cache_.kFloat64); + } else if (value->ToUint32(&u)) { + IntersectResult(expr, cache_.kInt32); + } else if (value->ToInt32(&i)) { + IntersectResult(expr, cache_.kInt32); + } else { + FAIL(expr, "illegal number"); + } + } else if (value->IsString()) { + IntersectResult(expr, Type::String()); + } else if (value->IsUndefined()) { + IntersectResult(expr, Type::Undefined()); + } else { + FAIL(expr, "illegal literal"); + } +} + + +void AsmTyper::VisitRegExpLiteral(RegExpLiteral* expr) { + FAIL(expr, "regular expression encountered"); +} + + +void AsmTyper::VisitObjectLiteral(ObjectLiteral* expr) { + if (in_function_) { + FAIL(expr, "object literal in function"); + } + // Allowed for asm module's export declaration. + ZoneList* props = expr->properties(); + for (int i = 0; i < props->length(); ++i) { + ObjectLiteralProperty* prop = props->at(i); + RECURSE(VisitWithExpectation(prop->value(), Type::Any(zone()), + "object property expected to be a function")); + if (!computed_type_->IsFunction()) { + FAIL(prop->value(), "non-function in function table"); + } + } + IntersectResult(expr, Type::Object(zone())); +} + + +void AsmTyper::VisitArrayLiteral(ArrayLiteral* expr) { + if (in_function_) { + FAIL(expr, "array literal inside a function"); + } + // Allowed for function tables. + ZoneList* values = expr->values(); + Type* elem_type = Type::None(zone()); + for (int i = 0; i < values->length(); ++i) { + Expression* value = values->at(i); + RECURSE(VisitWithExpectation(value, Type::Any(), "UNREACHABLE")); + if (!computed_type_->IsFunction()) { + FAIL(value, "array component expected to be a function"); + } + elem_type = Type::Union(elem_type, computed_type_, zone()); + } + array_size_ = values->length(); + IntersectResult(expr, Type::Array(elem_type, zone())); +} + + +void AsmTyper::VisitAssignment(Assignment* expr) { + // Handle function tables and everything else in different passes. + if (!in_function_) { + if (expr->value()->IsArrayLiteral()) { + if (!building_function_tables_) { + return; + } + } else { + if (building_function_tables_) { + return; + } + } + } + if (expr->is_compound()) FAIL(expr, "compound assignment encountered"); + Type* type = expected_type_; + RECURSE(VisitWithExpectation( + expr->value(), type, "assignment value expected to match surrounding")); + if (intish_ != 0) { + FAIL(expr, "value still an intish"); + } + RECURSE(VisitWithExpectation(expr->target(), computed_type_, + "assignment target expected to match value")); + if (intish_ != 0) { + FAIL(expr, "value still an intish"); + } + IntersectResult(expr, computed_type_); +} + + +void AsmTyper::VisitYield(Yield* expr) { + FAIL(expr, "yield expression encountered"); +} + + +void AsmTyper::VisitThrow(Throw* expr) { + FAIL(expr, "throw statement encountered"); +} + + +int AsmTyper::ElementShiftSize(Type* type) { + if (type->Is(cache_.kInt8) || type->Is(cache_.kUint8)) return 0; + if (type->Is(cache_.kInt16) || type->Is(cache_.kUint16)) return 1; + if (type->Is(cache_.kInt32) || type->Is(cache_.kUint32) || + type->Is(cache_.kFloat32)) + return 2; + if (type->Is(cache_.kFloat64)) return 3; + return -1; +} + + +void AsmTyper::VisitHeapAccess(Property* expr) { + Type::ArrayType* array_type = computed_type_->AsArray(); + size_t size = array_size_; + Type* type = array_type->AsArray()->Element(); + if (type->IsFunction()) { + BinaryOperation* bin = expr->key()->AsBinaryOperation(); + if (bin == NULL || bin->op() != Token::BIT_AND) { + FAIL(expr->key(), "expected & in call"); + } + RECURSE(VisitWithExpectation(bin->left(), cache_.kInt32, + "array index expected to be integer")); + Literal* right = bin->right()->AsLiteral(); + if (right == NULL || right->raw_value()->ContainsDot()) { + FAIL(right, "call mask must be integer"); + } + RECURSE(VisitWithExpectation(bin->right(), cache_.kInt32, + "call mask expected to be integer")); + if (static_cast(right->raw_value()->AsNumber()) != size - 1) { + FAIL(right, "call mask must match function table"); + } + bin->set_bounds(Bounds(cache_.kInt32)); + } else { + BinaryOperation* bin = expr->key()->AsBinaryOperation(); + if (bin == NULL || bin->op() != Token::SAR) { + FAIL(expr->key(), "expected >> in heap access"); + } + RECURSE(VisitWithExpectation(bin->left(), cache_.kInt32, + "array index expected to be integer")); + Literal* right = bin->right()->AsLiteral(); + if (right == NULL || right->raw_value()->ContainsDot()) { + FAIL(right, "heap access shift must be integer"); + } + RECURSE(VisitWithExpectation(bin->right(), cache_.kInt32, + "array shift expected to be integer")); + int n = static_cast(right->raw_value()->AsNumber()); + int expected_shift = ElementShiftSize(type); + if (expected_shift < 0 || n != expected_shift) { + FAIL(right, "heap access shift must match element size"); + } + bin->set_bounds(Bounds(cache_.kInt32)); + } + IntersectResult(expr, type); +} + + +void AsmTyper::VisitProperty(Property* expr) { + // stdlib.Math.x + Property* inner_prop = expr->obj()->AsProperty(); + if (inner_prop != NULL) { + // Get property name. + Literal* key = expr->key()->AsLiteral(); + if (key == NULL || !key->IsPropertyName()) + FAIL(expr, "invalid type annotation on property 2"); + Handle name = key->AsPropertyName(); + + // Check that inner property name is "Math". + Literal* math_key = inner_prop->key()->AsLiteral(); + if (math_key == NULL || !math_key->IsPropertyName() || + !math_key->AsPropertyName()->IsUtf8EqualTo(CStrVector("Math"))) + FAIL(expr, "invalid type annotation on stdlib (a1)"); + + // Check that object is stdlib. + VariableProxy* proxy = inner_prop->obj()->AsVariableProxy(); + if (proxy == NULL) FAIL(expr, "invalid type annotation on stdlib (a2)"); + Variable* var = proxy->var(); + if (var->location() != VariableLocation::PARAMETER || var->index() != 0) + FAIL(expr, "invalid type annotation on stdlib (a3)"); + + // Look up library type. + Type* type = LibType(stdlib_math_types_, name); + if (type == NULL) FAIL(expr, "unknown standard function 3 "); + SetResult(expr, type); + return; + } + + // Only recurse at this point so that we avoid needing + // stdlib.Math to have a real type. + RECURSE(VisitWithExpectation(expr->obj(), Type::Any(), + "property holder expected to be object")); + + // For heap view or function table access. + if (computed_type_->IsArray()) { + VisitHeapAccess(expr); + return; + } + + // Get property name. + Literal* key = expr->key()->AsLiteral(); + if (key == NULL || !key->IsPropertyName()) + FAIL(expr, "invalid type annotation on property 3"); + Handle name = key->AsPropertyName(); + + // stdlib.x or foreign.x + VariableProxy* proxy = expr->obj()->AsVariableProxy(); + if (proxy != NULL) { + Variable* var = proxy->var(); + if (var->location() != VariableLocation::PARAMETER) { + FAIL(expr, "invalid type annotation on variable"); + } + switch (var->index()) { + case 0: { + // Object is stdlib, look up library type. + Type* type = LibType(stdlib_types_, name); + if (type == NULL) { + FAIL(expr, "unknown standard function 4"); + } + SetResult(expr, type); + return; + } + case 1: + // Object is foreign lib. + SetResult(expr, expected_type_); + return; + default: + FAIL(expr, "invalid type annotation on parameter"); + } + } + + FAIL(expr, "invalid property access"); +} + + +void AsmTyper::VisitCall(Call* expr) { + RECURSE(VisitWithExpectation(expr->expression(), Type::Any(), + "callee expected to be any")); + if (computed_type_->IsFunction()) { + Type::FunctionType* fun_type = computed_type_->AsFunction(); + ZoneList* args = expr->arguments(); + if (fun_type->Arity() != args->length()) { + FAIL(expr, "call with wrong arity"); + } + for (int i = 0; i < args->length(); ++i) { + Expression* arg = args->at(i); + RECURSE(VisitWithExpectation( + arg, fun_type->Parameter(i), + "call argument expected to match callee parameter")); + } + IntersectResult(expr, fun_type->Result()); + } else if (computed_type_->Is(Type::Any())) { + // For foreign calls. + ZoneList* args = expr->arguments(); + for (int i = 0; i < args->length(); ++i) { + Expression* arg = args->at(i); + RECURSE(VisitWithExpectation(arg, Type::Any(), + "foreign call argument expected to be any")); + } + IntersectResult(expr, Type::Number()); + } else { + FAIL(expr, "invalid callee"); + } +} + + +void AsmTyper::VisitCallNew(CallNew* expr) { + if (in_function_) { + FAIL(expr, "new not allowed in module function"); + } + RECURSE(VisitWithExpectation(expr->expression(), Type::Any(), + "expected stdlib function")); + if (computed_type_->IsFunction()) { + Type::FunctionType* fun_type = computed_type_->AsFunction(); + ZoneList* args = expr->arguments(); + if (fun_type->Arity() != args->length()) + FAIL(expr, "call with wrong arity"); + for (int i = 0; i < args->length(); ++i) { + Expression* arg = args->at(i); + RECURSE(VisitWithExpectation( + arg, fun_type->Parameter(i), + "constructor argument expected to match callee parameter")); + } + IntersectResult(expr, fun_type->Result()); + return; + } + + FAIL(expr, "ill-typed new operator"); +} + + +void AsmTyper::VisitCallRuntime(CallRuntime* expr) { + // Allow runtime calls for now. +} + + +void AsmTyper::VisitUnaryOperation(UnaryOperation* expr) { + switch (expr->op()) { + case Token::NOT: // Used to encode != and !== + RECURSE(VisitWithExpectation(expr->expression(), cache_.kInt32, + "operand expected to be integer")); + IntersectResult(expr, cache_.kInt32); + return; + case Token::DELETE: + FAIL(expr, "delete operator encountered"); + case Token::VOID: + FAIL(expr, "void operator encountered"); + case Token::TYPEOF: + FAIL(expr, "typeof operator encountered"); + default: + UNREACHABLE(); + } +} + + +void AsmTyper::VisitCountOperation(CountOperation* expr) { + FAIL(expr, "increment or decrement operator encountered"); +} + + +void AsmTyper::VisitBinaryOperation(BinaryOperation* expr) { + switch (expr->op()) { + case Token::COMMA: { + RECURSE(VisitWithExpectation(expr->left(), Type::Any(), + "left comma operand expected to be any")); + RECURSE(VisitWithExpectation(expr->right(), Type::Any(), + "right comma operand expected to be any")); + IntersectResult(expr, computed_type_); + return; + } + case Token::OR: + case Token::AND: + FAIL(expr, "logical operator encountered"); + case Token::BIT_OR: + case Token::BIT_AND: + case Token::BIT_XOR: + case Token::SHL: + case Token::SHR: + case Token::SAR: { + // BIT_OR allows Any since it is used as a type coercion. + // BIT_XOR allows Number since it is used as a type coercion (encoding ~). + Type* expectation = + expr->op() == Token::BIT_OR + ? Type::Any() + : expr->op() == Token::BIT_XOR ? Type::Number() : cache_.kInt32; + Type* result = + expr->op() == Token::SHR ? Type::Unsigned32() : cache_.kInt32; + RECURSE(VisitWithExpectation(expr->left(), expectation, + "left bit operand expected to be integer")); + int left_intish = intish_; + RECURSE(VisitWithExpectation(expr->right(), expectation, + "right bit operand expected to be integer")); + int right_intish = intish_; + if (left_intish > kMaxUncombinedAdditiveSteps) { + FAIL(expr, "too many consecutive additive ops"); + } + if (right_intish > kMaxUncombinedAdditiveSteps) { + FAIL(expr, "too many consecutive additive ops"); + } + intish_ = 0; + IntersectResult(expr, result); + return; + } + case Token::ADD: + case Token::SUB: + case Token::MUL: + case Token::DIV: + case Token::MOD: { + RECURSE(VisitWithExpectation( + expr->left(), Type::Number(), + "left arithmetic operand expected to be number")); + Type* left_type = computed_type_; + int left_intish = intish_; + RECURSE(VisitWithExpectation( + expr->right(), Type::Number(), + "right arithmetic operand expected to be number")); + Type* right_type = computed_type_; + int right_intish = intish_; + Type* type = Type::Union(left_type, right_type, zone()); + if (type->Is(cache_.kInt32)) { + if (expr->op() == Token::MUL) { + if (!expr->left()->IsLiteral() && !expr->right()->IsLiteral()) { + FAIL(expr, "direct integer multiply forbidden"); + } + intish_ = 0; + IntersectResult(expr, cache_.kInt32); + return; + } else { + intish_ = left_intish + right_intish + 1; + if (expr->op() == Token::ADD || expr->op() == Token::SUB) { + if (intish_ > kMaxUncombinedAdditiveSteps) { + FAIL(expr, "too many consecutive additive ops"); + } + } else { + if (intish_ > kMaxUncombinedMultiplicativeSteps) { + FAIL(expr, "too many consecutive multiplicative ops"); + } + } + IntersectResult(expr, cache_.kInt32); + return; + } + } else if (type->Is(Type::Number())) { + IntersectResult(expr, cache_.kFloat64); + return; + } else { + FAIL(expr, "ill-typed arithmetic operation"); + } + } + default: + UNREACHABLE(); + } +} + + +void AsmTyper::VisitCompareOperation(CompareOperation* expr) { + RECURSE( + VisitWithExpectation(expr->left(), Type::Number(), + "left comparison operand expected to be number")); + Type* left_type = computed_type_; + RECURSE( + VisitWithExpectation(expr->right(), Type::Number(), + "right comparison operand expected to be number")); + Type* right_type = computed_type_; + Type* type = Type::Union(left_type, right_type, zone()); + expr->set_combined_type(type); + if (type->Is(Type::Integral32()) || type->Is(Type::UntaggedFloat64())) { + IntersectResult(expr, cache_.kInt32); + } else { + FAIL(expr, "ill-typed comparison operation"); + } +} + + +void AsmTyper::VisitThisFunction(ThisFunction* expr) { + FAIL(expr, "this function not allowed"); +} + + +void AsmTyper::VisitDeclarations(ZoneList* decls) { + for (int i = 0; i < decls->length(); ++i) { + Declaration* decl = decls->at(i); + RECURSE(Visit(decl)); + } +} + + +void AsmTyper::VisitImportDeclaration(ImportDeclaration* decl) { + FAIL(decl, "import declaration encountered"); +} + + +void AsmTyper::VisitExportDeclaration(ExportDeclaration* decl) { + FAIL(decl, "export declaration encountered"); +} + + +void AsmTyper::VisitClassLiteral(ClassLiteral* expr) { + FAIL(expr, "class literal not allowed"); +} + + +void AsmTyper::VisitSpread(Spread* expr) { FAIL(expr, "spread not allowed"); } + + +void AsmTyper::VisitSuperPropertyReference(SuperPropertyReference* expr) { + FAIL(expr, "super property reference not allowed"); +} + + +void AsmTyper::VisitSuperCallReference(SuperCallReference* expr) { + FAIL(expr, "call reference not allowed"); +} + + +void AsmTyper::InitializeStdlib() { + Type* number_type = Type::Number(zone()); + Type* double_type = cache_.kFloat64; + Type* double_fn1_type = Type::Function(double_type, double_type, zone()); + Type* double_fn2_type = + Type::Function(double_type, double_type, double_type, zone()); + + Type* fround_type = Type::Function(cache_.kFloat32, number_type, zone()); + Type* imul_type = + Type::Function(cache_.kInt32, cache_.kInt32, cache_.kInt32, zone()); + // TODO(bradnelson): currently only approximating the proper intersection type + // (which we cannot currently represent). + Type* abs_type = Type::Function(number_type, number_type, zone()); + + struct Assignment { + const char* name; + Type* type; + }; + + const Assignment math[] = { + {"PI", double_type}, {"E", double_type}, + {"LN2", double_type}, {"LN10", double_type}, + {"LOG2E", double_type}, {"LOG10E", double_type}, + {"SQRT2", double_type}, {"SQRT1_2", double_type}, + {"imul", imul_type}, {"abs", abs_type}, + {"ceil", double_fn1_type}, {"floor", double_fn1_type}, + {"fround", fround_type}, {"pow", double_fn2_type}, + {"exp", double_fn1_type}, {"log", double_fn1_type}, + {"min", double_fn2_type}, {"max", double_fn2_type}, + {"sqrt", double_fn1_type}, {"cos", double_fn1_type}, + {"sin", double_fn1_type}, {"tan", double_fn1_type}, + {"acos", double_fn1_type}, {"asin", double_fn1_type}, + {"atan", double_fn1_type}, {"atan2", double_fn2_type}}; + for (unsigned i = 0; i < arraysize(math); ++i) { + stdlib_math_types_[math[i].name] = math[i].type; + } + + stdlib_types_["Infinity"] = double_type; + stdlib_types_["NaN"] = double_type; + Type* buffer_type = Type::Any(zone()); +#define TYPED_ARRAY(TypeName, type_name, TYPE_NAME, ctype, size) \ + stdlib_types_[#TypeName "Array"] = \ + Type::Function(cache_.k##TypeName##Array, buffer_type, zone()); + TYPED_ARRAYS(TYPED_ARRAY) +#undef TYPED_ARRAY + +#define TYPED_ARRAY(TypeName, type_name, TYPE_NAME, ctype, size) \ + stdlib_heap_types_[#TypeName "Array"] = \ + Type::Function(cache_.k##TypeName##Array, buffer_type, zone()); + TYPED_ARRAYS(TYPED_ARRAY) +#undef TYPED_ARRAY +} + + +Type* AsmTyper::LibType(ObjectTypeMap map, Handle name) { + base::SmartArrayPointer aname = name->ToCString(); + ObjectTypeMap::iterator i = map.find(std::string(aname.get())); + if (i == map.end()) { + return NULL; + } + return i->second; +} + + +void AsmTyper::SetType(Variable* variable, Type* type) { + ZoneHashMap::Entry* entry; + if (in_function_) { + entry = local_variable_type_.LookupOrInsert( + variable, ComputePointerHash(variable), ZoneAllocationPolicy(zone())); + } else { + entry = global_variable_type_.LookupOrInsert( + variable, ComputePointerHash(variable), ZoneAllocationPolicy(zone())); + } + entry->value = reinterpret_cast(type); +} + + +Type* AsmTyper::GetType(Variable* variable) { + i::ZoneHashMap::Entry* entry = NULL; + if (in_function_) { + entry = local_variable_type_.Lookup(variable, ComputePointerHash(variable)); + } + if (entry == NULL) { + entry = + global_variable_type_.Lookup(variable, ComputePointerHash(variable)); + } + if (entry == NULL) { + return NULL; + } else { + return reinterpret_cast(entry->value); + } +} + + +void AsmTyper::SetResult(Expression* expr, Type* type) { + computed_type_ = type; + expr->set_bounds(Bounds(computed_type_)); +} + + +void AsmTyper::IntersectResult(Expression* expr, Type* type) { + computed_type_ = type; + Type* bounded_type = Type::Intersect(computed_type_, expected_type_, zone()); + expr->set_bounds(Bounds(bounded_type)); +} + + +void AsmTyper::VisitWithExpectation(Expression* expr, Type* expected_type, + const char* msg) { + Type* save = expected_type_; + expected_type_ = expected_type; + RECURSE(Visit(expr)); + Type* bounded_type = Type::Intersect(computed_type_, expected_type_, zone()); + if (bounded_type->Is(Type::None(zone()))) { +#ifdef DEBUG + PrintF("Computed type: "); + computed_type_->Print(); + PrintF("Expected type: "); + expected_type_->Print(); +#endif + FAIL(expr, msg); + } + expected_type_ = save; +} +} +} // namespace v8::internal diff --git a/src/typing-asm.h b/src/typing-asm.h new file mode 100644 index 000000000..74c28fb3c --- /dev/null +++ b/src/typing-asm.h @@ -0,0 +1,95 @@ +// Copyright 2015 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef V8_TYPING_ASM_H_ +#define V8_TYPING_ASM_H_ + +#include "src/allocation.h" +#include "src/ast.h" +#include "src/effects.h" +#include "src/type-info.h" +#include "src/types.h" +#include "src/zone.h" + +namespace v8 { +namespace internal { + +class ZoneTypeCache; + +class AsmTyper : public AstVisitor { + public: + explicit AsmTyper(Isolate* isolate, Zone* zone, Script* script, + FunctionLiteral* root); + bool Validate(); + const char* error_message() { return error_message_; } + + DEFINE_AST_VISITOR_SUBCLASS_MEMBERS(); + + private: + Script* script_; + FunctionLiteral* root_; + bool valid_; + + // Information for bi-directional typing with a cap on nesting depth. + Type* expected_type_; + Type* computed_type_; + int intish_; // How many ops we've gone without a x|0. + + Type* return_type_; // Return type of last function. + size_t array_size_; // Array size of last ArrayLiteral. + + typedef ZoneMap ObjectTypeMap; + ObjectTypeMap stdlib_types_; + ObjectTypeMap stdlib_heap_types_; + ObjectTypeMap stdlib_math_types_; + + // Map from Variable* to global/local variable Type*. + ZoneHashMap global_variable_type_; + ZoneHashMap local_variable_type_; + + bool in_function_; // In module function? + bool building_function_tables_; + + ZoneTypeCache const& cache_; + + static const int kErrorMessageLimit = 100; + char error_message_[kErrorMessageLimit]; + + static const int kMaxUncombinedAdditiveSteps = 1 << 20; + static const int kMaxUncombinedMultiplicativeSteps = 1; + + void InitializeStdlib(); + + void VisitDeclarations(ZoneList* d) override; + void VisitStatements(ZoneList* s) override; + + void VisitExpressionAnnotation(Expression* e); + void VisitFunctionAnnotation(FunctionLiteral* f); + void VisitAsmModule(FunctionLiteral* f); + + void VisitHeapAccess(Property* expr); + + int ElementShiftSize(Type* type); + + void SetType(Variable* variable, Type* type); + Type* GetType(Variable* variable); + + Type* LibType(ObjectTypeMap map, Handle name); + + void SetResult(Expression* expr, Type* type); + void IntersectResult(Expression* expr, Type* type); + + void VisitWithExpectation(Expression* expr, Type* expected_type, + const char* msg); + +#define DECLARE_VISIT(type) virtual void Visit##type(type* node) override; + AST_NODE_LIST(DECLARE_VISIT) +#undef DECLARE_VISIT + + DISALLOW_COPY_AND_ASSIGN(AsmTyper); +}; +} +} // namespace v8::internal + +#endif // V8_TYPING_ASM_H_ diff --git a/test/cctest/cctest.gyp b/test/cctest/cctest.gyp index 5199471f4..f9ffade34 100644 --- a/test/cctest/cctest.gyp +++ b/test/cctest/cctest.gyp @@ -101,6 +101,7 @@ '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', diff --git a/test/cctest/test-asm-validator.cc b/test/cctest/test-asm-validator.cc new file mode 100644 index 000000000..886c0377a --- /dev/null +++ b/test/cctest/test-asm-validator.cc @@ -0,0 +1,918 @@ +// Copyright 2015 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/v8.h" + +#include "src/ast.h" +#include "src/ast-expression-visitor.h" +#include "src/parser.h" +#include "src/rewriter.h" +#include "src/scopes.h" +#include "src/typing-asm.h" +#include "src/zone-type-cache.h" +#include "test/cctest/cctest.h" +#include "test/cctest/expression-type-collector.h" +#include "test/cctest/expression-type-collector-macros.h" + +// Macros for function types. +#define FUNC_V_TYPE Bounds(Type::Function(Type::Undefined(), zone)) +#define FUNC_I_TYPE Bounds(Type::Function(cache.kInt32, zone)) +#define FUNC_F_TYPE Bounds(Type::Function(cache.kFloat32, zone)) +#define FUNC_D_TYPE Bounds(Type::Function(cache.kFloat64, zone)) +#define FUNC_D2D_TYPE \ + Bounds(Type::Function(cache.kFloat64, cache.kFloat64, zone)) +#define FUNC_N2F_TYPE \ + Bounds(Type::Function(cache.kFloat32, Type::Number(), zone)) +#define FUNC_I2I_TYPE Bounds(Type::Function(cache.kInt32, cache.kInt32, zone)) +#define FUNC_II2D_TYPE \ + Bounds(Type::Function(cache.kFloat64, cache.kInt32, cache.kInt32, zone)) +#define FUNC_II2I_TYPE \ + Bounds(Type::Function(cache.kInt32, cache.kInt32, cache.kInt32, zone)) +#define FUNC_DD2D_TYPE \ + Bounds(Type::Function(cache.kFloat64, cache.kFloat64, cache.kFloat64, zone)) +#define FUNC_N2N_TYPE \ + Bounds(Type::Function(Type::Number(), Type::Number(), zone)) + +// Macros for array types. +#define FLOAT64_ARRAY_TYPE Bounds(Type::Array(cache.kFloat64, zone)) +#define FUNC_I2I_ARRAY_TYPE \ + Bounds(Type::Array(Type::Function(cache.kInt32, cache.kInt32, zone), zone)) + +using namespace v8::internal; + +namespace { + +std::string Validate(Zone* zone, const char* source, + ZoneVector* types) { + i::Isolate* isolate = CcTest::i_isolate(); + i::Factory* factory = isolate->factory(); + + i::Handle source_code = + factory->NewStringFromUtf8(i::CStrVector(source)).ToHandleChecked(); + + i::Handle script = factory->NewScript(source_code); + + i::ParseInfo info(zone, script); + i::Parser parser(&info); + parser.set_allow_harmony_arrow_functions(true); + parser.set_allow_harmony_sloppy(true); + info.set_global(); + info.set_lazy(false); + info.set_allow_lazy_parsing(false); + info.set_toplevel(true); + + i::CompilationInfo compilation_info(&info); + CHECK(i::Compiler::ParseAndAnalyze(&info)); + info.set_literal( + info.scope()->declarations()->at(0)->AsFunctionDeclaration()->fun()); + + AsmTyper typer( + isolate, zone, *script, + info.scope()->declarations()->at(0)->AsFunctionDeclaration()->fun()); + if (typer.Validate()) { + ExpressionTypeCollector(&compilation_info, types).Run(); + return ""; + } else { + return typer.error_message(); + } +} +} + + +TEST(ValidateMinimum) { + const char test_function[] = + "function GeometricMean(stdlib, foreign, buffer) {\n" + " \"use asm\";\n" + "\n" + " var exp = stdlib.Math.exp;\n" + " var log = stdlib.Math.log;\n" + " var values = new stdlib.Float64Array(buffer);\n" + "\n" + " function logSum(start, end) {\n" + " start = start|0;\n" + " end = end|0;\n" + "\n" + " var sum = 0.0, p = 0, q = 0;\n" + "\n" + " // asm.js forces byte addressing of the heap by requiring shifting " + "by 3\n" + " for (p = start << 3, q = end << 3; (p|0) < (q|0); p = (p + 8)|0) {\n" + " sum = sum + +log(values[p>>3]);\n" + " }\n" + "\n" + " return +sum;\n" + " }\n" + "\n" + " function geometricMean(start, end) {\n" + " start = start|0;\n" + " end = end|0;\n" + "\n" + " return +exp(+logSum(start, end) / +((end - start)|0));\n" + " }\n" + "\n" + " return { geometricMean: geometricMean };\n" + "}\n"; + + v8::V8::Initialize(); + HandleAndZoneScope handles; + Zone* zone = handles.main_zone(); + ZoneVector types(zone); + CHECK_EQ("", Validate(zone, test_function, &types)); + ZoneTypeCache cache; + + CHECK_TYPES_BEGIN { + // Module. + CHECK_EXPR(FunctionLiteral, Bounds::Unbounded()) { + // function logSum + CHECK_EXPR(FunctionLiteral, FUNC_II2D_TYPE) { + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(start, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(start, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(end, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(end, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(Assignment, Bounds(cache.kFloat64)) { + CHECK_VAR(sum, Bounds(cache.kFloat64)); + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(p, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(q, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + // for (p = start << 3, q = end << 3; + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(p, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(start, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(q, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(end, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + } + // (p|0) < (q|0); + CHECK_EXPR(CompareOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(p, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(q, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + // p = (p + 8)|0) {\n" + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(p, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(p, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + // sum = sum + +log(values[p>>3]); + CHECK_EXPR(Assignment, Bounds(cache.kFloat64)) { + CHECK_VAR(sum, Bounds(cache.kFloat64)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kFloat64)) { + CHECK_VAR(sum, Bounds(cache.kFloat64)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kFloat64)) { + CHECK_EXPR(Call, Bounds(cache.kFloat64)) { + CHECK_VAR(log, FUNC_D2D_TYPE); + CHECK_EXPR(Property, Bounds(cache.kFloat64)) { + CHECK_VAR(values, FLOAT64_ARRAY_TYPE); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(p, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + } + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + } + } + // return +sum; + CHECK_EXPR(BinaryOperation, Bounds(cache.kFloat64)) { + CHECK_VAR(sum, Bounds(cache.kFloat64)); + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + } + // function geometricMean + CHECK_EXPR(FunctionLiteral, FUNC_II2D_TYPE) { + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(start, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(start, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(end, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(end, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + // return +exp(+logSum(start, end) / +((end - start)|0)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kFloat64)) { + CHECK_EXPR(Call, Bounds(cache.kFloat64)) { + CHECK_VAR(exp, FUNC_D2D_TYPE); + CHECK_EXPR(BinaryOperation, Bounds(cache.kFloat64)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kFloat64)) { + CHECK_EXPR(Call, Bounds(cache.kFloat64)) { + CHECK_VAR(logSum, FUNC_II2D_TYPE); + CHECK_VAR(start, Bounds(cache.kInt32)); + CHECK_VAR(end, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + CHECK_EXPR(BinaryOperation, Bounds(cache.kFloat64)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(end, Bounds(cache.kInt32)); + CHECK_VAR(start, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + } + } + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + } + // "use asm"; + CHECK_EXPR(Literal, Bounds(Type::String())); + // var exp = stdlib.Math.exp; + CHECK_EXPR(Assignment, FUNC_D2D_TYPE) { + CHECK_VAR(exp, FUNC_D2D_TYPE); + CHECK_EXPR(Property, FUNC_D2D_TYPE) { + CHECK_EXPR(Property, Bounds::Unbounded()) { + CHECK_VAR(stdlib, Bounds::Unbounded()); + CHECK_EXPR(Literal, Bounds::Unbounded()); + } + CHECK_EXPR(Literal, Bounds::Unbounded()); + } + } + // var log = stdlib.Math.log; + CHECK_EXPR(Assignment, FUNC_D2D_TYPE) { + CHECK_VAR(log, FUNC_D2D_TYPE); + CHECK_EXPR(Property, FUNC_D2D_TYPE) { + CHECK_EXPR(Property, Bounds::Unbounded()) { + CHECK_VAR(stdlib, Bounds::Unbounded()); + CHECK_EXPR(Literal, Bounds::Unbounded()); + } + CHECK_EXPR(Literal, Bounds::Unbounded()); + } + } + // var values = new stdlib.Float64Array(buffer); + CHECK_EXPR(Assignment, FLOAT64_ARRAY_TYPE) { + CHECK_VAR(values, FLOAT64_ARRAY_TYPE); + CHECK_EXPR(CallNew, FLOAT64_ARRAY_TYPE) { + CHECK_EXPR(Property, Bounds::Unbounded()) { + CHECK_VAR(stdlib, Bounds::Unbounded()); + CHECK_EXPR(Literal, Bounds::Unbounded()); + } + CHECK_VAR(buffer, Bounds::Unbounded()); + } + } + // return { geometricMean: geometricMean }; + CHECK_EXPR(ObjectLiteral, Bounds::Unbounded()) { + CHECK_VAR(geometricMean, FUNC_II2D_TYPE); + } + } + } + CHECK_TYPES_END +} + + +#define HARNESS_STDLIB() \ + "var Infinity = stdlib.Infinity;\n" \ + "var NaN = stdlib.NaN;\n" \ + "var acos = stdlib.Math.acos;\n" \ + "var asin = stdlib.Math.asin;\n" \ + "var atan = stdlib.Math.atan;\n" \ + "var cos = stdlib.Math.cos;\n" \ + "var sin = stdlib.Math.sin;\n" \ + "var tan = stdlib.Math.tan;\n" \ + "var exp = stdlib.Math.exp;\n" \ + "var log = stdlib.Math.log;\n" \ + "var ceil = stdlib.Math.ceil;\n" \ + "var floor = stdlib.Math.floor;\n" \ + "var sqrt = stdlib.Math.sqrt;\n" \ + "var min = stdlib.Math.min;\n" \ + "var max = stdlib.Math.max;\n" \ + "var atan2 = stdlib.Math.atan2;\n" \ + "var pow = stdlib.Math.pow;\n" \ + "var abs = stdlib.Math.abs;\n" \ + "var imul = stdlib.Math.imul;\n" \ + "var fround = stdlib.Math.fround;\n" \ + "var E = stdlib.Math.E;\n" \ + "var LN10 = stdlib.Math.LN10;\n" \ + "var LN2 = stdlib.Math.LN2;\n" \ + "var LOG2E = stdlib.Math.LOG2E;\n" \ + "var LOG10E = stdlib.Math.LOG10E;\n" \ + "var PI = stdlib.Math.PI;\n" \ + "var SQRT1_2 = stdlib.Math.SQRT1_2;\n" \ + "var SQRT2 = stdlib.Math.SQRT2;\n" + + +#define HARNESS_HEAP() \ + "var u8 = new stdlib.Uint8Array(buffer);\n" \ + "var i8 = new stdlib.Int8Array(buffer);\n" \ + "var u16 = new stdlib.Uint16Array(buffer);\n" \ + "var i16 = new stdlib.Int16Array(buffer);\n" \ + "var u32 = new stdlib.Uint32Array(buffer);\n" \ + "var i32 = new stdlib.Int32Array(buffer);\n" \ + "var f32 = new stdlib.Float32Array(buffer);\n" \ + "var f64 = new stdlib.Float64Array(buffer);\n" + + +#define HARNESS_PREAMBLE() \ + const char test_function[] = \ + "function Module(stdlib, foreign, buffer) {\n" \ + "\"use asm\";\n" HARNESS_STDLIB() HARNESS_HEAP() + + +#define HARNESS_POSTAMBLE() \ + "return { foo: foo };\n" \ + "}\n"; + + +#define CHECK_VAR_MATH_SHORTCUT(name, type) \ + CHECK_EXPR(Assignment, type) { \ + CHECK_VAR(name, type); \ + CHECK_EXPR(Property, type) { \ + CHECK_EXPR(Property, Bounds::Unbounded()) { \ + CHECK_VAR(stdlib, Bounds::Unbounded()); \ + CHECK_EXPR(Literal, Bounds::Unbounded()); \ + } \ + CHECK_EXPR(Literal, Bounds::Unbounded()); \ + } \ + } + + +#define CHECK_VAR_SHORTCUT(name, type) \ + CHECK_EXPR(Assignment, type) { \ + CHECK_VAR(name, type); \ + CHECK_EXPR(Property, type) { \ + CHECK_VAR(stdlib, Bounds::Unbounded()); \ + CHECK_EXPR(Literal, Bounds::Unbounded()); \ + } \ + } + + +#define CHECK_VAR_NEW_SHORTCUT(name, type) \ + CHECK_EXPR(Assignment, type) { \ + CHECK_VAR(name, type); \ + CHECK_EXPR(CallNew, type) { \ + CHECK_EXPR(Property, Bounds::Unbounded()) { \ + CHECK_VAR(stdlib, Bounds::Unbounded()); \ + CHECK_EXPR(Literal, Bounds::Unbounded()); \ + } \ + CHECK_VAR(buffer, Bounds::Unbounded()); \ + } \ + } + + +namespace { + +void CheckStdlibShortcuts(Zone* zone, ZoneVector& types, + size_t& index, int& depth, ZoneTypeCache& cache) { + // var exp = stdlib.*; (D * 12) + CHECK_VAR_SHORTCUT(Infinity, Bounds(cache.kFloat64)); + CHECK_VAR_SHORTCUT(NaN, Bounds(cache.kFloat64)); + // var x = stdlib.Math.x; D2D + CHECK_VAR_MATH_SHORTCUT(acos, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(asin, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(atan, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(cos, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(sin, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(tan, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(exp, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(log, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(ceil, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(floor, FUNC_D2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(sqrt, FUNC_D2D_TYPE); + // var exp = stdlib.Math.*; (DD2D * 12) + CHECK_VAR_MATH_SHORTCUT(min, FUNC_DD2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(max, FUNC_DD2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(atan2, FUNC_DD2D_TYPE); + CHECK_VAR_MATH_SHORTCUT(pow, FUNC_DD2D_TYPE); + // Special ones. + CHECK_VAR_MATH_SHORTCUT(abs, FUNC_N2N_TYPE); + CHECK_VAR_MATH_SHORTCUT(imul, FUNC_II2I_TYPE); + CHECK_VAR_MATH_SHORTCUT(fround, FUNC_N2F_TYPE); + // var exp = stdlib.Math.*; (D * 12) + CHECK_VAR_MATH_SHORTCUT(E, Bounds(cache.kFloat64)); + CHECK_VAR_MATH_SHORTCUT(LN10, Bounds(cache.kFloat64)); + CHECK_VAR_MATH_SHORTCUT(LN2, Bounds(cache.kFloat64)); + CHECK_VAR_MATH_SHORTCUT(LOG2E, Bounds(cache.kFloat64)); + CHECK_VAR_MATH_SHORTCUT(LOG10E, Bounds(cache.kFloat64)); + CHECK_VAR_MATH_SHORTCUT(PI, Bounds(cache.kFloat64)); + CHECK_VAR_MATH_SHORTCUT(SQRT1_2, Bounds(cache.kFloat64)); + CHECK_VAR_MATH_SHORTCUT(SQRT2, Bounds(cache.kFloat64)); + // var values = new stdlib.*Array(buffer); + CHECK_VAR_NEW_SHORTCUT(u8, Bounds(cache.kUint8Array)); + CHECK_VAR_NEW_SHORTCUT(i8, Bounds(cache.kInt8Array)); + CHECK_VAR_NEW_SHORTCUT(u16, Bounds(cache.kUint16Array)); + CHECK_VAR_NEW_SHORTCUT(i16, Bounds(cache.kInt16Array)); + CHECK_VAR_NEW_SHORTCUT(u32, Bounds(cache.kUint32Array)); + CHECK_VAR_NEW_SHORTCUT(i32, Bounds(cache.kInt32Array)); + CHECK_VAR_NEW_SHORTCUT(f32, Bounds(cache.kFloat32Array)); + CHECK_VAR_NEW_SHORTCUT(f64, Bounds(cache.kFloat64Array)); +} +} + + +#define CHECK_FUNC_TYPES_BEGIN(func) \ + HARNESS_PREAMBLE() \ + func "\n" HARNESS_POSTAMBLE(); \ + \ + v8::V8::Initialize(); \ + HandleAndZoneScope handles; \ + Zone* zone = handles.main_zone(); \ + ZoneVector types(zone); \ + CHECK_EQ("", Validate(zone, test_function, &types)); \ + ZoneTypeCache cache; \ + \ + CHECK_TYPES_BEGIN { \ + /* Module. */ \ + CHECK_EXPR(FunctionLiteral, Bounds::Unbounded()) { +#define CHECK_FUNC_TYPES_END_1() \ + /* "use asm"; */ \ + CHECK_EXPR(Literal, Bounds(Type::String())); \ + /* stdlib shortcuts. */ \ + CheckStdlibShortcuts(zone, types, index, depth, cache); + + +#define CHECK_FUNC_TYPES_END_2() \ + /* return { foo: foo }; */ \ + CHECK_EXPR(ObjectLiteral, Bounds::Unbounded()) { \ + CHECK_VAR(foo, FUNC_V_TYPE); \ + } \ + } \ + } \ + 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(); \ + \ + v8::V8::Initialize(); \ + HandleAndZoneScope handles; \ + Zone* zone = handles.main_zone(); \ + ZoneVector types(zone); \ + CHECK_EQ(message, Validate(zone, test_function, &types)); + + +TEST(BareHarness) { + CHECK_FUNC_TYPES_BEGIN("function foo() {}") { + CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) {} + } + CHECK_FUNC_TYPES_END +} + + +TEST(ReturnVoid) { + CHECK_FUNC_TYPES_BEGIN( + "function bar() { return; }\n" + "function foo() { bar(); }") { + CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) { + // return undefined; + CHECK_EXPR(Literal, Bounds(Type::Undefined())); + } + CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) { + CHECK_EXPR(Call, Bounds(Type::Undefined())) { + CHECK_VAR(bar, FUNC_V_TYPE); + } + } + } + CHECK_FUNC_TYPES_END +} + + +TEST(ReturnInt32Literal) { + CHECK_FUNC_TYPES_BEGIN( + "function bar() { return 1; }\n" + "function foo() { bar(); }") { + CHECK_EXPR(FunctionLiteral, FUNC_I_TYPE) { + // return 1; + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) { + CHECK_EXPR(Call, Bounds(cache.kInt32)) { CHECK_VAR(bar, FUNC_I_TYPE); } + } + } + CHECK_FUNC_TYPES_END +} + + +TEST(ReturnFloat64Literal) { + CHECK_FUNC_TYPES_BEGIN( + "function bar() { return 1.0; }\n" + "function foo() { bar(); }") { + CHECK_EXPR(FunctionLiteral, FUNC_D_TYPE) { + // return 1.0; + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) { + CHECK_EXPR(Call, Bounds(cache.kFloat64)) { CHECK_VAR(bar, FUNC_D_TYPE); } + } + } + CHECK_FUNC_TYPES_END +} + + +TEST(ReturnFloat32Literal) { + CHECK_FUNC_TYPES_BEGIN( + "function bar() { return fround(1.0); }\n" + "function foo() { bar(); }") { + CHECK_EXPR(FunctionLiteral, FUNC_F_TYPE) { + // return fround(1.0); + CHECK_EXPR(Call, Bounds(cache.kFloat32)) { + CHECK_VAR(fround, FUNC_N2F_TYPE); + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + } + CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) { + CHECK_EXPR(Call, Bounds(cache.kFloat32)) { CHECK_VAR(bar, FUNC_F_TYPE); } + } + } + CHECK_FUNC_TYPES_END +} + + +TEST(ReturnFloat64Var) { + CHECK_FUNC_TYPES_BEGIN( + "function bar() { var x = 1.0; return +x; }\n" + "function foo() { bar(); }") { + CHECK_EXPR(FunctionLiteral, FUNC_D_TYPE) { + // return 1.0; + CHECK_EXPR(Assignment, Bounds(cache.kFloat64)) { + CHECK_VAR(x, Bounds(cache.kFloat64)); + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + // return 1.0; + CHECK_EXPR(BinaryOperation, Bounds(cache.kFloat64)) { + CHECK_VAR(x, Bounds(cache.kFloat64)); + CHECK_EXPR(Literal, Bounds(cache.kFloat64)); + } + } + CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) { + CHECK_EXPR(Call, Bounds(cache.kFloat64)) { CHECK_VAR(bar, FUNC_D_TYPE); } + } + } + CHECK_FUNC_TYPES_END +} + + +TEST(Addition2) { + CHECK_FUNC_TYPES_BEGIN( + "function bar() { var x = 1; var y = 2; return (x+y)|0; }\n" + "function foo() { bar(); }") { + CHECK_EXPR(FunctionLiteral, FUNC_I_TYPE) { + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(y, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_VAR(y, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_SKIP(); + } + CHECK_FUNC_TYPES_END +} + + +TEST(Addition4) { + CHECK_FUNC_TYPES_BEGIN( + "function bar() { var x = 1; var y = 2; return (x+y+x+y)|0; }\n" + "function foo() { bar(); }") { + CHECK_EXPR(FunctionLiteral, FUNC_I_TYPE) { + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(y, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_VAR(y, Bounds(cache.kInt32)); + } + CHECK_VAR(x, Bounds(cache.kInt32)); + } + CHECK_VAR(y, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_SKIP(); + } + CHECK_FUNC_TYPES_END +} + + +TEST(Multiplication2) { + CHECK_FUNC_ERROR( + "function bar() { var x = 1; var y = 2; return (x*y)|0; }\n" + "function foo() { bar(); }", + "asm: line 39: direct integer multiply forbidden\n"); +} + + +TEST(Division4) { + CHECK_FUNC_ERROR( + "function bar() { var x = 1; var y = 2; return (x/y/x/y)|0; }\n" + "function foo() { bar(); }", + "asm: line 39: too many consecutive multiplicative ops\n"); +} + + +TEST(Load1) { + CHECK_FUNC_TYPES_BEGIN( + "function bar() { var x = 1; var y = i8[x>>0]|0; }\n" + "function foo() { bar(); }") { + CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) { + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(y, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(Property, Bounds(cache.kInt8)) { + CHECK_VAR(i8, Bounds(cache.kInt8Array)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + } + CHECK_SKIP(); + } + CHECK_FUNC_TYPES_END +} + + +TEST(FunctionTables) { + CHECK_FUNC_TYPES_BEGIN( + "function func1(x) { x = x | 0; return (x * 5) | 0; }\n" + "function func2(x) { x = x | 0; return (x * 25) | 0; }\n" + "var table1 = [func1, func2];\n" + "function bar(x, y) { x = x | 0; y = y | 0;\n" + " return table1[x & 1](y)|0; }\n" + "function foo() { bar(1, 2); }") { + CHECK_EXPR(FunctionLiteral, FUNC_I2I_TYPE) { + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(FunctionLiteral, FUNC_I2I_TYPE) { + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(FunctionLiteral, FUNC_II2I_TYPE) { + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(Assignment, Bounds(cache.kInt32)) { + CHECK_VAR(y, Bounds(cache.kInt32)); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(y, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(Call, Bounds(cache.kInt32)) { + CHECK_EXPR(Property, FUNC_I2I_TYPE) { + CHECK_VAR(table1, FUNC_I2I_ARRAY_TYPE); + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_VAR(x, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_VAR(y, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_SKIP(); + } + CHECK_FUNC_TYPES_END_1(); + CHECK_EXPR(Assignment, FUNC_I2I_ARRAY_TYPE) { + CHECK_VAR(table1, FUNC_I2I_ARRAY_TYPE); + CHECK_EXPR(ArrayLiteral, FUNC_I2I_ARRAY_TYPE) { + CHECK_VAR(func1, FUNC_I2I_TYPE); + CHECK_VAR(func2, FUNC_I2I_TYPE); + } + } + CHECK_FUNC_TYPES_END_2(); +} + + +TEST(BadFunctionTable) { + CHECK_FUNC_ERROR( + "function func1(x) { x = x | 0; return (x * 5) | 0; }\n" + "var table1 = [func1, 1];\n" + "function bar(x, y) { x = x | 0; y = y | 0;\n" + " return table1[x & 1](y)|0; }\n" + "function foo() { bar(1, 2); }", + "asm: line 40: array component expected to be a function\n"); +} + + +TEST(MissingParameterTypes) { + CHECK_FUNC_ERROR( + "function bar(x) { var y = 1; }\n" + "function foo() { bar(2); }", + "asm: line 39: missing parameter type annotations\n"); +} + + +TEST(InvalidTypeAnnotationBinaryOpDiv) { + CHECK_FUNC_ERROR( + "function bar(x) { x = x / 4; }\n" + "function foo() { bar(2); }", + "asm: line 39: invalid type annotation on binary op\n"); +} + + +TEST(InvalidTypeAnnotationBinaryOpMul) { + CHECK_FUNC_ERROR( + "function bar(x) { x = x * 4.0; }\n" + "function foo() { bar(2); }", + "asm: line 39: invalid type annotation on binary op\n"); +} + + +TEST(InvalidArgumentCount) { + CHECK_FUNC_ERROR( + "function bar(x) { return fround(4, 5); }\n" + "function foo() { bar(); }", + "asm: line 39: invalid argument count calling fround\n"); +} + + +TEST(InvalidTypeAnnotationArity) { + CHECK_FUNC_ERROR( + "function bar(x) { x = max(x); }\n" + "function foo() { bar(3); }", + "asm: line 39: only fround allowed on expression annotations\n"); +} + + +TEST(InvalidTypeAnnotationOnlyFround) { + CHECK_FUNC_ERROR( + "function bar(x) { x = sin(x); }\n" + "function foo() { bar(3); }", + "asm: line 39: only fround allowed on expression annotations\n"); +} + + +TEST(InvalidTypeAnnotation) { + CHECK_FUNC_ERROR( + "function bar(x) { x = (x+x)(x); }\n" + "function foo() { bar(3); }", + "asm: line 39: invalid type annotation\n"); +} + + +TEST(WithStatement) { + CHECK_FUNC_ERROR( + "function bar() { var x = 0; with (x) { x = x + 1; } }\n" + "function foo() { bar(); }", + "asm: line 39: bad with statement\n"); +} + + +TEST(NestedFunction) { + CHECK_FUNC_ERROR( + "function bar() { function x() { return 1; } }\n" + "function foo() { bar(); }", + "asm: line 39: function declared inside another\n"); +} + + +TEST(UnboundVariable) { + CHECK_FUNC_ERROR( + "function bar() { var x = y; }\n" + "function foo() { bar(); }", + "asm: line 39: unbound variable\n"); +} + + +TEST(ForeignFunction) { + CHECK_FUNC_TYPES_BEGIN( + "var baz = foreign.baz;\n" + "function bar() { return baz(1, 2)|0; }\n" + "function foo() { bar(); }") { + CHECK_EXPR(FunctionLiteral, FUNC_I_TYPE) { + CHECK_EXPR(BinaryOperation, Bounds(cache.kInt32)) { + CHECK_EXPR(Call, Bounds(Type::Number(zone))) { + CHECK_VAR(baz, Bounds(Type::Any())); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + CHECK_EXPR(Literal, Bounds(cache.kInt32)); + } + } + CHECK_EXPR(FunctionLiteral, FUNC_V_TYPE) { + CHECK_EXPR(Call, Bounds(cache.kInt32)) { CHECK_VAR(bar, FUNC_I_TYPE); } + } + } + CHECK_FUNC_TYPES_END_1() + CHECK_EXPR(Assignment, Bounds(Type::Any())) { + CHECK_VAR(baz, Bounds(Type::Any())); + CHECK_EXPR(Property, Bounds(Type::Any())) { + CHECK_VAR(foreign, Bounds::Unbounded()); + CHECK_EXPR(Literal, Bounds::Unbounded()); + } + } + CHECK_FUNC_TYPES_END_2() +} + + +TEST(BadExports) { + HARNESS_PREAMBLE() + "function foo() {};\n" + "return {foo: foo, bar: 1};" + "}\n"; + + v8::V8::Initialize(); + HandleAndZoneScope handles; + Zone* zone = handles.main_zone(); + ZoneVector types(zone); + CHECK_EQ("asm: line 40: non-function in function table\n", + Validate(zone, test_function, &types)); +} diff --git a/tools/gyp/v8.gyp b/tools/gyp/v8.gyp index c00311e86..154b7097e 100644 --- a/tools/gyp/v8.gyp +++ b/tools/gyp/v8.gyp @@ -973,6 +973,8 @@ '../../src/types-inl.h', '../../src/types.cc', '../../src/types.h', + '../../src/typing-asm.cc', + '../../src/typing-asm.h', '../../src/typing-reset.cc', '../../src/typing-reset.h', '../../src/typing.cc', -- 2.34.1