// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/wasm/wasm-disassembler.h"

#include <iomanip>

#include "src/debug/debug-interface.h"
#include "src/numbers/conversions.h"
#include "src/wasm/module-decoder-impl.h"
#include "src/wasm/names-provider.h"
#include "src/wasm/wasm-disassembler-impl.h"
#include "src/wasm/wasm-opcodes-inl.h"

namespace v8 {
namespace internal {
namespace wasm {

////////////////////////////////////////////////////////////////////////////////
// Public interface.

void Disassemble(const WasmModule* module, ModuleWireBytes wire_bytes,
                 NamesProvider* names,
                 v8::debug::DisassemblyCollector* collector,
                 std::vector<int>* function_body_offsets) {
  MultiLineStringBuilder out;
  AccountingAllocator allocator;
  ModuleDisassembler md(out, module, names, wire_bytes, &allocator,
                        /* no offsets yet */ {}, function_body_offsets);
  md.PrintModule({0, 2}, v8_flags.wasm_disassembly_max_mb);
  out.ToDisassemblyCollector(collector);
}

void Disassemble(base::Vector<const uint8_t> wire_bytes,
                 v8::debug::DisassemblyCollector* collector,
                 std::vector<int>* function_body_offsets) {
  std::unique_ptr<OffsetsProvider> offsets = AllocateOffsetsProvider();
  ModuleResult result =
      DecodeWasmModuleForDisassembler(wire_bytes, offsets.get());
  MultiLineStringBuilder out;
  AccountingAllocator allocator;
  if (result.failed()) {
    WasmError error = result.error();
    out << "Decoding error: " << error.message() << " at offset "
        << error.offset();
    out.ToDisassemblyCollector(collector);
    return;
  }
  const WasmModule* module = result.value().get();
  NamesProvider names(module, wire_bytes);
  ModuleWireBytes module_bytes(wire_bytes);
  ModuleDisassembler md(out, module, &names, module_bytes, &allocator,
                        std::move(offsets), function_body_offsets);
  md.PrintModule({0, 2}, v8_flags.wasm_disassembly_max_mb);
  out.ToDisassemblyCollector(collector);
}

void MultiLineStringBuilder::ToDisassemblyCollector(
    v8::debug::DisassemblyCollector* collector) {
  if (length() != 0) NextLine(0);  // Finalize last line.
  collector->ReserveLineCount(lines_.size());
  for (const Line& l : lines_) {
    // Don't include trailing '\n'.
    collector->AddLine(l.data, l.len - 1, l.bytecode_offset);
  }
}

void DisassembleFunctionImpl(const WasmModule* module, int func_index,
                             base::Vector<const uint8_t> function_body,
                             ModuleWireBytes module_bytes, NamesProvider* names,
                             std::ostream& os, std::vector<uint32_t>* offsets) {
  MultiLineStringBuilder sb;
  const wasm::WasmFunction& func = module->functions[func_index];
  AccountingAllocator allocator;
  Zone zone(&allocator, "Wasm disassembler");
  bool shared = module->type(func.sig_index).is_shared;
  WasmDetectedFeatures detected;
  FunctionBodyDisassembler d(&zone, module, func_index, shared, &detected,
                             func.sig, function_body.begin(),
                             function_body.end(), func.code.offset(),
                             module_bytes, names);
  d.DecodeAsWat(sb, {0, 2}, FunctionBodyDisassembler::kPrintHeader);
  const bool print_offsets = false;
  sb.WriteTo(os, print_offsets, offsets);
}

void DisassembleFunction(const WasmModule* module, int func_index,
                         base::Vector<const uint8_t> wire_bytes,
                         NamesProvider* names, std::ostream& os) {
  DCHECK(func_index < static_cast<int>(module->functions.size()) &&
         func_index >= static_cast<int>(module->num_imported_functions));
  ModuleWireBytes module_bytes(wire_bytes);
  base::Vector<const uint8_t> code =
      module_bytes.GetFunctionBytes(&module->functions[func_index]);
  std::vector<uint32_t>* collect_offsets = nullptr;
  DisassembleFunctionImpl(module, func_index, code, module_bytes, names, os,
                          collect_offsets);
}

void DisassembleFunction(const WasmModule* module, int func_index,
                         base::Vector<const uint8_t> function_body,
                         base::Vector<const uint8_t> maybe_wire_bytes,
                         uint32_t function_body_offset, std::ostream& os,
                         std::vector<uint32_t>* offsets) {
  DCHECK(func_index < static_cast<int>(module->functions.size()) &&
         func_index >= static_cast<int>(module->num_imported_functions));
  NamesProvider fake_names(module, maybe_wire_bytes);
  DisassembleFunctionImpl(module, func_index, function_body,
                          ModuleWireBytes{nullptr, 0}, &fake_names, os,
                          offsets);
}

////////////////////////////////////////////////////////////////////////////////
// Helpers.

static constexpr char kHexChars[] = "0123456789abcdef";
static constexpr char kUpperHexChars[] = "0123456789ABCDEF";

// Returns the log2 of the alignment, e.g. "4" means 2<<4 == 16 bytes.
// This is the same format as used in .wasm binary modules.
uint32_t GetDefaultAlignment(WasmOpcode opcode) {
  switch (opcode) {
    case kExprS128LoadMem:
    case kExprS128StoreMem:
      return 4;
    case kExprS128Load8x8S:
    case kExprS128Load8x8U:
    case kExprS128Load16x4S:
    case kExprS128Load16x4U:
    case kExprS128Load32x2S:
    case kExprS128Load32x2U:
    case kExprS128Load64Splat:
    case kExprS128Load64Zero:
    case kExprS128Load64Lane:
    case kExprS128Store64Lane:
      return 3;
    case kExprS128Load32Splat:
    case kExprS128Load32Zero:
    case kExprS128Load32Lane:
    case kExprS128Store32Lane:
      return 2;
    case kExprS128Load16Splat:
    case kExprS128Load16Lane:
    case kExprS128Store16Lane:
      return 1;
    case kExprS128Load8Splat:
    case kExprS128Load8Lane:
    case kExprS128Store8Lane:
      return 0;

#define CASE(Opcode, ...) \
  case kExpr##Opcode:     \
    return GetLoadType(kExpr##Opcode).size_log_2();
      FOREACH_LOAD_MEM_OPCODE(CASE)
#undef CASE
#define CASE(Opcode, ...) \
  case kExpr##Opcode:     \
    return GetStoreType(kExpr##Opcode).size_log_2();
      FOREACH_STORE_MEM_OPCODE(CASE)
#undef CASE

#define CASE(Opcode, Type) \
  case kExpr##Opcode:      \
    return ElementSizeLog2Of(MachineType::Type().representation());
      ATOMIC_OP_LIST(CASE)
      ATOMIC_STORE_OP_LIST(CASE)
#undef CASE

    default:
      UNREACHABLE();
  }
}

void PrintSignatureOneLine(StringBuilder& out, const FunctionSig* sig,
                           uint32_t func_index, NamesProvider* names,
                           bool param_names,
                           IndexAsComment indices_as_comments) {
  if (param_names) {
    for (uint32_t i = 0; i < sig->parameter_count(); i++) {
      out << " (param ";
      names->PrintLocalName(out, func_index, i, indices_as_comments);
      out << ' ';
      names->PrintValueType(out, sig->GetParam(i));
      out << ")";
    }
  } else if (sig->parameter_count() > 0) {
    out << " (param";
    for (uint32_t i = 0; i < sig->parameter_count(); i++) {
      out << " ";
      names->PrintValueType(out, sig->GetParam(i));
    }
    out << ")";
  }
  for (size_t i = 0; i < sig->return_count(); i++) {
    out << " (result ";
    names->PrintValueType(out, sig->GetReturn(i));
    out << ")";
  }
}

void PrintStringRaw(StringBuilder& out, const uint8_t* start,
                    const uint8_t* end) {
  for (const uint8_t* ptr = start; ptr < end; ptr++) {
    uint8_t b = *ptr;
    if (b < 32 || b >= 127 || b == '"' || b == '\\') {
      out << '\\' << kHexChars[b >> 4] << kHexChars[b & 0xF];
    } else {
      out << static_cast<char>(b);
    }
  }
}

////////////////////////////////////////////////////////////////////////////////
// FunctionBodyDisassembler.

void FunctionBodyDisassembler::DecodeAsWat(MultiLineStringBuilder& out,
                                           Indentation indentation,
                                           FunctionHeader include_header,
                                           uint32_t* first_instruction_offset) {
  out_ = &out;
  int base_indentation = indentation.current();
  // Print header.
  if (include_header == kPrintHeader) {
    out << indentation << "(func ";
    names_->PrintFunctionName(out, func_index_, NamesProvider::kDevTools);
    PrintSignatureOneLine(out, sig_, func_index_, names_, true,
                          kIndicesAsComments);
    out.NextLine(pc_offset());
  } else {
    out.set_current_line_bytecode_offset(pc_offset());
  }
  indentation.increase();

  // Decode and print locals.
  uint32_t locals_length = DecodeLocals(pc_);
  if (failed()) {
    // TODO(jkummerow): Improve error handling.
    out << "Failed to decode locals\n";
    return;
  }
  for (uint32_t i = static_cast<uint32_t>(sig_->parameter_count());
       i < num_locals_; i++) {
    out << indentation << "(local ";
    names_->PrintLocalName(out, func_index_, i);
    out << " ";
    names_->PrintValueType(out, local_type(i));
    out << ")";
    out.NextLine(pc_offset());
  }
  consume_bytes(locals_length);
  out.set_current_line_bytecode_offset(pc_offset());
  if (first_instruction_offset) *first_instruction_offset = pc_offset();

  // Main loop.
  while (pc_ < end_ && ok()) {
    WasmOpcode opcode = GetOpcode();
    current_opcode_ = opcode;  // Some immediates need to know this.

    // Deal with indentation.
    if (opcode == kExprEnd || opcode == kExprElse || opcode == kExprCatch ||
        opcode == kExprCatchAll || opcode == kExprDelegate) {
      if (indentation.current() >= base_indentation) {
        indentation.decrease();
      }
    }
    out << indentation;
    if (opcode == kExprElse || opcode == kExprCatch ||
        opcode == kExprCatchAll || opcode == kExprBlock || opcode == kExprIf ||
        opcode == kExprLoop || opcode == kExprTry || opcode == kExprTryTable) {
      indentation.increase();
    }

    // Print the opcode and its immediates.
    if (opcode == kExprEnd) {
      if (indentation.current() < base_indentation) {
        out << ";; Unexpected end byte";
      } else if (indentation.current() == base_indentation) {
        out << ")";  // End of the function.
      } else {
        out << "end";
        const LabelInfo& label = label_stack_.back();
        if (label.start != nullptr) {
          out << " ";
          out.write(label.start, label.length);
        }
        label_stack_.pop_back();
      }
    } else {
      out << WasmOpcodes::OpcodeName(opcode);
    }
    if (opcode == kExprBlock || opcode == kExprIf || opcode == kExprLoop ||
        opcode == kExprTry || opcode == kExprTryTable) {
      // Create the LabelInfo now to get the correct offset, but only push it
      // after printing the immediates because the immediates don't see the new
      // label yet.
      LabelInfo label(out.line_number(), out.length(),
                      label_occurrence_index_++);
      pc_ += PrintImmediatesAndGetLength(out);
      label_stack_.push_back(label);
    } else {
      pc_ += PrintImmediatesAndGetLength(out);
    }

    out.NextLine(pc_offset());
  }

  if (pc_ != end_) {
    // TODO(jkummerow): Improve error handling.
    out << "Beyond end of code";
  }
}

void FunctionBodyDisassembler::DecodeGlobalInitializer(StringBuilder& out) {
  while (pc_ < end_) {
    WasmOpcode opcode = GetOpcode();
    current_opcode_ = opcode;  // Some immediates need to know this.
    // Don't print the final "end".
    if (opcode == kExprEnd && pc_ + 1 == end_) break;
    uint32_t length;
    out << " (" << WasmOpcodes::OpcodeName(opcode);
    length = PrintImmediatesAndGetLength(out);
    out << ")";
    pc_ += length;
  }
}

WasmOpcode FunctionBodyDisassembler::GetOpcode() {
  WasmOpcode opcode = static_cast<WasmOpcode>(*pc_);
  if (!WasmOpcodes::IsPrefixOpcode(opcode)) return opcode;
  return read_prefixed_opcode<ValidationTag>(pc_).first;
}

void FunctionBodyDisassembler::PrintHexNumber(StringBuilder& out,
                                              uint64_t number) {
  constexpr size_t kBufferSize = sizeof(number) * 2 + 2;  // +2 for "0x".
  char buffer[kBufferSize];
  char* end = buffer + kBufferSize;
  char* ptr = end;
  do {
    *(--ptr) = kHexChars[number & 0xF];
    number >>= 4;
  } while (number > 0);
  *(--ptr) = 'x';
  *(--ptr) = '0';
  size_t length = static_cast<size_t>(end - ptr);
  char* output = out.allocate(length);
  memcpy(output, ptr, length);
}

////////////////////////////////////////////////////////////////////////////////
// ImmediatesPrinter.

template <typename ValidationTag>
class ImmediatesPrinter {
 public:
  ImmediatesPrinter(StringBuilder& out, FunctionBodyDisassembler* owner)
      : out_(out), owner_(owner) {}

  void PrintDepthAsLabel(int imm_depth) {
    out_ << " ";
    size_t label_start_position = out_.length();
    int depth = imm_depth;
    if (owner_->current_opcode_ == kExprDelegate) depth++;
    // Be robust: if the module is invalid, print what we got.
    if (depth < 0 || depth >= static_cast<int>(owner_->label_stack_.size())) {
      out_ << imm_depth;
      return;
    }
    // If the label's name has already been determined and backpatched, just
    // copy it here.
    LabelInfo& label_info = owner_->label_info(depth);
    if (label_info.start != nullptr) {
      out_.write(label_info.start, label_info.length);
      return;
    }
    // Determine the label's name and backpatch the line that opened the block.
    names()->PrintLabelName(out_, owner_->func_index_,
                            label_info.name_section_index,
                            owner_->label_generation_index_++);
    label_info.length = out_.length() - label_start_position;
    owner_->out_->PatchLabel(label_info, out_.start() + label_start_position);
  }

  void PrintSignature(ModuleTypeIndex sig_index) {
    if (owner_->module_->has_signature(sig_index)) {
      const FunctionSig* sig = owner_->module_->signature(sig_index);
      PrintSignatureOneLine(out_, sig, 0 /* ignored */, names(), false);
    } else {
      out_ << " (signature: " << sig_index << " INVALID)";
    }
  }

  void BlockType(BlockTypeImmediate& imm) {
    if (imm.sig.all().begin() == nullptr) {
      PrintSignature(imm.sig_index);
    } else {
      PrintSignatureOneLine(out_, &imm.sig, 0 /* ignored */, names(), false);
    }
  }

  void HeapType(HeapTypeImmediate& imm) {
    out_ << " ";
    names()->PrintHeapType(out_, imm.type);
    if (imm.type.is_index()) use_type(imm.type.ref_index());
  }

  void ValueType(ValueType type) {
    out_ << " ";
    names()->PrintValueType(out_, type);
    if (type.has_index()) use_type(type.ref_index());
  }

  void BrOnCastFlags(BrOnCastImmediate& flags) {
    // Ignored here. For printing text format, we do all the work via the
    // two calls to {ValueType()} that we get for a br_on_cast.
  }

  void BranchDepth(BranchDepthImmediate& imm) { PrintDepthAsLabel(imm.depth); }

  void BranchTable(BranchTableImmediate& imm) {
    const uint8_t* pc = imm.table;
    for (uint32_t i = 0; i <= imm.table_count; i++) {
      auto [target, length] = owner_->read_u32v<ValidationTag>(pc);
      PrintDepthAsLabel(target);
      pc += length;
    }
  }

  const char* CatchKindToString(CatchKind kind) {
    switch (kind) {
      case kCatch:
        return "catch";
      case kCatchRef:
        return "catch_ref";
      case kCatchAll:
        return "catch_all";
      case kCatchAllRef:
        return "catch_all_ref";
      default:
        return "<invalid>";
    }
  }

  void TryTable(TryTableImmediate& imm) {
    const uint8_t* pc = imm.table;
    for (uint32_t i = 0; i < imm.table_count; i++) {
      uint8_t kind = owner_->read_u8<ValidationTag>(pc);
      pc += 1;
      out_ << " " << CatchKindToString(static_cast<CatchKind>(kind));
      if (kind == kCatch || kind == kCatchRef) {
        auto [tag, length] = owner_->read_u32v<ValidationTag>(pc);
        out_ << " ";
        names()->PrintTagName(out_, tag);
        pc += length;
      }
      auto [target, length] = owner_->read_u32v<ValidationTag>(pc);
      PrintDepthAsLabel(target);
      pc += length;
    }
  }

  void EffectHandlerTable(EffectHandlerTableImmediate& imm) {
    const uint8_t* pc = imm.table;
    for (uint32_t i = 0; i < imm.table_count; i++) {
      uint8_t kind = owner_->read_u8<ValidationTag>(pc);
      pc += 1;
      auto [tag, length] = owner_->read_u32v<ValidationTag>(pc);
      out_ << "(on ";
      names()->PrintTagName(out_, tag);
      out_ << " ";
      pc += length;
      if (kind == kOnSuspend) {
        auto [target, tlen] = owner_->read_u32v<ValidationTag>(pc);
        PrintDepthAsLabel(target);
        pc += tlen;
        out_ << ")";
      } else {
        out_ << " switch)";
      }
    }
  }

  void CallIndirect(CallIndirectImmediate& imm) {
    PrintSignature(imm.sig_imm.index);
    if (imm.table_imm.index != 0) TableIndex(imm.table_imm);
  }

  void SelectType(SelectTypeImmediate& imm) {
    out_ << " ";
    names()->PrintValueType(out_, imm.type);
  }

  void MemoryAccess(MemoryAccessImmediate& imm) {
    if (imm.offset != 0) out_ << " offset=" << imm.offset;
    if (imm.alignment != GetDefaultAlignment(owner_->current_opcode_)) {
      out_ << " align=" << (1u << imm.alignment);
    }
  }

  void MemoryOrder(const MemoryOrderImmediate& memory_order) {
    switch (memory_order.order) {
      case AtomicMemoryOrder::kAcqRel:
        out_ << " acqrel";
        return;
      case AtomicMemoryOrder::kSeqCst:
        out_ << " seqcst";
        return;
    }
    out_ << " INVALID(" << static_cast<int>(memory_order.order) << ')';
  }

  void SimdLane(SimdLaneImmediate& imm) { out_ << " " << uint32_t{imm.lane}; }

  void Field(FieldImmediate& imm) {
    TypeIndex(imm.struct_imm);
    out_ << " ";
    names()->PrintFieldName(out_, imm.struct_imm.index.index,
                            imm.field_imm.index);
  }

  void Length(IndexImmediate& imm) {
    out_ << " " << imm.index;  // --
  }

  void TagIndex(TagIndexImmediate& imm) {
    out_ << " ";
    names()->PrintTagName(out_, imm.index);
  }

  void FunctionIndex(IndexImmediate& imm) {
    out_ << " ";
    names()->PrintFunctionName(out_, imm.index, NamesProvider::kDevTools);
  }

  void TypeIndex(TypeIndexImmediate& imm) {
    out_ << " ";
    names()->PrintTypeName(out_, imm.index);
    use_type(imm.index);
  }

  void LocalIndex(IndexImmediate& imm) {
    out_ << " ";
    names()->PrintLocalName(out_, func_index(), imm.index);
  }

  void GlobalIndex(IndexImmediate& imm) {
    out_ << " ";
    names()->PrintGlobalName(out_, imm.index);
  }

  void TableIndex(TableIndexImmediate& imm) {
    out_ << " ";
    names()->PrintTableName(out_, imm.index);
  }

  void MemoryIndex(MemoryIndexImmediate& imm) {
    if (imm.index == 0) return;
    out_ << " " << imm.index;
  }

  void DataSegmentIndex(IndexImmediate& imm) {
    if (kSkipDataSegmentNames) {
      out_ << " " << imm.index;
    } else {
      out_ << " ";
      names()->PrintDataSegmentName(out_, imm.index);
    }
  }

  void ElemSegmentIndex(IndexImmediate& imm) {
    out_ << " ";
    names()->PrintElementSegmentName(out_, imm.index);
  }

  void I32Const(ImmI32Immediate& imm) {
    out_ << " " << imm.value;  // --
  }

  void I64Const(ImmI64Immediate& imm) {
    if (imm.value >= 0) {
      out_ << " " << static_cast<uint64_t>(imm.value);
    } else {
      out_ << " -" << ((~static_cast<uint64_t>(imm.value)) + 1);
    }
  }

  void F32Const(ImmF32Immediate& imm) {
    float f = imm.value;
    if (f == 0) {
      out_ << (1 / f < 0 ? " -0.0" : " 0.0");
    } else if (std::isinf(f)) {
      out_ << (f > 0 ? " inf" : " -inf");
    } else if (std::isnan(f)) {
      uint32_t bits = base::bit_cast<uint32_t>(f);
      uint32_t payload = bits & 0x7F'FFFFu;
      uint32_t signbit = bits >> 31;
      if (payload == 0x40'0000u) {
        out_ << (signbit == 1 ? " -nan" : " nan");
      } else {
        out_ << (signbit == 1 ? " -nan:" : " +nan:");
        owner_->PrintHexNumber(out_, payload);
      }
    } else {
      std::ostringstream o;
      // TODO(dlehmann): Change to `std::format` (C++20) or to `std::to_chars`
      // (C++17) once available, so that `0.1` isn't printed as `0.100000001`
      // any more.
      o << std::setprecision(std::numeric_limits<float>::max_digits10) << f;
      out_ << " " << o.str();
    }
  }

  void F64Const(ImmF64Immediate& imm) {
    double d = imm.value;
    if (d == 0) {
      out_ << (1 / d < 0 ? " -0.0" : " 0.0");
    } else if (std::isinf(d)) {
      out_ << (d > 0 ? " inf" : " -inf");
    } else if (std::isnan(d)) {
      uint64_t bits = base::bit_cast<uint64_t>(d);
      uint64_t payload = bits & 0xF'FFFF'FFFF'FFFFull;
      uint64_t signbit = bits >> 63;
      if (payload == 0x8'0000'0000'0000ull) {
        out_ << (signbit == 1 ? " -nan" : " nan");
      } else {
        out_ << (signbit == 1 ? " -nan:" : " +nan:");
        owner_->PrintHexNumber(out_, payload);
      }
    } else {
      char buffer[100];
      std::string_view str =
          DoubleToStringView(d, base::VectorOf(buffer, 100u));
      out_ << " " << str;
    }
  }

  void S128Const(Simd128Immediate& imm) {
    if (owner_->current_opcode_ == kExprI8x16Shuffle) {
      for (int i = 0; i < 16; i++) {
        out_ << " " << uint32_t{imm.value[i]};
      }
    } else {
      DCHECK_EQ(owner_->current_opcode_, kExprS128Const);
      out_ << " i32x4";
      for (int i = 0; i < 4; i++) {
        out_ << " 0x";
        for (int j = 3; j >= 0; j--) {  // Little endian.
          uint8_t b = imm.value[i * 4 + j];
          out_ << kUpperHexChars[b >> 4];
          out_ << kUpperHexChars[b & 0xF];
        }
      }
    }
  }

  void StringConst(StringConstImmediate& imm) {
    if (imm.index >= owner_->module_->stringref_literals.size()) {
      out_ << " " << imm.index << " INVALID";
      return;
    }
    if (owner_->wire_bytes_.start() == nullptr) {
      out_ << " " << imm.index;
      return;
    }
    out_ << " \"";
    const WasmStringRefLiteral& lit =
        owner_->module_->stringref_literals[imm.index];
    const uint8_t* start = owner_->wire_bytes_.start() + lit.source.offset();
    static constexpr uint32_t kMaxCharsPrinted = 40;
    if (lit.source.length() <= kMaxCharsPrinted) {
      const uint8_t* end =
          owner_->wire_bytes_.start() + lit.source.end_offset();
      PrintStringRaw(out_, start, end);
    } else {
      const uint8_t* end = start + kMaxCharsPrinted - 1;
      PrintStringRaw(out_, start, end);
      out_ << "…";
    }
    out_ << '"';
    if (kIndicesAsComments) out_ << " (;" << imm.index << ";)";
  }

  void MemoryInit(MemoryInitImmediate& imm) {
    DataSegmentIndex(imm.data_segment);
    if (imm.memory.index != 0) out_ << " " << uint32_t{imm.memory.index};
  }

  void MemoryCopy(MemoryCopyImmediate& imm) {
    if (imm.memory_dst.index == 0 && imm.memory_src.index == 0) return;
    out_ << " " << uint32_t{imm.memory_dst.index};
    out_ << " " << uint32_t{imm.memory_src.index};
  }

  void TableInit(TableInitImmediate& imm) {
    if (imm.table.index != 0) TableIndex(imm.table);
    ElemSegmentIndex(imm.element_segment);
  }

  void TableCopy(TableCopyImmediate& imm) {
    if (imm.table_dst.index == 0 && imm.table_src.index == 0) return;
    out_ << " ";
    names()->PrintTableName(out_, imm.table_dst.index);
    out_ << " ";
    names()->PrintTableName(out_, imm.table_src.index);
  }

  void ArrayCopy(TypeIndexImmediate& dst, TypeIndexImmediate& src) {
    out_ << " ";
    names()->PrintTypeName(out_, dst.index);
    out_ << " ";
    names()->PrintTypeName(out_, src.index);
    use_type(dst.index);
    use_type(src.index);
  }

 private:
  void use_type(ModuleTypeIndex type_index) {
    owner_->used_types_.insert(type_index.index);
  }

  NamesProvider* names() { return owner_->names_; }

  uint32_t func_index() { return owner_->func_index_; }

  StringBuilder& out_;
  FunctionBodyDisassembler* owner_;
};

uint32_t FunctionBodyDisassembler::PrintImmediatesAndGetLength(
    StringBuilder& out) {
  using Printer = ImmediatesPrinter<ValidationTag>;
  Printer imm_printer(out, this);
  return WasmDecoder::OpcodeLength<Printer>(this, this->pc_, imm_printer);
}

////////////////////////////////////////////////////////////////////////////////
// OffsetsProvider.

void OffsetsProvider::CollectOffsets(const WasmModule* module,
                                     base::Vector<const uint8_t> wire_bytes) {
  num_imported_tables_ = module->num_imported_tables;
  num_imported_globals_ = module->num_imported_globals;
  num_imported_tags_ = module->num_imported_tags;
  type_offsets_.reserve(module->types.size());
  import_offsets_.reserve(module->import_table.size());
  table_offsets_.reserve(module->tables.size() - num_imported_tables_);
  tag_offsets_.reserve(module->tags.size() - num_imported_tags_);
  global_offsets_.reserve(module->globals.size() - num_imported_globals_);
  element_offsets_.reserve(module->elem_segments.size());
  data_offsets_.reserve(module->data_segments.size());
  recgroups_.reserve(4);  // We can't know, so this is just a guess.

  WasmDetectedFeatures unused_detected_features;
  ModuleDecoderImpl decoder{WasmEnabledFeatures::All(), wire_bytes, kWasmOrigin,
                            &unused_detected_features, this};
  constexpr bool kNoVerifyFunctions = false;
  decoder.DecodeModule(kNoVerifyFunctions);
}

////////////////////////////////////////////////////////////////////////////////
// ModuleDisassembler.

ModuleDisassembler::ModuleDisassembler(
    MultiLineStringBuilder& out, const WasmModule* module, NamesProvider* names,
    const ModuleWireBytes wire_bytes, AccountingAllocator* allocator,
    std::unique_ptr<OffsetsProvider> offsets_provider,
    std::vector<int>* function_body_offsets)
    : out_(out),
      module_(module),
      names_(names),
      wire_bytes_(wire_bytes),
      start_(wire_bytes_.start()),
      zone_(allocator, "disassembler zone"),
      offsets_(offsets_provider.release()),
      function_body_offsets_(function_body_offsets) {
  if (!offsets_) {
    offsets_ = std::make_unique<OffsetsProvider>();
    offsets_->CollectOffsets(module, wire_bytes_.module_bytes());
  }
}

ModuleDisassembler::~ModuleDisassembler() = default;

void ModuleDisassembler::PrintTypeDefinition(uint32_t type_index,
                                             Indentation indentation,
                                             IndexAsComment index_as_comment) {
  uint32_t offset = offsets_->type_offset(type_index);
  out_.NextLine(offset);
  out_ << indentation << "(type ";
  size_t num_closing_parens = 2;  // One for "(type", one for "(struct" etc.
  names_->PrintTypeName(out_, type_index, index_as_comment);
  const TypeDefinition& type = module_->types[type_index];
  if (type.supertype != kNoSuperType) {
    out_ << " (sub ";
    num_closing_parens++;
    if (type.is_final) out_ << "final ";
    names_->PrintTypeName(out_, type.supertype);
  }
  if (type.is_descriptor()) {
    out_ << " (describes ";
    names_->PrintTypeName(out_, type.describes);
    out_ << ")";
  }
  if (type.has_descriptor()) {
    out_ << " (descriptor ";
    names_->PrintTypeName(out_, type.descriptor);
    out_ << ")";
  }
  if (type.kind == TypeDefinition::kArray) {
    const ArrayType* atype = type.array_type;
    out_ << " (array";
    if (type.is_shared) out_ << " shared";
    out_ << " (field ";
    PrintMutableType(atype->mutability(), atype->element_type());
    num_closing_parens++;  // Closes "(field ...".
  } else if (type.kind == TypeDefinition::kStruct) {
    const StructType* stype = type.struct_type;
    out_ << " (struct";
    if (type.is_shared) out_ << " shared";
    bool break_lines = stype->field_count() > 2;
    for (uint32_t i = 0; i < stype->field_count(); i++) {
      LineBreakOrSpace(break_lines, indentation, offset);
      out_ << "(field ";
      names_->PrintFieldName(out_, type_index, i);
      out_ << " ";
      PrintMutableType(stype->mutability(i), stype->field(i));
      out_ << ")";
    }
  } else if (type.kind == TypeDefinition::kFunction) {
    const FunctionSig* sig = type.function_sig;
    out_ << " (func";
    if (type.is_shared) out_ << " shared";
    bool break_lines = sig->parameter_count() + sig->return_count() > 2;
    for (uint32_t i = 0; i < sig->parameter_count(); i++) {
      LineBreakOrSpace(break_lines, indentation, offset);
      out_ << "(param ";
      names_->PrintLocalName(out_, type_index, i);
      out_ << " ";
      names_->PrintValueType(out_, sig->GetParam(i));
      out_ << ")";
    }
    for (uint32_t i = 0; i < sig->return_count(); i++) {
      LineBreakOrSpace(break_lines, indentation, offset);
      out_ << "(result ";
      names_->PrintValueType(out_, sig->GetReturn(i));
      out_ << ")";
    }
  }
  constexpr const char* parens = ")))))";
  DCHECK_LE(num_closing_parens, strlen(parens));
  out_.write(parens, num_closing_parens);
}

void ModuleDisassembler::PrintModule(Indentation indentation, size_t max_mb) {
  // 0. General infrastructure.
  // We don't store import/export information on {WasmTag} currently.
  size_t num_tags = module_->tags.size();
  std::vector<bool> exported_tags(num_tags, false);
  for (const WasmExport& ex : module_->export_table) {
    if (ex.kind == kExternalTag) exported_tags[ex.index] = true;
  }

  // I. Module name.
  out_ << indentation << "(module";
  if (module_->name.is_set()) {
    out_ << " $";
    const uint8_t* name_start = start_ + module_->name.offset();
    out_.write(name_start, module_->name.length());
  }
  indentation.increase();

  // II. Types
  uint32_t recgroup_index = 0;
  OffsetsProvider::RecGroup recgroup = offsets_->recgroup(recgroup_index++);
  bool in_explicit_recgroup = false;
  for (uint32_t i = 0; i < module_->types.size(); i++) {
    // No need to check {recgroup.valid()}, as the comparison will simply
    // never be true otherwise.
    while (i == recgroup.start_type_index) {
      out_.NextLine(recgroup.offset);
      out_ << indentation << "(rec";
      if V8_UNLIKELY (recgroup.end_type_index == i) {
        // Empty recgroup.
        out_ << ")";
        DCHECK(!in_explicit_recgroup);
        recgroup = offsets_->recgroup(recgroup_index++);
        continue;
      } else {
        in_explicit_recgroup = true;
        indentation.increase();
        break;
      }
    }
    if (kSkipFunctionTypesInTypeSection &&
        module_->has_signature(ModuleTypeIndex{i}) && !in_explicit_recgroup) {
      continue;
    }
    PrintTypeDefinition(i, indentation, kIndicesAsComments);
    if (in_explicit_recgroup && i == recgroup.end_type_index - 1) {
      in_explicit_recgroup = false;
      indentation.decrease();
      // The end of a recgroup is implicit in the wire bytes, so repeat the
      // previous line's offset for it.
      uint32_t offset = out_.current_line_bytecode_offset();
      out_.NextLine(offset);
      out_ << indentation << ")";
      recgroup = offsets_->recgroup(recgroup_index++);
    }
  }
  while (recgroup.valid()) {
    // There could be empty recgroups at the end of the type section.
    DCHECK_GE(recgroup.start_type_index, module_->types.size());
    DCHECK_EQ(recgroup.start_type_index, recgroup.end_type_index);
    out_.NextLine(recgroup.offset);
    out_ << indentation << "(rec)";
    recgroup = offsets_->recgroup(recgroup_index++);
  }

  // III. Imports
  for (uint32_t i = 0; i < module_->import_table.size(); i++) {
    const WasmImport& import = module_->import_table[i];
    out_.NextLine(offsets_->import_offset(i));
    out_ << indentation;
    switch (import.kind) {
      case kExternalTable: {
        out_ << "(table ";
        names_->PrintTableName(out_, import.index, kIndicesAsComments);
        const WasmTable& table = module_->tables[import.index];
        if (table.exported) PrintExportName(kExternalTable, import.index);
        PrintImportName(import);
        PrintTable(table);
        break;
      }
      case kExternalFunction:
      case kExternalExactFunction: {
        out_ << "(func ";
        names_->PrintFunctionName(out_, import.index, NamesProvider::kDevTools,
                                  kIndicesAsComments);
        const WasmFunction& func = module_->functions[import.index];
        // Exports always use non-exact kExternalFunction, because exact
        // exports would provide no benefit.
        if (func.exported) PrintExportName(kExternalFunction, import.index);
        PrintImportName(import);
        if (import.kind == kExternalExactFunction) out_ << "(exact ";
        PrintSignatureOneLine(out_, func.sig, import.index, names_, false);
        if (import.kind == kExternalExactFunction) out_ << ")";
        break;
      }
      case kExternalGlobal: {
        out_ << "(global ";
        names_->PrintGlobalName(out_, import.index, kIndicesAsComments);
        const WasmGlobal& global = module_->globals[import.index];
        if (global.exported) PrintExportName(kExternalGlobal, import.index);
        PrintImportName(import);
        PrintGlobal(global);
        break;
      }
      case kExternalMemory:
        out_ << "(memory ";
        names_->PrintMemoryName(out_, import.index, kIndicesAsComments);
        if (module_->memories[import.index].exported) {
          PrintExportName(kExternalMemory, 0);
        }
        PrintImportName(import);
        PrintMemory(module_->memories[import.index]);
        break;
      case kExternalTag:
        out_ << "(tag ";
        names_->PrintTagName(out_, import.index, kIndicesAsComments);
        PrintImportName(import);
        if (exported_tags[import.index]) {
          PrintExportName(kExternalTag, import.index);
        }
        PrintTagSignature(module_->tags[import.index].sig);
        break;
    }
    out_ << ")";
  }

  // IV. Tables
  for (uint32_t i = module_->num_imported_tables; i < module_->tables.size();
       i++) {
    const WasmTable& table = module_->tables[i];
    DCHECK(!table.imported);
    out_.NextLine(offsets_->table_offset(i));
    out_ << indentation << "(table ";
    names_->PrintTableName(out_, i, kIndicesAsComments);
    if (table.exported) PrintExportName(kExternalTable, i);
    PrintTable(table);
    out_ << ")";
  }

  // V. Memories
  uint32_t num_memories = static_cast<uint32_t>(module_->memories.size());
  for (uint32_t memory_index = 0; memory_index < num_memories; ++memory_index) {
    const WasmMemory& memory = module_->memories[memory_index];
    if (memory.imported) continue;
    out_.NextLine(offsets_->memory_offset());
    out_ << indentation << "(memory ";
    names_->PrintMemoryName(out_, memory_index, kIndicesAsComments);
    if (memory.exported) PrintExportName(kExternalMemory, memory_index);
    PrintMemory(memory);
    out_ << ")";
  }

  // VI.Tags
  for (uint32_t i = module_->num_imported_tags; i < module_->tags.size(); i++) {
    const WasmTag& tag = module_->tags[i];
    out_.NextLine(offsets_->tag_offset(i));
    out_ << indentation << "(tag ";
    names_->PrintTagName(out_, i, kIndicesAsComments);
    if (exported_tags[i]) PrintExportName(kExternalTag, i);
    PrintTagSignature(tag.sig);
    out_ << ")";
  }

  // VII. String literals
  size_t num_strings = module_->stringref_literals.size();
  for (uint32_t i = 0; i < num_strings; i++) {
    const WasmStringRefLiteral lit = module_->stringref_literals[i];
    out_.NextLine(offsets_->string_offset(i));
    out_ << indentation << "(string \"";
    PrintString(lit.source);
    out_ << '"';
    if (kIndicesAsComments) out_ << " (;" << i << ";)";
    out_ << ")";
  }

  // VIII. Globals
  for (uint32_t i = module_->num_imported_globals; i < module_->globals.size();
       i++) {
    const WasmGlobal& global = module_->globals[i];
    DCHECK(!global.imported);
    out_.NextLine(offsets_->global_offset(i));
    out_ << indentation << "(global ";
    names_->PrintGlobalName(out_, i, kIndicesAsComments);
    if (global.exported) PrintExportName(kExternalGlobal, i);
    PrintGlobal(global);
    PrintInitExpression(global.init, global.type);
    out_ << ")";
  }

  // IX. Start
  if (module_->start_function_index >= 0) {
    out_.NextLine(offsets_->start_offset());
    out_ << indentation << "(start ";
    names_->PrintFunctionName(out_, module_->start_function_index,
                              NamesProvider::kDevTools);
    out_ << ")";
  }

  // X. Elements
  for (uint32_t i = 0; i < module_->elem_segments.size(); i++) {
    const WasmElemSegment& elem = module_->elem_segments[i];
    out_.NextLine(offsets_->element_offset(i));
    out_ << indentation << "(elem ";
    names_->PrintElementSegmentName(out_, i, kIndicesAsComments);
    if (elem.status == WasmElemSegment::kStatusDeclarative) {
      out_ << " declare";
    } else if (elem.status == WasmElemSegment::kStatusActive) {
      if (elem.table_index != 0) {
        out_ << " (table ";
        names_->PrintTableName(out_, elem.table_index);
        out_ << ")";
      }
      PrintInitExpression(elem.offset, kWasmI32);
    }
    out_ << " ";
    if (elem.shared) out_ << "shared ";
    names_->PrintValueType(out_, elem.type);

    WasmDetectedFeatures unused_detected_features;
    ModuleDecoderImpl decoder(
        WasmEnabledFeatures::All(), wire_bytes_.module_bytes(),
        ModuleOrigin::kWasmOrigin, &unused_detected_features);
    decoder.consume_bytes(elem.elements_wire_bytes_offset);
    for (size_t j = 0; j < elem.element_count; j++) {
      ConstantExpression entry = decoder.consume_element_segment_entry(
          const_cast<WasmModule*>(module_), elem);
      PrintInitExpression(entry, elem.type);
    }
    out_ << ")";
  }

  // For the FunctionBodyDisassembler, we flip the convention: {NextLine} is
  // now called *after* printing something, instead of before.
  if (out_.length() != 0) out_.NextLine(0);

  // XI. Code / function bodies.
  if (function_body_offsets_ != nullptr) {
    size_t num_defined_functions =
        module_->functions.size() - module_->num_imported_functions;
    function_body_offsets_->reserve(num_defined_functions * 2);
  }
  for (uint32_t i = module_->num_imported_functions;
       i < module_->functions.size(); i++) {
    const WasmFunction* func = &module_->functions[i];
    out_.set_current_line_bytecode_offset(func->code.offset());
    out_ << indentation << "(func ";
    names_->PrintFunctionName(out_, i, NamesProvider::kDevTools,
                              kIndicesAsComments);
    if (func->exported) PrintExportName(kExternalFunction, i);
    PrintSignatureOneLine(out_, func->sig, i, names_, true, kIndicesAsComments);
    out_.NextLine(func->code.offset());
    bool shared = module_->type(func->sig_index).is_shared;
    WasmDetectedFeatures detected;
    base::Vector<const uint8_t> code = wire_bytes_.GetFunctionBytes(func);
    FunctionBodyDisassembler d(&zone_, module_, i, shared, &detected, func->sig,
                               code.begin(), code.end(), func->code.offset(),
                               wire_bytes_, names_);
    uint32_t first_instruction_offset;
    d.DecodeAsWat(out_, indentation, FunctionBodyDisassembler::kSkipHeader,
                  &first_instruction_offset);
    if (function_body_offsets_ != nullptr) {
      function_body_offsets_->push_back(first_instruction_offset);
      function_body_offsets_->push_back(d.pc_offset());
    }
    if (out_.ApproximateSizeMB() > max_mb) {
      out_ << "<truncated...>";
      return;
    }
  }

  // XII. Data
  for (uint32_t i = 0; i < module_->data_segments.size(); i++) {
    const WasmDataSegment& data = module_->data_segments[i];
    out_.set_current_line_bytecode_offset(offsets_->data_offset(i));
    out_ << indentation << "(data";
    if (!kSkipDataSegmentNames) {
      out_ << " ";
      names_->PrintDataSegmentName(out_, i, kIndicesAsComments);
    }
    if (data.shared) out_ << " shared";
    if (data.active) {
      ValueType type = module_->memories[data.memory_index].is_memory64()
                           ? kWasmI64
                           : kWasmI32;
      PrintInitExpression(data.dest_addr, type);
    }
    out_ << " \"";
    PrintString(data.source);
    out_ << "\")";
    out_.NextLine(0);

    if (out_.ApproximateSizeMB() > max_mb) {
      out_ << "<truncated...>";
      return;
    }
  }

  indentation.decrease();
  out_.set_current_line_bytecode_offset(
      static_cast<uint32_t>(wire_bytes_.length()));
  out_ << indentation << ")";  // End of the module.
  out_.NextLine(0);
}

void ModuleDisassembler::PrintImportName(const WasmImport& import) {
  out_ << " (import \"";
  PrintString(import.module_name);
  out_ << "\" \"";
  PrintString(import.field_name);
  out_ << "\")";
}

void ModuleDisassembler::PrintExportName(ImportExportKindCode kind,
                                         uint32_t index) {
  for (const WasmExport& ex : module_->export_table) {
    if (ex.kind != kind || ex.index != index) continue;
    out_ << " (export \"";
    PrintStringAsJSON(ex.name);
    out_ << "\")";
  }
}

void ModuleDisassembler::PrintMutableType(bool mutability, ValueType type) {
  if (mutability) out_ << "(mut ";
  names_->PrintValueType(out_, type);
  if (mutability) out_ << ")";
}

void ModuleDisassembler::PrintTable(const WasmTable& table) {
  if (table.shared) out_ << " shared";
  out_ << " " << table.initial_size << " ";
  if (table.has_maximum_size) out_ << table.maximum_size << " ";
  names_->PrintValueType(out_, table.type);
}

void ModuleDisassembler::PrintMemory(const WasmMemory& memory) {
  out_ << " " << memory.initial_pages;
  if (memory.has_maximum_pages) out_ << " " << memory.maximum_pages;
  if (memory.is_shared) out_ << " shared";
}

void ModuleDisassembler::PrintGlobal(const WasmGlobal& global) {
  out_ << " ";
  if (global.shared) out_ << "shared ";
  PrintMutableType(global.mutability, global.type);
}

void ModuleDisassembler::PrintInitExpression(const ConstantExpression& init,
                                             ValueType expected_type) {
  switch (init.kind()) {
    case ConstantExpression::Kind::kEmpty:
      break;
    case ConstantExpression::Kind::kI32Const:
      out_ << " (i32.const " << init.i32_value() << ")";
      break;
    case ConstantExpression::Kind::kRefNull:
      out_ << " (ref.null ";
      names_->PrintHeapType(out_, init.type());
      out_ << ")";
      break;
    case ConstantExpression::Kind::kRefFunc:
      out_ << " (ref.func ";
      names_->PrintFunctionName(out_, init.index(), NamesProvider::kDevTools);
      out_ << ")";
      break;
    case ConstantExpression::Kind::kWireBytesRef:
      WireBytesRef ref = init.wire_bytes_ref();
      const uint8_t* start = start_ + ref.offset();
      const uint8_t* end = start_ + ref.end_offset();

      auto sig = FixedSizeSignature<ValueType>::Returns(expected_type);
      WasmDetectedFeatures detected;
      FunctionBodyDisassembler d(&zone_, module_, 0, false, &detected, &sig,
                                 start, end, ref.offset(), wire_bytes_, names_);
      d.DecodeGlobalInitializer(out_);
      break;
  }
}

void ModuleDisassembler::PrintTagSignature(const FunctionSig* sig) {
  for (uint32_t i = 0; i < sig->parameter_count(); i++) {
    out_ << " (param ";
    names_->PrintValueType(out_, sig->GetParam(i));
    out_ << ")";
  }
}

void ModuleDisassembler::PrintString(WireBytesRef ref) {
  PrintStringRaw(out_, start_ + ref.offset(), start_ + ref.end_offset());
}

// This mimics legacy wasmparser behavior. It might be a questionable choice,
// but we'll follow suit for now.
void ModuleDisassembler::PrintStringAsJSON(WireBytesRef ref) {
  i::wasm::PrintStringAsJSON(out_, start_, ref);
}

void PrintStringAsJSON(StringBuilder& out, const uint8_t* start,
                       WireBytesRef ref) {
  for (const uint8_t* ptr = start + ref.offset();
       ptr < start + ref.end_offset(); ptr++) {
    uint8_t b = *ptr;
    if (b <= 34) {
      switch (b) {
        // clang-format off
        case '\b': out << "\\b";  break;
        case '\t': out << "\\t";  break;
        case '\n': out << "\\n";  break;
        case '\f': out << "\\f";  break;
        case '\r': out << "\\r";  break;
        case ' ':  out << ' ';    break;
        case '!':  out << '!';    break;
        case '"':  out << "\\\""; break;
        // clang-format on
        default:
          out << "\\u00" << kHexChars[b >> 4] << kHexChars[b & 0xF];
          break;
      }
    } else if (b != 127 && b != '\\') {
      out << static_cast<char>(b);
    } else if (b == '\\') {
      out << "\\\\";
    } else {
      out << "\\x7F";
    }
  }
}

void ModuleDisassembler::LineBreakOrSpace(bool break_lines,
                                          Indentation indentation,
                                          uint32_t byte_offset) {
  if (break_lines) {
    out_.NextLine(byte_offset);
    out_ << indentation.Extra(2);
  } else {
    out_ << " ";
  }
}

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