// Copyright 2025 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_REGEXP_REGEXP_BYTECODES_INL_H_
#define V8_REGEXP_REGEXP_BYTECODES_INL_H_

#include "irregexp/imported/regexp-bytecodes.h"
// Include the non-inl header before the rest of the headers.

#include <array>
#include <limits>
#include <string_view>
#include <type_traits>

#include "irregexp/imported/regexp-macro-assembler.h"  // For StackCheckFlag

namespace v8 {
namespace internal {
namespace regexp {

template <BytecodeOperandType>
struct OperandTypeTraits;

#define DECLARE_BASIC_OPERAND_TYPE_TRAITS(Name, CType)                      \
  template <>                                                               \
  struct OperandTypeTraits<BytecodeOperandType::k##Name> {                  \
    static_assert(!std::is_pointer_v<CType>);                               \
    static constexpr uint8_t kSize = sizeof(CType);                         \
    using kCType = CType;                                                   \
    static constexpr bool kIsBasic = true;                                  \
    static constexpr kCType kMinValue = std::numeric_limits<kCType>::min(); \
    static constexpr kCType kMaxValue = std::numeric_limits<kCType>::max(); \
    static constexpr size_t kAlignment = kSize;                             \
  };
BASIC_BYTECODE_OPERAND_TYPE_LIST(DECLARE_BASIC_OPERAND_TYPE_TRAITS)
#undef DECLARE_OPERAND_TYPE_TRAITS

#define DECLARE_BASIC_OPERAND_TYPE_LIMITS_TRAITS(Name, CType, MinValue, \
                                                 MaxValue)              \
  template <>                                                           \
  struct OperandTypeTraits<BytecodeOperandType::k##Name> {              \
    static_assert(!std::is_pointer_v<CType>);                           \
    static constexpr uint8_t kSize = sizeof(CType);                     \
    using kCType = CType;                                               \
    static constexpr bool kIsBasic = true;                              \
    static_assert(std::is_enum_v<kCType> ||                             \
                  MinValue >= std::numeric_limits<kCType>::min());      \
    static_assert(std::is_enum_v<kCType> ||                             \
                  MaxValue <= std::numeric_limits<kCType>::max());      \
    static constexpr kCType kMinValue = MinValue;                       \
    static constexpr kCType kMaxValue = MaxValue;                       \
    static constexpr size_t kAlignment = kSize;                         \
  };
BASIC_BYTECODE_OPERAND_TYPE_LIMITS_LIST(
    DECLARE_BASIC_OPERAND_TYPE_LIMITS_TRAITS)
#undef DECLARE_OPERAND_TYPE_LIMITS_TRAITS

#define DECLARE_SPECIAL_OPERAND_TYPE_TRAITS(Name, Size, Alignment) \
  template <>                                                      \
  struct OperandTypeTraits<BytecodeOperandType::k##Name> {         \
    static constexpr uint8_t kSize = Size;                         \
    static constexpr bool kIsBasic = false;                        \
    static constexpr size_t kAlignment = Alignment;                \
    static_assert(IsAligned(kSize, kAlignment));                   \
  };
SPECIAL_BYTECODE_OPERAND_TYPE_LIST(DECLARE_SPECIAL_OPERAND_TYPE_TRAITS)
#undef DECLARE_OPERAND_TYPE_TRAITS

namespace detail {

template <auto... Args>
constexpr int CountOf() {
  return sizeof...(Args);
}

template <size_t N>
constexpr std::array<std::string_view, N> SplitNames(const char* raw_names) {
  std::array<std::string_view, N> result;
  std::string_view names(raw_names);

  // Remove '(' and ')'.
  DCHECK_EQ(names.front(), '(');
  DCHECK_EQ(names.back(), ')');
  size_t start = 1;
  size_t names_size = names.size() - 1;

  for (size_t i = 0; i < N; ++i) {
    size_t comma = names.find(',', start);
    DCHECK_EQ(i == N - 1, comma == std::string_view::npos);

    // Trim whitespace.
    start = names.find_first_not_of(" ", start);
    size_t end = (comma == std::string_view::npos) ? names_size : comma;
    end = names.find_last_not_of(" ,)", end) + 1;
    result[i] = names.substr(start, end - start);

    start = comma + 1;
  }

  return result;
}

// Calculates packed offsets for each Bytecode operand.
// All operands are aligned to their own size.
template <BytecodeOperandType... operand_types>
constexpr auto CalculateAlignedOffsets() {
  constexpr int N = sizeof...(operand_types);
  constexpr std::array<uint8_t, N> kOperandSizes = {
      OperandTypeTraits<operand_types>::kSize...};
  constexpr std::array<uint8_t, N> kOperandAlignments = {
      OperandTypeTraits<operand_types>::kAlignment...};

  std::array<int, N> offsets{};
  int first_offset = sizeof(Bytecode);
  int offset = first_offset;

  for (size_t i = 0; i < N; ++i) {
    uint8_t operand_size = kOperandSizes[i];
    size_t operand_alignment = kOperandAlignments[i];

    offset = RoundUp(offset, operand_alignment);

    // If the operand doesn't fit into the current 4-byte block, start a new
    // 4-byte block.
    if ((offset % kBytecodeAlignment) + operand_size > kBytecodeAlignment) {
      offset = RoundUp<kBytecodeAlignment>(offset);
    }

    offsets[i] = offset;
    offset += operand_size;
  }

  return offsets;
}

template <BytecodeOperandType... ops>
struct BytecodeOperandsTraits {
  static constexpr int kOperandCount = sizeof...(ops);
  static constexpr std::array<BytecodeOperandType, kOperandCount>
      kOperandTypes = {ops...};
  static constexpr std::array<uint8_t, kOperandCount> kOperandSizes = {
      OperandTypeTraits<ops>::kSize...};
  static constexpr std::array<uint8_t, kOperandCount> kOperandAlignments = {
      OperandTypeTraits<ops>::kAlignment...};
  static constexpr std::array<int, kOperandCount> kOperandOffsets =
      CalculateAlignedOffsets<ops...>();
  static constexpr int kSize = RoundUp<kBytecodeAlignment>(
      kOperandCount == 0 ? sizeof(Bytecode)
                         : kOperandOffsets.back() + kOperandSizes.back());
};

template <Bytecode bc>
struct BytecodeOperandNames;

#define DECLARE_OPERAND_NAMES(CamelName, OpNames, OpTypes, ...)           \
  template <>                                                             \
  struct BytecodeOperandNames<Bytecode::k##CamelName> {                   \
    enum Operand { UNPAREN(OpNames) };                                    \
    static constexpr size_t kCount = detail::CountOf<UNPAREN(OpNames)>(); \
    static constexpr auto kNames = detail::SplitNames<kCount>(#OpNames);  \
    static constexpr std::string_view Name(Operand op) {                  \
      return kNames[static_cast<size_t>(op)];                             \
    }                                                                     \
  };
REGEXP_BYTECODE_LIST(DECLARE_OPERAND_NAMES)
#undef DECLARE_OPERAND_NAMES

template <Bytecode bc, BytecodeOperandType... OpTypes>
class BytecodeOperandsBase : public BytecodeOperandNames<bc> {
 public:
  static constexpr Bytecode kBytecode = bc;
  using Operand = BytecodeOperandNames<bc>::Operand;
  using Traits = BytecodeOperandsTraits<OpTypes...>;
  static constexpr int kCount = Traits::kOperandCount;
  static constexpr int kTotalSize = Traits::kSize;
  static constexpr int Index(Operand op) { return static_cast<uint8_t>(op); }
  static constexpr int Size(Operand op) {
    return Traits::kOperandSizes[Index(op)];
  }
  static constexpr int Offset(Operand op) {
    return Traits::kOperandOffsets[Index(op)];
  }
  static constexpr BytecodeOperandType Type(Operand op) {
    return Traits::kOperandTypes[Index(op)];
  }

  static constexpr std::string_view Name(Operand op) {
    return BytecodeOperandNames<bc>::Name(op);
  }

  // Returns a tuple of all operands.
  static constexpr auto GetOperandsTuple() {
    return []<size_t... Is>(std::index_sequence<Is...>) {
      return std::tuple_cat([]<size_t I>() {
        constexpr auto id = static_cast<Operand>(I);
        return std::tuple(std::integral_constant<Operand, id>{});
      }.template operator()<Is>()...);
    }(std::make_index_sequence<kCount>{});
  }

  // Calls |f| templatized by Operand for each Operand in the Operands list.
  // Example:
  // using Operands = BytecodeOperands<Bytecode::...>;
  // size_t op_sizes = 0;
  // Operands::ForEachOperand([]<auto op>() {
  //   op_sizes += Operands::Size(op);
  // });
  // Note that this gets evaluated at compile time, so op_sizes in the example
  // above is essentially a constant.
  template <typename Func>
  static constexpr void ForEachOperand(Func&& f) {
    constexpr auto filtered_ops = GetOperandsTuple();
    std::apply([&](auto... ops) { (..., f.template operator()<ops.value>()); },
               filtered_ops);
  }

  // Similar to ForEachOperand, but additionally provides the current index as
  // a template argument. The index is a sequential index of operands.
  template <typename Func>
  static constexpr void ForEachOperandWithIndex(Func&& f) {
    constexpr auto filtered_ops = GetOperandsTuple();
    [&]<size_t... I>(std::index_sequence<I...>) {
      (...,
       f.template operator()<
           std::tuple_element_t<I, decltype(filtered_ops)>::value /* Operand */,
           I /* Index */>());
    }(std::make_index_sequence<std::tuple_size_v<decltype(filtered_ops)>>{});
  }

  // Similar to above, but calls |f| only for operands of a given type.
  template <BytecodeOperandType OpType, typename Func>
  static constexpr void ForEachOperandOfType(Func&& f) {
    ForEachOperand([&]<auto operand>() {
      if constexpr (Type(operand) == OpType) {
        f.template operator()<operand>();
      }
    });
  }

 public:
  template <Operand op>
    requires(OperandTypeTraits<Type(op)>::kIsBasic)
  static auto Get(const uint8_t* pc, const DisallowGarbageCollection& no_gc) {
    DCHECK_EQ(Bytecodes::FromPtr(pc), bc);
    constexpr BytecodeOperandType OperandType = Type(op);
    constexpr int offset = Offset(op);
    using CType = OperandTypeTraits<OperandType>::kCType;
    DCHECK(IsAligned(offset, sizeof(CType)));
    return *reinterpret_cast<const CType*>(pc + offset);
  }

  template <Operand op>
    requires(OperandTypeTraits<Type(op)>::kIsBasic)
  static auto Get(DirectHandle<TrustedByteArray> bytecode, int offset,
                  Zone* zone) {
    // Basic operand types won't allocate, so we can always fallback to the
    // GC-unsafe version.
    DisallowGarbageCollection no_gc;
    return Get<op>(bytecode->begin() + offset, no_gc);
  }

  template <Operand op>
    requires(Type(op) == BytecodeOperandType::kBitTable)
  static auto Get(const uint8_t* pc,
                  const DisallowGarbageCollection& no_gc V8_LIFETIME_BOUND) {
    static_assert(Size(op) == RegExpMacroAssembler::kTableSize / kBitsPerByte);
    DCHECK_EQ(Bytecodes::FromPtr(pc), bc);
    constexpr int offset = Offset(op);
    return pc + offset;
  }

  template <Operand op>
    requires(Type(op) == BytecodeOperandType::kBitTable)
  static auto Get(DirectHandle<TrustedByteArray> bytecode, int offset,
                  Zone* zone) {
    static_assert(Size(op) == RegExpMacroAssembler::kTableSize / kBitsPerByte);
    DCHECK_EQ(Bytecodes::FromPtr(bytecode->begin() + offset), bc);
    constexpr int op_offset = Offset(op);
    const uint8_t* start = bytecode->begin() + offset + op_offset;
    const uint8_t* end = start + Size(op);
    return ZoneVector<uint8_t>(start, end, zone);
  }
};

}  // namespace detail

#define PACK_OPTIONAL(x, ...) x __VA_OPT__(, ) __VA_ARGS__

#define DECLARE_OPERANDS(CamelName, OpNames, OpTypes, ...) \
  template <>                                              \
  class BytecodeOperands<Bytecode::k##CamelName> final     \
      : public detail::BytecodeOperandsBase<PACK_OPTIONAL( \
            Bytecode::k##CamelName, UNPAREN(OpTypes))>,    \
        public AllStatic {                                 \
  };

REGEXP_BYTECODE_LIST(DECLARE_OPERANDS)
#undef DECLARE_OPERANDS

namespace detail {

#define DECLARE_BYTECODE_NAMES(CamelName, ...) #CamelName,
static constexpr const char* kBytecodeNames[] = {
    REGEXP_BYTECODE_LIST(DECLARE_BYTECODE_NAMES)};
#undef DECLARE_BYTECODE_NAMES

#define DECLARE_BYTECODE_SIZES(CamelName, ...) \
  BytecodeOperands<Bytecode::k##CamelName>::kTotalSize,
static constexpr uint8_t kBytecodeSizes[] = {
    REGEXP_BYTECODE_LIST(DECLARE_BYTECODE_SIZES)};
#undef DECLARE_BYTECODE_SIZES

#define DECLARE_BYTECODE_FLAGS(CamelName, OpNames, OpTypes, Flags) \
  BytecodeFlags(UNPAREN(Flags)),
static constexpr BytecodeFlags kBytecodeFlags[] = {
    REGEXP_BYTECODE_LIST(DECLARE_BYTECODE_FLAGS)};
#undef DECLARE_BYTECODE_FLAGS

#define DECLARE_OPERAND_TYPE_SIZE(Name, ...) \
  OperandTypeTraits<BytecodeOperandType::k##Name>::kSize,
static constexpr uint8_t kOperandTypeSizes[] = {
    BYTECODE_OPERAND_TYPE_LIST(DECLARE_OPERAND_TYPE_SIZE)};
#undef DECLARE_OPERAND_TYPE_SIZE

}  // namespace detail

// static
template <typename Func>
decltype(auto) Bytecodes::DispatchOnBytecode(Bytecode bytecode, Func&& f) {
  switch (bytecode) {
#define CASE(CamelName, ...)   \
  case Bytecode::k##CamelName: \
    return f.template operator()<Bytecode::k##CamelName>();
    REGEXP_BYTECODE_LIST(CASE)
#undef CASE
  }
  UNREACHABLE();
}

// static
constexpr const char* Bytecodes::Name(Bytecode bytecode) {
  return Name(ToByte(bytecode));
}

// static
constexpr const char* Bytecodes::Name(uint8_t bytecode) {
  DCHECK_LT(bytecode, kCount);
  return detail::kBytecodeNames[bytecode];
}

// static
constexpr uint8_t Bytecodes::Size(Bytecode bytecode) {
  return Size(ToByte(bytecode));
}

// static
constexpr uint8_t Bytecodes::Size(uint8_t bytecode) {
  DCHECK_LT(bytecode, kCount);
  return detail::kBytecodeSizes[bytecode];
}

// static
constexpr uint8_t Bytecodes::Size(BytecodeOperandType type) {
  return detail::kOperandTypeSizes[static_cast<int>(type)];
}

// static
constexpr BytecodeFlags Bytecodes::Flags(Bytecode bytecode) {
  return Flags(ToByte(bytecode));
}

// static
constexpr BytecodeFlags Bytecodes::Flags(uint8_t bytecode) {
  DCHECK_LT(bytecode, kCount);
  return detail::kBytecodeFlags[bytecode];
}

}  // namespace regexp
}  // namespace internal
}  // namespace v8

#endif  // V8_REGEXP_REGEXP_BYTECODES_INL_H_
