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

#ifndef V8_WASM_STRING_BUILDER_MULTILINE_H_
#define V8_WASM_STRING_BUILDER_MULTILINE_H_

#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif  // !V8_ENABLE_WEBASSEMBLY

#include <cstring>
#include <iostream>
#include <string>
#include <vector>

#include "src/wasm/string-builder.h"

namespace v8 {

namespace debug {
class DisassemblyCollector;
}  // namespace debug

namespace internal {
namespace wasm {

// Computes the number of decimal digits required to print {value}.
inline int GetNumDigits(uint32_t value) {
  int digits = 1;
  for (uint32_t compare = 10; value >= compare; compare *= 10) digits++;
  return digits;
}

struct LabelInfo {
  LabelInfo(size_t line_number, size_t offset,
            uint32_t index_by_occurrence_order)
      : name_section_index(index_by_occurrence_order),
        line_number(line_number),
        offset(offset) {}
  uint32_t name_section_index;
  size_t line_number;
  size_t offset;
  const char* start{nullptr};
  size_t length{0};
};

class MultiLineStringBuilder : public StringBuilder {
 public:
  MultiLineStringBuilder() : StringBuilder(kKeepOldChunks) {}

  void NextLine(uint32_t byte_offset) {
    *allocate(1) = '\n';
    size_t len = length();
    lines_.emplace_back(start(), len, pending_bytecode_offset_);
    start_here();
    pending_bytecode_offset_ = byte_offset;
  }
  size_t line_number() { return lines_.size(); }

  void set_current_line_bytecode_offset(uint32_t offset) {
    pending_bytecode_offset_ = offset;
  }
  uint32_t current_line_bytecode_offset() { return pending_bytecode_offset_; }

  // Label backpatching support. Parameters:
  // {label}: Information about where to insert the label. Fields {line_number},
  // {offset}, and {length} must already be populated; {start} will be populated
  // with the location where the inserted label was written in memory. Note that
  // this will become stale/invalid if the same line is patched again!
  // {label_source}: Pointer to the characters forming the snippet that is to
  // be inserted into the position described by {label}. The length of this
  // snippet is passed in {label.length}.
  void PatchLabel(LabelInfo& label, const char* label_source) {
    DCHECK_GT(label.length, 0);
    DCHECK_LT(label.line_number, lines_.size());

    // Step 1: Patching a line makes it longer, and we can't grow it in-place
    // because it's boxed in, so allocate space for its patched copy.
    char* patched_line;
    Line& l = lines_[label.line_number];
    // +1 because we add a space before the label: "block" -> "block $label0",
    // "block i32" -> "block $label0 i32".
    size_t patched_length = l.len + label.length + 1;
    if (length() == 0) {
      // No current unfinished line. Allocate the patched line as if it was
      // the next line.
      patched_line = allocate(patched_length);
      start_here();
    } else {
      // Shift the current unfinished line out of the way.
      // TODO(jkummerow): This approach ends up being O(n²) for a `br_table`
      // with `n` labels. If that ever becomes a problem, we could allocate a
      // separate new chunk for patched copies of old lines, then we wouldn't
      // need to shift the unfinished line around.
      const char* unfinished_start = start();  // Remember the unfinished
      size_t unfinished_length = length();     // line, and...
      rewind_to_start();                       // ...free up its space.
      patched_line = allocate(patched_length);
      // Write the unfinished line into its new location.
      start_here();
      char* new_location = allocate(unfinished_length);
      memmove(new_location, unfinished_start, unfinished_length);
      if (label_source >= unfinished_start &&
          label_source < unfinished_start + unfinished_length) {
        label_source = new_location + (label_source - unfinished_start);
      }
    }

    // Step 2: Write the patched copy of the line to be patched.
    char* cursor = patched_line;
    memcpy(cursor, l.data, label.offset);
    cursor += label.offset;
    *(cursor++) = ' ';
    label.start = cursor;
    memcpy(cursor, label_source, label.length);
    cursor += label.length;
    memcpy(cursor, l.data + label.offset, l.len - label.offset);
    l.data = patched_line;
    l.len = patched_length;
  }

  // Note: implemented in wasm-disassembler.cc (which is also the only user).
  void ToDisassemblyCollector(v8::debug::DisassemblyCollector* collector);

  void WriteTo(std::ostream& out, bool print_offsets,
               std::vector<uint32_t>* collect_offsets = nullptr) {
    if (length() != 0) NextLine(0);
    if (lines_.size() == 0) return;

    if (print_offsets) {
      // The last offset is expected to be the largest. However, when callers
      // emit multiple snippets (such as wami's --single-wat mode), this is
      // not always true. Approximate detection of that case.
      uint32_t largest_offset = lines_.back().bytecode_offset;
      if (lines_[0].bytecode_offset > largest_offset) {
        for (const Line& l : lines_) {
          if (l.bytecode_offset > largest_offset) {
            largest_offset = l.bytecode_offset;
          }
        }
      }
      int width = GetNumDigits(largest_offset);
      // We could have used std::setw(width), but this is faster.
      constexpr int kBufSize = 12;  // Enough for any uint32 plus '|'.
      char buffer[kBufSize] = {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, '|'};
      char* const buffer_end = buffer + kBufSize - 1;
      char* const buffer_start = buffer_end - width;
      uint32_t prev_offset = 0;
      for (const Line& l : lines_) {
        uint32_t offset = l.bytecode_offset;
        // We usually encounter offsets in ascending order, so we can just
        // overwrite the previous value without clearing it first; unless we
        // just skipped to a new snippet with a smaller offset.
        if (offset < prev_offset) {
          memset(buffer_start, 32, width);
        }
        prev_offset = offset;
        char* ptr = buffer_end;
        do {
          *(--ptr) = '0' + (offset % 10);
          offset /= 10;
        } while (offset > 0);
        out.write(buffer_start, width + 1);  // +1 for the '|'.
        out.write(l.data, l.len);
      }
      return;
    }
    // In the name of speed, batch up lines that happen to be stored
    // consecutively.
    const Line& first = lines_[0];
    const char* last_start = first.data;
    size_t len = first.len;
    for (size_t i = 1; i < lines_.size(); i++) {
      const Line& l = lines_[i];
      if (last_start + len == l.data) {
        len += l.len;
      } else {
        out.write(last_start, len);
        last_start = l.data;
        len = l.len;
      }
    }
    out.write(last_start, len);
    if (collect_offsets) {
      collect_offsets->reserve(lines_.size());
      for (const Line& l : lines_) {
        collect_offsets->push_back(l.bytecode_offset);
      }
    }
  }

  size_t ApproximateSizeMB() { return approximate_size_mb(); }

 private:
  struct Line {
    Line(const char* d, size_t length, uint32_t bytecode_offset)
        : data(d), len(length), bytecode_offset(bytecode_offset) {}
    const char* data;
    size_t len;
    uint32_t bytecode_offset;
  };

  std::vector<Line> lines_;
  uint32_t pending_bytecode_offset_ = 0;
};

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

#endif  // V8_WASM_STRING_BUILDER_MULTILINE_H_