Add asm.js typer / validator.
authorbradnelson <bradnelson@google.com>
Tue, 1 Sep 2015 18:30:34 +0000 (11:30 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 1 Sep 2015 18:30:43 +0000 (18:30 +0000)
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
src/typing-asm.cc [new file with mode: 0644]
src/typing-asm.h [new file with mode: 0644]
test/cctest/cctest.gyp
test/cctest/test-asm-validator.cc [new file with mode: 0644]
tools/gyp/v8.gyp

index 6d07d77000288590145384e9e90bce0a843fdfff..2b8f46a967637705de29c5ea2f5e24ef2c6c9a67 100644 (file)
--- 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 (file)
index 0000000..a8d0fbc
--- /dev/null
@@ -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<ZoneTypeCache>::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<Declaration*>* 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<Statement*>* 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<Statement*>* 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<CaseClause*>* 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<Object> value = label->AsLiteral()->value();
+    int32_t value32;
+    if (!value->ToInt32(&value32)) FAIL(label, "illegal case label value");
+    // TODO(bradnelson): Detect duplicates.
+    ZoneList<Statement*>* 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<Object> 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<ObjectLiteralProperty*>* 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<Expression*>* 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<size_t>(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<int>(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<String> 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<String> 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<Expression*>* 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<Expression*>* 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<Expression*>* 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<Declaration*>* 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<String> name) {
+  base::SmartArrayPointer<char> 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<void*>(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<Type*>(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 (file)
index 0000000..74c28fb
--- /dev/null
@@ -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<std::string, Type*> 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<Declaration*>* d) override;
+  void VisitStatements(ZoneList<Statement*>* 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<String> 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_
index 5199471f4a7c943c57ea0e68f507dba7a6a3a325..f9ffade345779fa8d62aad4d05a3071cf776f6d2 100644 (file)
         '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 (file)
index 0000000..886c037
--- /dev/null
@@ -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<ExpressionTypeEntry>* types) {
+  i::Isolate* isolate = CcTest::i_isolate();
+  i::Factory* factory = isolate->factory();
+
+  i::Handle<i::String> source_code =
+      factory->NewStringFromUtf8(i::CStrVector(source)).ToHandleChecked();
+
+  i::Handle<i::Script> 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<ExpressionTypeEntry> 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<ExpressionTypeEntry>& 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<ExpressionTypeEntry> 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<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) {}
+  }
+  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<ExpressionTypeEntry> types(zone);
+  CHECK_EQ("asm: line 40: non-function in function table\n",
+           Validate(zone, test_function, &types));
+}
index c00311e8660167d684036bee40debf2f517cdd36..154b7097ec9d88ca08dd96ac3e6331b051c9b4ec 100644 (file)
         '../../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',