// Copyright 2021 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_WASM_WASM_INIT_EXPR_H_
#define V8_WASM_WASM_INIT_EXPR_H_

#if !V8_ENABLE_WEBASSEMBLY
#error This header should only be included if WebAssembly is enabled.
#endif  // !V8_ENABLE_WEBASSEMBLY

#include <memory>

#include "src/wasm/value-type.h"
#include "src/zone/zone-containers.h"

namespace v8 {
namespace internal {
namespace wasm {

struct WasmModule;

// Representation of an constant expression. Unlike {ConstantExpression}, this
// does not use {WireBytesRef}, i.e., it does not depend on a wasm module's
// bytecode representation.
class WasmInitExpr : public ZoneObject {
 public:
  enum Operator {
    kGlobalGet,
    kI32Const,
    kI64Const,
    kF32Const,
    kF64Const,
    kS128Const,
    kI32Add,
    kI32Sub,
    kI32Mul,
    kI64Add,
    kI64Sub,
    kI64Mul,
    kRefNullConst,
    kRefFuncConst,
    kStructNew,
    kStructNewDefault,
    kArrayNew,
    kArrayNewDefault,
    kArrayNewFixed,
    kRefI31,
    kStringConst,
    kAnyConvertExtern,
    kExternConvertAny
  };

  union Immediate {
    int32_t i32_const;
    int64_t i64_const;
    float f32_const;
    double f64_const;
    std::array<uint8_t, kSimd128Size> s128_const;
    uint32_t index;
    uint32_t heap_type_;  // Read with {heap_type()}.
  };

  explicit WasmInitExpr(int32_t v) : kind_(kI32Const), operands_(nullptr) {
    immediate_.i32_const = v;
  }
  explicit WasmInitExpr(int64_t v) : kind_(kI64Const), operands_(nullptr) {
    immediate_.i64_const = v;
  }
  explicit WasmInitExpr(float v) : kind_(kF32Const), operands_(nullptr) {
    immediate_.f32_const = v;
  }
  explicit WasmInitExpr(double v) : kind_(kF64Const), operands_(nullptr) {
    immediate_.f64_const = v;
  }
  explicit WasmInitExpr(uint8_t v[kSimd128Size])
      : kind_(kS128Const), operands_(nullptr) {
    memcpy(immediate_.s128_const.data(), v, kSimd128Size);
  }

  HeapType heap_type() const {
    return HeapType::FromBits(immediate_.heap_type_);
  }

  static WasmInitExpr Binop(Zone* zone, Operator op, WasmInitExpr lhs,
                            WasmInitExpr rhs) {
    DCHECK(op == kI32Add || op == kI32Sub || op == kI32Mul || op == kI64Add ||
           op == kI64Sub || op == kI64Mul);
    return WasmInitExpr(zone, op, {lhs, rhs});
  }

  static WasmInitExpr GlobalGet(uint32_t index) {
    WasmInitExpr expr(kGlobalGet);
    expr.immediate_.index = index;
    return expr;
  }

  static WasmInitExpr RefFuncConst(uint32_t index) {
    WasmInitExpr expr(kRefFuncConst);
    expr.immediate_.index = index;
    return expr;
  }

  static WasmInitExpr RefNullConst(HeapType heap_type) {
    WasmInitExpr expr(kRefNullConst);
    expr.immediate_.heap_type_ = heap_type.raw_bit_field();
    return expr;
  }

  static WasmInitExpr StructNew(ModuleTypeIndex index,
                                ZoneVector<WasmInitExpr>* elements) {
    WasmInitExpr expr(kStructNew, elements);
    expr.immediate_.index = index.index;
    return expr;
  }

  static WasmInitExpr StructNewDefault(ModuleTypeIndex index) {
    WasmInitExpr expr(kStructNewDefault);
    expr.immediate_.index = index.index;
    return expr;
  }

  static WasmInitExpr ArrayNew(Zone* zone, ModuleTypeIndex index,
                               WasmInitExpr initial, WasmInitExpr length) {
    WasmInitExpr expr(zone, kArrayNew, {initial, length});
    expr.immediate_.index = index.index;
    return expr;
  }

  static WasmInitExpr ArrayNewDefault(Zone* zone, ModuleTypeIndex index,
                                      WasmInitExpr length) {
    WasmInitExpr expr(zone, kArrayNewDefault, {length});
    expr.immediate_.index = index.index;
    return expr;
  }

  static WasmInitExpr ArrayNewFixed(ModuleTypeIndex index,
                                    ZoneVector<WasmInitExpr>* elements) {
    WasmInitExpr expr(kArrayNewFixed, elements);
    expr.immediate_.index = index.index;
    return expr;
  }

  static WasmInitExpr RefI31(Zone* zone, WasmInitExpr value) {
    WasmInitExpr expr(zone, kRefI31, {value});
    return expr;
  }

  static WasmInitExpr StringConst(uint32_t index) {
    WasmInitExpr expr(kStringConst);
    expr.immediate_.index = index;
    return expr;
  }

  static WasmInitExpr AnyConvertExtern(Zone* zone, WasmInitExpr arg) {
    return WasmInitExpr(zone, kAnyConvertExtern, {arg});
  }

  static WasmInitExpr ExternConvertAny(Zone* zone, WasmInitExpr arg) {
    return WasmInitExpr(zone, kExternConvertAny, {arg});
  }

  Immediate immediate() const { return immediate_; }
  Operator kind() const { return kind_; }
  const ZoneVector<WasmInitExpr>* operands() const { return operands_; }

  bool operator==(const WasmInitExpr& other) const {
    if (kind() != other.kind()) return false;
    switch (kind()) {
      case kGlobalGet:
      case kRefFuncConst:
      case kStringConst:
        return immediate().index == other.immediate().index;
      case kI32Const:
        return immediate().i32_const == other.immediate().i32_const;
      case kI64Const:
        return immediate().i64_const == other.immediate().i64_const;
      case kF32Const:
        return immediate().f32_const == other.immediate().f32_const;
      case kF64Const:
        return immediate().f64_const == other.immediate().f64_const;
      case kI32Add:
      case kI32Sub:
      case kI32Mul:
      case kI64Add:
      case kI64Sub:
      case kI64Mul:
        return operands_[0] == other.operands_[0] &&
               operands_[1] == other.operands_[1];
      case kS128Const:
        return immediate().s128_const == other.immediate().s128_const;
      case kRefNullConst:
        return heap_type() == other.heap_type();
      case kStructNew:
      case kStructNewDefault:
      case kArrayNew:
      case kArrayNewDefault:
        if (immediate().index != other.immediate().index) return false;
        DCHECK_EQ(operands()->size(), other.operands()->size());
        for (uint32_t i = 0; i < operands()->size(); i++) {
          if (operands()[i] != other.operands()[i]) return false;
        }
        return true;
      case kArrayNewFixed:
        if (immediate().index != other.immediate().index) return false;
        if (operands()->size() != other.operands()->size()) return false;
        for (uint32_t i = 0; i < operands()->size(); i++) {
          if (operands()[i] != other.operands()[i]) return false;
        }
        return true;
      case kRefI31:
      case kAnyConvertExtern:
      case kExternConvertAny:
        return operands_[0] == other.operands_[0];
    }
  }

  static WasmInitExpr DefaultValue(ValueType type) {
    // No initializer, emit a default value.
    switch (type.kind()) {
      case kI8:
      case kI16:
      case kI32:
        return WasmInitExpr(int32_t{0});
      case kI64:
        return WasmInitExpr(int64_t{0});
      case kF16:
      case kF32:
        return WasmInitExpr(0.0f);
      case kF64:
        return WasmInitExpr(0.0);
      case kRefNull:
        return WasmInitExpr::RefNullConst(type.heap_type());
      case kS128: {
        uint8_t value[kSimd128Size] = {0};
        return WasmInitExpr(value);
      }
      case kVoid:
      case kTop:
      case kBottom:
      case kRef:
        UNREACHABLE();
    }
  }

 private:
  WasmInitExpr(Operator kind, const ZoneVector<WasmInitExpr>* operands)
      : kind_(kind), operands_(operands) {}
  explicit WasmInitExpr(Operator kind) : kind_(kind), operands_(nullptr) {}
  WasmInitExpr(Zone* zone, Operator kind,
               std::initializer_list<WasmInitExpr> operands)
      : kind_(kind),
        operands_(zone->New<ZoneVector<WasmInitExpr>>(operands, zone)) {}
  Immediate immediate_;
  Operator kind_;
  const ZoneVector<WasmInitExpr>* operands_;
};

ASSERT_TRIVIALLY_COPYABLE(WasmInitExpr);

}  // namespace wasm
}  // namespace internal
}  // namespace v8

#endif  // V8_WASM_WASM_INIT_EXPR_H_
