/*
 * Copyright 2018 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.
 */

// Abstracts out operations from specific opcodes.

#ifndef wasm_ir_abstract_h
#define wasm_ir_abstract_h

#include <wasm.h>

namespace wasm {

namespace Abstract {

enum Op {
  // Unary
  Neg,
  // Binary
  Add,
  Sub,
  Mul,
  DivU,
  DivS,
  Rem,
  RemU,
  RemS,
  Shl,
  ShrU,
  ShrS,
  And,
  Or,
  Xor,
  // Relational
  Eq,
  Ne,
};

// Provide a wasm type and an abstract op and get the concrete one. For example,
// you can provide i32 and Add and receive the specific opcode for a 32-bit
// addition, AddInt32. If the op does not exist, it returns Invalid.
inline UnaryOp getUnary(Type type, Op op) {
  switch (type.getSingle()) {
    case Type::i32: {
      return InvalidUnary;
    }
    case Type::i64: {
      return InvalidUnary;
    }
    case Type::f32: {
      switch (op) {
        case Neg:
          return NegFloat32;
        default:
          return InvalidUnary;
      }
      break;
    }
    case Type::f64: {
      switch (op) {
        case Neg:
          return NegFloat64;
        default:
          return InvalidUnary;
      }
      break;
    }
    case Type::v128: {
      WASM_UNREACHABLE("v128 not implemented yet");
    }
    case Type::funcref:
    case Type::anyref:
    case Type::nullref:
    case Type::exnref:
    case Type::none:
    case Type::unreachable: {
      return InvalidUnary;
    }
  }
  WASM_UNREACHABLE("invalid type");
}

inline BinaryOp getBinary(Type type, Op op) {
  switch (type.getSingle()) {
    case Type::i32: {
      switch (op) {
        case Add:
          return AddInt32;
        case Sub:
          return SubInt32;
        case Mul:
          return MulInt32;
        case DivU:
          return DivUInt32;
        case DivS:
          return DivSInt32;
        case RemU:
          return RemUInt32;
        case RemS:
          return RemSInt32;
        case Shl:
          return ShlInt32;
        case ShrU:
          return ShrUInt32;
        case ShrS:
          return ShrSInt32;
        case And:
          return AndInt32;
        case Or:
          return OrInt32;
        case Xor:
          return XorInt32;
        case Eq:
          return EqInt32;
        case Ne:
          return NeInt32;
        default:
          return InvalidBinary;
      }
      break;
    }
    case Type::i64: {
      switch (op) {
        case Add:
          return AddInt64;
        case Sub:
          return SubInt64;
        case Mul:
          return MulInt64;
        case DivU:
          return DivUInt64;
        case DivS:
          return DivSInt64;
        case RemU:
          return RemUInt64;
        case RemS:
          return RemSInt64;
        case Shl:
          return ShlInt64;
        case ShrU:
          return ShrUInt64;
        case ShrS:
          return ShrSInt64;
        case And:
          return AndInt64;
        case Or:
          return OrInt64;
        case Xor:
          return XorInt64;
        case Eq:
          return EqInt64;
        case Ne:
          return NeInt64;
        default:
          return InvalidBinary;
      }
      break;
    }
    case Type::f32: {
      switch (op) {
        case Add:
          return AddFloat32;
        case Sub:
          return SubFloat32;
        case Mul:
          return MulFloat32;
        case DivU:
          return DivFloat32;
        case DivS:
          return DivFloat32;
        case Eq:
          return EqFloat32;
        case Ne:
          return NeFloat32;
        default:
          return InvalidBinary;
      }
      break;
    }
    case Type::f64: {
      switch (op) {
        case Add:
          return AddFloat64;
        case Sub:
          return SubFloat64;
        case Mul:
          return MulFloat64;
        case DivU:
          return DivFloat64;
        case DivS:
          return DivFloat64;
        case Eq:
          return EqFloat64;
        case Ne:
          return NeFloat64;
        default:
          return InvalidBinary;
      }
      break;
    }
    case Type::v128: {
      WASM_UNREACHABLE("v128 not implemented yet");
    }
    case Type::funcref:
    case Type::anyref:
    case Type::nullref:
    case Type::exnref:
    case Type::none:
    case Type::unreachable: {
      return InvalidBinary;
    }
  }
  WASM_UNREACHABLE("invalid type");
}

} // namespace Abstract

} // namespace wasm

#endif // wasm_ir_abstract_h
