// Copyright 2016 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-text.h"

#include "src/debug/interface-types.h"
#include "src/objects-inl.h"
#include "src/ostreams.h"
#include "src/vector.h"
#include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/function-body-decoder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/zone/zone.h"

#if __clang__
// TODO(mostynb@opera.com): remove the using statements and these pragmas.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wheader-hygiene"
#endif

using namespace v8;
using namespace v8::internal;
using namespace v8::internal::wasm;

#if __clang__
// TODO(mostynb@opera.com): remove the using statements and these pragmas.
#pragma clang diagnostic pop
#endif

namespace {
bool IsValidFunctionName(const Vector<const char> &name) {
  if (name.is_empty()) return false;
  const char *special_chars = "_.+-*/\\^~=<>!?@#$%&|:'`";
  for (char c : name) {
    bool valid_char = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
                      (c >= 'A' && c <= 'Z') || strchr(special_chars, c);
    if (!valid_char) return false;
  }
  return true;
}

}  // namespace

void wasm::PrintWasmText(const WasmModule *module,
                         const ModuleWireBytes &wire_bytes, uint32_t func_index,
                         std::ostream &os,
                         debug::WasmDisassembly::OffsetTable *offset_table) {
  DCHECK_NOT_NULL(module);
  DCHECK_GT(module->functions.size(), func_index);
  const WasmFunction *fun = &module->functions[func_index];

  AccountingAllocator allocator;
  Zone zone(&allocator, ZONE_NAME);
  int line_nr = 0;
  int control_depth = 1;

  // Print the function signature.
  os << "func";
  WasmName fun_name = wire_bytes.GetNameOrNull(fun);
  if (IsValidFunctionName(fun_name)) {
    os << " $";
    os.write(fun_name.start(), fun_name.length());
  }
  if (fun->sig->parameter_count()) {
    os << " (param";
    for (auto param : fun->sig->parameters())
      os << ' ' << WasmOpcodes::TypeName(param);
    os << ')';
  }
  if (fun->sig->return_count()) {
    os << " (result";
    for (auto ret : fun->sig->returns())
      os << ' ' << WasmOpcodes::TypeName(ret);
    os << ')';
  }
  os << "\n";
  ++line_nr;

  // Print the local declarations.
  BodyLocalDecls decls(&zone);
  Vector<const byte> func_bytes = wire_bytes.GetFunctionBytes(fun);
  BytecodeIterator i(func_bytes.begin(), func_bytes.end(), &decls);
  DCHECK_LT(func_bytes.begin(), i.pc());
  if (!decls.type_list.empty()) {
    os << "(local";
    for (const ValueType &v : decls.type_list) {
      os << ' ' << WasmOpcodes::TypeName(v);
    }
    os << ")\n";
    ++line_nr;
  }

  for (; i.has_next(); i.next()) {
    WasmOpcode opcode = i.current();
    if (opcode == kExprElse || opcode == kExprEnd) --control_depth;

    DCHECK_LE(0, control_depth);
    const int kMaxIndentation = 64;
    int indentation = std::min(kMaxIndentation, 2 * control_depth);
    if (offset_table) {
      offset_table->emplace_back(i.pc_offset(), line_nr, indentation);
    }

    // 64 whitespaces
    const char padding[kMaxIndentation + 1] =
        "                                                                ";
    os.write(padding, indentation);

    switch (opcode) {
      case kExprLoop:
      case kExprIf:
      case kExprBlock:
      case kExprTry: {
        BlockTypeOperand<false> operand(&i, i.pc());
        os << WasmOpcodes::OpcodeName(opcode);
        for (unsigned i = 0; i < operand.arity; i++) {
          os << " " << WasmOpcodes::TypeName(operand.read_entry(i));
        }
        control_depth++;
        break;
      }
      case kExprBr:
      case kExprBrIf: {
        BreakDepthOperand<false> operand(&i, i.pc());
        os << WasmOpcodes::OpcodeName(opcode) << ' ' << operand.depth;
        break;
      }
      case kExprElse:
        os << "else";
        control_depth++;
        break;
      case kExprEnd:
        os << "end";
        break;
      case kExprBrTable: {
        BranchTableOperand<false> operand(&i, i.pc());
        BranchTableIterator<false> iterator(&i, operand);
        os << "br_table";
        while (iterator.has_next()) os << ' ' << iterator.next();
        break;
      }
      case kExprCallIndirect: {
        CallIndirectOperand<false> operand(&i, i.pc());
        DCHECK_EQ(0, operand.table_index);
        os << "call_indirect " << operand.index;
        break;
      }
      case kExprCallFunction: {
        CallFunctionOperand<false> operand(&i, i.pc());
        os << "call " << operand.index;
        break;
      }
      case kExprGetLocal:
      case kExprSetLocal:
      case kExprTeeLocal: {
        LocalIndexOperand<false> operand(&i, i.pc());
        os << WasmOpcodes::OpcodeName(opcode) << ' ' << operand.index;
        break;
      }
      case kExprThrow:
      case kExprCatch: {
        ExceptionIndexOperand<false> operand(&i, i.pc());
        os << WasmOpcodes::OpcodeName(opcode) << ' ' << operand.index;
        break;
      }
      case kExprGetGlobal:
      case kExprSetGlobal: {
        GlobalIndexOperand<false> operand(&i, i.pc());
        os << WasmOpcodes::OpcodeName(opcode) << ' ' << operand.index;
        break;
      }
#define CASE_CONST(type, str, cast_type)                           \
  case kExpr##type##Const: {                                       \
    Imm##type##Operand<false> operand(&i, i.pc());                 \
    os << #str ".const " << static_cast<cast_type>(operand.value); \
    break;                                                         \
  }
        CASE_CONST(I32, i32, int32_t)
        CASE_CONST(I64, i64, int64_t)
        CASE_CONST(F32, f32, float)
        CASE_CONST(F64, f64, double)

#define CASE_OPCODE(opcode, _, __) case kExpr##opcode:
        FOREACH_LOAD_MEM_OPCODE(CASE_OPCODE)
        FOREACH_STORE_MEM_OPCODE(CASE_OPCODE) {
          MemoryAccessOperand<false> operand(&i, i.pc(), kMaxUInt32);
          os << WasmOpcodes::OpcodeName(opcode) << " offset=" << operand.offset
             << " align=" << (1ULL << operand.alignment);
          break;
        }

        FOREACH_SIMPLE_OPCODE(CASE_OPCODE)
      case kExprUnreachable:
      case kExprNop:
      case kExprReturn:
      case kExprMemorySize:
      case kExprGrowMemory:
      case kExprDrop:
      case kExprSelect:
        os << WasmOpcodes::OpcodeName(opcode);
        break;

        // This group is just printed by their internal opcode name, as they
        // should never be shown to end-users.
        FOREACH_ASMJS_COMPAT_OPCODE(CASE_OPCODE)
        // TODO(wasm): Add correct printing for SIMD and atomic opcodes once
        // they are publicly available.
        FOREACH_SIMD_0_OPERAND_OPCODE(CASE_OPCODE)
        FOREACH_SIMD_1_OPERAND_OPCODE(CASE_OPCODE)
        FOREACH_SIMD_MASK_OPERAND_OPCODE(CASE_OPCODE)
        FOREACH_SIMD_MEM_OPCODE(CASE_OPCODE)
        FOREACH_ATOMIC_OPCODE(CASE_OPCODE)
        os << WasmOpcodes::OpcodeName(opcode);
        break;

      default:
        UNREACHABLE();
        break;
    }
    os << '\n';
    ++line_nr;
  }
  DCHECK_EQ(0, control_depth);
  DCHECK(i.ok());
}
