//===- WriterUtils.cpp ----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "WriterUtils.h"
#include "lld/Common/ErrorHandler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/EndianStream.h"
#include "llvm/Support/LEB128.h"

#define DEBUG_TYPE "lld"

using namespace llvm;
using namespace llvm::wasm;

namespace lld {
std::string toString(ValType type) {
  switch (type) {
  case ValType::I32:
    return "i32";
  case ValType::I64:
    return "i64";
  case ValType::F32:
    return "f32";
  case ValType::F64:
    return "f64";
  case ValType::V128:
    return "v128";
  case ValType::FUNCREF:
    return "funcref";
  case ValType::EXTERNREF:
    return "externref";
  }
  llvm_unreachable("Invalid wasm::ValType");
}

std::string toString(const WasmSignature &sig) {
  SmallString<128> s("(");
  for (ValType type : sig.Params) {
    if (s.size() != 1)
      s += ", ";
    s += toString(type);
  }
  s += ") -> ";
  if (sig.Returns.empty())
    s += "void";
  else
    s += toString(sig.Returns[0]);
  return std::string(s.str());
}

std::string toString(const WasmGlobalType &type) {
  return (type.Mutable ? "var " : "const ") +
         toString(static_cast<ValType>(type.Type));
}

static std::string toString(const llvm::wasm::WasmLimits &limits) {
  std::string ret;
  ret += "flags=0x" + std::to_string(limits.Flags);
  ret += "; min=" + std::to_string(limits.Minimum);
  if (limits.Flags & WASM_LIMITS_FLAG_HAS_MAX)
    ret += "; max=" + std::to_string(limits.Maximum);
  return ret;
}

std::string toString(const WasmTableType &type) {
  SmallString<128> ret("");
  return "type=" + toString(static_cast<ValType>(type.ElemType)) +
         "; limits=[" + toString(type.Limits) + "]";
}

namespace wasm {
#ifdef LLVM_DEBUG
void debugWrite(uint64_t offset, const Twine &msg) {
  LLVM_DEBUG(dbgs() << format("  | %08lld: ", offset) << msg << "\n");
}
#endif

void writeUleb128(raw_ostream &os, uint64_t number, const Twine &msg) {
  debugWrite(os.tell(), msg + "[" + utohexstr(number) + "]");
  encodeULEB128(number, os);
}

void writeSleb128(raw_ostream &os, int64_t number, const Twine &msg) {
  debugWrite(os.tell(), msg + "[" + utohexstr(number) + "]");
  encodeSLEB128(number, os);
}

void writeBytes(raw_ostream &os, const char *bytes, size_t count,
                      const Twine &msg) {
  debugWrite(os.tell(), msg + " [data[" + Twine(count) + "]]");
  os.write(bytes, count);
}

void writeStr(raw_ostream &os, StringRef string, const Twine &msg) {
  debugWrite(os.tell(),
             msg + " [str[" + Twine(string.size()) + "]: " + string + "]");
  encodeULEB128(string.size(), os);
  os.write(string.data(), string.size());
}

void writeU8(raw_ostream &os, uint8_t byte, const Twine &msg) {
  debugWrite(os.tell(), msg + " [0x" + utohexstr(byte) + "]");
  os << byte;
}

void writeU32(raw_ostream &os, uint32_t number, const Twine &msg) {
  debugWrite(os.tell(), msg + "[0x" + utohexstr(number) + "]");
  support::endian::write(os, number, support::little);
}

void writeU64(raw_ostream &os, uint64_t number, const Twine &msg) {
  debugWrite(os.tell(), msg + "[0x" + utohexstr(number) + "]");
  support::endian::write(os, number, support::little);
}

void writeValueType(raw_ostream &os, ValType type, const Twine &msg) {
  writeU8(os, static_cast<uint8_t>(type),
          msg + "[type: " + toString(type) + "]");
}

void writeSig(raw_ostream &os, const WasmSignature &sig) {
  writeU8(os, WASM_TYPE_FUNC, "signature type");
  writeUleb128(os, sig.Params.size(), "param Count");
  for (ValType paramType : sig.Params) {
    writeValueType(os, paramType, "param type");
  }
  writeUleb128(os, sig.Returns.size(), "result Count");
  for (ValType returnType : sig.Returns) {
    writeValueType(os, returnType, "result type");
  }
}

void writeI32Const(raw_ostream &os, int32_t number, const Twine &msg) {
  writeU8(os, WASM_OPCODE_I32_CONST, "i32.const");
  writeSleb128(os, number, msg);
}

void writeI64Const(raw_ostream &os, int64_t number, const Twine &msg) {
  writeU8(os, WASM_OPCODE_I64_CONST, "i64.const");
  writeSleb128(os, number, msg);
}

void writePtrConst(raw_ostream &os, int64_t number, bool is64,
                   const Twine &msg) {
  if (is64)
    writeI64Const(os, number, msg);
  else
    writeI32Const(os, static_cast<int32_t>(number), msg);
}

void writeMemArg(raw_ostream &os, uint32_t alignment, uint64_t offset) {
  writeUleb128(os, alignment, "alignment");
  writeUleb128(os, offset, "offset");
}

void writeInitExpr(raw_ostream &os, const WasmInitExpr &initExpr) {
  writeU8(os, initExpr.Opcode, "opcode");
  switch (initExpr.Opcode) {
  case WASM_OPCODE_I32_CONST:
    writeSleb128(os, initExpr.Value.Int32, "literal (i32)");
    break;
  case WASM_OPCODE_I64_CONST:
    writeSleb128(os, initExpr.Value.Int64, "literal (i64)");
    break;
  case WASM_OPCODE_F32_CONST:
    writeU32(os, initExpr.Value.Float32, "literal (f32)");
    break;
  case WASM_OPCODE_F64_CONST:
    writeU64(os, initExpr.Value.Float64, "literal (f64)");
    break;
  case WASM_OPCODE_GLOBAL_GET:
    writeUleb128(os, initExpr.Value.Global, "literal (global index)");
    break;
  case WASM_OPCODE_REF_NULL:
    writeValueType(os, ValType::EXTERNREF, "literal (externref type)");
    break;
  default:
    fatal("unknown opcode in init expr: " + Twine(initExpr.Opcode));
  }
  writeU8(os, WASM_OPCODE_END, "opcode:end");
}

void writeLimits(raw_ostream &os, const WasmLimits &limits) {
  writeU8(os, limits.Flags, "limits flags");
  writeUleb128(os, limits.Minimum, "limits min");
  if (limits.Flags & WASM_LIMITS_FLAG_HAS_MAX)
    writeUleb128(os, limits.Maximum, "limits max");
}

void writeGlobalType(raw_ostream &os, const WasmGlobalType &type) {
  // TODO: Update WasmGlobalType to use ValType and remove this cast.
  writeValueType(os, ValType(type.Type), "global type");
  writeU8(os, type.Mutable, "global mutable");
}

void writeTableType(raw_ostream &os, const WasmTableType &type) {
  writeValueType(os, ValType(type.ElemType), "table type");
  writeLimits(os, type.Limits);
}

void writeImport(raw_ostream &os, const WasmImport &import) {
  writeStr(os, import.Module, "import module name");
  writeStr(os, import.Field, "import field name");
  writeU8(os, import.Kind, "import kind");
  switch (import.Kind) {
  case WASM_EXTERNAL_FUNCTION:
    writeUleb128(os, import.SigIndex, "import sig index");
    break;
  case WASM_EXTERNAL_GLOBAL:
    writeGlobalType(os, import.Global);
    break;
  case WASM_EXTERNAL_TAG:
    writeUleb128(os, 0, "tag attribute"); // Reserved "attribute" field
    writeUleb128(os, import.SigIndex, "import sig index");
    break;
  case WASM_EXTERNAL_MEMORY:
    writeLimits(os, import.Memory);
    break;
  case WASM_EXTERNAL_TABLE:
    writeTableType(os, import.Table);
    break;
  default:
    fatal("unsupported import type: " + Twine(import.Kind));
  }
}

void writeExport(raw_ostream &os, const WasmExport &export_) {
  writeStr(os, export_.Name, "export name");
  writeU8(os, export_.Kind, "export kind");
  switch (export_.Kind) {
  case WASM_EXTERNAL_FUNCTION:
    writeUleb128(os, export_.Index, "function index");
    break;
  case WASM_EXTERNAL_GLOBAL:
    writeUleb128(os, export_.Index, "global index");
    break;
  case WASM_EXTERNAL_TAG:
    writeUleb128(os, export_.Index, "tag index");
    break;
  case WASM_EXTERNAL_MEMORY:
    writeUleb128(os, export_.Index, "memory index");
    break;
  case WASM_EXTERNAL_TABLE:
    writeUleb128(os, export_.Index, "table index");
    break;
  default:
    fatal("unsupported export type: " + Twine(export_.Kind));
  }
}

} // namespace wasm
} // namespace lld
