/*
 * Copyright 2017 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "wabt/opcode.h"

#include "wabt/feature.h"

namespace wabt {

// static
Opcode::Info Opcode::infos_[] = {
#define WABT_OPCODE(rtype, type1, type2, type3, mem_size, prefix, code, Name, \
                    text, decomp)                                             \
  {text,     decomp, Type::rtype, {Type::type1, Type::type2, Type::type3},    \
   mem_size, prefix, code,        PrefixCode(prefix, code)},
#include "wabt/opcode.def"
#undef WABT_OPCODE

  {"<invalid>", "", Type::Void, {Type::Void, Type::Void, Type::Void}, 0, 0, 0, 0},
};

#define WABT_OPCODE(rtype, type1, type2, type3, mem_size, prefix, code, Name, \
                    text, decomp)                                             \
  /* static */ Opcode Opcode::Name##_Opcode(Opcode::Name);
#include "wabt/opcode.def"
#undef WABT_OPCODE

Opcode::Info Opcode::GetInfo() const {
  if (enum_ < Invalid) {
    return infos_[enum_];
  }

  Info invalid_info = infos_[Opcode::Invalid];
  DecodeInvalidOpcode(enum_, &invalid_info.prefix, &invalid_info.code);
  invalid_info.prefix_code = PrefixCode(invalid_info.prefix, invalid_info.code);
  return invalid_info;
}

bool Opcode::IsNaturallyAligned(Address alignment) const {
  Address opcode_align = GetMemorySize();
  return alignment == WABT_USE_NATURAL_ALIGNMENT || alignment == opcode_align;
}

Address Opcode::GetAlignment(Address alignment) const {
  if (alignment == WABT_USE_NATURAL_ALIGNMENT) {
    return GetMemorySize();
  }
  return alignment;
}

bool Opcode::IsEnabled(const Features& features) const {
  switch (enum_) {
    case Opcode::Try:
    case Opcode::Catch:
    case Opcode::Delegate:
    case Opcode::Throw:
    case Opcode::Rethrow:
      return features.exceptions_enabled();

    case Opcode::ReturnCallIndirect:
    case Opcode::ReturnCall:
      return features.tail_call_enabled();

    case Opcode::I32TruncSatF32S:
    case Opcode::I32TruncSatF32U:
    case Opcode::I32TruncSatF64S:
    case Opcode::I32TruncSatF64U:
    case Opcode::I64TruncSatF32S:
    case Opcode::I64TruncSatF32U:
    case Opcode::I64TruncSatF64S:
    case Opcode::I64TruncSatF64U:
      return features.sat_float_to_int_enabled();

    case Opcode::I32Extend8S:
    case Opcode::I32Extend16S:
    case Opcode::I64Extend8S:
    case Opcode::I64Extend16S:
    case Opcode::I64Extend32S:
      return features.sign_extension_enabled();

    case Opcode::MemoryAtomicNotify:
    case Opcode::MemoryAtomicWait32:
    case Opcode::MemoryAtomicWait64:
    case Opcode::AtomicFence:
    case Opcode::I32AtomicLoad:
    case Opcode::I64AtomicLoad:
    case Opcode::I32AtomicLoad8U:
    case Opcode::I32AtomicLoad16U:
    case Opcode::I64AtomicLoad8U:
    case Opcode::I64AtomicLoad16U:
    case Opcode::I64AtomicLoad32U:
    case Opcode::I32AtomicStore:
    case Opcode::I64AtomicStore:
    case Opcode::I32AtomicStore8:
    case Opcode::I32AtomicStore16:
    case Opcode::I64AtomicStore8:
    case Opcode::I64AtomicStore16:
    case Opcode::I64AtomicStore32:
    case Opcode::I32AtomicRmwAdd:
    case Opcode::I64AtomicRmwAdd:
    case Opcode::I32AtomicRmw8AddU:
    case Opcode::I32AtomicRmw16AddU:
    case Opcode::I64AtomicRmw8AddU:
    case Opcode::I64AtomicRmw16AddU:
    case Opcode::I64AtomicRmw32AddU:
    case Opcode::I32AtomicRmwSub:
    case Opcode::I64AtomicRmwSub:
    case Opcode::I32AtomicRmw8SubU:
    case Opcode::I32AtomicRmw16SubU:
    case Opcode::I64AtomicRmw8SubU:
    case Opcode::I64AtomicRmw16SubU:
    case Opcode::I64AtomicRmw32SubU:
    case Opcode::I32AtomicRmwAnd:
    case Opcode::I64AtomicRmwAnd:
    case Opcode::I32AtomicRmw8AndU:
    case Opcode::I32AtomicRmw16AndU:
    case Opcode::I64AtomicRmw8AndU:
    case Opcode::I64AtomicRmw16AndU:
    case Opcode::I64AtomicRmw32AndU:
    case Opcode::I32AtomicRmwOr:
    case Opcode::I64AtomicRmwOr:
    case Opcode::I32AtomicRmw8OrU:
    case Opcode::I32AtomicRmw16OrU:
    case Opcode::I64AtomicRmw8OrU:
    case Opcode::I64AtomicRmw16OrU:
    case Opcode::I64AtomicRmw32OrU:
    case Opcode::I32AtomicRmwXor:
    case Opcode::I64AtomicRmwXor:
    case Opcode::I32AtomicRmw8XorU:
    case Opcode::I32AtomicRmw16XorU:
    case Opcode::I64AtomicRmw8XorU:
    case Opcode::I64AtomicRmw16XorU:
    case Opcode::I64AtomicRmw32XorU:
    case Opcode::I32AtomicRmwXchg:
    case Opcode::I64AtomicRmwXchg:
    case Opcode::I32AtomicRmw8XchgU:
    case Opcode::I32AtomicRmw16XchgU:
    case Opcode::I64AtomicRmw8XchgU:
    case Opcode::I64AtomicRmw16XchgU:
    case Opcode::I64AtomicRmw32XchgU:
    case Opcode::I32AtomicRmwCmpxchg:
    case Opcode::I64AtomicRmwCmpxchg:
    case Opcode::I32AtomicRmw8CmpxchgU:
    case Opcode::I32AtomicRmw16CmpxchgU:
    case Opcode::I64AtomicRmw8CmpxchgU:
    case Opcode::I64AtomicRmw16CmpxchgU:
    case Opcode::I64AtomicRmw32CmpxchgU:
      return features.threads_enabled();

    case Opcode::V128Const:
    case Opcode::V128Load:
    case Opcode::V128Store:
    case Opcode::I8X16Splat:
    case Opcode::I16X8Splat:
    case Opcode::I32X4Splat:
    case Opcode::I64X2Splat:
    case Opcode::F32X4Splat:
    case Opcode::F64X2Splat:
    case Opcode::I8X16ExtractLaneS:
    case Opcode::I8X16ExtractLaneU:
    case Opcode::I16X8ExtractLaneS:
    case Opcode::I16X8ExtractLaneU:
    case Opcode::I32X4ExtractLane:
    case Opcode::I64X2ExtractLane:
    case Opcode::F32X4ExtractLane:
    case Opcode::F64X2ExtractLane:
    case Opcode::I8X16ReplaceLane:
    case Opcode::I16X8ReplaceLane:
    case Opcode::I32X4ReplaceLane:
    case Opcode::I64X2ReplaceLane:
    case Opcode::F32X4ReplaceLane:
    case Opcode::F64X2ReplaceLane:
    case Opcode::I8X16Add:
    case Opcode::I16X8Add:
    case Opcode::I32X4Add:
    case Opcode::I64X2Add:
    case Opcode::I8X16Sub:
    case Opcode::I16X8Sub:
    case Opcode::I32X4Sub:
    case Opcode::I64X2Sub:
    case Opcode::I16X8Mul:
    case Opcode::I32X4Mul:
    case Opcode::I8X16Neg:
    case Opcode::I16X8Neg:
    case Opcode::I32X4Neg:
    case Opcode::I64X2Neg:
    case Opcode::I8X16AddSatS:
    case Opcode::I8X16AddSatU:
    case Opcode::I16X8AddSatS:
    case Opcode::I16X8AddSatU:
    case Opcode::I8X16SubSatS:
    case Opcode::I8X16SubSatU:
    case Opcode::I16X8SubSatS:
    case Opcode::I16X8SubSatU:
    case Opcode::I8X16Shl:
    case Opcode::I16X8Shl:
    case Opcode::I32X4Shl:
    case Opcode::I64X2Shl:
    case Opcode::I8X16ShrS:
    case Opcode::I8X16ShrU:
    case Opcode::I16X8ShrS:
    case Opcode::I16X8ShrU:
    case Opcode::I32X4ShrS:
    case Opcode::I32X4ShrU:
    case Opcode::I64X2ShrS:
    case Opcode::I64X2ShrU:
    case Opcode::V128And:
    case Opcode::V128Or:
    case Opcode::V128Xor:
    case Opcode::V128Not:
    case Opcode::V128BitSelect:
    case Opcode::V128AnyTrue:
    case Opcode::I8X16Bitmask:
    case Opcode::I16X8Bitmask:
    case Opcode::I32X4Bitmask:
    case Opcode::I64X2Bitmask:
    case Opcode::I8X16AllTrue:
    case Opcode::I16X8AllTrue:
    case Opcode::I32X4AllTrue:
    case Opcode::I64X2AllTrue:
    case Opcode::I8X16Eq:
    case Opcode::I16X8Eq:
    case Opcode::I32X4Eq:
    case Opcode::F32X4Eq:
    case Opcode::F64X2Eq:
    case Opcode::I8X16Ne:
    case Opcode::I16X8Ne:
    case Opcode::I32X4Ne:
    case Opcode::F32X4Ne:
    case Opcode::F64X2Ne:
    case Opcode::I8X16LtS:
    case Opcode::I8X16LtU:
    case Opcode::I16X8LtS:
    case Opcode::I16X8LtU:
    case Opcode::I32X4LtS:
    case Opcode::I32X4LtU:
    case Opcode::F32X4Lt:
    case Opcode::F64X2Lt:
    case Opcode::I8X16LeS:
    case Opcode::I8X16LeU:
    case Opcode::I16X8LeS:
    case Opcode::I16X8LeU:
    case Opcode::I32X4LeS:
    case Opcode::I32X4LeU:
    case Opcode::F32X4Le:
    case Opcode::F64X2Le:
    case Opcode::I8X16GtS:
    case Opcode::I8X16GtU:
    case Opcode::I16X8GtS:
    case Opcode::I16X8GtU:
    case Opcode::I32X4GtS:
    case Opcode::I32X4GtU:
    case Opcode::F32X4Gt:
    case Opcode::F64X2Gt:
    case Opcode::I8X16GeS:
    case Opcode::I8X16GeU:
    case Opcode::I16X8GeS:
    case Opcode::I16X8GeU:
    case Opcode::I32X4GeS:
    case Opcode::I32X4GeU:
    case Opcode::F32X4Ge:
    case Opcode::F64X2Ge:
    case Opcode::F32X4Neg:
    case Opcode::F64X2Neg:
    case Opcode::F32X4Abs:
    case Opcode::F64X2Abs:
    case Opcode::F32X4Min:
    case Opcode::F32X4PMin:
    case Opcode::F64X2Min:
    case Opcode::F64X2PMin:
    case Opcode::F32X4Max:
    case Opcode::F32X4PMax:
    case Opcode::F64X2Max:
    case Opcode::F64X2PMax:
    case Opcode::F32X4Add:
    case Opcode::F64X2Add:
    case Opcode::F32X4Sub:
    case Opcode::F64X2Sub:
    case Opcode::F32X4Div:
    case Opcode::F64X2Div:
    case Opcode::F32X4Mul:
    case Opcode::F64X2Mul:
    case Opcode::F32X4Sqrt:
    case Opcode::F64X2Sqrt:
    case Opcode::F32X4ConvertI32X4S:
    case Opcode::F32X4ConvertI32X4U:
    case Opcode::I32X4TruncSatF32X4S:
    case Opcode::I32X4TruncSatF32X4U:
    case Opcode::I8X16Swizzle:
    case Opcode::I8X16Shuffle:
    case Opcode::V128Load8Splat:
    case Opcode::V128Load16Splat:
    case Opcode::V128Load32Splat:
    case Opcode::V128Load64Splat:
    case Opcode::V128Load8Lane:
    case Opcode::V128Load16Lane:
    case Opcode::V128Load32Lane:
    case Opcode::V128Load64Lane:
    case Opcode::V128Store8Lane:
    case Opcode::V128Store16Lane:
    case Opcode::V128Store32Lane:
    case Opcode::V128Store64Lane:
    case Opcode::I8X16Abs:
    case Opcode::I16X8Abs:
    case Opcode::I32X4Abs:
      return features.simd_enabled();

    case Opcode::I8X16RelaxedSwizzle:
    case Opcode::I32X4RelaxedTruncF32X4S:
    case Opcode::I32X4RelaxedTruncF32X4U:
    case Opcode::I32X4RelaxedTruncF64X2SZero:
    case Opcode::I32X4RelaxedTruncF64X2UZero:
    case Opcode::F32X4RelaxedMadd:
    case Opcode::F32X4RelaxedNmadd:
    case Opcode::F64X2RelaxedMadd:
    case Opcode::F64X2RelaxedNmadd:
    case Opcode::I8X16RelaxedLaneSelect:
    case Opcode::I16X8RelaxedLaneSelect:
    case Opcode::I32X4RelaxedLaneSelect:
    case Opcode::I64X2RelaxedLaneSelect:
    case Opcode::F32X4RelaxedMin:
    case Opcode::F32X4RelaxedMax:
    case Opcode::F64X2RelaxedMin:
    case Opcode::F64X2RelaxedMax:
    case Opcode::I16X8RelaxedQ15mulrS:
    case Opcode::I16X8DotI8X16I7X16S:
    case Opcode::I32X4DotI8X16I7X16AddS:
      return features.relaxed_simd_enabled();

    case Opcode::MemoryInit:
    case Opcode::DataDrop:
    case Opcode::MemoryCopy:
    case Opcode::MemoryFill:
    case Opcode::TableInit:
    case Opcode::ElemDrop:
    case Opcode::TableCopy:
      return features.bulk_memory_enabled();

    case Opcode::TableGet:
    case Opcode::TableSet:
    case Opcode::TableGrow:
    case Opcode::TableSize:
    case Opcode::RefNull:
    case Opcode::RefIsNull:
      return features.reference_types_enabled();

    case Opcode::CallRef:
      return features.function_references_enabled();

    // Interpreter opcodes are never "enabled".
    case Opcode::InterpAlloca:
    case Opcode::InterpBrUnless:
    case Opcode::InterpCallImport:
    case Opcode::InterpData:
    case Opcode::InterpDropKeep:
      return false;

    default:
      return true;
  }
}

uint32_t Opcode::GetSimdLaneCount() const {
  switch (enum_) {
    case Opcode::I8X16ExtractLaneS:
    case Opcode::I8X16ExtractLaneU:
    case Opcode::I8X16ReplaceLane:
    case Opcode::V128Load8Lane:
    case Opcode::V128Store8Lane:
      return 16;
      break;
    case Opcode::I16X8ExtractLaneS:
    case Opcode::I16X8ExtractLaneU:
    case Opcode::I16X8ReplaceLane:
    case Opcode::V128Load16Lane:
    case Opcode::V128Store16Lane:
      return 8;
      break;
    case Opcode::F32X4ExtractLane:
    case Opcode::F32X4ReplaceLane:
    case Opcode::I32X4ExtractLane:
    case Opcode::I32X4ReplaceLane:
    case Opcode::V128Load32Lane:
    case Opcode::V128Store32Lane:
      return 4;
      break;
    case Opcode::F64X2ExtractLane:
    case Opcode::F64X2ReplaceLane:
    case Opcode::I64X2ExtractLane:
    case Opcode::I64X2ReplaceLane:
    case Opcode::V128Load64Lane:
    case Opcode::V128Store64Lane:
      return 2;
      break;
    default:
      WABT_UNREACHABLE;
  }
}

// Get the byte sequence for this opcode, including prefix.
std::vector<uint8_t> Opcode::GetBytes() const {
  std::vector<uint8_t> result;
  if (HasPrefix()) {
    result.push_back(GetPrefix());
    uint8_t buffer[5];
    Offset length =
        WriteU32Leb128Raw(buffer, buffer + sizeof(buffer), GetCode());
    assert(length != 0);
    result.insert(result.end(), buffer, buffer + length);
  } else {
    result.push_back(GetCode());
  }
  return result;
}

}  // namespace wabt
