/*
 * 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/binary-reader-ir.h"

#include <cassert>
#include <cinttypes>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <deque>
#include <vector>

#include "wabt/binary-reader-nop.h"
#include "wabt/cast.h"
#include "wabt/common.h"
#include "wabt/ir.h"

namespace wabt {

namespace {

struct LabelNode {
  LabelNode(LabelType, ExprList* exprs, Expr* context = nullptr);

  LabelType label_type;
  ExprList* exprs;
  Expr* context;
};

LabelNode::LabelNode(LabelType label_type, ExprList* exprs, Expr* context)
    : label_type(label_type), exprs(exprs), context(context) {}

class CodeMetadataExprQueue {
 private:
  struct Entry {
    Func* func;
    std::deque<std::unique_ptr<CodeMetadataExpr>> func_queue;
    Entry(Func* f) : func(f) {}
  };
  std::deque<Entry> entries;

 public:
  CodeMetadataExprQueue() {}
  void push_func(Func* f) { entries.emplace_back(f); }
  void push_metadata(std::unique_ptr<CodeMetadataExpr> meta) {
    assert(!entries.empty());
    entries.back().func_queue.push_back(std::move(meta));
  }

  std::unique_ptr<CodeMetadataExpr> pop_match(Func* f, Offset offset) {
    std::unique_ptr<CodeMetadataExpr> ret;
    if (entries.empty()) {
      return ret;
    }

    auto& current_entry = entries.front();

    if (current_entry.func != f)
      return ret;
    if (current_entry.func_queue.empty()) {
      entries.pop_front();
      return ret;
    }

    auto& current_metadata = current_entry.func_queue.front();
    if (current_metadata->loc.offset + current_entry.func->loc.offset !=
        offset) {
      return ret;
    }

    current_metadata->loc = Location(offset);
    ret = std::move(current_metadata);
    current_entry.func_queue.pop_front();

    return ret;
  }
};

class BinaryReaderIR : public BinaryReaderNop {
  static constexpr size_t kMaxNestingDepth = 16384;  // max depth of label stack
  static constexpr size_t kMaxFunctionLocals = 50000;  // matches V8
  static constexpr size_t kMaxFunctionParams = 1000;   // matches V8
  static constexpr size_t kMaxFunctionResults = 1000;  // matches V8

 public:
  BinaryReaderIR(Module* out_module, const char* filename, Errors* errors);

  bool OnError(const Error&) override;

  Result OnTypeCount(Index count) override;
  Result OnFuncType(Index index,
                    Index param_count,
                    Type* param_types,
                    Index result_count,
                    Type* result_types) override;
  Result OnStructType(Index index, Index field_count, TypeMut* fields) override;
  Result OnArrayType(Index index, TypeMut field) override;

  Result OnImportCount(Index count) 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,
                        uint32_t page_size) 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,
                  uint32_t page_size) 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 OnExportCount(Index count) override;
  Result OnExport(Index index,
                  ExternalKind kind,
                  Index item_index,
                  std::string_view name) override;

  Result OnStartFunction(Index func_index) override;

  Result OnFunctionBodyCount(Index count) override;
  Result BeginFunctionBody(Index index, Offset size) 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 OnCatchExpr(Index tag_index) override;
  Result OnCatchAllExpr() override;
  Result OnCallIndirectExpr(Index sig_index, Index table_index) override;
  Result OnCallRefExpr() 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 OnDelegateExpr(Index depth) 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 OnMemoryFillExpr(Index memidx) override;
  Result OnMemoryGrowExpr(Index memidx) override;
  Result OnMemoryInitExpr(Index segment_index, Index memidx) override;
  Result OnMemorySizeExpr(Index memidx) override;
  Result OnTableCopyExpr(Index dst_index, Index src_index) override;
  Result OnElemDropExpr(Index segment_index) override;
  Result OnTableInitExpr(Index segment_index, Index table_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 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 OnThrowExpr(Index tag_index) override;
  Result OnTryExpr(Type sig_type) override;
  Result OnUnaryExpr(Opcode opcode) override;
  Result OnTernaryExpr(Opcode opcode) 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 OnDataSegmentCount(Index count) override;
  Result BeginDataSegment(Index index,
                          Index memory_index,
                          uint8_t flags) override;
  Result BeginDataSegmentInitExpr(Index index) override;
  Result EndDataSegmentInitExpr(Index index) override;
  Result OnDataSegmentData(Index index,
                           const void* data,
                           Address size) override;

  Result OnModuleName(std::string_view module_name) override;
  Result OnFunctionNamesCount(Index num_functions) override;
  Result OnFunctionName(Index function_index,
                        std::string_view function_name) override;
  Result OnLocalNameLocalCount(Index function_index, Index num_locals) override;
  Result OnLocalName(Index function_index,
                     Index local_index,
                     std::string_view local_name) override;
  Result OnNameEntry(NameSectionSubsection type,
                     Index index,
                     std::string_view name) override;

  Result OnGenericCustomSection(std::string_view name,
                                const void* data,
                                Offset size) override;

  Result BeginTagSection(Offset size) override { return Result::Ok; }
  Result OnTagCount(Index count) override { return Result::Ok; }
  Result OnTagType(Index index, Index sig_index) override;
  Result EndTagSection() override { return Result::Ok; }

  Result OnDataSymbol(Index index,
                      uint32_t flags,
                      std::string_view name,
                      Index segment,
                      uint32_t offset,
                      uint32_t size) override;
  Result OnFunctionSymbol(Index index,
                          uint32_t flags,
                          std::string_view name,
                          Index func_index) override;
  Result OnGlobalSymbol(Index index,
                        uint32_t flags,
                        std::string_view name,
                        Index global_index) override;
  Result OnSectionSymbol(Index index,
                         uint32_t flags,
                         Index section_index) override;
  /* Code Metadata sections */
  Result BeginCodeMetadataSection(std::string_view name, Offset size) override;
  Result OnCodeMetadataFuncCount(Index count) override;
  Result OnCodeMetadataCount(Index function_index, Index count) override;
  Result OnCodeMetadata(Offset offset, const void* data, Address size) override;

  Result OnTagSymbol(Index index,
                     uint32_t flags,
                     std::string_view name,
                     Index tag_index) override;
  Result OnTableSymbol(Index index,
                       uint32_t flags,
                       std::string_view name,
                       Index table_index) override;

 private:
  Location GetLocation() const;
  void PrintError(const char* format, ...);
  Result PushLabel(LabelType label_type,
                   ExprList* first,
                   Expr* context = nullptr);
  Result BeginInitExpr(ExprList* init_expr);
  Result EndInitExpr();
  Result PopLabel();
  Result GetLabelAt(LabelNode** label, Index depth);
  Result TopLabel(LabelNode** label);
  Result TopLabelExpr(LabelNode** label, Expr** expr);
  Result AppendExpr(std::unique_ptr<Expr> expr);
  Result AppendCatch(Catch&& catch_);
  void SetFuncDeclaration(FuncDeclaration* decl, Var var);
  void SetBlockDeclaration(BlockDeclaration* decl, Type sig_type);
  Result SetMemoryName(Index index, std::string_view name);
  Result SetTableName(Index index, std::string_view name);
  Result SetFunctionName(Index index, std::string_view name);
  Result SetTypeName(Index index, std::string_view name);
  Result SetGlobalName(Index index, std::string_view name);
  Result SetDataSegmentName(Index index, std::string_view name);
  Result SetElemSegmentName(Index index, std::string_view name);
  Result SetTagName(Index index, std::string_view name);

  std::string GetUniqueName(BindingHash* bindings,
                            const std::string& original_name);

  Errors* errors_ = nullptr;
  Module* module_ = nullptr;

  Func* current_func_ = nullptr;
  std::vector<LabelNode> label_stack_;
  const char* filename_;

  CodeMetadataExprQueue code_metadata_queue_;
  std::string_view current_metadata_name_;
};

BinaryReaderIR::BinaryReaderIR(Module* out_module,
                               const char* filename,
                               Errors* errors)
    : errors_(errors), module_(out_module), filename_(filename) {}

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

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

Result BinaryReaderIR::PushLabel(LabelType label_type,
                                 ExprList* first,
                                 Expr* context) {
  if (label_stack_.size() >= kMaxNestingDepth) {
    PrintError("label stack exceeds max nesting depth");
    return Result::Error;
  }
  label_stack_.emplace_back(label_type, first, context);
  return Result::Ok;
}

Result BinaryReaderIR::PopLabel() {
  if (label_stack_.size() == 0) {
    PrintError("popping empty label stack");
    return Result::Error;
  }

  label_stack_.pop_back();
  return Result::Ok;
}

Result BinaryReaderIR::GetLabelAt(LabelNode** label, Index depth) {
  if (depth >= label_stack_.size()) {
    PrintError("accessing stack depth: %" PRIindex " >= max: %" PRIzd, depth,
               label_stack_.size());
    return Result::Error;
  }

  *label = &label_stack_[label_stack_.size() - depth - 1];
  return Result::Ok;
}

Result BinaryReaderIR::TopLabel(LabelNode** label) {
  return GetLabelAt(label, 0);
}

Result BinaryReaderIR::TopLabelExpr(LabelNode** label, Expr** expr) {
  CHECK_RESULT(TopLabel(label));
  LabelNode* parent_label;
  CHECK_RESULT(GetLabelAt(&parent_label, 1));
  if (parent_label->exprs->empty()) {
    PrintError("TopLabelExpr: parent label has empty expr list");
    return Result::Error;
  }
  *expr = &parent_label->exprs->back();
  return Result::Ok;
}

Result BinaryReaderIR::AppendExpr(std::unique_ptr<Expr> expr) {
  expr->loc = GetLocation();
  LabelNode* label;
  CHECK_RESULT(TopLabel(&label));
  label->exprs->push_back(std::move(expr));
  return Result::Ok;
}

void BinaryReaderIR::SetFuncDeclaration(FuncDeclaration* decl, Var var) {
  decl->has_func_type = true;
  decl->type_var = var;
  if (auto* func_type = module_->GetFuncType(var)) {
    decl->sig = func_type->sig;
  }
}

void BinaryReaderIR::SetBlockDeclaration(BlockDeclaration* decl,
                                         Type sig_type) {
  if (sig_type.IsIndex()) {
    Index type_index = sig_type.GetIndex();
    SetFuncDeclaration(decl, Var(type_index, GetLocation()));
  } else {
    decl->has_func_type = false;
    decl->sig.param_types.clear();
    decl->sig.result_types = sig_type.GetInlineVector();
  }
}

std::string BinaryReaderIR::GetUniqueName(BindingHash* bindings,
                                          const std::string& orig_name) {
  int counter = 1;
  std::string unique_name = orig_name;
  while (bindings->count(unique_name) != 0) {
    unique_name = orig_name + "." + std::to_string(counter++);
  }
  return unique_name;
}

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

Result BinaryReaderIR::OnTypeCount(Index count) {
  WABT_TRY
  module_->types.reserve(count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::OnFuncType(Index index,
                                  Index param_count,
                                  Type* param_types,
                                  Index result_count,
                                  Type* result_types) {
  if (param_count > kMaxFunctionParams) {
    PrintError("FuncType param count exceeds maximum value");
    return Result::Error;
  }

  if (result_count > kMaxFunctionResults) {
    PrintError("FuncType result count exceeds maximum value");
    return Result::Error;
  }

  auto field = std::make_unique<TypeModuleField>(GetLocation());
  auto func_type = std::make_unique<FuncType>();
  func_type->sig.param_types.assign(param_types, param_types + param_count);
  func_type->sig.result_types.assign(result_types, result_types + result_count);

  module_->features_used.simd |=
      std::any_of(func_type->sig.param_types.begin(),
                  func_type->sig.param_types.end(),
                  [](auto x) { return x == Type::V128; }) ||
      std::any_of(func_type->sig.result_types.begin(),
                  func_type->sig.result_types.end(),
                  [](auto x) { return x == Type::V128; });
  module_->features_used.exceptions |=
      std::any_of(func_type->sig.param_types.begin(),
                  func_type->sig.param_types.end(),
                  [](auto x) { return x == Type::ExnRef; }) ||
      std::any_of(func_type->sig.result_types.begin(),
                  func_type->sig.result_types.end(),
                  [](auto x) { return x == Type::ExnRef; });

  field->type = std::move(func_type);
  module_->AppendField(std::move(field));
  return Result::Ok;
}

Result BinaryReaderIR::OnStructType(Index index,
                                    Index field_count,
                                    TypeMut* fields) {
  auto field = std::make_unique<TypeModuleField>(GetLocation());
  auto struct_type = std::make_unique<StructType>();
  struct_type->fields.resize(field_count);
  for (Index i = 0; i < field_count; ++i) {
    struct_type->fields[i].type = fields[i].type;
    struct_type->fields[i].mutable_ = fields[i].mutable_;
    module_->features_used.simd |= (fields[i].type == Type::V128);
    module_->features_used.exceptions |= (fields[i].type == Type::ExnRef);
  }
  field->type = std::move(struct_type);
  module_->AppendField(std::move(field));
  return Result::Ok;
}

Result BinaryReaderIR::OnArrayType(Index index, TypeMut type_mut) {
  auto field = std::make_unique<TypeModuleField>(GetLocation());
  auto array_type = std::make_unique<ArrayType>();
  array_type->field.type = type_mut.type;
  array_type->field.mutable_ = type_mut.mutable_;
  module_->features_used.simd |= (type_mut.type == Type::V128);
  module_->features_used.exceptions |= (type_mut.type == Type::ExnRef);
  field->type = std::move(array_type);
  module_->AppendField(std::move(field));
  return Result::Ok;
}

Result BinaryReaderIR::OnImportCount(Index count) {
  WABT_TRY
  module_->imports.reserve(count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::OnImportFunc(Index import_index,
                                    std::string_view module_name,
                                    std::string_view field_name,
                                    Index func_index,
                                    Index sig_index) {
  auto import = std::make_unique<FuncImport>();
  import->module_name = module_name;
  import->field_name = field_name;
  SetFuncDeclaration(&import->func.decl, Var(sig_index, GetLocation()));
  module_->AppendField(
      std::make_unique<ImportModuleField>(std::move(import), GetLocation()));
  return Result::Ok;
}

Result BinaryReaderIR::OnImportTable(Index import_index,
                                     std::string_view module_name,
                                     std::string_view field_name,
                                     Index table_index,
                                     Type elem_type,
                                     const Limits* elem_limits) {
  auto import = std::make_unique<TableImport>();
  import->module_name = module_name;
  import->field_name = field_name;
  import->table.elem_limits = *elem_limits;
  import->table.elem_type = elem_type;
  module_->AppendField(
      std::make_unique<ImportModuleField>(std::move(import), GetLocation()));
  return Result::Ok;
}

Result BinaryReaderIR::OnImportMemory(Index import_index,
                                      std::string_view module_name,
                                      std::string_view field_name,
                                      Index memory_index,
                                      const Limits* page_limits,
                                      uint32_t page_size) {
  auto import = std::make_unique<MemoryImport>();
  import->module_name = module_name;
  import->field_name = field_name;
  import->memory.page_limits = *page_limits;
  import->memory.page_size = page_size;
  if (import->memory.page_limits.is_shared) {
    module_->features_used.threads = true;
  }
  module_->AppendField(
      std::make_unique<ImportModuleField>(std::move(import), GetLocation()));
  return Result::Ok;
}

Result BinaryReaderIR::OnImportGlobal(Index import_index,
                                      std::string_view module_name,
                                      std::string_view field_name,
                                      Index global_index,
                                      Type type,
                                      bool mutable_) {
  auto import = std::make_unique<GlobalImport>();
  import->module_name = module_name;
  import->field_name = field_name;
  import->global.type = type;
  import->global.mutable_ = mutable_;
  module_->AppendField(
      std::make_unique<ImportModuleField>(std::move(import), GetLocation()));
  module_->features_used.simd |= (type == Type::V128);
  module_->features_used.exceptions |= (type == Type::ExnRef);
  return Result::Ok;
}

Result BinaryReaderIR::OnImportTag(Index import_index,
                                   std::string_view module_name,
                                   std::string_view field_name,
                                   Index tag_index,
                                   Index sig_index) {
  auto import = std::make_unique<TagImport>();
  import->module_name = module_name;
  import->field_name = field_name;
  SetFuncDeclaration(&import->tag.decl, Var(sig_index, GetLocation()));
  module_->AppendField(
      std::make_unique<ImportModuleField>(std::move(import), GetLocation()));
  module_->features_used.exceptions = true;
  return Result::Ok;
}

Result BinaryReaderIR::OnFunctionCount(Index count) {
  WABT_TRY
  module_->funcs.reserve(module_->num_func_imports + count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::OnFunction(Index index, Index sig_index) {
  auto field = std::make_unique<FuncModuleField>(GetLocation());
  Func& func = field->func;
  SetFuncDeclaration(&func.decl, Var(sig_index, GetLocation()));
  module_->AppendField(std::move(field));
  return Result::Ok;
}

Result BinaryReaderIR::OnTableCount(Index count) {
  WABT_TRY
  module_->tables.reserve(module_->num_table_imports + count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::OnTable(Index index,
                               Type elem_type,
                               const Limits* elem_limits) {
  auto field = std::make_unique<TableModuleField>(GetLocation());
  Table& table = field->table;
  table.elem_limits = *elem_limits;
  table.elem_type = elem_type;
  module_->features_used.exceptions |= (elem_type == Type::ExnRef);
  module_->AppendField(std::move(field));
  return Result::Ok;
}

Result BinaryReaderIR::OnMemoryCount(Index count) {
  WABT_TRY
  module_->memories.reserve(module_->num_memory_imports + count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::OnMemory(Index index,
                                const Limits* page_limits,
                                uint32_t page_size) {
  auto field = std::make_unique<MemoryModuleField>(GetLocation());
  Memory& memory = field->memory;
  memory.page_limits = *page_limits;
  memory.page_size = page_size;
  if (memory.page_limits.is_shared) {
    module_->features_used.threads = true;
  }
  module_->AppendField(std::move(field));
  return Result::Ok;
}

Result BinaryReaderIR::OnGlobalCount(Index count) {
  WABT_TRY
  module_->globals.reserve(module_->num_global_imports + count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::BeginGlobal(Index index, Type type, bool mutable_) {
  auto field = std::make_unique<GlobalModuleField>(GetLocation());
  Global& global = field->global;
  global.type = type;
  global.mutable_ = mutable_;
  module_->AppendField(std::move(field));
  module_->features_used.simd |= (type == Type::V128);
  module_->features_used.exceptions |= (type == Type::ExnRef);
  return Result::Ok;
}

Result BinaryReaderIR::BeginGlobalInitExpr(Index index) {
  assert(index == module_->globals.size() - 1);
  Global* global = module_->globals[index];
  return BeginInitExpr(&global->init_expr);
}

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

Result BinaryReaderIR::OnExportCount(Index count) {
  WABT_TRY
  module_->exports.reserve(count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::OnExport(Index index,
                                ExternalKind kind,
                                Index item_index,
                                std::string_view name) {
  auto field = std::make_unique<ExportModuleField>(GetLocation());
  Export& export_ = field->export_;
  export_.name = name;
  export_.var = Var(item_index, GetLocation());
  export_.kind = kind;
  module_->AppendField(std::move(field));
  return Result::Ok;
}

Result BinaryReaderIR::OnStartFunction(Index func_index) {
  Var start(func_index, GetLocation());
  module_->AppendField(
      std::make_unique<StartModuleField>(start, GetLocation()));
  return Result::Ok;
}

Result BinaryReaderIR::OnFunctionBodyCount(Index count) {
  // Can hit this case on a malformed module if we don't stop on first error.
  if (module_->num_func_imports + count != module_->funcs.size()) {
    PrintError(
        "number of imported func + func count in code section does not match "
        "actual number of funcs in module");
    return Result::Error;
  }
  return Result::Ok;
}

Result BinaryReaderIR::BeginFunctionBody(Index index, Offset size) {
  current_func_ = module_->funcs[index];
  current_func_->loc = GetLocation();
  return PushLabel(LabelType::Func, &current_func_->exprs);
}

Result BinaryReaderIR::OnLocalDecl(Index decl_index, Index count, Type type) {
  current_func_->local_types.AppendDecl(type, count);

  if (current_func_->GetNumLocals() > kMaxFunctionLocals) {
    PrintError("function local count exceeds maximum value");
    return Result::Error;
  }

  module_->features_used.simd |= (type == Type::V128);
  module_->features_used.exceptions |= (type == Type::ExnRef);
  return Result::Ok;
}

Result BinaryReaderIR::OnOpcode(Opcode opcode) {
  std::unique_ptr<CodeMetadataExpr> metadata =
      code_metadata_queue_.pop_match(current_func_, GetLocation().offset - 1);
  if (metadata) {
    return AppendExpr(std::move(metadata));
  }
  module_->features_used.simd |= (opcode.GetResultType() == Type::V128);
  module_->features_used.threads |= (opcode.GetPrefix() == 0xfe);
  return Result::Ok;
}

Result BinaryReaderIR::OnAtomicLoadExpr(Opcode opcode,
                                        Index memidx,
                                        Address alignment_log2,
                                        Address offset) {
  return AppendExpr(std::make_unique<AtomicLoadExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnAtomicStoreExpr(Opcode opcode,
                                         Index memidx,
                                         Address alignment_log2,
                                         Address offset) {
  return AppendExpr(std::make_unique<AtomicStoreExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnAtomicRmwExpr(Opcode opcode,
                                       Index memidx,
                                       Address alignment_log2,
                                       Address offset) {
  return AppendExpr(std::make_unique<AtomicRmwExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnAtomicRmwCmpxchgExpr(Opcode opcode,
                                              Index memidx,
                                              Address alignment_log2,
                                              Address offset) {
  return AppendExpr(std::make_unique<AtomicRmwCmpxchgExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnAtomicWaitExpr(Opcode opcode,
                                        Index memidx,
                                        Address alignment_log2,
                                        Address offset) {
  return AppendExpr(std::make_unique<AtomicWaitExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnAtomicFenceExpr(uint32_t consistency_model) {
  return AppendExpr(std::make_unique<AtomicFenceExpr>(consistency_model));
}

Result BinaryReaderIR::OnAtomicNotifyExpr(Opcode opcode,
                                          Index memidx,
                                          Address alignment_log2,
                                          Address offset) {
  return AppendExpr(std::make_unique<AtomicNotifyExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnBinaryExpr(Opcode opcode) {
  return AppendExpr(std::make_unique<BinaryExpr>(opcode));
}

Result BinaryReaderIR::OnBlockExpr(Type sig_type) {
  auto expr = std::make_unique<BlockExpr>();
  SetBlockDeclaration(&expr->block.decl, sig_type);
  ExprList* expr_list = &expr->block.exprs;
  CHECK_RESULT(AppendExpr(std::move(expr)));
  return PushLabel(LabelType::Block, expr_list);
}

Result BinaryReaderIR::OnBrExpr(Index depth) {
  return AppendExpr(std::make_unique<BrExpr>(Var(depth, GetLocation())));
}

Result BinaryReaderIR::OnBrIfExpr(Index depth) {
  return AppendExpr(std::make_unique<BrIfExpr>(Var(depth, GetLocation())));
}

Result BinaryReaderIR::OnBrTableExpr(Index num_targets,
                                     Index* target_depths,
                                     Index default_target_depth) {
  auto expr = std::make_unique<BrTableExpr>();
  expr->default_target = Var(default_target_depth, GetLocation());
  expr->targets.resize(num_targets);
  for (Index i = 0; i < num_targets; ++i) {
    expr->targets[i] = Var(target_depths[i], GetLocation());
  }
  return AppendExpr(std::move(expr));
}

Result BinaryReaderIR::OnCallExpr(Index func_index) {
  return AppendExpr(std::make_unique<CallExpr>(Var(func_index, GetLocation())));
}

Result BinaryReaderIR::OnCallIndirectExpr(Index sig_index, Index table_index) {
  auto expr = std::make_unique<CallIndirectExpr>();
  SetFuncDeclaration(&expr->decl, Var(sig_index, GetLocation()));
  expr->table = Var(table_index, GetLocation());
  return AppendExpr(std::move(expr));
}

Result BinaryReaderIR::OnCallRefExpr() {
  return AppendExpr(std::make_unique<CallRefExpr>());
}

Result BinaryReaderIR::OnReturnCallExpr(Index func_index) {
  if (current_func_) {
    // syntactically, a return_call expr can occur in an init expression
    // (outside a function)
    current_func_->features_used.tailcall = true;
  }
  return AppendExpr(
      std::make_unique<ReturnCallExpr>(Var(func_index, GetLocation())));
}

Result BinaryReaderIR::OnReturnCallIndirectExpr(Index sig_index,
                                                Index table_index) {
  if (current_func_) {
    // syntactically, a return_call_indirect expr can occur in an init
    // expression (outside a function)
    current_func_->features_used.tailcall = true;
  }
  auto expr = std::make_unique<ReturnCallIndirectExpr>();
  SetFuncDeclaration(&expr->decl, Var(sig_index, GetLocation()));
  expr->table = Var(table_index, GetLocation());
  FuncType* type = module_->GetFuncType(Var(sig_index, GetLocation()));
  if (type) {
    type->features_used.tailcall = true;
  }
  return AppendExpr(std::move(expr));
}

Result BinaryReaderIR::OnCompareExpr(Opcode opcode) {
  return AppendExpr(std::make_unique<CompareExpr>(opcode));
}

Result BinaryReaderIR::OnConvertExpr(Opcode opcode) {
  return AppendExpr(std::make_unique<ConvertExpr>(opcode));
}

Result BinaryReaderIR::OnDropExpr() {
  return AppendExpr(std::make_unique<DropExpr>());
}

Result BinaryReaderIR::OnElseExpr() {
  LabelNode* label;
  Expr* expr;
  CHECK_RESULT(TopLabelExpr(&label, &expr));

  if (label->label_type == LabelType::If) {
    auto* if_expr = cast<IfExpr>(expr);
    if_expr->true_.end_loc = GetLocation();
    label->exprs = &if_expr->false_;
    label->label_type = LabelType::Else;
  } else {
    PrintError("else expression without matching if");
    return Result::Error;
  }

  return Result::Ok;
}

Result BinaryReaderIR::OnEndExpr() {
  if (label_stack_.size() > 1) {
    LabelNode* label;
    Expr* expr;
    CHECK_RESULT(TopLabelExpr(&label, &expr));
    switch (label->label_type) {
      case LabelType::Block:
        cast<BlockExpr>(expr)->block.end_loc = GetLocation();
        break;
      case LabelType::Loop:
        cast<LoopExpr>(expr)->block.end_loc = GetLocation();
        break;
      case LabelType::If:
        cast<IfExpr>(expr)->true_.end_loc = GetLocation();
        break;
      case LabelType::Else:
        cast<IfExpr>(expr)->false_end_loc = GetLocation();
        break;
      case LabelType::Try:
        cast<TryExpr>(expr)->block.end_loc = GetLocation();
        break;

      case LabelType::InitExpr:
      case LabelType::Func:
      case LabelType::Catch:
        break;
    }
  }

  return PopLabel();
}

Result BinaryReaderIR::OnF32ConstExpr(uint32_t value_bits) {
  return AppendExpr(
      std::make_unique<ConstExpr>(Const::F32(value_bits, GetLocation())));
}

Result BinaryReaderIR::OnF64ConstExpr(uint64_t value_bits) {
  return AppendExpr(
      std::make_unique<ConstExpr>(Const::F64(value_bits, GetLocation())));
}

Result BinaryReaderIR::OnV128ConstExpr(v128 value_bits) {
  return AppendExpr(
      std::make_unique<ConstExpr>(Const::V128(value_bits, GetLocation())));
}

Result BinaryReaderIR::OnGlobalGetExpr(Index global_index) {
  return AppendExpr(
      std::make_unique<GlobalGetExpr>(Var(global_index, GetLocation())));
}

Result BinaryReaderIR::OnLocalGetExpr(Index local_index) {
  return AppendExpr(
      std::make_unique<LocalGetExpr>(Var(local_index, GetLocation())));
}

Result BinaryReaderIR::OnI32ConstExpr(uint32_t value) {
  return AppendExpr(
      std::make_unique<ConstExpr>(Const::I32(value, GetLocation())));
}

Result BinaryReaderIR::OnI64ConstExpr(uint64_t value) {
  return AppendExpr(
      std::make_unique<ConstExpr>(Const::I64(value, GetLocation())));
}

Result BinaryReaderIR::OnIfExpr(Type sig_type) {
  auto expr = std::make_unique<IfExpr>();
  SetBlockDeclaration(&expr->true_.decl, sig_type);
  ExprList* expr_list = &expr->true_.exprs;
  CHECK_RESULT(AppendExpr(std::move(expr)));
  return PushLabel(LabelType::If, expr_list);
}

Result BinaryReaderIR::OnLoadExpr(Opcode opcode,
                                  Index memidx,
                                  Address alignment_log2,
                                  Address offset) {
  return AppendExpr(std::make_unique<LoadExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnLoopExpr(Type sig_type) {
  auto expr = std::make_unique<LoopExpr>();
  SetBlockDeclaration(&expr->block.decl, sig_type);
  ExprList* expr_list = &expr->block.exprs;
  CHECK_RESULT(AppendExpr(std::move(expr)));
  return PushLabel(LabelType::Loop, expr_list);
}

Result BinaryReaderIR::OnMemoryCopyExpr(Index destmemidx, Index srcmemidx) {
  return AppendExpr(std::make_unique<MemoryCopyExpr>(
      Var(destmemidx, GetLocation()), Var(srcmemidx, GetLocation())));
}

Result BinaryReaderIR::OnDataDropExpr(Index segment) {
  return AppendExpr(
      std::make_unique<DataDropExpr>(Var(segment, GetLocation())));
}

Result BinaryReaderIR::OnMemoryFillExpr(Index memidx) {
  return AppendExpr(
      std::make_unique<MemoryFillExpr>(Var(memidx, GetLocation())));
}

Result BinaryReaderIR::OnMemoryGrowExpr(Index memidx) {
  return AppendExpr(
      std::make_unique<MemoryGrowExpr>(Var(memidx, GetLocation())));
}

Result BinaryReaderIR::OnMemoryInitExpr(Index segment, Index memidx) {
  return AppendExpr(std::make_unique<MemoryInitExpr>(
      Var(segment, GetLocation()), Var(memidx, GetLocation())));
}

Result BinaryReaderIR::OnMemorySizeExpr(Index memidx) {
  return AppendExpr(
      std::make_unique<MemorySizeExpr>(Var(memidx, GetLocation())));
}

Result BinaryReaderIR::OnTableCopyExpr(Index dst_index, Index src_index) {
  return AppendExpr(std::make_unique<TableCopyExpr>(
      Var(dst_index, GetLocation()), Var(src_index, GetLocation())));
}

Result BinaryReaderIR::OnElemDropExpr(Index segment) {
  return AppendExpr(
      std::make_unique<ElemDropExpr>(Var(segment, GetLocation())));
}

Result BinaryReaderIR::OnTableInitExpr(Index segment, Index table_index) {
  return AppendExpr(std::make_unique<TableInitExpr>(
      Var(segment, GetLocation()), Var(table_index, GetLocation())));
}

Result BinaryReaderIR::OnTableGetExpr(Index table_index) {
  return AppendExpr(
      std::make_unique<TableGetExpr>(Var(table_index, GetLocation())));
}

Result BinaryReaderIR::OnTableSetExpr(Index table_index) {
  return AppendExpr(
      std::make_unique<TableSetExpr>(Var(table_index, GetLocation())));
}

Result BinaryReaderIR::OnTableGrowExpr(Index table_index) {
  return AppendExpr(
      std::make_unique<TableGrowExpr>(Var(table_index, GetLocation())));
}

Result BinaryReaderIR::OnTableSizeExpr(Index table_index) {
  return AppendExpr(
      std::make_unique<TableSizeExpr>(Var(table_index, GetLocation())));
}

Result BinaryReaderIR::OnTableFillExpr(Index table_index) {
  return AppendExpr(
      std::make_unique<TableFillExpr>(Var(table_index, GetLocation())));
}

Result BinaryReaderIR::OnRefFuncExpr(Index func_index) {
  module_->used_func_refs.insert(func_index);
  return AppendExpr(
      std::make_unique<RefFuncExpr>(Var(func_index, GetLocation())));
}

Result BinaryReaderIR::OnRefNullExpr(Type type) {
  module_->features_used.exceptions |= (type == Type::ExnRef);
  return AppendExpr(std::make_unique<RefNullExpr>(type));
}

Result BinaryReaderIR::OnRefIsNullExpr() {
  return AppendExpr(std::make_unique<RefIsNullExpr>());
}

Result BinaryReaderIR::OnNopExpr() {
  return AppendExpr(std::make_unique<NopExpr>());
}

Result BinaryReaderIR::OnRethrowExpr(Index depth) {
  return AppendExpr(std::make_unique<RethrowExpr>(Var(depth, GetLocation())));
}

Result BinaryReaderIR::OnReturnExpr() {
  return AppendExpr(std::make_unique<ReturnExpr>());
}

Result BinaryReaderIR::OnSelectExpr(Index result_count, Type* result_types) {
  TypeVector results;
  results.assign(result_types, result_types + result_count);
  return AppendExpr(std::make_unique<SelectExpr>(results));
}

Result BinaryReaderIR::OnGlobalSetExpr(Index global_index) {
  return AppendExpr(
      std::make_unique<GlobalSetExpr>(Var(global_index, GetLocation())));
}

Result BinaryReaderIR::OnLocalSetExpr(Index local_index) {
  return AppendExpr(
      std::make_unique<LocalSetExpr>(Var(local_index, GetLocation())));
}

Result BinaryReaderIR::OnStoreExpr(Opcode opcode,
                                   Index memidx,
                                   Address alignment_log2,
                                   Address offset) {
  return AppendExpr(std::make_unique<StoreExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnThrowExpr(Index tag_index) {
  module_->features_used.exceptions = true;
  return AppendExpr(std::make_unique<ThrowExpr>(Var(tag_index, GetLocation())));
}

Result BinaryReaderIR::OnLocalTeeExpr(Index local_index) {
  return AppendExpr(
      std::make_unique<LocalTeeExpr>(Var(local_index, GetLocation())));
}

Result BinaryReaderIR::OnTryExpr(Type sig_type) {
  auto expr_ptr = std::make_unique<TryExpr>();
  // Save expr so it can be used below, after expr_ptr has been moved.
  TryExpr* expr = expr_ptr.get();
  ExprList* expr_list = &expr->block.exprs;
  SetBlockDeclaration(&expr->block.decl, sig_type);
  CHECK_RESULT(AppendExpr(std::move(expr_ptr)));
  module_->features_used.exceptions = true;
  return PushLabel(LabelType::Try, expr_list, expr);
}

Result BinaryReaderIR::AppendCatch(Catch&& catch_) {
  LabelNode* label = nullptr;
  CHECK_RESULT(TopLabel(&label));

  if (label->label_type != LabelType::Try) {
    PrintError("catch not inside try block");
    return Result::Error;
  }

  auto* try_ = cast<TryExpr>(label->context);

  if (catch_.IsCatchAll() && !try_->catches.empty() &&
      try_->catches.back().IsCatchAll()) {
    PrintError("only one catch_all allowed in try block");
    return Result::Error;
  }

  if (try_->kind == TryKind::Plain) {
    try_->kind = TryKind::Catch;
  } else if (try_->kind != TryKind::Catch) {
    PrintError("catch not allowed in try-delegate");
    return Result::Error;
  }

  try_->catches.push_back(std::move(catch_));
  label->exprs = &try_->catches.back().exprs;
  return Result::Ok;
}

Result BinaryReaderIR::OnCatchExpr(Index except_index) {
  return AppendCatch(Catch(Var(except_index, GetLocation())));
}

Result BinaryReaderIR::OnCatchAllExpr() {
  return AppendCatch(Catch(GetLocation()));
}

Result BinaryReaderIR::OnDelegateExpr(Index depth) {
  LabelNode* label = nullptr;
  CHECK_RESULT(TopLabel(&label));

  if (label->label_type != LabelType::Try) {
    PrintError("delegate not inside try block");
    return Result::Error;
  }

  auto* try_ = cast<TryExpr>(label->context);

  if (try_->kind == TryKind::Plain) {
    try_->kind = TryKind::Delegate;
  } else if (try_->kind != TryKind::Delegate) {
    PrintError("delegate not allowed in try-catch");
    return Result::Error;
  }

  try_->delegate_target = Var(depth, GetLocation());

  PopLabel();
  return Result::Ok;
}

Result BinaryReaderIR::OnUnaryExpr(Opcode opcode) {
  return AppendExpr(std::make_unique<UnaryExpr>(opcode));
}

Result BinaryReaderIR::OnTernaryExpr(Opcode opcode) {
  return AppendExpr(std::make_unique<TernaryExpr>(opcode));
}

Result BinaryReaderIR::OnUnreachableExpr() {
  return AppendExpr(std::make_unique<UnreachableExpr>());
}

Result BinaryReaderIR::EndFunctionBody(Index index) {
  current_func_ = nullptr;
  if (!label_stack_.empty()) {
    PrintError("function %" PRIindex " missing end marker", index);
    return Result::Error;
  }
  return Result::Ok;
}

Result BinaryReaderIR::OnSimdLaneOpExpr(Opcode opcode, uint64_t value) {
  return AppendExpr(std::make_unique<SimdLaneOpExpr>(opcode, value));
}

Result BinaryReaderIR::OnSimdLoadLaneExpr(Opcode opcode,
                                          Index memidx,
                                          Address alignment_log2,
                                          Address offset,
                                          uint64_t value) {
  return AppendExpr(std::make_unique<SimdLoadLaneExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset,
      value));
}

Result BinaryReaderIR::OnSimdStoreLaneExpr(Opcode opcode,
                                           Index memidx,
                                           Address alignment_log2,
                                           Address offset,
                                           uint64_t value) {
  return AppendExpr(std::make_unique<SimdStoreLaneExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset,
      value));
}

Result BinaryReaderIR::OnSimdShuffleOpExpr(Opcode opcode, v128 value) {
  return AppendExpr(std::make_unique<SimdShuffleOpExpr>(opcode, value));
}

Result BinaryReaderIR::OnLoadSplatExpr(Opcode opcode,
                                       Index memidx,
                                       Address alignment_log2,
                                       Address offset) {
  return AppendExpr(std::make_unique<LoadSplatExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnLoadZeroExpr(Opcode opcode,
                                      Index memidx,
                                      Address alignment_log2,
                                      Address offset) {
  return AppendExpr(std::make_unique<LoadZeroExpr>(
      opcode, Var(memidx, GetLocation()), 1ull << alignment_log2, offset));
}

Result BinaryReaderIR::OnElemSegmentCount(Index count) {
  WABT_TRY
  module_->elem_segments.reserve(count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::BeginElemSegment(Index index,
                                        Index table_index,
                                        uint8_t flags) {
  auto field = std::make_unique<ElemSegmentModuleField>(GetLocation());
  ElemSegment& elem_segment = field->elem_segment;
  elem_segment.table_var = Var(table_index, GetLocation());
  if ((flags & SegDeclared) == SegDeclared) {
    elem_segment.kind = SegmentKind::Declared;
  } else if ((flags & SegPassive) == SegPassive) {
    elem_segment.kind = SegmentKind::Passive;
  } else {
    elem_segment.kind = SegmentKind::Active;
  }
  module_->AppendField(std::move(field));
  return Result::Ok;
}

Result BinaryReaderIR::BeginInitExpr(ExprList* expr) {
  return PushLabel(LabelType::InitExpr, expr);
}

Result BinaryReaderIR::BeginElemSegmentInitExpr(Index index) {
  assert(index == module_->elem_segments.size() - 1);
  ElemSegment* segment = module_->elem_segments[index];
  return BeginInitExpr(&segment->offset);
}

Result BinaryReaderIR::EndInitExpr() {
  if (!label_stack_.empty()) {
    PrintError("init expression missing end marker");
    return Result::Error;
  }
  return Result::Ok;
}

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

Result BinaryReaderIR::OnElemSegmentElemType(Index index, Type elem_type) {
  assert(index == module_->elem_segments.size() - 1);
  ElemSegment* segment = module_->elem_segments[index];
  segment->elem_type = elem_type;
  return Result::Ok;
}

Result BinaryReaderIR::OnElemSegmentElemExprCount(Index index, Index count) {
  assert(index == module_->elem_segments.size() - 1);
  ElemSegment* segment = module_->elem_segments[index];
  WABT_TRY
  segment->elem_exprs.reserve(count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::BeginElemExpr(Index elem_index, Index expr_index) {
  assert(elem_index == module_->elem_segments.size() - 1);
  ElemSegment* segment = module_->elem_segments[elem_index];
  assert(expr_index == segment->elem_exprs.size());
  segment->elem_exprs.emplace_back();
  return BeginInitExpr(&segment->elem_exprs.back());
}

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

Result BinaryReaderIR::OnDataSegmentCount(Index count) {
  WABT_TRY
  module_->data_segments.reserve(count);
  WABT_CATCH_BAD_ALLOC
  return Result::Ok;
}

Result BinaryReaderIR::BeginDataSegment(Index index,
                                        Index memory_index,
                                        uint8_t flags) {
  auto field = std::make_unique<DataSegmentModuleField>(GetLocation());
  DataSegment& data_segment = field->data_segment;
  data_segment.memory_var = Var(memory_index, GetLocation());
  if ((flags & SegPassive) == SegPassive) {
    data_segment.kind = SegmentKind::Passive;
  } else {
    data_segment.kind = SegmentKind::Active;
  }
  module_->AppendField(std::move(field));
  return Result::Ok;
}

Result BinaryReaderIR::BeginDataSegmentInitExpr(Index index) {
  assert(index == module_->data_segments.size() - 1);
  DataSegment* segment = module_->data_segments[index];
  return BeginInitExpr(&segment->offset);
}

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

Result BinaryReaderIR::OnDataSegmentData(Index index,
                                         const void* data,
                                         Address size) {
  assert(index == module_->data_segments.size() - 1);
  DataSegment* segment = module_->data_segments[index];
  segment->data.resize(size);
  if (size > 0) {
    memcpy(segment->data.data(), data, size);
  }
  return Result::Ok;
}

Result BinaryReaderIR::OnFunctionNamesCount(Index count) {
  if (count > module_->funcs.size()) {
    PrintError("expected function name count (%" PRIindex
               ") <= function count (%" PRIzd ")",
               count, module_->funcs.size());
    return Result::Error;
  }
  return Result::Ok;
}

static std::string MakeDollarName(std::string_view name) {
  return std::string("$") + std::string(name);
}

Result BinaryReaderIR::OnModuleName(std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }

  module_->name = MakeDollarName(name);
  return Result::Ok;
}

Result BinaryReaderIR::SetGlobalName(Index index, std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (index >= module_->globals.size()) {
    PrintError("invalid global index: %" PRIindex, index);
    return Result::Error;
  }
  Global* glob = module_->globals[index];
  std::string dollar_name =
      GetUniqueName(&module_->global_bindings, MakeDollarName(name));
  glob->name = dollar_name;
  module_->global_bindings.emplace(dollar_name, Binding(index));
  return Result::Ok;
}

Result BinaryReaderIR::SetFunctionName(Index index, std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (index >= module_->funcs.size()) {
    PrintError("invalid function index: %" PRIindex, index);
    return Result::Error;
  }
  Func* func = module_->funcs[index];
  std::string dollar_name =
      GetUniqueName(&module_->func_bindings, MakeDollarName(name));
  func->name = dollar_name;
  module_->func_bindings.emplace(dollar_name, Binding(index));
  return Result::Ok;
}

Result BinaryReaderIR::SetTypeName(Index index, std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (index >= module_->types.size()) {
    PrintError("invalid type index: %" PRIindex, index);
    return Result::Error;
  }
  TypeEntry* type = module_->types[index];
  std::string dollar_name =
      GetUniqueName(&module_->type_bindings, MakeDollarName(name));
  type->name = dollar_name;
  module_->type_bindings.emplace(dollar_name, Binding(index));
  return Result::Ok;
}

Result BinaryReaderIR::SetTableName(Index index, std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (index >= module_->tables.size()) {
    PrintError("invalid table index: %" PRIindex, index);
    return Result::Error;
  }
  Table* table = module_->tables[index];
  std::string dollar_name =
      GetUniqueName(&module_->table_bindings, MakeDollarName(name));
  table->name = dollar_name;
  module_->table_bindings.emplace(dollar_name, Binding(index));
  return Result::Ok;
}

Result BinaryReaderIR::SetDataSegmentName(Index index, std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (index >= module_->data_segments.size()) {
    PrintError("invalid data segment index: %" PRIindex, index);
    return Result::Error;
  }
  DataSegment* segment = module_->data_segments[index];
  std::string dollar_name =
      GetUniqueName(&module_->data_segment_bindings, MakeDollarName(name));
  segment->name = dollar_name;
  module_->data_segment_bindings.emplace(dollar_name, Binding(index));
  return Result::Ok;
}

Result BinaryReaderIR::SetElemSegmentName(Index index, std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (index >= module_->elem_segments.size()) {
    PrintError("invalid elem segment index: %" PRIindex, index);
    return Result::Error;
  }
  ElemSegment* segment = module_->elem_segments[index];
  std::string dollar_name =
      GetUniqueName(&module_->elem_segment_bindings, MakeDollarName(name));
  segment->name = dollar_name;
  module_->elem_segment_bindings.emplace(dollar_name, Binding(index));
  return Result::Ok;
}

Result BinaryReaderIR::SetMemoryName(Index index, std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (index >= module_->memories.size()) {
    PrintError("invalid memory index: %" PRIindex, index);
    return Result::Error;
  }
  Memory* memory = module_->memories[index];
  std::string dollar_name =
      GetUniqueName(&module_->memory_bindings, MakeDollarName(name));
  memory->name = dollar_name;
  module_->memory_bindings.emplace(dollar_name, Binding(index));
  return Result::Ok;
}

Result BinaryReaderIR::SetTagName(Index index, std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (index >= module_->tags.size()) {
    PrintError("invalid tag index: %" PRIindex, index);
    return Result::Error;
  }
  Tag* tag = module_->tags[index];
  std::string dollar_name =
      GetUniqueName(&module_->tag_bindings, MakeDollarName(name));
  tag->name = dollar_name;
  module_->tag_bindings.emplace(dollar_name, Binding(index));
  return Result::Ok;
}

Result BinaryReaderIR::OnFunctionName(Index index, std::string_view name) {
  return SetFunctionName(index, name);
}

Result BinaryReaderIR::OnNameEntry(NameSectionSubsection type,
                                   Index index,
                                   std::string_view name) {
  switch (type) {
    // TODO(sbc): remove OnFunctionName in favor of just using
    // OnNameEntry so that this works
    case NameSectionSubsection::Function:
    case NameSectionSubsection::Local:
    case NameSectionSubsection::Module:
    case NameSectionSubsection::Label:
    case NameSectionSubsection::Field:
      break;
    case NameSectionSubsection::Type:
      SetTypeName(index, name);
      break;
    case NameSectionSubsection::Tag:
      SetTagName(index, name);
      break;
    case NameSectionSubsection::Global:
      SetGlobalName(index, name);
      break;
    case NameSectionSubsection::Table:
      SetTableName(index, name);
      break;
    case NameSectionSubsection::DataSegment:
      SetDataSegmentName(index, name);
      break;
    case NameSectionSubsection::Memory:
      SetMemoryName(index, name);
      break;
    case NameSectionSubsection::ElemSegment:
      SetElemSegmentName(index, name);
      break;
  }
  return Result::Ok;
}

Result BinaryReaderIR::OnLocalNameLocalCount(Index index, Index count) {
  assert(index < module_->funcs.size());
  Func* func = module_->funcs[index];
  Index num_params_and_locals = func->GetNumParamsAndLocals();
  if (count > num_params_and_locals) {
    PrintError("expected local name count (%" PRIindex
               ") <= local count (%" PRIindex ")",
               count, num_params_and_locals);
    return Result::Error;
  }
  return Result::Ok;
}

Result BinaryReaderIR::BeginCodeMetadataSection(std::string_view name,
                                                Offset size) {
  current_metadata_name_ = name;
  return Result::Ok;
}

Result BinaryReaderIR::OnCodeMetadataFuncCount(Index count) {
  return Result::Ok;
}

Result BinaryReaderIR::OnCodeMetadataCount(Index function_index, Index count) {
  code_metadata_queue_.push_func(module_->funcs[function_index]);
  return Result::Ok;
}

Result BinaryReaderIR::OnCodeMetadata(Offset offset,
                                      const void* data,
                                      Address size) {
  std::vector<uint8_t> data_(static_cast<const uint8_t*>(data),
                             static_cast<const uint8_t*>(data) + size);
  auto meta = std::make_unique<CodeMetadataExpr>(current_metadata_name_,
                                                 std::move(data_));
  meta->loc.offset = offset;
  code_metadata_queue_.push_metadata(std::move(meta));
  return Result::Ok;
}

Result BinaryReaderIR::OnLocalName(Index func_index,
                                   Index local_index,
                                   std::string_view name) {
  if (name.empty()) {
    return Result::Ok;
  }

  Func* func = module_->funcs[func_index];
  func->bindings.emplace(GetUniqueName(&func->bindings, MakeDollarName(name)),
                         Binding(local_index));
  return Result::Ok;
}

Result BinaryReaderIR::OnTagType(Index index, Index sig_index) {
  auto field = std::make_unique<TagModuleField>(GetLocation());
  Tag& tag = field->tag;
  SetFuncDeclaration(&tag.decl, Var(sig_index, GetLocation()));
  module_->AppendField(std::move(field));
  module_->features_used.exceptions = true;
  return Result::Ok;
}

Result BinaryReaderIR::OnDataSymbol(Index index,
                                    uint32_t flags,
                                    std::string_view name,
                                    Index segment,
                                    uint32_t offset,
                                    uint32_t size) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (flags & WABT_SYMBOL_FLAG_UNDEFINED) {
    // Refers to data in another file, `segment` not valid.
    return Result::Ok;
  }
  if (offset) {
    // If it is pointing into the data segment, then it's not really naming
    // the whole segment.
    return Result::Ok;
  }
  if (segment >= module_->data_segments.size()) {
    PrintError("invalid data segment index: %" PRIindex, segment);
    return Result::Error;
  }
  DataSegment* seg = module_->data_segments[segment];
  std::string dollar_name =
      GetUniqueName(&module_->data_segment_bindings, MakeDollarName(name));
  seg->name = dollar_name;
  module_->data_segment_bindings.emplace(dollar_name, Binding(segment));
  return Result::Ok;
}

Result BinaryReaderIR::OnFunctionSymbol(Index index,
                                        uint32_t flags,
                                        std::string_view name,
                                        Index func_index) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (func_index >= module_->funcs.size()) {
    PrintError("invalid function index: %" PRIindex, func_index);
    return Result::Error;
  }
  Func* func = module_->funcs[func_index];
  if (!func->name.empty()) {
    // The name section has already named this function.
    return Result::Ok;
  }
  std::string dollar_name =
      GetUniqueName(&module_->func_bindings, MakeDollarName(name));
  func->name = dollar_name;
  module_->func_bindings.emplace(dollar_name, Binding(func_index));
  return Result::Ok;
}

Result BinaryReaderIR::OnGlobalSymbol(Index index,
                                      uint32_t flags,
                                      std::string_view name,
                                      Index global_index) {
  return SetGlobalName(global_index, name);
}

Result BinaryReaderIR::OnSectionSymbol(Index index,
                                       uint32_t flags,
                                       Index section_index) {
  return Result::Ok;
}

Result BinaryReaderIR::OnTagSymbol(Index index,
                                   uint32_t flags,
                                   std::string_view name,
                                   Index tag_index) {
  if (name.empty()) {
    return Result::Ok;
  }
  if (tag_index >= module_->tags.size()) {
    PrintError("invalid tag index: %" PRIindex, tag_index);
    return Result::Error;
  }
  Tag* tag = module_->tags[tag_index];
  std::string dollar_name =
      GetUniqueName(&module_->tag_bindings, MakeDollarName(name));
  tag->name = dollar_name;
  module_->tag_bindings.emplace(dollar_name, Binding(tag_index));
  return Result::Ok;
}

Result BinaryReaderIR::OnTableSymbol(Index index,
                                     uint32_t flags,
                                     std::string_view name,
                                     Index table_index) {
  return SetTableName(table_index, name);
}

Result BinaryReaderIR::OnGenericCustomSection(std::string_view name,
                                              const void* data,
                                              Offset size) {
  Custom custom = Custom(GetLocation(), name);
  custom.data.resize(size);
  if (size > 0) {
    memcpy(custom.data.data(), data, size);
  }
  module_->customs.push_back(std::move(custom));
  return Result::Ok;
}

}  // end anonymous namespace

Result ReadBinaryIr(const char* filename,
                    const void* data,
                    size_t size,
                    const ReadBinaryOptions& options,
                    Errors* errors,
                    Module* out_module) {
  BinaryReaderIR reader(out_module, filename, errors);
  return ReadBinary(data, size, &reader, options);
}

}  // namespace wabt