/*
 * Copyright 2016 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.
 */

#include "wabt/interp/binary-reader-interp.h"

#include <map>
#include <set>

#include "wabt/binary-reader-nop.h"
#include "wabt/feature.h"
#include "wabt/interp/interp.h"
#include "wabt/shared-validator.h"
#include "wabt/stream.h"

namespace wabt {
namespace interp {

namespace {

ValueTypes ToInterp(Index count, Type* types) {
  return ValueTypes(&types[0], &types[count]);
}

Mutability ToMutability(bool mut) {
  return mut ? Mutability::Var : Mutability::Const;
}

SegmentMode ToSegmentMode(uint8_t flags) {
  if ((flags & SegDeclared) == SegDeclared) {
    return SegmentMode::Declared;
  } else if ((flags & SegPassive) == SegPassive) {
    return SegmentMode::Passive;
  } else {
    return SegmentMode::Active;
  }
}

// This is only used to distinguish try blocks and all other blocks,
// so there are only two kinds.
enum class LabelKind { Block, Try };

struct Label {
  LabelKind kind;
  Istream::Offset offset;
  Istream::Offset fixup_offset;
  // Only needs to be set for try blocks.
  u32 handler_desc_index;
};

struct FixupMap {
  using Offset = Istream::Offset;
  using Fixups = std::vector<Offset>;

  void Clear();
  void Append(Index, Offset);
  void Resolve(Istream&, Index);

  std::map<Index, Fixups> map;
};

class BinaryReaderInterp : public BinaryReaderNop {
 public:
  BinaryReaderInterp(ModuleDesc* module,
                     std::string_view filename,
                     Errors* errors,
                     const Features& features);

  // Implement BinaryReader.
  bool OnError(const Error&) override;

  Result EndModule() override;

  Result OnTypeCount(Index count) override;
  Result OnFuncType(Index index,
                    Index param_count,
                    Type* param_types,
                    Index result_count,
                    Type* result_types) override;

  Result OnImportFunc(Index import_index,
                      std::string_view module_name,
                      std::string_view field_name,
                      Index func_index,
                      Index sig_index) override;
  Result OnImportTable(Index import_index,
                       std::string_view module_name,
                       std::string_view field_name,
                       Index table_index,
                       Type elem_type,
                       const Limits* elem_limits) override;
  Result OnImportMemory(Index import_index,
                        std::string_view module_name,
                        std::string_view field_name,
                        Index memory_index,
                        const Limits* page_limits) override;
  Result OnImportGlobal(Index import_index,
                        std::string_view module_name,
                        std::string_view field_name,
                        Index global_index,
                        Type type,
                        bool mutable_) override;
  Result OnImportTag(Index import_index,
                     std::string_view module_name,
                     std::string_view field_name,
                     Index tag_index,
                     Index sig_index) override;

  Result OnFunctionCount(Index count) override;
  Result OnFunction(Index index, Index sig_index) override;

  Result OnTableCount(Index count) override;
  Result OnTable(Index index,
                 Type elem_type,
                 const Limits* elem_limits) override;

  Result OnMemoryCount(Index count) override;
  Result OnMemory(Index index, const Limits* limits) override;

  Result OnGlobalCount(Index count) override;
  Result BeginGlobal(Index index, Type type, bool mutable_) override;
  Result BeginGlobalInitExpr(Index index) override;
  Result EndGlobalInitExpr(Index index) override;

  Result OnTagCount(Index count) override;
  Result OnTagType(Index index, Index sig_index) override;

  Result OnExport(Index index,
                  ExternalKind kind,
                  Index item_index,
                  std::string_view name) override;

  Result OnStartFunction(Index func_index) override;

  Result BeginFunctionBody(Index index, Offset size) override;
  Result OnLocalDeclCount(Index count) override;
  Result OnLocalDecl(Index decl_index, Index count, Type type) override;

  Result OnOpcode(Opcode Opcode) override;
  Result OnAtomicLoadExpr(Opcode opcode,
                          Index memidx,
                          Address alignment_log2,
                          Address offset) override;
  Result OnAtomicStoreExpr(Opcode opcode,
                           Index memidx,
                           Address alignment_log2,
                           Address offset) override;
  Result OnAtomicRmwExpr(Opcode opcode,
                         Index memidx,
                         Address alignment_log2,
                         Address offset) override;
  Result OnAtomicRmwCmpxchgExpr(Opcode opcode,
                                Index memidx,
                                Address alignment_log2,
                                Address offset) override;
  Result OnAtomicWaitExpr(Opcode opcode,
                          Index memidx,
                          Address alignment_log2,
                          Address offset) override;
  Result OnAtomicFenceExpr(uint32_t consistency_model) override;
  Result OnAtomicNotifyExpr(Opcode opcode,
                            Index memidx,
                            Address alignment_log2,
                            Address offset) override;
  Result OnBinaryExpr(Opcode opcode) override;
  Result OnBlockExpr(Type sig_type) override;
  Result OnBrExpr(Index depth) override;
  Result OnBrIfExpr(Index depth) override;
  Result OnBrTableExpr(Index num_targets,
                       Index* target_depths,
                       Index default_target_depth) override;
  Result OnCallExpr(Index func_index) override;
  Result OnCallIndirectExpr(Index sig_index, Index table_index) override;
  Result OnCatchExpr(Index tag_index) override;
  Result OnCatchAllExpr() override;
  Result OnDelegateExpr(Index depth) override;
  Result OnReturnCallExpr(Index func_index) override;
  Result OnReturnCallIndirectExpr(Index sig_index, Index table_index) override;
  Result OnCompareExpr(Opcode opcode) override;
  Result OnConvertExpr(Opcode opcode) override;
  Result OnDropExpr() override;
  Result OnElseExpr() override;
  Result OnEndExpr() override;
  Result OnF32ConstExpr(uint32_t value_bits) override;
  Result OnF64ConstExpr(uint64_t value_bits) override;
  Result OnV128ConstExpr(v128 value_bits) override;
  Result OnGlobalGetExpr(Index global_index) override;
  Result OnGlobalSetExpr(Index global_index) override;
  Result OnI32ConstExpr(uint32_t value) override;
  Result OnI64ConstExpr(uint64_t value) override;
  Result OnIfExpr(Type sig_type) override;
  Result OnLoadExpr(Opcode opcode,
                    Index memidx,
                    Address alignment_log2,
                    Address offset) override;
  Result OnLocalGetExpr(Index local_index) override;
  Result OnLocalSetExpr(Index local_index) override;
  Result OnLocalTeeExpr(Index local_index) override;
  Result OnLoopExpr(Type sig_type) override;
  Result OnMemoryCopyExpr(Index destmemidx, Index srcmemidx) override;
  Result OnDataDropExpr(Index segment_index) override;
  Result OnMemoryGrowExpr(Index memidx) override;
  Result OnMemoryFillExpr(Index memidx) override;
  Result OnMemoryInitExpr(Index segment_index, Index memidx) override;
  Result OnMemorySizeExpr(Index memidx) override;
  Result OnRefFuncExpr(Index func_index) override;
  Result OnRefNullExpr(Type type) override;
  Result OnRefIsNullExpr() override;
  Result OnNopExpr() override;
  Result OnRethrowExpr(Index depth) override;
  Result OnReturnExpr() override;
  Result OnSelectExpr(Index result_count, Type* result_types) override;
  Result OnStoreExpr(Opcode opcode,
                     Index memidx,
                     Address alignment_log2,
                     Address offset) override;
  Result OnUnaryExpr(Opcode opcode) override;
  Result OnTableCopyExpr(Index dst_index, Index src_index) override;
  Result OnTableGetExpr(Index table_index) override;
  Result OnTableSetExpr(Index table_index) override;
  Result OnTableGrowExpr(Index table_index) override;
  Result OnTableSizeExpr(Index table_index) override;
  Result OnTableFillExpr(Index table_index) override;
  Result OnElemDropExpr(Index segment_index) override;
  Result OnTableInitExpr(Index segment_index, Index table_index) override;
  Result OnTernaryExpr(Opcode opcode) override;
  Result OnThrowExpr(Index tag_index) override;
  Result OnTryExpr(Type sig_type) override;
  Result OnUnreachableExpr() override;
  Result EndFunctionBody(Index index) override;
  Result OnSimdLaneOpExpr(Opcode opcode, uint64_t value) override;
  Result OnSimdLoadLaneExpr(Opcode opcode,
                            Index memidx,
                            Address alignment_log2,
                            Address offset,
                            uint64_t value) override;
  Result OnSimdStoreLaneExpr(Opcode opcode,
                             Index memidx,
                             Address alignment_log2,
                             Address offset,
                             uint64_t value) override;
  Result OnSimdShuffleOpExpr(Opcode opcode, v128 value) override;
  Result OnLoadSplatExpr(Opcode opcode,
                         Index memidx,
                         Address alignment_log2,
                         Address offset) override;
  Result OnLoadZeroExpr(Opcode opcode,
                        Index memidx,
                        Address alignment_log2,
                        Address offset) override;

  Result OnElemSegmentCount(Index count) override;
  Result BeginElemSegment(Index index,
                          Index table_index,
                          uint8_t flags) override;
  Result BeginElemSegmentInitExpr(Index index) override;
  Result EndElemSegmentInitExpr(Index index) override;
  Result OnElemSegmentElemType(Index index, Type elem_type) override;
  Result OnElemSegmentElemExprCount(Index index, Index count) override;
  Result BeginElemExpr(Index elem_index, Index expr_index) override;
  Result EndElemExpr(Index elem_index, Index expr_index) override;

  Result OnDataCount(Index count) override;
  Result BeginDataSegmentInitExpr(Index index) override;
  Result EndDataSegmentInitExpr(Index index) override;
  Result BeginDataSegment(Index index,
                          Index memory_index,
                          uint8_t flags) override;
  Result OnDataSegmentData(Index index,
                           const void* data,
                           Address size) override;

 private:
  Location GetLocation() const;
  Label* GetLabel(Index depth);
  Label* GetNearestTryLabel(Index depth);
  Label* TopLabel();
  void PushLabel(LabelKind label = LabelKind::Block,
                 Istream::Offset offset = Istream::kInvalidOffset,
                 Istream::Offset fixup_offset = Istream::kInvalidOffset,
                 u32 handler_desc_index = kInvalidIndex);
  void PopLabel();

  void PrintError(const char* format, ...);

  Result GetDropCount(Index keep_count,
                      size_t type_stack_limit,
                      Index* out_drop_count);
  Result GetBrDropKeepCount(Index depth,
                            Index* out_drop_count,
                            Index* out_keep_count);
  Result GetReturnDropKeepCount(Index* out_drop_count, Index* out_keep_count);
  Result GetReturnCallDropKeepCount(const FuncType&,
                                    Index keep_extra,
                                    Index* out_drop_count,
                                    Index* out_keep_count);
  Result BeginInitExpr(FuncDesc* init_func);
  Result EndInitExpr();

  void EmitBr(Index depth,
              Index drop_count,
              Index keep_count,
              Index catch_drop_count);
  void FixupTopLabel();
  u32 GetFuncOffset(Index func_index);

  Index TranslateLocalIndex(Index local_index);

  Index num_func_imports() const;

  Errors* errors_ = nullptr;
  ModuleDesc& module_;
  Istream& istream_;

  SharedValidator validator_;

  FuncDesc* func_;
  std::vector<Label> label_stack_;
  FixupMap depth_fixups_;
  FixupMap func_fixups_;

  u32 local_decl_count_;
  u32 local_count_;

  std::vector<FuncType> func_types_;      // Includes imported and defined.
  std::vector<TableType> table_types_;    // Includes imported and defined.
  std::vector<MemoryType> memory_types_;  // Includes imported and defined.
  std::vector<GlobalType> global_types_;  // Includes imported and defined.
  std::vector<TagType> tag_types_;        // Includes imported and defined.

  std::string_view filename_;
};

Location BinaryReaderInterp::GetLocation() const {
  Location loc;
  loc.filename = filename_;
  loc.offset = state->offset;
  return loc;
}

void FixupMap::Clear() {
  map.clear();
}

void FixupMap::Append(Index index, Offset offset) {
  map[index].push_back(offset);
}

void FixupMap::Resolve(Istream& istream, Index index) {
  auto iter = map.find(index);
  if (iter == map.end()) {
    return;
  }
  for (Offset offset : iter->second) {
    istream.ResolveFixupU32(offset);
  }
  map.erase(iter);
}

BinaryReaderInterp::BinaryReaderInterp(ModuleDesc* module,
                                       std::string_view filename,
                                       Errors* errors,
                                       const Features& features)
    : errors_(errors),
      module_(*module),
      istream_(module->istream),
      validator_(errors, ValidateOptions(features)),
      filename_(filename) {}

Label* BinaryReaderInterp::GetLabel(Index depth) {
  assert(depth < label_stack_.size());
  return &label_stack_[label_stack_.size() - depth - 1];
}

Label* BinaryReaderInterp::GetNearestTryLabel(Index depth) {
  for (size_t i = depth; i < label_stack_.size(); i++) {
    Label* label = &label_stack_[label_stack_.size() - i - 1];
    if (label->kind == LabelKind::Try) {
      return label;
    }
  }
  return nullptr;
}

Label* BinaryReaderInterp::TopLabel() {
  return GetLabel(0);
}

void WABT_PRINTF_FORMAT(2, 3) BinaryReaderInterp::PrintError(const char* format,
                                                             ...) {
  WABT_SNPRINTF_ALLOCA(buffer, length, format);
  errors_->emplace_back(ErrorLevel::Error, Location(kInvalidOffset), buffer);
}

Result BinaryReaderInterp::GetDropCount(Index keep_count,
                                        size_t type_stack_limit,
                                        Index* out_drop_count) {
  assert(validator_.type_stack_size() >= type_stack_limit);
  Index type_stack_count = validator_.type_stack_size() - type_stack_limit;
  // The keep_count may be larger than the type_stack_count if the typechecker
  // is currently unreachable. In that case, it doesn't matter what value we
  // drop, but 0 is a reasonable choice.
  *out_drop_count =
      type_stack_count >= keep_count ? type_stack_count - keep_count : 0;
  return Result::Ok;
}

Result BinaryReaderInterp::GetBrDropKeepCount(Index depth,
                                              Index* out_drop_count,
                                              Index* out_keep_count) {
  SharedValidator::Label* label;
  CHECK_RESULT(validator_.GetLabel(depth, &label));
  Index keep_count = label->br_types().size();
  CHECK_RESULT(
      GetDropCount(keep_count, label->type_stack_limit, out_drop_count));
  *out_keep_count = keep_count;
  return Result::Ok;
}

Result BinaryReaderInterp::GetReturnDropKeepCount(Index* out_drop_count,
                                                  Index* out_keep_count) {
  CHECK_RESULT(GetBrDropKeepCount(label_stack_.size() - 1, out_drop_count,
                                  out_keep_count));
  *out_drop_count += validator_.GetLocalCount();
  return Result::Ok;
}

Result BinaryReaderInterp::GetReturnCallDropKeepCount(const FuncType& func_type,
                                                      Index keep_extra,
                                                      Index* out_drop_count,
                                                      Index* out_keep_count) {
  Index keep_count = static_cast<Index>(func_type.params.size()) + keep_extra;
  CHECK_RESULT(GetDropCount(keep_count, 0, out_drop_count));
  *out_drop_count += validator_.GetLocalCount();
  *out_keep_count = keep_count;
  return Result::Ok;
}

void BinaryReaderInterp::EmitBr(Index depth,
                                Index drop_count,
                                Index keep_count,
                                Index catch_drop_count) {
  istream_.EmitDropKeep(drop_count, keep_count);
  istream_.EmitCatchDrop(catch_drop_count);
  Istream::Offset offset = GetLabel(depth)->offset;
  istream_.Emit(Opcode::Br);
  if (offset == Istream::kInvalidOffset) {
    // depth_fixups_ stores the depth counting up from zero, where zero is the
    // top-level function scope.
    depth_fixups_.Append(label_stack_.size() - 1 - depth, istream_.end());
  }
  istream_.Emit(offset);
}

void BinaryReaderInterp::FixupTopLabel() {
  depth_fixups_.Resolve(istream_, label_stack_.size() - 1);
}

u32 BinaryReaderInterp::GetFuncOffset(Index func_index) {
  assert(func_index >= num_func_imports());
  FuncDesc& func = module_.funcs[func_index - num_func_imports()];
  if (func.code_offset == Istream::kInvalidOffset) {
    func_fixups_.Append(func_index, istream_.end());
  }
  return func.code_offset;
}

bool BinaryReaderInterp::OnError(const Error& error) {
  errors_->push_back(error);
  return true;
}

Result BinaryReaderInterp::EndModule() {
  CHECK_RESULT(validator_.EndModule());
  return Result::Ok;
}

Result BinaryReaderInterp::OnTypeCount(Index count) {
  module_.func_types.reserve(count);
  return Result::Ok;
}

Result BinaryReaderInterp::OnFuncType(Index index,
                                      Index param_count,
                                      Type* param_types,
                                      Index result_count,
                                      Type* result_types) {
  CHECK_RESULT(validator_.OnFuncType(GetLocation(), param_count, param_types,
                                     result_count, result_types, index));
  module_.func_types.push_back(FuncType(ToInterp(param_count, param_types),
                                        ToInterp(result_count, result_types)));
  return Result::Ok;
}

Result BinaryReaderInterp::OnImportFunc(Index import_index,
                                        std::string_view module_name,
                                        std::string_view field_name,
                                        Index func_index,
                                        Index sig_index) {
  CHECK_RESULT(
      validator_.OnFunction(GetLocation(), Var(sig_index, GetLocation())));
  FuncType& func_type = module_.func_types[sig_index];
  module_.imports.push_back(ImportDesc{ImportType(
      std::string(module_name), std::string(field_name), func_type.Clone())});
  func_types_.push_back(func_type);
  return Result::Ok;
}

Result BinaryReaderInterp::OnImportTable(Index import_index,
                                         std::string_view module_name,
                                         std::string_view field_name,
                                         Index table_index,
                                         Type elem_type,
                                         const Limits* elem_limits) {
  CHECK_RESULT(validator_.OnTable(GetLocation(), elem_type, *elem_limits));
  TableType table_type{elem_type, *elem_limits};
  module_.imports.push_back(ImportDesc{ImportType(
      std::string(module_name), std::string(field_name), table_type.Clone())});
  table_types_.push_back(table_type);
  return Result::Ok;
}

Result BinaryReaderInterp::OnImportMemory(Index import_index,
                                          std::string_view module_name,
                                          std::string_view field_name,
                                          Index memory_index,
                                          const Limits* page_limits) {
  CHECK_RESULT(validator_.OnMemory(GetLocation(), *page_limits));
  MemoryType memory_type{*page_limits};
  module_.imports.push_back(ImportDesc{ImportType(
      std::string(module_name), std::string(field_name), memory_type.Clone())});
  memory_types_.push_back(memory_type);
  return Result::Ok;
}

Result BinaryReaderInterp::OnImportGlobal(Index import_index,
                                          std::string_view module_name,
                                          std::string_view field_name,
                                          Index global_index,
                                          Type type,
                                          bool mutable_) {
  CHECK_RESULT(validator_.OnGlobalImport(GetLocation(), type, mutable_));
  GlobalType global_type{type, ToMutability(mutable_)};
  module_.imports.push_back(ImportDesc{ImportType(
      std::string(module_name), std::string(field_name), global_type.Clone())});
  global_types_.push_back(global_type);
  return Result::Ok;
}

Result BinaryReaderInterp::OnImportTag(Index import_index,
                                       std::string_view module_name,
                                       std::string_view field_name,
                                       Index tag_index,
                                       Index sig_index) {
  CHECK_RESULT(validator_.OnTag(GetLocation(), Var(sig_index, GetLocation())));
  FuncType& func_type = module_.func_types[sig_index];
  TagType tag_type{TagAttr::Exception, func_type.params};
  module_.imports.push_back(ImportDesc{ImportType(
      std::string(module_name), std::string(field_name), tag_type.Clone())});
  tag_types_.push_back(tag_type);
  return Result::Ok;
}

Result BinaryReaderInterp::OnFunctionCount(Index count) {
  module_.funcs.reserve(count);
  return Result::Ok;
}

Result BinaryReaderInterp::OnFunction(Index index, Index sig_index) {
  CHECK_RESULT(
      validator_.OnFunction(GetLocation(), Var(sig_index, GetLocation())));
  FuncType& func_type = module_.func_types[sig_index];
  module_.funcs.push_back(FuncDesc{func_type, {}, Istream::kInvalidOffset, {}});
  func_types_.push_back(func_type);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTableCount(Index count) {
  module_.tables.reserve(count);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTable(Index index,
                                   Type elem_type,
                                   const Limits* elem_limits) {
  CHECK_RESULT(validator_.OnTable(GetLocation(), elem_type, *elem_limits));
  TableType table_type{elem_type, *elem_limits};
  module_.tables.push_back(TableDesc{table_type});
  table_types_.push_back(table_type);
  return Result::Ok;
}

Result BinaryReaderInterp::OnMemoryCount(Index count) {
  module_.memories.reserve(count);
  return Result::Ok;
}

Result BinaryReaderInterp::OnMemory(Index index, const Limits* limits) {
  CHECK_RESULT(validator_.OnMemory(GetLocation(), *limits));
  MemoryType memory_type{*limits};
  module_.memories.push_back(MemoryDesc{memory_type});
  memory_types_.push_back(memory_type);
  return Result::Ok;
}

Result BinaryReaderInterp::OnGlobalCount(Index count) {
  module_.globals.reserve(count);
  return Result::Ok;
}

Result BinaryReaderInterp::BeginGlobal(Index index, Type type, bool mutable_) {
  CHECK_RESULT(validator_.OnGlobal(GetLocation(), type, mutable_));
  GlobalType global_type{type, ToMutability(mutable_)};
  FuncDesc init_func{FuncType{{}, {type}}, {}, Istream::kInvalidOffset, {}};
  module_.globals.push_back(GlobalDesc{global_type, init_func});
  global_types_.push_back(global_type);
  return Result::Ok;
}

Result BinaryReaderInterp::BeginGlobalInitExpr(Index index) {
  GlobalDesc& global = module_.globals.back();
  return BeginInitExpr(&global.init_func);
}

Result BinaryReaderInterp::EndInitExpr() {
  FixupTopLabel();
  CHECK_RESULT(validator_.EndInitExpr());
  istream_.Emit(Opcode::Return);
  PopLabel();
  return Result::Ok;
}

Result BinaryReaderInterp::BeginInitExpr(FuncDesc* func) {
  label_stack_.clear();
  func_ = func;
  func_->code_offset = istream_.end();
  Type type = func->type.results[0];
  CHECK_RESULT(validator_.BeginInitExpr(GetLocation(), type));
  // Push implicit init func label (equivalent to return).
  PushLabel(LabelKind::Try, Istream::kInvalidOffset, Istream::kInvalidOffset);
  return Result::Ok;
}

Result BinaryReaderInterp::EndGlobalInitExpr(Index index) {
  return EndInitExpr();
}

Result BinaryReaderInterp::OnTagCount(Index count) {
  module_.tags.reserve(count);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTagType(Index index, Index sig_index) {
  CHECK_RESULT(validator_.OnTag(GetLocation(), Var(sig_index, GetLocation())));
  FuncType& func_type = module_.func_types[sig_index];
  TagType tag_type{TagAttr::Exception, func_type.params};
  module_.tags.push_back(TagDesc{tag_type});
  tag_types_.push_back(tag_type);
  return Result::Ok;
}

Result BinaryReaderInterp::OnExport(Index index,
                                    ExternalKind kind,
                                    Index item_index,
                                    std::string_view name) {
  CHECK_RESULT(validator_.OnExport(GetLocation(), kind,
                                   Var(item_index, GetLocation()), name));

  std::unique_ptr<ExternType> type;
  switch (kind) {
    case ExternalKind::Func:   type = func_types_[item_index].Clone(); break;
    case ExternalKind::Table:  type = table_types_[item_index].Clone(); break;
    case ExternalKind::Memory: type = memory_types_[item_index].Clone(); break;
    case ExternalKind::Global: type = global_types_[item_index].Clone(); break;
    case ExternalKind::Tag:    type = tag_types_[item_index].Clone(); break;
  }
  module_.exports.push_back(
      ExportDesc{ExportType(std::string(name), std::move(type)), item_index});
  return Result::Ok;
}

Result BinaryReaderInterp::OnStartFunction(Index func_index) {
  CHECK_RESULT(
      validator_.OnStart(GetLocation(), Var(func_index, GetLocation())));
  module_.starts.push_back(StartDesc{func_index});
  return Result::Ok;
}

Result BinaryReaderInterp::OnElemSegmentCount(Index count) {
  module_.elems.reserve(count);
  return Result::Ok;
}

Result BinaryReaderInterp::BeginElemSegment(Index index,
                                            Index table_index,
                                            uint8_t flags) {
  auto mode = ToSegmentMode(flags);
  CHECK_RESULT(validator_.OnElemSegment(GetLocation(),
                                        Var(table_index, GetLocation()), mode));

  ValueType offset_type = ValueType::I32;
  if (table_index < table_types_.size() &&
      table_types_[table_index].limits.is_64) {
    offset_type = ValueType::I64;
  }
  FuncDesc init_func{
      FuncType{{}, {offset_type}}, {}, Istream::kInvalidOffset, {}};
  ElemDesc desc{{}, ValueType::Void, mode, table_index, init_func};
  module_.elems.push_back(desc);
  return Result::Ok;
}

Result BinaryReaderInterp::BeginElemSegmentInitExpr(Index index) {
  ElemDesc& elem = module_.elems.back();
  return BeginInitExpr(&elem.init_func);
}

Result BinaryReaderInterp::EndElemSegmentInitExpr(Index index) {
  return EndInitExpr();
}

Result BinaryReaderInterp::OnElemSegmentElemType(Index index, Type elem_type) {
  ElemDesc& elem = module_.elems.back();
  elem.type = elem_type;
  return validator_.OnElemSegmentElemType(GetLocation(), elem_type);
}

Result BinaryReaderInterp::OnElemSegmentElemExprCount(Index index,
                                                      Index count) {
  ElemDesc& elem = module_.elems.back();
  elem.elements.reserve(count);
  return Result::Ok;
}

Result BinaryReaderInterp::BeginElemExpr(Index elem_index, Index expr_index) {
  assert(elem_index == module_.elems.size() - 1);
  ElemDesc& elem = module_.elems.back();
  elem.elements.push_back(
      {FuncType{{}, {elem.type}}, {}, Istream::kInvalidOffset, {}});
  assert(expr_index == elem.elements.size() - 1);
  return BeginInitExpr(&elem.elements.back());
}

Result BinaryReaderInterp::EndElemExpr(Index elem_index, Index expr_index) {
  return EndInitExpr();
}

Result BinaryReaderInterp::OnDataCount(Index count) {
  validator_.OnDataCount(count);
  module_.datas.reserve(count);
  return Result::Ok;
}

Result BinaryReaderInterp::BeginDataSegmentInitExpr(Index index) {
  DataDesc& data = module_.datas.back();
  return BeginInitExpr(&data.init_func);
}

Result BinaryReaderInterp::EndDataSegmentInitExpr(Index index) {
  return EndInitExpr();
}

Result BinaryReaderInterp::BeginDataSegment(Index index,
                                            Index memory_index,
                                            uint8_t flags) {
  auto mode = ToSegmentMode(flags);
  CHECK_RESULT(validator_.OnDataSegment(
      GetLocation(), Var(memory_index, GetLocation()), mode));

  ValueType offset_type = ValueType::I32;
  if (memory_index < memory_types_.size() &&
      memory_types_[memory_index].limits.is_64) {
    offset_type = ValueType::I64;
  }
  FuncDesc init_func{
      FuncType{{}, {offset_type}}, {}, Istream::kInvalidOffset, {}};
  DataDesc desc{{}, mode, memory_index, init_func};
  module_.datas.push_back(desc);
  return Result::Ok;
}

Result BinaryReaderInterp::OnDataSegmentData(Index index,
                                             const void* src_data,
                                             Address size) {
  DataDesc& dst_data = module_.datas.back();
  if (size > 0) {
    dst_data.data.resize(size);
    memcpy(dst_data.data.data(), src_data, size);
  }
  return Result::Ok;
}

void BinaryReaderInterp::PushLabel(LabelKind kind,
                                   Istream::Offset offset,
                                   Istream::Offset fixup_offset,
                                   u32 handler_desc_index) {
  label_stack_.push_back(Label{kind, offset, fixup_offset, handler_desc_index});
}

void BinaryReaderInterp::PopLabel() {
  label_stack_.pop_back();
}

Result BinaryReaderInterp::BeginFunctionBody(Index index, Offset size) {
  Index defined_index = index - num_func_imports();
  func_ = &module_.funcs[defined_index];
  func_->code_offset = istream_.end();

  depth_fixups_.Clear();
  label_stack_.clear();

  func_fixups_.Resolve(istream_, defined_index);

  CHECK_RESULT(validator_.BeginFunctionBody(GetLocation(), index));

  // Push implicit func label (equivalent to return).
  // With exception handling it acts as a catch-less try block, which is
  // needed to support delegating to the caller of a function using the
  // try-delegate instruction.
  PushLabel(LabelKind::Try, Istream::kInvalidOffset, Istream::kInvalidOffset,
            func_->handlers.size());
  func_->handlers.push_back(HandlerDesc{HandlerKind::Catch,
                                        istream_.end(),
                                        Istream::kInvalidOffset,
                                        {},
                                        {Istream::kInvalidOffset},
                                        static_cast<u32>(func_->locals.size()),
                                        0});
  return Result::Ok;
}

Result BinaryReaderInterp::EndFunctionBody(Index index) {
  FixupTopLabel();
  Index drop_count, keep_count;
  CHECK_RESULT(GetReturnDropKeepCount(&drop_count, &keep_count));
  CHECK_RESULT(validator_.EndFunctionBody(GetLocation()));
  istream_.EmitDropKeep(drop_count, keep_count);
  istream_.Emit(Opcode::Return);
  PopLabel();
  func_ = nullptr;
  return Result::Ok;
}

Result BinaryReaderInterp::OnLocalDeclCount(Index count) {
  local_decl_count_ = count;
  local_count_ = 0;
  return Result::Ok;
}

Result BinaryReaderInterp::OnLocalDecl(Index decl_index,
                                       Index count,
                                       Type type) {
  CHECK_RESULT(validator_.OnLocalDecl(GetLocation(), count, type));

  local_count_ += count;
  func_->locals.push_back(LocalDesc{type, count, local_count_});

  if (decl_index == local_decl_count_ - 1) {
    istream_.Emit(Opcode::InterpAlloca, local_count_);
  }
  return Result::Ok;
}

Index BinaryReaderInterp::num_func_imports() const {
  return func_types_.size() - module_.funcs.size();
}

Result BinaryReaderInterp::OnOpcode(Opcode opcode) {
  if (func_ == nullptr || label_stack_.empty()) {
    PrintError("Unexpected instruction after end of function");
    return Result::Error;
  }
  return Result::Ok;
}

Result BinaryReaderInterp::OnUnaryExpr(Opcode opcode) {
  CHECK_RESULT(validator_.OnUnary(GetLocation(), opcode));
  istream_.Emit(opcode);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTernaryExpr(Opcode opcode) {
  CHECK_RESULT(validator_.OnTernary(GetLocation(), opcode));
  istream_.Emit(opcode);
  return Result::Ok;
}

Result BinaryReaderInterp::OnSimdLaneOpExpr(Opcode opcode, uint64_t value) {
  CHECK_RESULT(validator_.OnSimdLaneOp(GetLocation(), opcode, value));
  istream_.Emit(opcode, static_cast<u8>(value));
  return Result::Ok;
}

uint32_t GetAlignment(Address alignment_log2) {
  return alignment_log2 < 32 ? 1 << alignment_log2 : ~0u;
}

Result BinaryReaderInterp::OnSimdLoadLaneExpr(Opcode opcode,
                                              Index memidx,
                                              Address alignment_log2,
                                              Address offset,
                                              uint64_t value) {
  CHECK_RESULT(validator_.OnSimdLoadLane(
      GetLocation(), opcode, Var(memidx, GetLocation()),
      GetAlignment(alignment_log2), offset, value));
  istream_.Emit(opcode, memidx, offset, static_cast<u8>(value));
  return Result::Ok;
}

Result BinaryReaderInterp::OnSimdStoreLaneExpr(Opcode opcode,
                                               Index memidx,
                                               Address alignment_log2,
                                               Address offset,
                                               uint64_t value) {
  CHECK_RESULT(validator_.OnSimdStoreLane(
      GetLocation(), opcode, Var(memidx, GetLocation()),
      GetAlignment(alignment_log2), offset, value));
  istream_.Emit(opcode, memidx, offset, static_cast<u8>(value));
  return Result::Ok;
}

Result BinaryReaderInterp::OnSimdShuffleOpExpr(Opcode opcode, v128 value) {
  CHECK_RESULT(validator_.OnSimdShuffleOp(GetLocation(), opcode, value));
  istream_.Emit(opcode, value);
  return Result::Ok;
}

Result BinaryReaderInterp::OnLoadSplatExpr(Opcode opcode,
                                           Index memidx,
                                           Address align_log2,
                                           Address offset) {
  CHECK_RESULT(validator_.OnLoadSplat(GetLocation(), opcode,
                                      Var(memidx, GetLocation()),
                                      GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnLoadZeroExpr(Opcode opcode,
                                          Index memidx,
                                          Address align_log2,
                                          Address offset) {
  CHECK_RESULT(validator_.OnLoadZero(GetLocation(), opcode,
                                     Var(memidx, GetLocation()),
                                     GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnAtomicLoadExpr(Opcode opcode,
                                            Index memidx,
                                            Address align_log2,
                                            Address offset) {
  CHECK_RESULT(validator_.OnAtomicLoad(GetLocation(), opcode,
                                       Var(memidx, GetLocation()),
                                       GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnAtomicStoreExpr(Opcode opcode,
                                             Index memidx,
                                             Address align_log2,
                                             Address offset) {
  CHECK_RESULT(validator_.OnAtomicStore(GetLocation(), opcode,
                                        Var(memidx, GetLocation()),
                                        GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnAtomicRmwExpr(Opcode opcode,
                                           Index memidx,
                                           Address align_log2,
                                           Address offset) {
  CHECK_RESULT(validator_.OnAtomicRmw(GetLocation(), opcode,
                                      Var(memidx, GetLocation()),
                                      GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnAtomicRmwCmpxchgExpr(Opcode opcode,
                                                  Index memidx,
                                                  Address align_log2,
                                                  Address offset) {
  CHECK_RESULT(validator_.OnAtomicRmwCmpxchg(GetLocation(), opcode,
                                             Var(memidx, GetLocation()),
                                             GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnBinaryExpr(Opcode opcode) {
  CHECK_RESULT(validator_.OnBinary(GetLocation(), opcode));
  istream_.Emit(opcode);
  return Result::Ok;
}

Result BinaryReaderInterp::OnBlockExpr(Type sig_type) {
  CHECK_RESULT(validator_.OnBlock(GetLocation(), sig_type));
  PushLabel();
  return Result::Ok;
}

Result BinaryReaderInterp::OnLoopExpr(Type sig_type) {
  CHECK_RESULT(validator_.OnLoop(GetLocation(), sig_type));
  PushLabel(LabelKind::Block, istream_.end());
  return Result::Ok;
}

Result BinaryReaderInterp::OnIfExpr(Type sig_type) {
  CHECK_RESULT(validator_.OnIf(GetLocation(), sig_type));
  istream_.Emit(Opcode::InterpBrUnless);
  auto fixup = istream_.EmitFixupU32();
  PushLabel(LabelKind::Block, Istream::kInvalidOffset, fixup);
  return Result::Ok;
}

Result BinaryReaderInterp::OnElseExpr() {
  CHECK_RESULT(validator_.OnElse(GetLocation()));
  Label* label = TopLabel();
  Istream::Offset fixup_cond_offset = label->fixup_offset;
  istream_.Emit(Opcode::Br);
  label->fixup_offset = istream_.EmitFixupU32();
  istream_.ResolveFixupU32(fixup_cond_offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnEndExpr() {
  if (label_stack_.size() == 1) {
    return Result::Ok;
  }
  SharedValidator::Label* label;
  CHECK_RESULT(validator_.GetLabel(0, &label));
  LabelType label_type = label->label_type;
  CHECK_RESULT(validator_.OnEnd(GetLocation()));
  if (label_type == LabelType::If || label_type == LabelType::Else) {
    istream_.ResolveFixupU32(TopLabel()->fixup_offset);
  } else if (label_type == LabelType::Try) {
    // Catch-less try blocks need to fill in the handler description
    // so that it can trigger an exception rethrow when it's reached.
    Label* local_label = TopLabel();
    HandlerDesc& desc = func_->handlers[local_label->handler_desc_index];
    desc.try_end_offset = istream_.end();
    assert(desc.catches.size() == 0);
  } else if (label_type == LabelType::Catch) {
    istream_.EmitCatchDrop(1);
  }
  FixupTopLabel();
  PopLabel();
  return Result::Ok;
}

Result BinaryReaderInterp::OnBrExpr(Index depth) {
  Index drop_count, keep_count, catch_drop_count;
  CHECK_RESULT(GetBrDropKeepCount(depth, &drop_count, &keep_count));
  CHECK_RESULT(validator_.GetCatchCount(depth, &catch_drop_count));
  CHECK_RESULT(validator_.OnBr(GetLocation(), Var(depth, GetLocation())));
  EmitBr(depth, drop_count, keep_count, catch_drop_count);
  return Result::Ok;
}

Result BinaryReaderInterp::OnBrIfExpr(Index depth) {
  Index drop_count, keep_count, catch_drop_count;
  CHECK_RESULT(validator_.OnBrIf(GetLocation(), Var(depth, GetLocation())));
  CHECK_RESULT(GetBrDropKeepCount(depth, &drop_count, &keep_count));
  CHECK_RESULT(validator_.GetCatchCount(depth, &catch_drop_count));
  // Flip the br_if so if <cond> is true it can drop values from the stack.
  istream_.Emit(Opcode::InterpBrUnless);
  auto fixup = istream_.EmitFixupU32();
  EmitBr(depth, drop_count, keep_count, catch_drop_count);
  istream_.ResolveFixupU32(fixup);
  return Result::Ok;
}

Result BinaryReaderInterp::OnBrTableExpr(Index num_targets,
                                         Index* target_depths,
                                         Index default_target_depth) {
  CHECK_RESULT(validator_.BeginBrTable(GetLocation()));
  Index drop_count, keep_count, catch_drop_count;
  istream_.Emit(Opcode::BrTable, num_targets);

  for (Index i = 0; i < num_targets; ++i) {
    Index depth = target_depths[i];
    CHECK_RESULT(
        validator_.OnBrTableTarget(GetLocation(), Var(depth, GetLocation())));
    CHECK_RESULT(GetBrDropKeepCount(depth, &drop_count, &keep_count));
    CHECK_RESULT(validator_.GetCatchCount(depth, &catch_drop_count));
    // Emit DropKeep directly (instead of using EmitDropKeep) so the
    // instruction has a fixed size. Same for CatchDrop as well.
    istream_.Emit(Opcode::InterpDropKeep, drop_count, keep_count);
    istream_.Emit(Opcode::InterpCatchDrop, catch_drop_count);
    EmitBr(depth, 0, 0, 0);
  }
  CHECK_RESULT(validator_.OnBrTableTarget(
      GetLocation(), Var(default_target_depth, GetLocation())));
  CHECK_RESULT(
      GetBrDropKeepCount(default_target_depth, &drop_count, &keep_count));
  CHECK_RESULT(
      validator_.GetCatchCount(default_target_depth, &catch_drop_count));
  // The default case doesn't need a fixed size, since it is never jumped over.
  istream_.EmitDropKeep(drop_count, keep_count);
  istream_.Emit(Opcode::InterpCatchDrop, catch_drop_count);
  EmitBr(default_target_depth, 0, 0, 0);

  CHECK_RESULT(validator_.EndBrTable(GetLocation()));
  return Result::Ok;
}

Result BinaryReaderInterp::OnCallExpr(Index func_index) {
  CHECK_RESULT(
      validator_.OnCall(GetLocation(), Var(func_index, GetLocation())));

  if (func_index >= num_func_imports()) {
    istream_.Emit(Opcode::Call, func_index);
  } else {
    istream_.Emit(Opcode::InterpCallImport, func_index);
  }

  return Result::Ok;
}

Result BinaryReaderInterp::OnCallIndirectExpr(Index sig_index,
                                              Index table_index) {
  CHECK_RESULT(validator_.OnCallIndirect(GetLocation(),
                                         Var(sig_index, GetLocation()),
                                         Var(table_index, GetLocation())));
  istream_.Emit(Opcode::CallIndirect, table_index, sig_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnReturnCallExpr(Index func_index) {
  CHECK_RESULT(
      validator_.OnReturnCall(GetLocation(), Var(func_index, GetLocation())));

  FuncType& func_type = func_types_[func_index];

  Index drop_count, keep_count, catch_drop_count;
  CHECK_RESULT(
      GetReturnCallDropKeepCount(func_type, 0, &drop_count, &keep_count));
  CHECK_RESULT(
      validator_.GetCatchCount(label_stack_.size() - 1, &catch_drop_count));
  // The validator must be run after we get the drop/keep counts, since it
  // will change the type stack.
  CHECK_RESULT(
      validator_.OnReturnCall(GetLocation(), Var(func_index, GetLocation())));
  istream_.EmitDropKeep(drop_count, keep_count);
  istream_.EmitCatchDrop(catch_drop_count);

  if (func_index >= num_func_imports()) {
    istream_.Emit(Opcode::InterpAdjustFrameForReturnCall, func_index);
    istream_.Emit(Opcode::Br);
    // We emit this separately to ensure that the fixup generated by
    // GetFuncOffset comes after the Br opcode.
    istream_.Emit(GetFuncOffset(func_index));
  } else {
    istream_.Emit(Opcode::InterpCallImport, func_index);
    istream_.Emit(Opcode::Return);
  }

  return Result::Ok;
}

Result BinaryReaderInterp::OnReturnCallIndirectExpr(Index sig_index,
                                                    Index table_index) {
  CHECK_RESULT(validator_.OnReturnCallIndirect(
      GetLocation(), Var(sig_index, GetLocation()),
      Var(table_index, GetLocation())));

  FuncType& func_type = module_.func_types[sig_index];

  Index drop_count, keep_count, catch_drop_count;
  // +1 to include the index of the function.
  CHECK_RESULT(
      GetReturnCallDropKeepCount(func_type, +1, &drop_count, &keep_count));
  CHECK_RESULT(
      validator_.GetCatchCount(label_stack_.size() - 1, &catch_drop_count));
  // The validator must be run after we get the drop/keep counts, since it
  // changes the type stack.
  CHECK_RESULT(validator_.OnReturnCallIndirect(
      GetLocation(), Var(sig_index, GetLocation()),
      Var(table_index, GetLocation())));
  istream_.EmitDropKeep(drop_count, keep_count);
  istream_.EmitCatchDrop(catch_drop_count);
  istream_.Emit(Opcode::ReturnCallIndirect, table_index, sig_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnCompareExpr(Opcode opcode) {
  CHECK_RESULT(validator_.OnCompare(GetLocation(), opcode));
  istream_.Emit(opcode);
  return Result::Ok;
}

Result BinaryReaderInterp::OnConvertExpr(Opcode opcode) {
  CHECK_RESULT(validator_.OnConvert(GetLocation(), opcode));
  istream_.Emit(opcode);
  return Result::Ok;
}

Result BinaryReaderInterp::OnDropExpr() {
  CHECK_RESULT(validator_.OnDrop(GetLocation()));
  istream_.Emit(Opcode::Drop);
  return Result::Ok;
}

Result BinaryReaderInterp::OnI32ConstExpr(uint32_t value) {
  CHECK_RESULT(validator_.OnConst(GetLocation(), Type::I32));
  istream_.Emit(Opcode::I32Const, value);
  return Result::Ok;
}

Result BinaryReaderInterp::OnI64ConstExpr(uint64_t value) {
  CHECK_RESULT(validator_.OnConst(GetLocation(), Type::I64));
  istream_.Emit(Opcode::I64Const, value);
  return Result::Ok;
}

Result BinaryReaderInterp::OnF32ConstExpr(uint32_t value_bits) {
  CHECK_RESULT(validator_.OnConst(GetLocation(), Type::F32));
  istream_.Emit(Opcode::F32Const, value_bits);
  return Result::Ok;
}

Result BinaryReaderInterp::OnF64ConstExpr(uint64_t value_bits) {
  CHECK_RESULT(validator_.OnConst(GetLocation(), Type::F64));
  istream_.Emit(Opcode::F64Const, value_bits);
  return Result::Ok;
}

Result BinaryReaderInterp::OnV128ConstExpr(v128 value_bits) {
  CHECK_RESULT(validator_.OnConst(GetLocation(), Type::V128));
  istream_.Emit(Opcode::V128Const, value_bits);
  return Result::Ok;
}

Result BinaryReaderInterp::OnGlobalGetExpr(Index global_index) {
  CHECK_RESULT(
      validator_.OnGlobalGet(GetLocation(), Var(global_index, GetLocation())));
  istream_.Emit(Opcode::GlobalGet, global_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnGlobalSetExpr(Index global_index) {
  CHECK_RESULT(
      validator_.OnGlobalSet(GetLocation(), Var(global_index, GetLocation())));
  istream_.Emit(Opcode::GlobalSet, global_index);
  return Result::Ok;
}

Index BinaryReaderInterp::TranslateLocalIndex(Index local_index) {
  return validator_.type_stack_size() + validator_.GetLocalCount() -
         local_index;
}

Result BinaryReaderInterp::OnLocalGetExpr(Index local_index) {
  // Get the translated index before calling validator_.OnLocalGet because it
  // will update the type stack size. We need the index to be relative to the
  // old stack size.
  Index translated_local_index = TranslateLocalIndex(local_index);
  CHECK_RESULT(
      validator_.OnLocalGet(GetLocation(), Var(local_index, GetLocation())));
  istream_.Emit(Opcode::LocalGet, translated_local_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnLocalSetExpr(Index local_index) {
  // See comment in OnLocalGetExpr above.
  Index translated_local_index = TranslateLocalIndex(local_index);
  CHECK_RESULT(
      validator_.OnLocalSet(GetLocation(), Var(local_index, GetLocation())));
  istream_.Emit(Opcode::LocalSet, translated_local_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnLocalTeeExpr(Index local_index) {
  CHECK_RESULT(
      validator_.OnLocalTee(GetLocation(), Var(local_index, GetLocation())));
  istream_.Emit(Opcode::LocalTee, TranslateLocalIndex(local_index));
  return Result::Ok;
}

Result BinaryReaderInterp::OnLoadExpr(Opcode opcode,
                                      Index memidx,
                                      Address align_log2,
                                      Address offset) {
  CHECK_RESULT(validator_.OnLoad(GetLocation(), opcode,
                                 Var(memidx, GetLocation()),
                                 GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnStoreExpr(Opcode opcode,
                                       Index memidx,
                                       Address align_log2,
                                       Address offset) {
  CHECK_RESULT(validator_.OnStore(GetLocation(), opcode,
                                  Var(memidx, GetLocation()),
                                  GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnMemoryGrowExpr(Index memidx) {
  CHECK_RESULT(
      validator_.OnMemoryGrow(GetLocation(), Var(memidx, GetLocation())));
  istream_.Emit(Opcode::MemoryGrow, memidx);
  return Result::Ok;
}

Result BinaryReaderInterp::OnMemorySizeExpr(Index memidx) {
  CHECK_RESULT(
      validator_.OnMemorySize(GetLocation(), Var(memidx, GetLocation())));
  istream_.Emit(Opcode::MemorySize, memidx);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTableGrowExpr(Index table_index) {
  CHECK_RESULT(
      validator_.OnTableGrow(GetLocation(), Var(table_index, GetLocation())));
  istream_.Emit(Opcode::TableGrow, table_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTableSizeExpr(Index table_index) {
  CHECK_RESULT(
      validator_.OnTableSize(GetLocation(), Var(table_index, GetLocation())));
  istream_.Emit(Opcode::TableSize, table_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTableFillExpr(Index table_index) {
  CHECK_RESULT(
      validator_.OnTableFill(GetLocation(), Var(table_index, GetLocation())));
  istream_.Emit(Opcode::TableFill, table_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnRefFuncExpr(Index func_index) {
  CHECK_RESULT(
      validator_.OnRefFunc(GetLocation(), Var(func_index, GetLocation())));
  istream_.Emit(Opcode::RefFunc, func_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnRefNullExpr(Type type) {
  CHECK_RESULT(validator_.OnRefNull(GetLocation(), type));
  istream_.Emit(Opcode::RefNull);
  return Result::Ok;
}

Result BinaryReaderInterp::OnRefIsNullExpr() {
  CHECK_RESULT(validator_.OnRefIsNull(GetLocation()));
  istream_.Emit(Opcode::RefIsNull);
  return Result::Ok;
}

Result BinaryReaderInterp::OnNopExpr() {
  CHECK_RESULT(validator_.OnNop(GetLocation()));
  return Result::Ok;
}

Result BinaryReaderInterp::OnReturnExpr() {
  Index drop_count, keep_count, catch_drop_count;
  CHECK_RESULT(GetReturnDropKeepCount(&drop_count, &keep_count));
  CHECK_RESULT(
      validator_.GetCatchCount(label_stack_.size() - 1, &catch_drop_count));
  CHECK_RESULT(validator_.OnReturn(GetLocation()));
  istream_.EmitDropKeep(drop_count, keep_count);
  istream_.EmitCatchDrop(catch_drop_count);
  istream_.Emit(Opcode::Return);
  return Result::Ok;
}

Result BinaryReaderInterp::OnSelectExpr(Index result_count,
                                        Type* result_types) {
  CHECK_RESULT(validator_.OnSelect(GetLocation(), result_count, result_types));
  istream_.Emit(Opcode::Select);
  return Result::Ok;
}

Result BinaryReaderInterp::OnUnreachableExpr() {
  CHECK_RESULT(validator_.OnUnreachable(GetLocation()));
  istream_.Emit(Opcode::Unreachable);
  return Result::Ok;
}

Result BinaryReaderInterp::OnAtomicWaitExpr(Opcode opcode,
                                            Index memidx,
                                            Address align_log2,
                                            Address offset) {
  CHECK_RESULT(validator_.OnAtomicWait(GetLocation(), opcode,
                                       Var(memidx, GetLocation()),
                                       GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnAtomicFenceExpr(uint32_t consistency_model) {
  CHECK_RESULT(validator_.OnAtomicFence(GetLocation(), consistency_model));
  istream_.Emit(Opcode::AtomicFence, consistency_model);
  return Result::Ok;
}

Result BinaryReaderInterp::OnAtomicNotifyExpr(Opcode opcode,
                                              Index memidx,
                                              Address align_log2,
                                              Address offset) {
  CHECK_RESULT(validator_.OnAtomicNotify(GetLocation(), opcode,
                                         Var(memidx, GetLocation()),
                                         GetAlignment(align_log2), offset));
  istream_.Emit(opcode, memidx, offset);
  return Result::Ok;
}

Result BinaryReaderInterp::OnMemoryCopyExpr(Index destmemidx, Index srcmemidx) {
  CHECK_RESULT(validator_.OnMemoryCopy(GetLocation(),
                                       Var(destmemidx, GetLocation()),
                                       Var(srcmemidx, GetLocation())));
  istream_.Emit(Opcode::MemoryCopy, destmemidx, srcmemidx);
  return Result::Ok;
}

Result BinaryReaderInterp::OnDataDropExpr(Index segment_index) {
  CHECK_RESULT(
      validator_.OnDataDrop(GetLocation(), Var(segment_index, GetLocation())));
  istream_.Emit(Opcode::DataDrop, segment_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnMemoryFillExpr(Index memidx) {
  CHECK_RESULT(
      validator_.OnMemoryFill(GetLocation(), Var(memidx, GetLocation())));
  istream_.Emit(Opcode::MemoryFill, memidx);
  return Result::Ok;
}

Result BinaryReaderInterp::OnMemoryInitExpr(Index segment_index, Index memidx) {
  CHECK_RESULT(validator_.OnMemoryInit(GetLocation(),
                                       Var(segment_index, GetLocation()),
                                       Var(memidx, GetLocation())));
  istream_.Emit(Opcode::MemoryInit, memidx, segment_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTableGetExpr(Index table_index) {
  CHECK_RESULT(
      validator_.OnTableGet(GetLocation(), Var(table_index, GetLocation())));
  istream_.Emit(Opcode::TableGet, table_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTableSetExpr(Index table_index) {
  CHECK_RESULT(
      validator_.OnTableSet(GetLocation(), Var(table_index, GetLocation())));
  istream_.Emit(Opcode::TableSet, table_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTableCopyExpr(Index dst_index, Index src_index) {
  CHECK_RESULT(validator_.OnTableCopy(GetLocation(),
                                      Var(dst_index, GetLocation()),
                                      Var(src_index, GetLocation())));
  istream_.Emit(Opcode::TableCopy, dst_index, src_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnElemDropExpr(Index segment_index) {
  CHECK_RESULT(
      validator_.OnElemDrop(GetLocation(), Var(segment_index, GetLocation())));
  istream_.Emit(Opcode::ElemDrop, segment_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTableInitExpr(Index segment_index,
                                           Index table_index) {
  CHECK_RESULT(validator_.OnTableInit(GetLocation(),
                                      Var(segment_index, GetLocation()),
                                      Var(table_index, GetLocation())));
  istream_.Emit(Opcode::TableInit, table_index, segment_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnThrowExpr(Index tag_index) {
  CHECK_RESULT(
      validator_.OnThrow(GetLocation(), Var(tag_index, GetLocation())));
  istream_.Emit(Opcode::Throw, tag_index);
  return Result::Ok;
}

Result BinaryReaderInterp::OnRethrowExpr(Index depth) {
  Index catch_depth;
  CHECK_RESULT(validator_.OnRethrow(GetLocation(), Var(depth, GetLocation())));
  CHECK_RESULT(validator_.GetCatchCount(depth, &catch_depth));
  // The rethrow opcode takes an index into the exception stack rather than
  // the number of catch nestings, so we subtract one here.
  istream_.Emit(Opcode::Rethrow, catch_depth - 1);
  return Result::Ok;
}

Result BinaryReaderInterp::OnTryExpr(Type sig_type) {
  u32 exn_stack_height;
  CHECK_RESULT(
      validator_.GetCatchCount(label_stack_.size() - 1, &exn_stack_height));
  u32 value_stack_height = validator_.type_stack_size();
  CHECK_RESULT(validator_.OnTry(GetLocation(), sig_type));
  // Push a label that tracks mapping of exn -> catch
  PushLabel(LabelKind::Try, Istream::kInvalidOffset, Istream::kInvalidOffset,
            func_->handlers.size());
  func_->handlers.push_back(HandlerDesc{HandlerKind::Catch,
                                        istream_.end(),
                                        Istream::kInvalidOffset,
                                        {},
                                        {Istream::kInvalidOffset},
                                        value_stack_height,
                                        exn_stack_height});
  return Result::Ok;
}

Result BinaryReaderInterp::OnCatchExpr(Index tag_index) {
  CHECK_RESULT(
      validator_.OnCatch(GetLocation(), Var(tag_index, GetLocation()), false));
  Label* label = TopLabel();
  HandlerDesc& desc = func_->handlers[label->handler_desc_index];
  desc.kind = HandlerKind::Catch;
  // Drop the previous block's exception if it was a catch.
  if (label->kind == LabelKind::Block) {
    istream_.EmitCatchDrop(1);
  }
  // Jump to the end of the block at the end of the previous try or catch.
  Istream::Offset offset = label->offset;
  istream_.Emit(Opcode::Br);
  assert(offset == Istream::kInvalidOffset);
  depth_fixups_.Append(label_stack_.size() - 1, istream_.end());
  istream_.Emit(offset);
  // The offset is only set after the first catch block, as the offset range
  // should only cover the try block itself.
  if (desc.try_end_offset == Istream::kInvalidOffset) {
    desc.try_end_offset = istream_.end();
  }
  // The label kind is switched to Block from Try in order to distinguish
  // catch blocks from try blocks. This is used to ensure that a try-delegate
  // inside this catch will not delegate to the catch, and instead find outer
  // try blocks to use as a delegate target.
  label->kind = LabelKind::Block;
  desc.catches.push_back(CatchDesc{tag_index, istream_.end()});
  return Result::Ok;
}

Result BinaryReaderInterp::OnCatchAllExpr() {
  CHECK_RESULT(validator_.OnCatch(GetLocation(), Var(), true));
  Label* label = TopLabel();
  HandlerDesc& desc = func_->handlers[label->handler_desc_index];
  desc.kind = HandlerKind::Catch;
  if (label->kind == LabelKind::Block) {
    istream_.EmitCatchDrop(1);
  }
  Istream::Offset offset = label->offset;
  istream_.Emit(Opcode::Br);
  assert(offset == Istream::kInvalidOffset);
  depth_fixups_.Append(label_stack_.size() - 1, istream_.end());
  istream_.Emit(offset);
  if (desc.try_end_offset == Istream::kInvalidOffset) {
    desc.try_end_offset = istream_.end();
  }
  label->kind = LabelKind::Block;
  desc.catch_all_offset = istream_.end();
  return Result::Ok;
}

Result BinaryReaderInterp::OnDelegateExpr(Index depth) {
  CHECK_RESULT(validator_.OnDelegate(GetLocation(), Var(depth, GetLocation())));
  Label* label = TopLabel();
  assert(label->kind == LabelKind::Try);
  HandlerDesc& desc = func_->handlers[label->handler_desc_index];
  desc.kind = HandlerKind::Delegate;
  Istream::Offset offset = label->offset;
  istream_.Emit(Opcode::Br);
  assert(offset == Istream::kInvalidOffset);
  depth_fixups_.Append(label_stack_.size() - 1, istream_.end());
  istream_.Emit(offset);
  desc.try_end_offset = istream_.end();
  Label* target_label = GetNearestTryLabel(depth + 1);
  assert(target_label);
  desc.delegate_handler_index = target_label->handler_desc_index;
  FixupTopLabel();
  PopLabel();
  return Result::Ok;
}

}  // namespace

Result ReadBinaryInterp(std::string_view filename,
                        const void* data,
                        size_t size,
                        const ReadBinaryOptions& options,
                        Errors* errors,
                        ModuleDesc* out_module) {
  BinaryReaderInterp reader(out_module, filename, errors, options.features);
  return ReadBinary(data, size, &reader, options);
}

}  // namespace interp
}  // namespace wabt