/*
 * Copyright 2018 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef wasm_stack_h
#define wasm_stack_h

#include "ir/branch-utils.h"
#include "pass.h"
#include "wasm-binary.h"
#include "wasm-traversal.h"
#include "wasm.h"

namespace wasm {

// Stack IR: an IR that represents code at the wasm binary format level,
// that is, a stack machine. Binaryen IR is *almost* identical to this,
// but as documented in README.md, there are a few differences, intended
// to make Binaryen IR fast and flexible for maximal optimization. Stack
// IR, on the other hand, is designed to optimize a few final things that
// can only really be done when modeling the stack machine format precisely.

// Currently the benefits of Stack IR are minor, less than 1% reduction in
// code size. For that reason it is just a secondary IR, run optionally
// after the main IR has been optimized. However, if we improve Stack IR
// optimizations to a point where they have a significant impact, it's
// possible that could motivate investigating replacing the main IR with Stack
// IR (so that we have just a single IR).

// A StackIR instance (see wasm.h) contains a linear sequence of
// stack instructions. This representation is very simple: just a single vector
// of all instructions, in order.
//  * nullptr is allowed in the vector, representing something to skip.
//    This is useful as a common thing optimizations do is remove instructions,
//    so this way we can do so without compacting the vector all the time.

// Direct writing binaryen IR to binary is fast. Otherwise, StackIRGenerator
// lets you optimize the Stack IR before emitting stack IR to binary (but the
// cost is that the extra IR in the middle makes things 20% slower than emitting
// binaryen IR to binary directly).

// A Stack IR instruction. Most just directly reflect a Binaryen IR node,
// but we need extra ones for certain things.
class StackInst {
public:
  StackInst(MixedArena&) {}

  enum Op {
    Basic,      // an instruction directly corresponding to a non-control-flow
                // Binaryen IR node
    BlockBegin, // the beginning of a block
    BlockEnd,   // the ending of a block
    IfBegin,    // the beginning of a if
    IfElse,     // the else of a if
    IfEnd,      // the ending of a if
    LoopBegin,  // the beginning of a loop
    LoopEnd,    // the ending of a loop
    TryBegin,   // the beginning of a try
    Catch,      // the catch within a try
    TryEnd      // the ending of a try
  } op;

  Expression* origin; // the expression this originates from

  // the type - usually identical to the origin type, but e.g. wasm has no
  // unreachable blocks, they must be none
  Type type;
};

class BinaryInstWriter : public OverriddenVisitor<BinaryInstWriter> {
public:
  BinaryInstWriter(WasmBinaryWriter& parent,
                   BufferWithRandomAccess& o,
                   Function* func,
                   bool sourceMap)
    : parent(parent), o(o), func(func), sourceMap(sourceMap) {}

  void visit(Expression* curr) {
    if (func && !sourceMap) {
      parent.writeDebugLocation(curr, func);
    }
    OverriddenVisitor<BinaryInstWriter>::visit(curr);
    if (func && !sourceMap) {
      parent.writeDebugLocationEnd(curr, func);
    }
  }

  void visitBlock(Block* curr);
  void visitIf(If* curr);
  void visitLoop(Loop* curr);
  void visitBreak(Break* curr);
  void visitSwitch(Switch* curr);
  void visitCall(Call* curr);
  void visitCallIndirect(CallIndirect* curr);
  void visitLocalGet(LocalGet* curr);
  void visitLocalSet(LocalSet* curr);
  void visitGlobalGet(GlobalGet* curr);
  void visitGlobalSet(GlobalSet* curr);
  void visitLoad(Load* curr);
  void visitStore(Store* curr);
  void visitAtomicRMW(AtomicRMW* curr);
  void visitAtomicCmpxchg(AtomicCmpxchg* curr);
  void visitAtomicWait(AtomicWait* curr);
  void visitAtomicNotify(AtomicNotify* curr);
  void visitAtomicFence(AtomicFence* curr);
  void visitSIMDExtract(SIMDExtract* curr);
  void visitSIMDReplace(SIMDReplace* curr);
  void visitSIMDShuffle(SIMDShuffle* curr);
  void visitSIMDTernary(SIMDTernary* curr);
  void visitSIMDShift(SIMDShift* curr);
  void visitSIMDLoad(SIMDLoad* curr);
  void visitMemoryInit(MemoryInit* curr);
  void visitDataDrop(DataDrop* curr);
  void visitMemoryCopy(MemoryCopy* curr);
  void visitMemoryFill(MemoryFill* curr);
  void visitConst(Const* curr);
  void visitUnary(Unary* curr);
  void visitBinary(Binary* curr);
  void visitSelect(Select* curr);
  void visitReturn(Return* curr);
  void visitHost(Host* curr);
  void visitRefNull(RefNull* curr);
  void visitRefIsNull(RefIsNull* curr);
  void visitRefFunc(RefFunc* curr);
  void visitTry(Try* curr);
  void visitThrow(Throw* curr);
  void visitRethrow(Rethrow* curr);
  void visitBrOnExn(BrOnExn* curr);
  void visitNop(Nop* curr);
  void visitUnreachable(Unreachable* curr);
  void visitDrop(Drop* curr);
  void visitPush(Push* curr);
  void visitPop(Pop* curr);
  void visitTupleMake(TupleMake* curr);
  void visitTupleExtract(TupleExtract* curr);

  void emitResultType(Type type);
  void emitIfElse(If* curr);
  void emitCatch(Try* curr);
  // emit an end at the end of a block/loop/if/try
  void emitScopeEnd(Expression* curr);
  // emit an end at the end of a function
  void emitFunctionEnd();
  void emitUnreachable();
  void mapLocalsAndEmitHeader();

private:
  void emitMemoryAccess(size_t alignment, size_t bytes, uint32_t offset);
  int32_t getBreakIndex(Name name);

  WasmBinaryWriter& parent;
  BufferWithRandomAccess& o;
  Function* func = nullptr;
  bool sourceMap;

  std::vector<Name> breakStack;

  // type => number of locals of that type in the compact form
  std::map<Type, size_t> numLocalsByType;
  // (local index, tuple index) => binary local index
  std::map<std::pair<Index, Index>, size_t> mappedLocals;

  // Keeps track of the binary index of the scratch locals used to lower
  // tuple.extract.
  std::map<Type, Index> scratchLocals;
  void countScratchLocals();
  void setScratchLocals();
};

// Takes binaryen IR and converts it to something else (binary or stack IR)
template<typename SubType>
class BinaryenIRWriter : public OverriddenVisitor<BinaryenIRWriter<SubType>> {
public:
  BinaryenIRWriter(Function* func) : func(func) {}

  void write();

  // visits a node, emitting the proper code for it
  void visit(Expression* curr);

  void visitBlock(Block* curr);
  void visitIf(If* curr);
  void visitLoop(Loop* curr);
  void visitBreak(Break* curr);
  void visitSwitch(Switch* curr);
  void visitCall(Call* curr);
  void visitCallIndirect(CallIndirect* curr);
  void visitLocalGet(LocalGet* curr);
  void visitLocalSet(LocalSet* curr);
  void visitGlobalGet(GlobalGet* curr);
  void visitGlobalSet(GlobalSet* curr);
  void visitLoad(Load* curr);
  void visitStore(Store* curr);
  void visitAtomicRMW(AtomicRMW* curr);
  void visitAtomicCmpxchg(AtomicCmpxchg* curr);
  void visitAtomicWait(AtomicWait* curr);
  void visitAtomicNotify(AtomicNotify* curr);
  void visitAtomicFence(AtomicFence* curr);
  void visitSIMDExtract(SIMDExtract* curr);
  void visitSIMDReplace(SIMDReplace* curr);
  void visitSIMDShuffle(SIMDShuffle* curr);
  void visitSIMDTernary(SIMDTernary* curr);
  void visitSIMDShift(SIMDShift* curr);
  void visitSIMDLoad(SIMDLoad* curr);
  void visitMemoryInit(MemoryInit* curr);
  void visitDataDrop(DataDrop* curr);
  void visitMemoryCopy(MemoryCopy* curr);
  void visitMemoryFill(MemoryFill* curr);
  void visitConst(Const* curr);
  void visitUnary(Unary* curr);
  void visitBinary(Binary* curr);
  void visitSelect(Select* curr);
  void visitReturn(Return* curr);
  void visitHost(Host* curr);
  void visitRefNull(RefNull* curr);
  void visitRefIsNull(RefIsNull* curr);
  void visitRefFunc(RefFunc* curr);
  void visitTry(Try* curr);
  void visitThrow(Throw* curr);
  void visitRethrow(Rethrow* curr);
  void visitBrOnExn(BrOnExn* curr);
  void visitNop(Nop* curr);
  void visitUnreachable(Unreachable* curr);
  void visitDrop(Drop* curr);
  void visitPush(Push* curr);
  void visitPop(Pop* curr);
  void visitTupleMake(TupleMake* curr);
  void visitTupleExtract(TupleExtract* curr);

protected:
  Function* func = nullptr;

private:
  void emit(Expression* curr) { static_cast<SubType*>(this)->emit(curr); }
  void emitHeader() { static_cast<SubType*>(this)->emitHeader(); }
  void emitIfElse(If* curr) { static_cast<SubType*>(this)->emitIfElse(curr); }
  void emitCatch(Try* curr) { static_cast<SubType*>(this)->emitCatch(curr); }
  void emitScopeEnd(Expression* curr) {
    static_cast<SubType*>(this)->emitScopeEnd(curr);
  }
  void emitFunctionEnd() { static_cast<SubType*>(this)->emitFunctionEnd(); }
  void emitUnreachable() { static_cast<SubType*>(this)->emitUnreachable(); }
  void emitDebugLocation(Expression* curr) {
    static_cast<SubType*>(this)->emitDebugLocation(curr);
  }
  void visitPossibleBlockContents(Expression* curr);
};

template<typename SubType> void BinaryenIRWriter<SubType>::write() {
  assert(func && "BinaryenIRWriter: function is not set");
  emitHeader();
  visitPossibleBlockContents(func->body);
  emitFunctionEnd();
}

// emits a node, but if it is a block with no name, emit a list of its contents
template<typename SubType>
void BinaryenIRWriter<SubType>::visitPossibleBlockContents(Expression* curr) {
  auto* block = curr->dynCast<Block>();
  if (!block || BranchUtils::BranchSeeker::has(block, block->name)) {
    visit(curr);
    return;
  }
  for (auto* child : block->list) {
    visit(child);
  }
  if (block->type == Type::unreachable &&
      block->list.back()->type != Type::unreachable) {
    // similar to in visitBlock, here we could skip emitting the block itself,
    // but must still end the 'block' (the contents, really) with an unreachable
    emitUnreachable();
  }
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visit(Expression* curr) {
  emitDebugLocation(curr);
  OverriddenVisitor<BinaryenIRWriter>::visit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitBlock(Block* curr) {
  auto visitChildren = [this](Block* curr, Index from) {
    auto& list = curr->list;
    while (from < list.size()) {
      visit(list[from++]);
    }
  };

  auto afterChildren = [this](Block* curr) {
    if (curr->type == Type::unreachable) {
      // an unreachable block is one that cannot be exited. We cannot encode
      // this directly in wasm, where blocks must be none,i32,i64,f32,f64. Since
      // the block cannot be exited, we can emit an unreachable at the end, and
      // that will always be valid, and then the block is ok as a none
      emitUnreachable();
    }
    emitScopeEnd(curr);
    if (curr->type == Type::unreachable) {
      // and emit an unreachable *outside* the block too, so later things can
      // pop anything
      emitUnreachable();
    }
  };

  // Handle very deeply nested blocks in the first position efficiently,
  // avoiding heavy recursion. We only start to do this if we see it will help
  // us (to avoid allocation of the vector).
  if (!curr->list.empty() && curr->list[0]->is<Block>()) {
    std::vector<Block*> parents;
    Block* child;
    while (!curr->list.empty() && (child = curr->list[0]->dynCast<Block>())) {
      parents.push_back(curr);
      emit(curr);
      curr = child;
    }
    // Emit the current block, which does not have a block as a child in the
    // first position.
    emit(curr);
    visitChildren(curr, 0);
    afterChildren(curr);
    // Finish the later parts of all the parent blocks.
    while (!parents.empty()) {
      auto* parent = parents.back();
      parents.pop_back();
      visitChildren(parent, 1);
      afterChildren(parent);
    }
    return;
  }
  // Simple case of not having a nested block in the first position.
  emit(curr);
  visitChildren(curr, 0);
  afterChildren(curr);
}

template<typename SubType> void BinaryenIRWriter<SubType>::visitIf(If* curr) {
  visit(curr->condition);
  if (curr->condition->type == Type::unreachable) {
    // this if-else is unreachable because of the condition, i.e., the condition
    // does not exit. So don't emit the if (but do consume the condition)
    emitUnreachable();
    return;
  }
  emit(curr);
  visitPossibleBlockContents(curr->ifTrue);

  if (curr->ifFalse) {
    emitIfElse(curr);
    visitPossibleBlockContents(curr->ifFalse);
  }

  emitScopeEnd(curr);
  if (curr->type == Type::unreachable) {
    // we already handled the case of the condition being unreachable.
    // otherwise, we may still be unreachable, if we are an if-else with both
    // sides unreachable. wasm does not allow this to be emitted directly, so we
    // must do something more. we could do better, but for now we emit an extra
    // unreachable instruction after the if, so it is not consumed itself,
    assert(curr->ifFalse);
    emitUnreachable();
  }
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitLoop(Loop* curr) {
  emit(curr);
  visitPossibleBlockContents(curr->body);
  if (curr->type == Type::unreachable) {
    // we emitted a loop without a return type, and the body might be block
    // contents, so ensure it is not consumed
    emitUnreachable();
  }
  emitScopeEnd(curr);
  if (curr->type == Type::unreachable) {
    // we emitted a loop without a return type, so it must not be consumed
    emitUnreachable();
  }
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitBreak(Break* curr) {
  if (curr->value) {
    visit(curr->value);
  }
  if (curr->condition) {
    visit(curr->condition);
  }
  emit(curr);
  if (curr->condition && curr->type == Type::unreachable) {
    // a br_if is normally none or emits a value. if it is unreachable, then
    // either the condition or the value is unreachable, which is extremely
    // rare, and may require us to make the stack polymorphic (if the block we
    // branch to has a value, we may lack one as we are not a reachable branch;
    // the wasm spec on the other hand does presume the br_if emits a value of
    // the right type, even if it popped unreachable)
    emitUnreachable();
  }
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitSwitch(Switch* curr) {
  if (curr->value) {
    visit(curr->value);
  }
  visit(curr->condition);
  if (!BranchUtils::isBranchReachable(curr)) {
    // if the branch is not reachable, then it's dangerous to emit it, as wasm
    // type checking rules are different, especially in unreachable code. so
    // just don't emit that unreachable code.
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitCall(Call* curr) {
  for (auto* operand : curr->operands) {
    visit(operand);
  }

  // For non-control-flow value-returning instructions, if the type of an
  // expression is unreachable, we emit an unreachable and don't emit the
  // instruction itself. If we don't emit an unreachable, instructions that
  // follow can have a validation failure in wasm binary format. For example:
  // [unreachable] (f32.add
  // [unreachable]   (i32.eqz
  // [unreachable]     (unreachable)
  //                 )
  //                 ...
  //               )
  // This is a valid prgram in binaryen IR, because the unreachable type
  // propagates out of an expression, making both i32.eqz and f32.add
  // unreachable. But in binary format, this becomes:
  // unreachable
  // i32.eqz
  // f32.add       ;; validation failure; it takes an i32!
  // And here f32.add causes validation failure in wasm validation. So in this
  // case we add an unreachable to prevent following instructions to consume
  // the current value (here i32.eqz).
  //
  // The same applies for other expressions.
  if (curr->type == Type::unreachable && !curr->isReturn) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitCallIndirect(CallIndirect* curr) {
  for (auto* operand : curr->operands) {
    visit(operand);
  }
  visit(curr->target);
  if (curr->type == Type::unreachable && !curr->isReturn) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitLocalGet(LocalGet* curr) {
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitLocalSet(LocalSet* curr) {
  visit(curr->value);
  if (curr->isTee() && curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitGlobalGet(GlobalGet* curr) {
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitGlobalSet(GlobalSet* curr) {
  visit(curr->value);
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitLoad(Load* curr) {
  visit(curr->ptr);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitStore(Store* curr) {
  visit(curr->ptr);
  visit(curr->value);
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicRMW(AtomicRMW* curr) {
  visit(curr->ptr);
  visit(curr->value);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicCmpxchg(AtomicCmpxchg* curr) {
  visit(curr->ptr);
  visit(curr->expected);
  visit(curr->replacement);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicWait(AtomicWait* curr) {
  visit(curr->ptr);
  visit(curr->expected);
  visit(curr->timeout);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicNotify(AtomicNotify* curr) {
  visit(curr->ptr);
  visit(curr->notifyCount);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitAtomicFence(AtomicFence* curr) {
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDExtract(SIMDExtract* curr) {
  visit(curr->vec);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDReplace(SIMDReplace* curr) {
  visit(curr->vec);
  visit(curr->value);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDShuffle(SIMDShuffle* curr) {
  visit(curr->left);
  visit(curr->right);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDTernary(SIMDTernary* curr) {
  visit(curr->a);
  visit(curr->b);
  visit(curr->c);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDShift(SIMDShift* curr) {
  visit(curr->vec);
  visit(curr->shift);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitSIMDLoad(SIMDLoad* curr) {
  visit(curr->ptr);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitMemoryInit(MemoryInit* curr) {
  visit(curr->dest);
  visit(curr->offset);
  visit(curr->size);
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitDataDrop(DataDrop* curr) {
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitMemoryCopy(MemoryCopy* curr) {
  visit(curr->dest);
  visit(curr->source);
  visit(curr->size);
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitMemoryFill(MemoryFill* curr) {
  visit(curr->dest);
  visit(curr->value);
  visit(curr->size);
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitConst(Const* curr) {
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitUnary(Unary* curr) {
  visit(curr->value);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitBinary(Binary* curr) {
  visit(curr->left);
  visit(curr->right);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitSelect(Select* curr) {
  visit(curr->ifTrue);
  visit(curr->ifFalse);
  visit(curr->condition);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitReturn(Return* curr) {
  if (curr->value) {
    visit(curr->value);
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitHost(Host* curr) {
  switch (curr->op) {
    case MemorySize: {
      break;
    }
    case MemoryGrow: {
      visit(curr->operands[0]);
      break;
    }
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitRefNull(RefNull* curr) {
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitRefIsNull(RefIsNull* curr) {
  visit(curr->value);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitRefFunc(RefFunc* curr) {
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

template<typename SubType> void BinaryenIRWriter<SubType>::visitTry(Try* curr) {
  emit(curr);
  visitPossibleBlockContents(curr->body);
  emitCatch(curr);
  visitPossibleBlockContents(curr->catchBody);
  emitScopeEnd(curr);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
  }
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitThrow(Throw* curr) {
  for (auto* operand : curr->operands) {
    visit(operand);
  }
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitRethrow(Rethrow* curr) {
  visit(curr->exnref);
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitBrOnExn(BrOnExn* curr) {
  visit(curr->exnref);
  emit(curr);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
  }
}

template<typename SubType> void BinaryenIRWriter<SubType>::visitNop(Nop* curr) {
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitUnreachable(Unreachable* curr) {
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitDrop(Drop* curr) {
  visit(curr->value);
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitPush(Push* curr) {
  visit(curr->value);
  emit(curr);
}

template<typename SubType> void BinaryenIRWriter<SubType>::visitPop(Pop* curr) {
  emit(curr);
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitTupleMake(TupleMake* curr) {
  for (auto* operand : curr->operands) {
    visit(operand);
  }
  emit(curr);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
  }
}

template<typename SubType>
void BinaryenIRWriter<SubType>::visitTupleExtract(TupleExtract* curr) {
  visit(curr->tuple);
  if (curr->type == Type::unreachable) {
    emitUnreachable();
    return;
  }
  emit(curr);
}

// Binaryen IR to binary writer
class BinaryenIRToBinaryWriter
  : public BinaryenIRWriter<BinaryenIRToBinaryWriter> {
public:
  BinaryenIRToBinaryWriter(WasmBinaryWriter& parent,
                           BufferWithRandomAccess& o,
                           Function* func = nullptr,
                           bool sourceMap = false)
    : BinaryenIRWriter<BinaryenIRToBinaryWriter>(func), parent(parent),
      writer(parent, o, func, sourceMap), sourceMap(sourceMap) {}

  void visit(Expression* curr) {
    BinaryenIRWriter<BinaryenIRToBinaryWriter>::visit(curr);
  }

  void emit(Expression* curr) { writer.visit(curr); }
  void emitHeader() {
    if (func->prologLocation.size()) {
      parent.writeDebugLocation(*func->prologLocation.begin());
    }
    writer.mapLocalsAndEmitHeader();
  }
  void emitIfElse(If* curr) { writer.emitIfElse(curr); }
  void emitCatch(Try* curr) { writer.emitCatch(curr); }
  void emitScopeEnd(Expression* curr) { writer.emitScopeEnd(curr); }
  void emitFunctionEnd() {
    if (func->epilogLocation.size()) {
      parent.writeDebugLocation(*func->epilogLocation.begin());
    }
    writer.emitFunctionEnd();
  }
  void emitUnreachable() { writer.emitUnreachable(); }
  void emitDebugLocation(Expression* curr) {
    if (sourceMap) {
      parent.writeDebugLocation(curr, func);
    }
  }

private:
  WasmBinaryWriter& parent;
  BinaryInstWriter writer;
  bool sourceMap = false;
};

// Binaryen IR to stack IR converter
// Queues the expressions linearly in Stack IR (SIR)
class StackIRGenerator : public BinaryenIRWriter<StackIRGenerator> {
public:
  StackIRGenerator(MixedArena& allocator, Function* func)
    : BinaryenIRWriter<StackIRGenerator>(func), allocator(allocator) {}

  void emit(Expression* curr);
  void emitScopeEnd(Expression* curr);
  void emitHeader() {}
  void emitIfElse(If* curr) {
    stackIR.push_back(makeStackInst(StackInst::IfElse, curr));
  }
  void emitCatch(Try* curr) {
    stackIR.push_back(makeStackInst(StackInst::Catch, curr));
  }
  void emitFunctionEnd() {}
  void emitUnreachable() {
    stackIR.push_back(makeStackInst(Builder(allocator).makeUnreachable()));
  }
  void emitDebugLocation(Expression* curr) {}

  StackIR& getStackIR() { return stackIR; }

private:
  StackInst* makeStackInst(StackInst::Op op, Expression* origin);
  StackInst* makeStackInst(Expression* origin) {
    return makeStackInst(StackInst::Basic, origin);
  }

  MixedArena& allocator;
  StackIR stackIR; // filled in write()
};

// Stack IR to binary writer
class StackIRToBinaryWriter {
public:
  StackIRToBinaryWriter(WasmBinaryWriter& parent,
                        BufferWithRandomAccess& o,
                        Function* func)
    : writer(parent, o, func, false /* sourceMap */), func(func) {}

  void write();

private:
  BinaryInstWriter writer;
  Function* func;
};

} // namespace wasm

#endif // wasm_stack_h