// 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"

// Required to get M_E etc. in MSVC.
#if defined(_WIN32)
#define _USE_MATH_DEFINES
#endif
#include <math.h>

#include "src/asmjs/asm-types.h"
#include "src/asmjs/asm-wasm-builder.h"
#include "src/asmjs/switch-logic.h"

#include "src/wasm/wasm-macro-gen.h"
#include "src/wasm/wasm-opcodes.h"

#include "src/ast/ast.h"
#include "src/ast/scopes.h"
#include "src/codegen.h"
#include "src/compilation-info.h"
#include "src/compiler.h"
#include "src/counters.h"
#include "src/isolate.h"
#include "src/objects-inl.h"
#include "src/parsing/parse-info.h"

namespace v8 {
namespace internal {
namespace wasm {

#define RECURSE(call)               \
  do {                              \
    DCHECK(!HasStackOverflow());    \
    call;                           \
    if (HasStackOverflow()) return; \
  } while (false)

namespace {

enum AsmScope { kModuleScope, kInitScope, kFuncScope, kExportScope };
enum ValueFate { kDrop, kLeaveOnStack };

struct ForeignVariable {
  Handle<Name> name;
  Variable* var;
  ValueType type;
};

enum TargetType : uint8_t { NoTarget, BreakTarget, ContinueTarget };

}  // namespace

class AsmWasmBuilderImpl final : public AstVisitor<AsmWasmBuilderImpl> {
 public:
  AsmWasmBuilderImpl(Isolate* isolate, Zone* zone, CompilationInfo* info,
                     AstValueFactory* ast_value_factory, Handle<Script> script,
                     FunctionLiteral* literal, AsmTyper* typer)
      : local_variables_(ZoneHashMap::kDefaultHashMapCapacity,
                         ZoneAllocationPolicy(zone)),
        functions_(ZoneHashMap::kDefaultHashMapCapacity,
                   ZoneAllocationPolicy(zone)),
        global_variables_(ZoneHashMap::kDefaultHashMapCapacity,
                          ZoneAllocationPolicy(zone)),
        scope_(kModuleScope),
        builder_(new (zone) WasmModuleBuilder(zone)),
        current_function_builder_(nullptr),
        literal_(literal),
        isolate_(isolate),
        zone_(zone),
        info_(info),
        ast_value_factory_(ast_value_factory),
        script_(script),
        typer_(typer),
        typer_failed_(false),
        typer_finished_(false),
        breakable_blocks_(zone),
        foreign_variables_(zone),
        init_function_(nullptr),
        foreign_init_function_(nullptr),
        function_tables_(ZoneHashMap::kDefaultHashMapCapacity,
                         ZoneAllocationPolicy(zone)),
        imported_function_table_(this),
        parent_binop_(nullptr) {
    InitializeAstVisitor(isolate);
  }

  void InitializeInitFunction() {
    FunctionSig::Builder b(zone(), 0, 0);
    init_function_ = builder_->AddFunction(b.Build());
    builder_->MarkStartFunction(init_function_);
  }

  void BuildForeignInitFunction() {
    foreign_init_function_ = builder_->AddFunction();
    FunctionSig::Builder b(zone(), 0, foreign_variables_.size());
    for (auto i = foreign_variables_.begin(); i != foreign_variables_.end();
         ++i) {
      b.AddParam(i->type);
    }
    foreign_init_function_->ExportAs(
        CStrVector(AsmWasmBuilder::foreign_init_name));
    foreign_init_function_->SetSignature(b.Build());
    for (size_t pos = 0; pos < foreign_variables_.size(); ++pos) {
      foreign_init_function_->EmitGetLocal(static_cast<uint32_t>(pos));
      ForeignVariable* fv = &foreign_variables_[pos];
      uint32_t index = LookupOrInsertGlobal(fv->var, fv->type);
      foreign_init_function_->EmitWithVarUint(kExprSetGlobal, index);
    }
    foreign_init_function_->Emit(kExprEnd);
  }

  Handle<FixedArray> GetForeignArgs() {
    Handle<FixedArray> ret = isolate_->factory()->NewFixedArray(
        static_cast<int>(foreign_variables_.size()));
    for (size_t i = 0; i < foreign_variables_.size(); ++i) {
      ForeignVariable* fv = &foreign_variables_[i];
      ret->set(static_cast<int>(i), *fv->name);
    }
    return ret;
  }

  bool Build() {
    InitializeInitFunction();
    if (!typer_->ValidateBeforeFunctionsPhase()) {
      return false;
    }
    DCHECK(!HasStackOverflow());
    VisitFunctionLiteral(literal_);
    if (HasStackOverflow()) {
      return false;
    }
    if (!typer_finished_ && !typer_failed_) {
      typer_->FailWithMessage("Module missing export section.");
      typer_failed_ = true;
    }
    if (typer_failed_) {
      return false;
    }
    BuildForeignInitFunction();
    init_function_->Emit(kExprEnd);  // finish init function.
    return true;
  }

  void VisitVariableDeclaration(VariableDeclaration* decl) {}

  void VisitFunctionDeclaration(FunctionDeclaration* decl) {
    DCHECK_EQ(kModuleScope, scope_);
    DCHECK_NULL(current_function_builder_);
    FunctionLiteral* old_func = decl->fun();
    DeclarationScope* new_func_scope = nullptr;
    std::unique_ptr<ParseInfo> info;
    if (decl->fun()->body() == nullptr) {
      // TODO(titzer/bradnelson): Reuse SharedFunctionInfos used here when
      // compiling the wasm module.
      Handle<SharedFunctionInfo> shared =
          Compiler::GetSharedFunctionInfo(decl->fun(), script_, info_);
      shared->set_is_toplevel(false);
      info.reset(new ParseInfo(script_));
      info->set_shared_info(shared);
      info->set_toplevel(false);
      info->set_language_mode(decl->fun()->scope()->language_mode());
      info->set_allow_lazy_parsing(false);
      info->set_function_literal_id(shared->function_literal_id());
      info->set_ast_value_factory(ast_value_factory_);
      info->set_ast_value_factory_owned(false);
      // Create fresh function scope to use to parse the function in.
      new_func_scope = new (info->zone()) DeclarationScope(
          info->zone(), decl->fun()->scope()->outer_scope(), FUNCTION_SCOPE);
      info->set_asm_function_scope(new_func_scope);
      if (!Compiler::ParseAndAnalyze(info.get())) {
        decl->fun()->scope()->outer_scope()->RemoveInnerScope(new_func_scope);
        if (isolate_->has_pending_exception()) {
          isolate_->clear_pending_exception();
        }
        typer_->TriggerParsingError();
        typer_failed_ = true;
        return;
      }
      FunctionLiteral* func = info->literal();
      DCHECK_NOT_NULL(func);
      decl->set_fun(func);
    }
    if (!typer_->ValidateInnerFunction(decl)) {
      typer_failed_ = true;
      decl->set_fun(old_func);
      if (new_func_scope != nullptr) {
        DCHECK_EQ(new_func_scope, decl->scope()->inner_scope());
        if (!decl->scope()->RemoveInnerScope(new_func_scope)) {
          UNREACHABLE();
        }
      }
      return;
    }
    current_function_builder_ = LookupOrInsertFunction(decl->proxy()->var());
    scope_ = kFuncScope;

    // Record start of the function, used as position for the stack check.
    current_function_builder_->SetAsmFunctionStartPosition(
        decl->fun()->start_position());

    RECURSE(Visit(decl->fun()));
    decl->set_fun(old_func);
    if (new_func_scope != nullptr) {
      DCHECK_EQ(new_func_scope, decl->scope()->inner_scope());
      if (!decl->scope()->RemoveInnerScope(new_func_scope)) {
        UNREACHABLE();
      }
    }
    scope_ = kModuleScope;
    current_function_builder_ = nullptr;
    local_variables_.Clear();
    typer_->ClearFunctionNodeTypes();
  }

  void VisitStatements(ZoneList<Statement*>* stmts) {
    for (int i = 0; i < stmts->length(); ++i) {
      Statement* stmt = stmts->at(i);
      ExpressionStatement* e = stmt->AsExpressionStatement();
      if (e != nullptr && e->expression()->IsUndefinedLiteral()) {
        continue;
      }
      RECURSE(Visit(stmt));
      if (typer_failed_) break;
    }
  }

  void VisitBlock(Block* stmt) {
    if (stmt->statements()->length() == 1) {
      ExpressionStatement* expr =
          stmt->statements()->at(0)->AsExpressionStatement();
      if (expr != nullptr) {
        if (expr->expression()->IsAssignment()) {
          RECURSE(VisitExpressionStatement(expr));
          return;
        }
      }
    }
    if (scope_ == kFuncScope) {
      BlockVisitor visitor(this, stmt->AsBreakableStatement(), kExprBlock,
                           BreakTarget);
      RECURSE(VisitStatements(stmt->statements()));
    } else {
      RECURSE(VisitStatements(stmt->statements()));
    }
  }

  class BlockVisitor {
   private:
    AsmWasmBuilderImpl* builder_;

   public:
    BlockVisitor(AsmWasmBuilderImpl* builder, BreakableStatement* stmt,
                 WasmOpcode opcode, TargetType target_type = NoTarget)
        : builder_(builder) {
      builder_->breakable_blocks_.emplace_back(stmt, target_type);
      // block and loops have a type immediate.
      builder_->current_function_builder_->EmitWithU8(opcode, kLocalVoid);
    }
    ~BlockVisitor() {
      builder_->current_function_builder_->Emit(kExprEnd);
      builder_->breakable_blocks_.pop_back();
    }
  };

  void VisitExpressionStatement(ExpressionStatement* stmt) {
    VisitForEffect(stmt->expression());
  }

  void VisitForEffect(Expression* expr) {
    if (expr->IsAssignment()) {
      // Don't emit drops for assignments. Instead use SetLocal/GetLocal.
      VisitAssignment(expr->AsAssignment(), kDrop);
      return;
    }
    if (expr->IsCall()) {
      // Only emit a drop if the call has a non-void return value.
      if (VisitCallExpression(expr->AsCall()) && scope_ == kFuncScope) {
        current_function_builder_->Emit(kExprDrop);
      }
      return;
    }
    if (expr->IsBinaryOperation()) {
      BinaryOperation* binop = expr->AsBinaryOperation();
      if (binop->op() == Token::COMMA) {
        VisitForEffect(binop->left());
        VisitForEffect(binop->right());
        return;
      }
    }
    RECURSE(Visit(expr));
    if (scope_ == kFuncScope) current_function_builder_->Emit(kExprDrop);
  }

  void VisitEmptyStatement(EmptyStatement* stmt) {}

  void VisitEmptyParentheses(EmptyParentheses* paren) { UNREACHABLE(); }

  void VisitGetIterator(GetIterator* expr) { UNREACHABLE(); }

  void VisitIfStatement(IfStatement* stmt) {
    DCHECK_EQ(kFuncScope, scope_);
    RECURSE(Visit(stmt->condition()));
    // Wasm ifs come with implicit blocks for both arms.
    BlockVisitor block(this, nullptr, kExprIf);
    if (stmt->HasThenStatement()) {
      RECURSE(Visit(stmt->then_statement()));
    }
    if (stmt->HasElseStatement()) {
      current_function_builder_->Emit(kExprElse);
      RECURSE(Visit(stmt->else_statement()));
    }
  }

  void DoBreakOrContinue(BreakableStatement* target, TargetType type) {
    DCHECK_EQ(kFuncScope, scope_);
    for (int i = static_cast<int>(breakable_blocks_.size()) - 1; i >= 0; --i) {
      auto elem = breakable_blocks_.at(i);
      if (elem.first == target && elem.second == type) {
        int block_distance = static_cast<int>(breakable_blocks_.size() - i - 1);
        current_function_builder_->EmitWithVarUint(kExprBr, block_distance);
        return;
      }
    }
    UNREACHABLE();  // statement not found
  }

  void VisitContinueStatement(ContinueStatement* stmt) {
    DoBreakOrContinue(stmt->target(), ContinueTarget);
  }

  void VisitBreakStatement(BreakStatement* stmt) {
    DoBreakOrContinue(stmt->target(), BreakTarget);
  }

  void VisitReturnStatement(ReturnStatement* stmt) {
    if (scope_ == kModuleScope) {
      if (typer_finished_) {
        typer_->FailWithMessage("Module has multiple returns.");
        typer_failed_ = true;
        return;
      }
      if (!typer_->ValidateAfterFunctionsPhase()) {
        typer_failed_ = true;
        return;
      }
      typer_finished_ = true;
      scope_ = kExportScope;
      RECURSE(Visit(stmt->expression()));
      scope_ = kModuleScope;
    } else if (scope_ == kFuncScope) {
      RECURSE(Visit(stmt->expression()));
      current_function_builder_->Emit(kExprReturn);
    } else {
      UNREACHABLE();
    }
  }

  void VisitWithStatement(WithStatement* stmt) { UNREACHABLE(); }

  void HandleCase(CaseNode* node,
                  ZoneMap<int, unsigned int>& case_to_block,
                  VariableProxy* tag, int default_block, int if_depth) {
    int prev_if_depth = if_depth;
    if (node->left != nullptr) {
      VisitVariableProxy(tag);
      current_function_builder_->EmitI32Const(node->begin);
      current_function_builder_->Emit(kExprI32LtS);
      current_function_builder_->EmitWithU8(kExprIf, kLocalVoid);
      if_depth++;
      breakable_blocks_.emplace_back(nullptr, NoTarget);
      HandleCase(node->left, case_to_block, tag, default_block, if_depth);
      current_function_builder_->Emit(kExprElse);
    }
    if (node->right != nullptr) {
      VisitVariableProxy(tag);
      current_function_builder_->EmitI32Const(node->end);
      current_function_builder_->Emit(kExprI32GtS);
      current_function_builder_->EmitWithU8(kExprIf, kLocalVoid);
      if_depth++;
      breakable_blocks_.emplace_back(nullptr, NoTarget);
      HandleCase(node->right, case_to_block, tag, default_block, if_depth);
      current_function_builder_->Emit(kExprElse);
    }
    if (node->begin == node->end) {
      VisitVariableProxy(tag);
      current_function_builder_->EmitI32Const(node->begin);
      current_function_builder_->Emit(kExprI32Eq);
      current_function_builder_->EmitWithU8(kExprIf, kLocalVoid);
      DCHECK(case_to_block.find(node->begin) != case_to_block.end());
      current_function_builder_->Emit(kExprBr);
      current_function_builder_->EmitVarUint(1 + if_depth +
                                             case_to_block[node->begin]);
      current_function_builder_->Emit(kExprEnd);
    } else {
      if (node->begin != 0) {
        VisitVariableProxy(tag);
        current_function_builder_->EmitI32Const(node->begin);
        current_function_builder_->Emit(kExprI32Sub);
      } else {
        VisitVariableProxy(tag);
      }
      current_function_builder_->Emit(kExprBrTable);
      current_function_builder_->EmitVarUint(node->end - node->begin + 1);
      for (int v = node->begin; v <= node->end; ++v) {
        if (case_to_block.find(v) != case_to_block.end()) {
          uint32_t target = if_depth + case_to_block[v];
          current_function_builder_->EmitVarUint(target);
        } else {
          uint32_t target = if_depth + default_block;
          current_function_builder_->EmitVarUint(target);
        }
        if (v == kMaxInt) {
          break;
        }
      }
      uint32_t target = if_depth + default_block;
      current_function_builder_->EmitVarUint(target);
    }

    while (if_depth-- != prev_if_depth) {
      breakable_blocks_.pop_back();
      current_function_builder_->Emit(kExprEnd);
    }
  }

  void VisitSwitchStatement(SwitchStatement* stmt) {
    VariableProxy* tag = stmt->tag()->AsVariableProxy();
    DCHECK_NOT_NULL(tag);
    ZoneList<CaseClause*>* clauses = stmt->cases();
    int case_count = clauses->length();
    if (case_count == 0) {
      return;
    }
    BlockVisitor visitor(this, stmt->AsBreakableStatement(), kExprBlock,
                         BreakTarget);
    ZoneVector<BlockVisitor*> blocks(zone_);
    ZoneVector<int32_t> cases(zone_);
    ZoneMap<int, unsigned int> case_to_block(zone_);
    bool has_default = false;
    for (int i = case_count - 1; i >= 0; --i) {
      CaseClause* clause = clauses->at(i);
      blocks.push_back(new BlockVisitor(this, nullptr, kExprBlock));
      if (!clause->is_default()) {
        Literal* label = clause->label()->AsLiteral();
        Handle<Object> value = label->value();
        int32_t label_value;
        bool label_is_i32 = value->ToInt32(&label_value);
        DCHECK(value->IsNumber() && label_is_i32);
        (void)label_is_i32;
        case_to_block[label_value] = i;
        cases.push_back(label_value);
      } else {
        DCHECK_EQ(i, case_count - 1);
        has_default = true;
      }
    }
    if (!has_default || case_count > 1) {
      int default_block = has_default ? case_count - 1 : case_count;
      BlockVisitor switch_logic_block(this, nullptr, kExprBlock);
      CaseNode* root = OrderCases(&cases, zone_);
      HandleCase(root, case_to_block, tag, default_block, 0);
      if (root->left != nullptr || root->right != nullptr ||
          root->begin == root->end) {
        current_function_builder_->Emit(kExprBr);
        current_function_builder_->EmitVarUint(default_block);
      }
    }
    for (int i = 0; i < case_count; ++i) {
      CaseClause* clause = clauses->at(i);
      RECURSE(VisitStatements(clause->statements()));
      BlockVisitor* v = blocks.at(case_count - i - 1);
      blocks.pop_back();
      delete v;
    }
  }

  void VisitCaseClause(CaseClause* clause) { UNREACHABLE(); }

  void VisitDoWhileStatement(DoWhileStatement* stmt) {
    DCHECK_EQ(kFuncScope, scope_);
    BlockVisitor block(this, stmt->AsBreakableStatement(), kExprBlock,
                       BreakTarget);
    BlockVisitor loop(this, stmt->AsBreakableStatement(), kExprLoop);
    {
      BlockVisitor inner_block(this, stmt->AsBreakableStatement(), kExprBlock,
                               ContinueTarget);
      RECURSE(Visit(stmt->body()));
    }
    RECURSE(Visit(stmt->cond()));
    current_function_builder_->EmitWithU8(kExprBrIf, 0);
  }

  void VisitWhileStatement(WhileStatement* stmt) {
    DCHECK_EQ(kFuncScope, scope_);
    BlockVisitor block(this, stmt->AsBreakableStatement(), kExprBlock,
                       BreakTarget);
    BlockVisitor loop(this, stmt->AsBreakableStatement(), kExprLoop,
                      ContinueTarget);
    RECURSE(Visit(stmt->cond()));
    BlockVisitor if_block(this, nullptr, kExprIf);
    RECURSE(Visit(stmt->body()));
    current_function_builder_->EmitWithU8(kExprBr, 1);
  }

  void VisitForStatement(ForStatement* stmt) {
    DCHECK_EQ(kFuncScope, scope_);
    if (stmt->init() != nullptr) {
      RECURSE(Visit(stmt->init()));
    }
    BlockVisitor block(this, stmt->AsBreakableStatement(), kExprBlock,
                       BreakTarget);
    BlockVisitor loop(this, stmt->AsBreakableStatement(), kExprLoop,
                      ContinueTarget);
    if (stmt->cond() != nullptr) {
      RECURSE(Visit(stmt->cond()));
      current_function_builder_->Emit(kExprI32Eqz);
      current_function_builder_->EmitWithU8(kExprIf, kLocalVoid);
      current_function_builder_->EmitWithU8(kExprBr, 2);
      current_function_builder_->Emit(kExprEnd);
    }
    if (stmt->body() != nullptr) {
      RECURSE(Visit(stmt->body()));
    }
    if (stmt->next() != nullptr) {
      RECURSE(Visit(stmt->next()));
    }
    current_function_builder_->EmitWithU8(kExprBr, 0);
  }

  void VisitForInStatement(ForInStatement* stmt) { UNREACHABLE(); }

  void VisitForOfStatement(ForOfStatement* stmt) { UNREACHABLE(); }

  void VisitTryCatchStatement(TryCatchStatement* stmt) { UNREACHABLE(); }

  void VisitTryFinallyStatement(TryFinallyStatement* stmt) { UNREACHABLE(); }

  void VisitDebuggerStatement(DebuggerStatement* stmt) { UNREACHABLE(); }

  void VisitFunctionLiteral(FunctionLiteral* expr) {
    DeclarationScope* scope = expr->scope();
    if (scope_ == kFuncScope) {
      if (auto* func_type = typer_->TypeOf(expr)->AsFunctionType()) {
        // Add the parameters for the function.
        const auto& arguments = func_type->Arguments();
        for (int i = 0; i < expr->parameter_count(); ++i) {
          ValueType type = TypeFrom(arguments[i]);
          DCHECK_NE(kWasmStmt, type);
          InsertParameter(scope->parameter(i), type, i);
        }
      } else {
        UNREACHABLE();
      }
    }
    RECURSE(VisitDeclarations(scope->declarations()));
    if (typer_failed_) return;
    RECURSE(VisitStatements(expr->body()));
    if (scope_ == kFuncScope) {
      // Finish the function-body scope block.
      current_function_builder_->Emit(kExprEnd);
    }
  }

  void VisitNativeFunctionLiteral(NativeFunctionLiteral* expr) {
    UNREACHABLE();
  }

  void VisitConditional(Conditional* expr) {
    DCHECK_EQ(kFuncScope, scope_);
    RECURSE(Visit(expr->condition()));
    // Wasm ifs come with implicit blocks for both arms.
    breakable_blocks_.emplace_back(nullptr, NoTarget);
    ValueTypeCode type;
    switch (TypeOf(expr)) {
      case kWasmI32:
        type = kLocalI32;
        break;
      case kWasmI64:
        type = kLocalI64;
        break;
      case kWasmF32:
        type = kLocalF32;
        break;
      case kWasmF64:
        type = kLocalF64;
        break;
      default:
        UNREACHABLE();
    }
    current_function_builder_->EmitWithU8(kExprIf, type);
    RECURSE(Visit(expr->then_expression()));
    current_function_builder_->Emit(kExprElse);
    RECURSE(Visit(expr->else_expression()));
    current_function_builder_->Emit(kExprEnd);
    breakable_blocks_.pop_back();
  }

  bool VisitStdlibConstant(Variable* var) {
    AsmTyper::StandardMember standard_object =
        typer_->VariableAsStandardMember(var);
    double value;
    switch (standard_object) {
      case AsmTyper::kInfinity: {
        value = std::numeric_limits<double>::infinity();
        break;
      }
      case AsmTyper::kNaN: {
        value = std::numeric_limits<double>::quiet_NaN();
        break;
      }
      case AsmTyper::kMathE: {
        value = M_E;
        break;
      }
      case AsmTyper::kMathLN10: {
        value = M_LN10;
        break;
      }
      case AsmTyper::kMathLN2: {
        value = M_LN2;
        break;
      }
      case AsmTyper::kMathLOG10E: {
        value = M_LOG10E;
        break;
      }
      case AsmTyper::kMathLOG2E: {
        value = M_LOG2E;
        break;
      }
      case AsmTyper::kMathPI: {
        value = M_PI;
        break;
      }
      case AsmTyper::kMathSQRT1_2: {
        value = M_SQRT1_2;
        break;
      }
      case AsmTyper::kMathSQRT2: {
        value = M_SQRT2;
        break;
      }
      default: { return false; }
    }
    byte code[] = {WASM_F64(value)};
    current_function_builder_->EmitCode(code, sizeof(code));
    return true;
  }

  void VisitVariableProxy(VariableProxy* expr) {
    if (scope_ == kFuncScope || scope_ == kInitScope) {
      Variable* var = expr->var();
      if (VisitStdlibConstant(var)) {
        return;
      }
      ValueType var_type = TypeOf(expr);
      DCHECK_NE(kWasmStmt, var_type);
      if (var->IsContextSlot()) {
        current_function_builder_->EmitWithVarUint(
            kExprGetGlobal, LookupOrInsertGlobal(var, var_type));
      } else {
        current_function_builder_->EmitGetLocal(
            LookupOrInsertLocal(var, var_type));
      }
    } else if (scope_ == kExportScope) {
      Variable* var = expr->var();
      DCHECK(var->is_function());
      WasmFunctionBuilder* function = LookupOrInsertFunction(var);
      function->ExportAs(CStrVector(AsmWasmBuilder::single_function_name));
    }
  }

  void VisitLiteral(Literal* expr) {
    Handle<Object> value = expr->value();
    if (!(value->IsNumber() || expr->raw_value()->IsTrue() ||
          expr->raw_value()->IsFalse()) ||
        (scope_ != kFuncScope && scope_ != kInitScope)) {
      return;
    }
    AsmType* type = typer_->TypeOf(expr);
    DCHECK_NE(type, AsmType::None());

    if (type->IsA(AsmType::Signed())) {
      int32_t i = 0;
      CHECK(value->ToInt32(&i));
      current_function_builder_->EmitI32Const(i);
    } else if (type->IsA(AsmType::Unsigned()) || type->IsA(AsmType::FixNum())) {
      uint32_t u = 0;
      CHECK(value->ToUint32(&u));
      current_function_builder_->EmitI32Const(bit_cast<int32_t>(u));
    } else if (type->IsA(AsmType::Int())) {
      // The parser can collapse !0, !1 etc to true / false.
      // Allow these as int literals.
      if (expr->raw_value()->IsTrue()) {
        byte code[] = {WASM_ONE};
        current_function_builder_->EmitCode(code, sizeof(code));
      } else if (expr->raw_value()->IsFalse()) {
        byte code[] = {WASM_ZERO};
        current_function_builder_->EmitCode(code, sizeof(code));
      } else if (expr->raw_value()->IsNumber()) {
        // This can happen when -x becomes x * -1 (due to the parser).
        int32_t i = 0;
        CHECK(value->ToInt32(&i) && i == -1);
        byte code[] = {WASM_I32V_1(-1)};
        current_function_builder_->EmitCode(code, sizeof(code));
      } else {
        UNREACHABLE();
      }
    } else if (type->IsA(AsmType::Double())) {
      // TODO(bradnelson): Pattern match the case where negation occurs and
      // emit f64.neg instead.
      double val = expr->raw_value()->AsNumber();
      byte code[] = {WASM_F64(val)};
      current_function_builder_->EmitCode(code, sizeof(code));
    } else if (type->IsA(AsmType::Float())) {
      // This can happen when -fround(x) becomes fround(x) * 1.0[float]
      // (due to the parser).
      // TODO(bradnelson): Pattern match this and emit f32.neg instead.
      double val = expr->raw_value()->AsNumber();
      DCHECK_EQ(-1.0, val);
      byte code[] = {WASM_F32(val)};
      current_function_builder_->EmitCode(code, sizeof(code));
    } else {
      UNREACHABLE();
    }
  }

  void VisitRegExpLiteral(RegExpLiteral* expr) { UNREACHABLE(); }

  void VisitObjectLiteral(ObjectLiteral* expr) {
    ZoneList<ObjectLiteralProperty*>* props = expr->properties();
    for (int i = 0; i < props->length(); ++i) {
      ObjectLiteralProperty* prop = props->at(i);
      DCHECK_EQ(kExportScope, scope_);
      VariableProxy* expr = prop->value()->AsVariableProxy();
      DCHECK_NOT_NULL(expr);
      Variable* var = expr->var();
      Literal* name = prop->key()->AsLiteral();
      DCHECK_NOT_NULL(name);
      DCHECK(name->IsPropertyName());
      Handle<String> function_name = name->AsPropertyName();
      int length;
      std::unique_ptr<char[]> utf8 = function_name->ToCString(
          DISALLOW_NULLS, FAST_STRING_TRAVERSAL, &length);
      if (var->is_function()) {
        WasmFunctionBuilder* function = LookupOrInsertFunction(var);
        function->ExportAs({utf8.get(), length});
      }
    }
  }

  void VisitArrayLiteral(ArrayLiteral* expr) { UNREACHABLE(); }

  void LoadInitFunction() {
    current_function_builder_ = init_function_;
    scope_ = kInitScope;
  }

  void UnLoadInitFunction() {
    scope_ = kModuleScope;
    current_function_builder_ = nullptr;
  }

  struct FunctionTableIndices : public ZoneObject {
    uint32_t start_index;
    uint32_t signature_index;
  };

  FunctionTableIndices* LookupOrAddFunctionTable(VariableProxy* table,
                                                 Property* p) {
    FunctionTableIndices* indices = LookupFunctionTable(table->var());
    if (indices != nullptr) {
      // Already setup.
      return indices;
    }
    indices = new (zone()) FunctionTableIndices();
    auto* func_type = typer_->TypeOf(p)->AsFunctionType();
    auto* func_table_type = typer_->TypeOf(p->obj()->AsVariableProxy()->var())
                                ->AsFunctionTableType();
    const auto& arguments = func_type->Arguments();
    ValueType return_type = TypeFrom(func_type->ReturnType());
    FunctionSig::Builder sig(zone(), return_type == kWasmStmt ? 0 : 1,
                             arguments.size());
    if (return_type != kWasmStmt) {
      sig.AddReturn(return_type);
    }
    for (auto* arg : arguments) {
      sig.AddParam(TypeFrom(arg));
    }
    uint32_t signature_index = builder_->AddSignature(sig.Build());
    indices->start_index = builder_->AllocateIndirectFunctions(
        static_cast<uint32_t>(func_table_type->length()));
    indices->signature_index = signature_index;
    ZoneHashMap::Entry* entry = function_tables_.LookupOrInsert(
        table->var(), ComputePointerHash(table->var()),
        ZoneAllocationPolicy(zone()));
    entry->value = indices;
    return indices;
  }

  FunctionTableIndices* LookupFunctionTable(Variable* v) {
    ZoneHashMap::Entry* entry =
        function_tables_.Lookup(v, ComputePointerHash(v));
    if (entry == nullptr) {
      return nullptr;
    }
    return reinterpret_cast<FunctionTableIndices*>(entry->value);
  }

  void PopulateFunctionTable(VariableProxy* table, ArrayLiteral* funcs) {
    FunctionTableIndices* indices = LookupFunctionTable(table->var());
    // Ignore unused function tables.
    if (indices == nullptr) {
      return;
    }
    for (int i = 0; i < funcs->values()->length(); ++i) {
      VariableProxy* func = funcs->values()->at(i)->AsVariableProxy();
      DCHECK_NOT_NULL(func);
      builder_->SetIndirectFunction(
          indices->start_index + i,
          LookupOrInsertFunction(func->var())->func_index());
    }
  }

  class ImportedFunctionTable {
   private:
    class ImportedFunctionIndices : public ZoneObject {
     public:
      const char* name_;
      int name_length_;
      WasmModuleBuilder::SignatureMap signature_to_index_;

      ImportedFunctionIndices(const char* name, int name_length, Zone* zone)
          : name_(name), name_length_(name_length), signature_to_index_(zone) {}
    };
    ZoneHashMap table_;
    AsmWasmBuilderImpl* builder_;

   public:
    explicit ImportedFunctionTable(AsmWasmBuilderImpl* builder)
        : table_(ZoneHashMap::kDefaultHashMapCapacity,
                 ZoneAllocationPolicy(builder->zone())),
          builder_(builder) {}

    ImportedFunctionIndices* LookupOrInsertImport(Variable* v) {
      auto* entry = table_.LookupOrInsert(
          v, ComputePointerHash(v), ZoneAllocationPolicy(builder_->zone()));
      ImportedFunctionIndices* indices;
      if (entry->value == nullptr) {
        indices = new (builder_->zone())
            ImportedFunctionIndices(nullptr, 0, builder_->zone());
        entry->value = indices;
      } else {
        indices = reinterpret_cast<ImportedFunctionIndices*>(entry->value);
      }
      return indices;
    }

    void SetImportName(Variable* v, const char* name, int name_length) {
      auto* indices = LookupOrInsertImport(v);
      indices->name_ = name;
      indices->name_length_ = name_length;
      for (auto i : indices->signature_to_index_) {
        builder_->builder_->SetImportName(i.second, indices->name_,
                                          indices->name_length_);
      }
    }

    // Get a function's index (or allocate if new).
    uint32_t LookupOrInsertImportUse(Variable* v, FunctionSig* sig) {
      auto* indices = LookupOrInsertImport(v);
      WasmModuleBuilder::SignatureMap::iterator pos =
          indices->signature_to_index_.find(sig);
      if (pos != indices->signature_to_index_.end()) {
        return pos->second;
      } else {
        uint32_t index = builder_->builder_->AddImport(
            indices->name_, indices->name_length_, sig);
        indices->signature_to_index_[sig] = index;
        return index;
      }
    }
  };

  void EmitAssignmentLhs(Expression* target, AsmType** atype) {
    // Match the left hand side of the assignment.
    VariableProxy* target_var = target->AsVariableProxy();
    if (target_var != nullptr) {
      // Left hand side is a local or a global variable, no code on LHS.
      return;
    }

    Property* target_prop = target->AsProperty();
    if (target_prop != nullptr) {
      // Left hand side is a property access, i.e. the asm.js heap.
      VisitPropertyAndEmitIndex(target_prop, atype);
      return;
    }

    if (target_var == nullptr && target_prop == nullptr) {
      UNREACHABLE();  // invalid assignment.
    }
  }

  void EmitAssignmentRhs(Expression* target, Expression* value, bool* is_nop) {
    BinaryOperation* binop = value->AsBinaryOperation();
    if (binop != nullptr) {
      if (scope_ == kInitScope) {
        // Handle foreign variables in the initialization scope.
        Property* prop = binop->left()->AsProperty();
        if (binop->op() == Token::MUL) {
          DCHECK(binop->right()->IsLiteral());
          DCHECK_EQ(1.0, binop->right()->AsLiteral()->raw_value()->AsNumber());
          DCHECK(binop->right()->AsLiteral()->raw_value()->ContainsDot());
          DCHECK(target->IsVariableProxy());
          VisitForeignVariable(true, target->AsVariableProxy()->var(), prop);
          *is_nop = true;
          return;
        } else if (binop->op() == Token::BIT_OR) {
          DCHECK(binop->right()->IsLiteral());
          DCHECK_EQ(0.0, binop->right()->AsLiteral()->raw_value()->AsNumber());
          DCHECK(!binop->right()->AsLiteral()->raw_value()->ContainsDot());
          DCHECK(target->IsVariableProxy());
          VisitForeignVariable(false, target->AsVariableProxy()->var(), prop);
          *is_nop = true;
          return;
        } else {
          UNREACHABLE();
        }
      }
      if (MatchBinaryOperation(binop) == kAsIs) {
        VariableProxy* target_var = target->AsVariableProxy();
        VariableProxy* effective_value_var = GetLeft(binop)->AsVariableProxy();
        if (target_var != nullptr && effective_value_var != nullptr &&
            target_var->var() == effective_value_var->var()) {
          *is_nop = true;
          return;
        }
      }
    }
    RECURSE(Visit(value));
  }

  void EmitAssignment(Assignment* expr, AsmType* type, ValueFate fate) {
    // Match the left hand side of the assignment.
    VariableProxy* target_var = expr->target()->AsVariableProxy();
    if (target_var != nullptr) {
      // Left hand side is a local or a global variable.
      Variable* var = target_var->var();
      ValueType var_type = TypeOf(expr);
      DCHECK_NE(kWasmStmt, var_type);
      if (var->IsContextSlot()) {
        uint32_t index = LookupOrInsertGlobal(var, var_type);
        current_function_builder_->EmitWithVarUint(kExprSetGlobal, index);
        if (fate == kLeaveOnStack) {
          current_function_builder_->EmitWithVarUint(kExprGetGlobal, index);
        }
      } else {
        if (fate == kDrop) {
          current_function_builder_->EmitSetLocal(
              LookupOrInsertLocal(var, var_type));
        } else {
          current_function_builder_->EmitTeeLocal(
              LookupOrInsertLocal(var, var_type));
        }
      }
    }

    Property* target_prop = expr->target()->AsProperty();
    if (target_prop != nullptr) {
      // Left hand side is a property access, i.e. the asm.js heap.
      if (TypeOf(expr->value()) == kWasmF64 && expr->target()->IsProperty() &&
          typer_->TypeOf(expr->target()->AsProperty()->obj())
              ->IsA(AsmType::Float32Array())) {
        current_function_builder_->Emit(kExprF32ConvertF64);
      }
      // Note that unlike StoreMem, AsmjsStoreMem ignores out-of-bounds writes.
      WasmOpcode opcode;
      if (type == AsmType::Int8Array()) {
        opcode = kExprI32AsmjsStoreMem8;
      } else if (type == AsmType::Uint8Array()) {
        opcode = kExprI32AsmjsStoreMem8;
      } else if (type == AsmType::Int16Array()) {
        opcode = kExprI32AsmjsStoreMem16;
      } else if (type == AsmType::Uint16Array()) {
        opcode = kExprI32AsmjsStoreMem16;
      } else if (type == AsmType::Int32Array()) {
        opcode = kExprI32AsmjsStoreMem;
      } else if (type == AsmType::Uint32Array()) {
        opcode = kExprI32AsmjsStoreMem;
      } else if (type == AsmType::Float32Array()) {
        opcode = kExprF32AsmjsStoreMem;
      } else if (type == AsmType::Float64Array()) {
        opcode = kExprF64AsmjsStoreMem;
      } else {
        UNREACHABLE();
      }
      current_function_builder_->Emit(opcode);
      if (fate == kDrop) {
        // Asm.js stores to memory leave their result on the stack.
        current_function_builder_->Emit(kExprDrop);
      }
    }

    if (target_var == nullptr && target_prop == nullptr) {
      UNREACHABLE();  // invalid assignment.
    }
  }

  void VisitAssignment(Assignment* expr) {
    VisitAssignment(expr, kLeaveOnStack);
  }

  void VisitAssignment(Assignment* expr, ValueFate fate) {
    bool as_init = false;
    if (scope_ == kModuleScope) {
      // Skip extra assignment inserted by the parser when in this form:
      // (function Module(a, b, c) {... })
      if (expr->target()->IsVariableProxy() &&
          expr->target()->AsVariableProxy()->var()->is_sloppy_function_name()) {
        return;
      }
      Property* prop = expr->value()->AsProperty();
      if (prop != nullptr) {
        VariableProxy* vp = prop->obj()->AsVariableProxy();
        if (vp != nullptr && vp->var()->IsParameter() &&
            vp->var()->index() == 1) {
          VariableProxy* target = expr->target()->AsVariableProxy();
          if (typer_->TypeOf(target)->AsFFIType() != nullptr) {
            const AstRawString* name =
                prop->key()->AsLiteral()->AsRawPropertyName();
            imported_function_table_.SetImportName(
                target->var(), reinterpret_cast<const char*>(name->raw_data()),
                name->length());
          }
        }
        // Property values in module scope don't emit code, so return.
        return;
      }
      ArrayLiteral* funcs = expr->value()->AsArrayLiteral();
      if (funcs != nullptr) {
        VariableProxy* target = expr->target()->AsVariableProxy();
        DCHECK_NOT_NULL(target);
        PopulateFunctionTable(target, funcs);
        // Only add to the function table. No init needed.
        return;
      }
      if (expr->value()->IsCallNew()) {
        // No init code to emit for CallNew nodes.
        return;
      }
      as_init = true;
    }

    if (as_init) LoadInitFunction();
    AsmType* atype = AsmType::None();
    bool is_nop = false;
    EmitAssignmentLhs(expr->target(), &atype);
    EmitAssignmentRhs(expr->target(), expr->value(), &is_nop);
    if (!is_nop) {
      EmitAssignment(expr, atype, fate);
    }
    if (as_init) UnLoadInitFunction();
  }

  void VisitYield(Yield* expr) { UNREACHABLE(); }

  void VisitThrow(Throw* expr) { UNREACHABLE(); }

  void VisitForeignVariable(bool is_float, Variable* var, Property* expr) {
    DCHECK(expr->obj()->AsVariableProxy());
    DCHECK(VariableLocation::PARAMETER ==
           expr->obj()->AsVariableProxy()->var()->location());
    DCHECK_EQ(1, expr->obj()->AsVariableProxy()->var()->index());
    Literal* key_literal = expr->key()->AsLiteral();
    DCHECK_NOT_NULL(key_literal);
    if (!key_literal->value().is_null()) {
      Handle<Name> name =
          Object::ToName(isolate_, key_literal->value()).ToHandleChecked();
      ValueType type = is_float ? kWasmF64 : kWasmI32;
      foreign_variables_.push_back({name, var, type});
    }
  }

  void VisitPropertyAndEmitIndex(Property* expr, AsmType** atype) {
    Expression* obj = expr->obj();
    *atype = typer_->TypeOf(obj);
    int32_t size = (*atype)->ElementSizeInBytes();
    if (size == 1) {
      // Allow more general expression in byte arrays than the spec
      // strictly permits.
      // Early versions of Emscripten emit HEAP8[HEAP32[..]|0] in
      // places that strictly should be HEAP8[HEAP32[..]>>0].
      RECURSE(Visit(expr->key()));
      return;
    }

    Literal* value = expr->key()->AsLiteral();
    if (value) {
      DCHECK(value->raw_value()->IsNumber());
      DCHECK_EQ(kWasmI32, TypeOf(value));
      int32_t val = static_cast<int32_t>(value->raw_value()->AsNumber());
      // TODO(titzer): handle overflow here.
      current_function_builder_->EmitI32Const(val * size);
      return;
    }
    BinaryOperation* binop = expr->key()->AsBinaryOperation();
    if (binop) {
      DCHECK_EQ(Token::SAR, binop->op());
      DCHECK(binop->right()->AsLiteral()->raw_value()->IsNumber());
      DCHECK(kWasmI32 == TypeOf(binop->right()->AsLiteral()));
      DCHECK_EQ(size,
                1 << static_cast<int>(
                    binop->right()->AsLiteral()->raw_value()->AsNumber()));
      // Mask bottom bits to match asm.js behavior.
      RECURSE(Visit(binop->left()));
      current_function_builder_->EmitI32Const(~(size - 1));
      current_function_builder_->Emit(kExprI32And);
      return;
    }
    UNREACHABLE();
  }

  void VisitProperty(Property* expr) {
    AsmType* type = AsmType::None();
    VisitPropertyAndEmitIndex(expr, &type);
    WasmOpcode opcode;
    if (type == AsmType::Int8Array()) {
      opcode = kExprI32AsmjsLoadMem8S;
    } else if (type == AsmType::Uint8Array()) {
      opcode = kExprI32AsmjsLoadMem8U;
    } else if (type == AsmType::Int16Array()) {
      opcode = kExprI32AsmjsLoadMem16S;
    } else if (type == AsmType::Uint16Array()) {
      opcode = kExprI32AsmjsLoadMem16U;
    } else if (type == AsmType::Int32Array()) {
      opcode = kExprI32AsmjsLoadMem;
    } else if (type == AsmType::Uint32Array()) {
      opcode = kExprI32AsmjsLoadMem;
    } else if (type == AsmType::Float32Array()) {
      opcode = kExprF32AsmjsLoadMem;
    } else if (type == AsmType::Float64Array()) {
      opcode = kExprF64AsmjsLoadMem;
    } else {
      UNREACHABLE();
    }

    current_function_builder_->Emit(opcode);
  }

  bool VisitStdlibFunction(Call* call, VariableProxy* expr) {
    Variable* var = expr->var();
    AsmTyper::StandardMember standard_object =
        typer_->VariableAsStandardMember(var);
    ZoneList<Expression*>* args = call->arguments();
    ValueType call_type = TypeOf(call);

    switch (standard_object) {
      case AsmTyper::kNone: {
        return false;
      }
      case AsmTyper::kMathAcos: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Acos);
        break;
      }
      case AsmTyper::kMathAsin: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Asin);
        break;
      }
      case AsmTyper::kMathAtan: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Atan);
        break;
      }
      case AsmTyper::kMathCos: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Cos);
        break;
      }
      case AsmTyper::kMathSin: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Sin);
        break;
      }
      case AsmTyper::kMathTan: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Tan);
        break;
      }
      case AsmTyper::kMathExp: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Exp);
        break;
      }
      case AsmTyper::kMathLog: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Log);
        break;
      }
      case AsmTyper::kMathCeil: {
        VisitCallArgs(call);
        if (call_type == kWasmF32) {
          current_function_builder_->Emit(kExprF32Ceil);
        } else if (call_type == kWasmF64) {
          current_function_builder_->Emit(kExprF64Ceil);
        } else {
          UNREACHABLE();
        }
        break;
      }
      case AsmTyper::kMathFloor: {
        VisitCallArgs(call);
        if (call_type == kWasmF32) {
          current_function_builder_->Emit(kExprF32Floor);
        } else if (call_type == kWasmF64) {
          current_function_builder_->Emit(kExprF64Floor);
        } else {
          UNREACHABLE();
        }
        break;
      }
      case AsmTyper::kMathSqrt: {
        VisitCallArgs(call);
        if (call_type == kWasmF32) {
          current_function_builder_->Emit(kExprF32Sqrt);
        } else if (call_type == kWasmF64) {
          current_function_builder_->Emit(kExprF64Sqrt);
        } else {
          UNREACHABLE();
        }
        break;
      }
      case AsmTyper::kMathClz32: {
        VisitCallArgs(call);
        DCHECK(call_type == kWasmI32);
        current_function_builder_->Emit(kExprI32Clz);
        break;
      }
      case AsmTyper::kMathAbs: {
        if (call_type == kWasmI32) {
          WasmTemporary tmp(current_function_builder_, kWasmI32);

          // if set_local(tmp, x) < 0
          Visit(call->arguments()->at(0));
          current_function_builder_->EmitTeeLocal(tmp.index());
          byte code[] = {WASM_ZERO};
          current_function_builder_->EmitCode(code, sizeof(code));
          current_function_builder_->Emit(kExprI32LtS);
          current_function_builder_->EmitWithU8(kExprIf, kLocalI32);

          // then (0 - tmp)
          current_function_builder_->EmitCode(code, sizeof(code));
          current_function_builder_->EmitGetLocal(tmp.index());
          current_function_builder_->Emit(kExprI32Sub);

          // else tmp
          current_function_builder_->Emit(kExprElse);
          current_function_builder_->EmitGetLocal(tmp.index());
          // end
          current_function_builder_->Emit(kExprEnd);

        } else if (call_type == kWasmF32) {
          VisitCallArgs(call);
          current_function_builder_->Emit(kExprF32Abs);
        } else if (call_type == kWasmF64) {
          VisitCallArgs(call);
          current_function_builder_->Emit(kExprF64Abs);
        } else {
          UNREACHABLE();
        }
        break;
      }
      case AsmTyper::kMathMin: {
        // TODO(bradnelson): Change wasm to match Math.min in asm.js mode.
        if (call_type == kWasmI32) {
          WasmTemporary tmp_x(current_function_builder_, kWasmI32);
          WasmTemporary tmp_y(current_function_builder_, kWasmI32);

          // if set_local(tmp_x, x) < set_local(tmp_y, y)
          Visit(call->arguments()->at(0));
          current_function_builder_->EmitTeeLocal(tmp_x.index());

          Visit(call->arguments()->at(1));
          current_function_builder_->EmitTeeLocal(tmp_y.index());

          current_function_builder_->Emit(kExprI32LeS);
          current_function_builder_->EmitWithU8(kExprIf, kLocalI32);

          // then tmp_x
          current_function_builder_->EmitGetLocal(tmp_x.index());

          // else tmp_y
          current_function_builder_->Emit(kExprElse);
          current_function_builder_->EmitGetLocal(tmp_y.index());
          current_function_builder_->Emit(kExprEnd);

        } else if (call_type == kWasmF32) {
          VisitCallArgs(call);
          current_function_builder_->Emit(kExprF32Min);
        } else if (call_type == kWasmF64) {
          VisitCallArgs(call);
          current_function_builder_->Emit(kExprF64Min);
        } else {
          UNREACHABLE();
        }
        break;
      }
      case AsmTyper::kMathMax: {
        // TODO(bradnelson): Change wasm to match Math.max in asm.js mode.
        if (call_type == kWasmI32) {
          WasmTemporary tmp_x(current_function_builder_, kWasmI32);
          WasmTemporary tmp_y(current_function_builder_, kWasmI32);

          // if set_local(tmp_x, x) < set_local(tmp_y, y)
          Visit(call->arguments()->at(0));

          current_function_builder_->EmitTeeLocal(tmp_x.index());

          Visit(call->arguments()->at(1));
          current_function_builder_->EmitTeeLocal(tmp_y.index());

          current_function_builder_->Emit(kExprI32LeS);
          current_function_builder_->EmitWithU8(kExprIf, kLocalI32);

          // then tmp_y
          current_function_builder_->EmitGetLocal(tmp_y.index());

          // else tmp_x
          current_function_builder_->Emit(kExprElse);
          current_function_builder_->EmitGetLocal(tmp_x.index());
          current_function_builder_->Emit(kExprEnd);

        } else if (call_type == kWasmF32) {
          VisitCallArgs(call);
          current_function_builder_->Emit(kExprF32Max);
        } else if (call_type == kWasmF64) {
          VisitCallArgs(call);
          current_function_builder_->Emit(kExprF64Max);
        } else {
          UNREACHABLE();
        }
        break;
      }
      case AsmTyper::kMathAtan2: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Atan2);
        break;
      }
      case AsmTyper::kMathPow: {
        VisitCallArgs(call);
        DCHECK_EQ(kWasmF64, call_type);
        current_function_builder_->Emit(kExprF64Pow);
        break;
      }
      case AsmTyper::kMathImul: {
        VisitCallArgs(call);
        current_function_builder_->Emit(kExprI32Mul);
        break;
      }
      case AsmTyper::kMathFround: {
        DCHECK(args->length() == 1);
        Literal* literal = args->at(0)->AsLiteral();
        if (literal != nullptr) {
          // constant fold Math.fround(#const);
          if (literal->raw_value()->IsNumber()) {
            float val = static_cast<float>(literal->raw_value()->AsNumber());
            byte code[] = {WASM_F32(val)};
            current_function_builder_->EmitCode(code, sizeof(code));
            return true;
          }
        }
        VisitCallArgs(call);
        static const bool kDontIgnoreSign = false;
        switch (TypeIndexOf(args->at(0), kDontIgnoreSign)) {
          case kInt32:
          case kFixnum:
            current_function_builder_->Emit(kExprF32SConvertI32);
            break;
          case kUint32:
            current_function_builder_->Emit(kExprF32UConvertI32);
            break;
          case kFloat32:
            break;
          case kFloat64:
            current_function_builder_->Emit(kExprF32ConvertF64);
            break;
          default:
            UNREACHABLE();
        }
        break;
      }
      default: {
        UNREACHABLE();
        break;
      }
    }
    return true;
  }

  void VisitCallArgs(Call* expr) {
    ZoneList<Expression*>* args = expr->arguments();
    for (int i = 0; i < args->length(); ++i) {
      Expression* arg = args->at(i);
      RECURSE(Visit(arg));
    }
  }

  void VisitCall(Call* expr) { VisitCallExpression(expr); }

  bool VisitCallExpression(Call* expr) {
    Call::CallType call_type = expr->GetCallType();
    bool returns_value = true;

    // Save the parent now, it might be overwritten in VisitCallArgs.
    BinaryOperation* parent_binop = parent_binop_;

    switch (call_type) {
      case Call::OTHER_CALL: {
        VariableProxy* proxy = expr->expression()->AsVariableProxy();
        if (proxy != nullptr) {
          DCHECK(kFuncScope == scope_ ||
                 typer_->VariableAsStandardMember(proxy->var()) ==
                     AsmTyper::kMathFround);
          if (VisitStdlibFunction(expr, proxy)) {
            return true;
          }
        }
        DCHECK(kFuncScope == scope_);
        VariableProxy* vp = expr->expression()->AsVariableProxy();
        DCHECK_NOT_NULL(vp);
        if (typer_->TypeOf(vp)->AsFFIType() != nullptr) {
          ValueType return_type = TypeOf(expr);
          ZoneList<Expression*>* args = expr->arguments();
          FunctionSig::Builder sig(zone(), return_type == kWasmStmt ? 0 : 1,
                                   args->length());
          if (return_type != kWasmStmt) {
            sig.AddReturn(return_type);
          } else {
            returns_value = false;
          }
          for (int i = 0; i < args->length(); ++i) {
            sig.AddParam(TypeOf(args->at(i)));
          }
          uint32_t index = imported_function_table_.LookupOrInsertImportUse(
              vp->var(), sig.Build());
          VisitCallArgs(expr);
          // For non-void functions, we must know the parent node.
          DCHECK_IMPLIES(returns_value, parent_binop != nullptr);
          DCHECK_IMPLIES(returns_value, parent_binop->left() == expr ||
                                            parent_binop->right() == expr);
          int pos = expr->position();
          int parent_pos = returns_value ? parent_binop->position() : pos;
          current_function_builder_->AddAsmWasmOffset(pos, parent_pos);
          current_function_builder_->Emit(kExprCallFunction);
          current_function_builder_->EmitVarUint(index);
        } else {
          WasmFunctionBuilder* function = LookupOrInsertFunction(vp->var());
          VisitCallArgs(expr);
          current_function_builder_->AddAsmWasmOffset(expr->position(),
                                                      expr->position());
          current_function_builder_->Emit(kExprCallFunction);
          current_function_builder_->EmitDirectCallIndex(
              function->func_index());
          returns_value = function->signature()->return_count() > 0;
        }
        break;
      }
      case Call::KEYED_PROPERTY_CALL: {
        DCHECK_EQ(kFuncScope, scope_);
        Property* p = expr->expression()->AsProperty();
        DCHECK_NOT_NULL(p);
        VariableProxy* var = p->obj()->AsVariableProxy();
        DCHECK_NOT_NULL(var);
        FunctionTableIndices* indices = LookupOrAddFunctionTable(var, p);
        Visit(p->key());  // TODO(titzer): should use RECURSE()

        // We have to use a temporary for the correct order of evaluation.
        current_function_builder_->EmitI32Const(indices->start_index);
        current_function_builder_->Emit(kExprI32Add);
        WasmTemporary tmp(current_function_builder_, kWasmI32);
        current_function_builder_->EmitSetLocal(tmp.index());

        VisitCallArgs(expr);

        current_function_builder_->EmitGetLocal(tmp.index());
        current_function_builder_->AddAsmWasmOffset(expr->position(),
                                                    expr->position());
        current_function_builder_->Emit(kExprCallIndirect);
        current_function_builder_->EmitVarUint(indices->signature_index);
        current_function_builder_->EmitVarUint(0);  // table index
        returns_value =
            builder_->GetSignature(indices->signature_index)->return_count() >
            0;
        break;
      }
      default:
        UNREACHABLE();
    }
    return returns_value;
  }

  void VisitCallNew(CallNew* expr) { UNREACHABLE(); }

  void VisitCallRuntime(CallRuntime* expr) { UNREACHABLE(); }

  void VisitUnaryOperation(UnaryOperation* expr) {
    RECURSE(Visit(expr->expression()));
    switch (expr->op()) {
      case Token::NOT: {
        DCHECK_EQ(kWasmI32, TypeOf(expr->expression()));
        current_function_builder_->Emit(kExprI32Eqz);
        break;
      }
      default:
        UNREACHABLE();
    }
  }

  void VisitCountOperation(CountOperation* expr) { UNREACHABLE(); }

  bool MatchIntBinaryOperation(BinaryOperation* expr, Token::Value op,
                               int32_t val) {
    DCHECK_NOT_NULL(expr->right());
    if (expr->op() == op && expr->right()->IsLiteral() &&
        TypeOf(expr) == kWasmI32) {
      Literal* right = expr->right()->AsLiteral();
      if (right->raw_value()->IsNumber() &&
          static_cast<int32_t>(right->raw_value()->AsNumber()) == val) {
        return true;
      }
    }
    return false;
  }

  bool MatchDoubleBinaryOperation(BinaryOperation* expr, Token::Value op,
                                  double val) {
    DCHECK_NOT_NULL(expr->right());
    if (expr->op() == op && expr->right()->IsLiteral() &&
        TypeOf(expr) == kWasmF64) {
      Literal* right = expr->right()->AsLiteral();
      DCHECK(right->raw_value()->IsNumber());
      if (right->raw_value()->AsNumber() == val) {
        return true;
      }
    }
    return false;
  }

  enum ConvertOperation { kNone, kAsIs, kToInt, kToDouble };

  ConvertOperation MatchOr(BinaryOperation* expr) {
    if (MatchIntBinaryOperation(expr, Token::BIT_OR, 0) &&
        (TypeOf(expr->left()) == kWasmI32)) {
      return kAsIs;
    } else {
      return kNone;
    }
  }

  ConvertOperation MatchShr(BinaryOperation* expr) {
    if (MatchIntBinaryOperation(expr, Token::SHR, 0)) {
      // TODO(titzer): this probably needs to be kToUint
      return (TypeOf(expr->left()) == kWasmI32) ? kAsIs : kToInt;
    } else {
      return kNone;
    }
  }

  ConvertOperation MatchXor(BinaryOperation* expr) {
    if (MatchIntBinaryOperation(expr, Token::BIT_XOR, 0xffffffff)) {
      DCHECK_EQ(kWasmI32, TypeOf(expr->left()));
      DCHECK_EQ(kWasmI32, TypeOf(expr->right()));
      BinaryOperation* op = expr->left()->AsBinaryOperation();
      if (op != nullptr) {
        if (MatchIntBinaryOperation(op, Token::BIT_XOR, 0xffffffff)) {
          DCHECK_EQ(kWasmI32, TypeOf(op->right()));
          if (TypeOf(op->left()) != kWasmI32) {
            return kToInt;
          } else {
            return kAsIs;
          }
        }
      }
    }
    return kNone;
  }

  ConvertOperation MatchMul(BinaryOperation* expr) {
    if (MatchDoubleBinaryOperation(expr, Token::MUL, 1.0)) {
      DCHECK_EQ(kWasmF64, TypeOf(expr->right()));
      if (TypeOf(expr->left()) != kWasmF64) {
        return kToDouble;
      } else {
        return kAsIs;
      }
    } else {
      return kNone;
    }
  }

  ConvertOperation MatchBinaryOperation(BinaryOperation* expr) {
    switch (expr->op()) {
      case Token::BIT_OR:
        return MatchOr(expr);
      case Token::SHR:
        return MatchShr(expr);
      case Token::BIT_XOR:
        return MatchXor(expr);
      case Token::MUL:
        return MatchMul(expr);
      default:
        return kNone;
    }
  }

// Work around Mul + Div being defined in PPC assembler.
#ifdef Mul
#undef Mul
#endif

#define NON_SIGNED_BINOP(op)      \
  static WasmOpcode opcodes[] = { \
    kExprI32##op,                 \
    kExprI32##op,                 \
    kExprF32##op,                 \
    kExprF64##op                  \
  }

#define SIGNED_BINOP(op)          \
  static WasmOpcode opcodes[] = { \
    kExprI32##op##S,              \
    kExprI32##op##U,              \
    kExprF32##op,                 \
    kExprF64##op                  \
  }

#define NON_SIGNED_INT_BINOP(op) \
  static WasmOpcode opcodes[] = { kExprI32##op, kExprI32##op }

#define BINOP_CASE(token, op, V, ignore_sign)                         \
  case token: {                                                       \
    V(op);                                                            \
    int type = TypeIndexOf(expr->left(), expr->right(), ignore_sign); \
    current_function_builder_->Emit(opcodes[type]);                   \
    break;                                                            \
  }

  Expression* GetLeft(BinaryOperation* expr) {
    if (expr->op() == Token::BIT_XOR) {
      return expr->left()->AsBinaryOperation()->left();
    } else {
      return expr->left();
    }
  }

  void VisitBinaryOperation(BinaryOperation* expr) {
    ConvertOperation convertOperation = MatchBinaryOperation(expr);
    static const bool kDontIgnoreSign = false;
    parent_binop_ = expr;
    if (convertOperation == kToDouble) {
      RECURSE(Visit(expr->left()));
      TypeIndex type = TypeIndexOf(expr->left(), kDontIgnoreSign);
      if (type == kInt32 || type == kFixnum) {
        current_function_builder_->Emit(kExprF64SConvertI32);
      } else if (type == kUint32) {
        current_function_builder_->Emit(kExprF64UConvertI32);
      } else if (type == kFloat32) {
        current_function_builder_->Emit(kExprF64ConvertF32);
      } else {
        UNREACHABLE();
      }
    } else if (convertOperation == kToInt) {
      RECURSE(Visit(GetLeft(expr)));
      TypeIndex type = TypeIndexOf(GetLeft(expr), kDontIgnoreSign);
      if (type == kFloat32) {
        current_function_builder_->Emit(kExprI32AsmjsSConvertF32);
      } else if (type == kFloat64) {
        current_function_builder_->Emit(kExprI32AsmjsSConvertF64);
      } else {
        UNREACHABLE();
      }
    } else if (convertOperation == kAsIs) {
      RECURSE(Visit(GetLeft(expr)));
    } else {
      if (expr->op() == Token::COMMA) {
        RECURSE(VisitForEffect(expr->left()));
        RECURSE(Visit(expr->right()));
        return;
      }
      RECURSE(Visit(expr->left()));
      RECURSE(Visit(expr->right()));

      switch (expr->op()) {
        BINOP_CASE(Token::ADD, Add, NON_SIGNED_BINOP, true);
        BINOP_CASE(Token::SUB, Sub, NON_SIGNED_BINOP, true);
        BINOP_CASE(Token::MUL, Mul, NON_SIGNED_BINOP, true);
        BINOP_CASE(Token::BIT_OR, Ior, NON_SIGNED_INT_BINOP, true);
        BINOP_CASE(Token::BIT_AND, And, NON_SIGNED_INT_BINOP, true);
        BINOP_CASE(Token::BIT_XOR, Xor, NON_SIGNED_INT_BINOP, true);
        BINOP_CASE(Token::SHL, Shl, NON_SIGNED_INT_BINOP, true);
        BINOP_CASE(Token::SAR, ShrS, NON_SIGNED_INT_BINOP, true);
        BINOP_CASE(Token::SHR, ShrU, NON_SIGNED_INT_BINOP, true);
        case Token::DIV: {
          static WasmOpcode opcodes[] = {kExprI32AsmjsDivS, kExprI32AsmjsDivU,
                                         kExprF32Div, kExprF64Div};
          int type = TypeIndexOf(expr->left(), expr->right(), false);
          current_function_builder_->Emit(opcodes[type]);
          break;
        }
        case Token::MOD: {
          TypeIndex type = TypeIndexOf(expr->left(), expr->right(), false);
          if (type == kInt32) {
            current_function_builder_->Emit(kExprI32AsmjsRemS);
          } else if (type == kUint32) {
            current_function_builder_->Emit(kExprI32AsmjsRemU);
          } else if (type == kFloat64) {
            current_function_builder_->Emit(kExprF64Mod);
            return;
          } else {
            UNREACHABLE();
          }
          break;
        }
        case Token::COMMA: {
          break;
        }
        default:
          UNREACHABLE();
      }
    }
  }

  void VisitCompareOperation(CompareOperation* expr) {
    RECURSE(Visit(expr->left()));
    RECURSE(Visit(expr->right()));
    switch (expr->op()) {
      BINOP_CASE(Token::EQ, Eq, NON_SIGNED_BINOP, false);
      BINOP_CASE(Token::LT, Lt, SIGNED_BINOP, false);
      BINOP_CASE(Token::LTE, Le, SIGNED_BINOP, false);
      BINOP_CASE(Token::GT, Gt, SIGNED_BINOP, false);
      BINOP_CASE(Token::GTE, Ge, SIGNED_BINOP, false);
      default:
        UNREACHABLE();
    }
  }

#undef BINOP_CASE
#undef NON_SIGNED_INT_BINOP
#undef SIGNED_BINOP
#undef NON_SIGNED_BINOP

  enum TypeIndex {
    kInt32 = 0,
    kUint32 = 1,
    kFloat32 = 2,
    kFloat64 = 3,
    kFixnum = 4
  };

  TypeIndex TypeIndexOf(Expression* left, Expression* right, bool ignore_sign) {
    TypeIndex left_index = TypeIndexOf(left, ignore_sign);
    TypeIndex right_index = TypeIndexOf(right, ignore_sign);
    if (left_index == kFixnum) {
      left_index = right_index;
    }
    if (right_index == kFixnum) {
      right_index = left_index;
    }
    if (left_index == kFixnum && right_index == kFixnum) {
      left_index = kInt32;
      right_index = kInt32;
    }
    if (left_index != right_index) {
      DCHECK(ignore_sign && (left_index <= 1) && (right_index <= 1));
    }
    return left_index;
  }

  TypeIndex TypeIndexOf(Expression* expr, bool ignore_sign) {
    AsmType* type = typer_->TypeOf(expr);
    if (type->IsA(AsmType::FixNum())) {
      return kFixnum;
    }

    if (type->IsA(AsmType::Signed())) {
      return kInt32;
    }

    if (type->IsA(AsmType::Unsigned())) {
      return kUint32;
    }

    if (type->IsA(AsmType::Intish())) {
      if (!ignore_sign) {
        // TODO(jpp): log a warning and move on.
      }
      return kInt32;
    }

    if (type->IsA(AsmType::Floatish())) {
      return kFloat32;
    }

    if (type->IsA(AsmType::DoubleQ())) {
      return kFloat64;
    }

    UNREACHABLE();
    return kInt32;
  }

#undef CASE
#undef NON_SIGNED_INT
#undef SIGNED
#undef NON_SIGNED

  void VisitThisFunction(ThisFunction* expr) { UNREACHABLE(); }

  void VisitDeclarations(Declaration::List* decls) {
    for (Declaration* decl : *decls) {
      RECURSE(Visit(decl));
      if (typer_failed_) {
        return;
      }
    }
  }

  void VisitClassLiteral(ClassLiteral* expr) { UNREACHABLE(); }

  void VisitSpread(Spread* expr) { UNREACHABLE(); }

  void VisitSuperPropertyReference(SuperPropertyReference* expr) {
    UNREACHABLE();
  }

  void VisitSuperCallReference(SuperCallReference* expr) { UNREACHABLE(); }

  void VisitSloppyBlockFunctionStatement(SloppyBlockFunctionStatement* expr) {
    UNREACHABLE();
  }

  void VisitDoExpression(DoExpression* expr) { UNREACHABLE(); }

  void VisitRewritableExpression(RewritableExpression* expr) { UNREACHABLE(); }

  struct IndexContainer : public ZoneObject {
    uint32_t index;
  };

  uint32_t LookupOrInsertLocal(Variable* v, ValueType type) {
    DCHECK_NOT_NULL(current_function_builder_);
    ZoneHashMap::Entry* entry =
        local_variables_.Lookup(v, ComputePointerHash(v));
    if (entry == nullptr) {
      uint32_t index;
      DCHECK(!v->IsParameter());
      index = current_function_builder_->AddLocal(type);
      IndexContainer* container = new (zone()) IndexContainer();
      container->index = index;
      entry = local_variables_.LookupOrInsert(v, ComputePointerHash(v),
                                              ZoneAllocationPolicy(zone()));
      entry->value = container;
    }
    return (reinterpret_cast<IndexContainer*>(entry->value))->index;
  }

  void InsertParameter(Variable* v, ValueType type, uint32_t index) {
    DCHECK(v->IsParameter());
    DCHECK_NOT_NULL(current_function_builder_);
    ZoneHashMap::Entry* entry =
        local_variables_.Lookup(v, ComputePointerHash(v));
    DCHECK_NULL(entry);
    IndexContainer* container = new (zone()) IndexContainer();
    container->index = index;
    entry = local_variables_.LookupOrInsert(v, ComputePointerHash(v),
                                            ZoneAllocationPolicy(zone()));
    entry->value = container;
  }

  uint32_t LookupOrInsertGlobal(Variable* v, ValueType type) {
    ZoneHashMap::Entry* entry =
        global_variables_.Lookup(v, ComputePointerHash(v));
    if (entry == nullptr) {
      uint32_t index = builder_->AddGlobal(type, 0);
      IndexContainer* container = new (zone()) IndexContainer();
      container->index = index;
      entry = global_variables_.LookupOrInsert(v, ComputePointerHash(v),
                                               ZoneAllocationPolicy(zone()));
      entry->value = container;
    }
    return (reinterpret_cast<IndexContainer*>(entry->value))->index;
  }

  WasmFunctionBuilder* LookupOrInsertFunction(Variable* v) {
    DCHECK_NOT_NULL(builder_);
    ZoneHashMap::Entry* entry = functions_.Lookup(v, ComputePointerHash(v));
    if (entry == nullptr) {
      auto* func_type = typer_->TypeOf(v)->AsFunctionType();
      DCHECK_NOT_NULL(func_type);
      // Build the signature for the function.
      ValueType return_type = TypeFrom(func_type->ReturnType());
      const auto& arguments = func_type->Arguments();
      FunctionSig::Builder b(zone(), return_type == kWasmStmt ? 0 : 1,
                             arguments.size());
      if (return_type != kWasmStmt) b.AddReturn(return_type);
      for (int i = 0; i < static_cast<int>(arguments.size()); ++i) {
        ValueType type = TypeFrom(arguments[i]);
        DCHECK_NE(kWasmStmt, type);
        b.AddParam(type);
      }

      WasmFunctionBuilder* function = builder_->AddFunction(b.Build());
      entry = functions_.LookupOrInsert(v, ComputePointerHash(v),
                                        ZoneAllocationPolicy(zone()));
      function->SetName(
          {reinterpret_cast<const char*>(v->raw_name()->raw_data()),
           v->raw_name()->length()});
      entry->value = function;
    }
    return (reinterpret_cast<WasmFunctionBuilder*>(entry->value));
  }

  ValueType TypeOf(Expression* expr) { return TypeFrom(typer_->TypeOf(expr)); }

  ValueType TypeFrom(AsmType* type) {
    if (type->IsA(AsmType::Intish())) {
      return kWasmI32;
    }

    if (type->IsA(AsmType::Floatish())) {
      return kWasmF32;
    }

    if (type->IsA(AsmType::DoubleQ())) {
      return kWasmF64;
    }

    return kWasmStmt;
  }

  Zone* zone() { return zone_; }

  ZoneHashMap local_variables_;
  ZoneHashMap functions_;
  ZoneHashMap global_variables_;
  AsmScope scope_;
  WasmModuleBuilder* builder_;
  WasmFunctionBuilder* current_function_builder_;
  FunctionLiteral* literal_;
  Isolate* isolate_;
  Zone* zone_;
  CompilationInfo* info_;
  AstValueFactory* ast_value_factory_;
  Handle<Script> script_;
  AsmTyper* typer_;
  bool typer_failed_;
  bool typer_finished_;
  ZoneVector<std::pair<BreakableStatement*, TargetType>> breakable_blocks_;
  ZoneVector<ForeignVariable> foreign_variables_;
  WasmFunctionBuilder* init_function_;
  WasmFunctionBuilder* foreign_init_function_;
  uint32_t next_table_index_;
  ZoneHashMap function_tables_;
  ImportedFunctionTable imported_function_table_;
  // Remember the parent node for reporting the correct location for ToNumber
  // conversions after calls.
  BinaryOperation* parent_binop_;

  DEFINE_AST_VISITOR_SUBCLASS_MEMBERS();

 private:
  DISALLOW_COPY_AND_ASSIGN(AsmWasmBuilderImpl);
};

AsmWasmBuilder::AsmWasmBuilder(CompilationInfo* info)
    : info_(info),
      typer_(info->isolate(), info->zone(), info->script(), info->literal()) {}

// TODO(aseemgarg): probably should take zone (to write wasm to) as input so
// that zone in constructor may be thrown away once wasm module is written.
AsmWasmBuilder::Result AsmWasmBuilder::Run(Handle<FixedArray>* foreign_args) {
  HistogramTimerScope asm_wasm_time_scope(
      info_->isolate()->counters()->asm_wasm_translation_time());

  Zone* zone = info_->zone();
  AsmWasmBuilderImpl impl(info_->isolate(), zone, info_,
                          info_->parse_info()->ast_value_factory(),
                          info_->script(), info_->literal(), &typer_);
  bool success = impl.Build();
  *foreign_args = impl.GetForeignArgs();
  ZoneBuffer* module_buffer = new (zone) ZoneBuffer(zone);
  impl.builder_->WriteTo(*module_buffer);
  ZoneBuffer* asm_offsets_buffer = new (zone) ZoneBuffer(zone);
  impl.builder_->WriteAsmJsOffsetTable(*asm_offsets_buffer);
  return {module_buffer, asm_offsets_buffer, success};
}

const char* AsmWasmBuilder::foreign_init_name = "__foreign_init__";
const char* AsmWasmBuilder::single_function_name = "__single_function__";

}  // namespace wasm
}  // namespace internal
}  // namespace v8