// The SIMD intrinsic registry: what a vector op is called, and what it takes.
//
// Ported from wax/src/lib-wasm/simd.ml, the single source of truth shared by
// the lowering, the decompiler and the type checker. Four consumers that must
// agree about ~250 operations, which is why the module derives its tables from
// one naming scheme rather than listing them.
//
// THE SURFACE CONVENTION. A vector op is written as a method intrinsic with the
// lane shape baked into the name -- `a.add_i32x4(b)`, `v.splat_i32x4()`,
// `v.extract_lane_s_i8x16(0)`. The Wax name is the WAT mnemonic `A.B` rewritten
// as `B_A`, so `i32x4.add` becomes `add_i32x4` and `v128.and` becomes
// `and_v128`. Constants and bitselect are free functions; loads and stores are
// methods on a memory object.

///|
/// A lane shape: how the 128 bits are divided, and into what.
pub(all) enum Shape {
  I8x16
  I16x8
  I32x4
  I64x2
  F32x4
  F64x2
} derive(Eq, Hash, Debug)

///|
pub fn Shape::to_str(self : Shape) -> String {
  match self {
    I8x16 => "i8x16"
    I16x8 => "i16x8"
    I32x4 => "i32x4"
    I64x2 => "i64x2"
    F32x4 => "f32x4"
    F64x2 => "f64x2"
  }
}

///|
pub fn Shape::of_str(s : String) -> Shape? {
  match s {
    "i8x16" => Some(I8x16)
    "i16x8" => Some(I16x8)
    "i32x4" => Some(I32x4)
    "i64x2" => Some(I64x2)
    "f32x4" => Some(F32x4)
    "f64x2" => Some(F64x2)
    _ => None
  }
}

///|
/// How many lanes the shape has. Also the number of literals a constant of that
/// shape takes, and the exclusive bound on a lane index.
pub fn Shape::lane_count(self : Shape) -> Int {
  match self {
    I8x16 => 16
    I16x8 => 8
    I32x4 | F32x4 => 4
    I64x2 | F64x2 => 2
  }
}

///|
/// The operand and result types a vector intrinsic deals in.
///
/// Kept apart from both the Wax and the wasm value-type representations, so
/// each consumer maps it to its own rather than this module picking one.
pub(all) enum Ty {
  TV128
  TI32
  TI64
  TF32
  TF64
} derive(Eq, Hash, Debug)

///|
/// The scalar type of one lane: what `splat` takes, what `extract` gives back,
/// and what `replace` puts in.
///
/// The narrow integer shapes work in i32, because wasm has no narrower scalar.
pub fn Shape::lane_scalar(self : Shape) -> Ty {
  match self {
    I8x16 | I16x8 | I32x4 => TI32
    I64x2 => TI64
    F32x4 => TF32
    F64x2 => TF64
  }
}

///|
/// Is this shape's lane a float?
pub fn Shape::is_float(self : Shape) -> Bool {
  self is (F32x4 | F64x2)
}

///|
pub let int_shapes : Array[Shape] = [I8x16, I16x8, I32x4, I64x2]

///|
pub let float_shapes : Array[Shape] = [F32x4, F64x2]

///|
pub let all_shapes : Array[Shape] = [I8x16, I16x8, I32x4, I64x2, F32x4, F64x2]

// ============================================================
// The free functions
// ============================================================

///|
/// The namespace the free vector intrinsics live in.
pub let free_namespace : String = "v128"

///|
/// The full name of a free intrinsic, from its member part.
pub fn free_full(part : String) -> String {
  free_namespace + "_" + part
}

///|
/// The member part of a full free-intrinsic name, if it is one.
pub fn free_member(full : String) -> String? {
  let prefix = free_namespace + "_"
  if full.has_prefix(prefix) {
    Some(full[prefix.length():].to_owned())
  } else {
    None
  }
}

///|
pub let bitselect_name : String = "v128_bitselect"

///|
/// A constant is named for its shape: `v128_i32x4(1, 2, 3, 4)`.
pub fn const_name(s : Shape) -> String {
  free_full(s.to_str())
}

///|
/// The shape a `v128_` constant name denotes, if the name is one.
pub fn const_shape_of_name(name : String) -> Shape? {
  match free_member(name) {
    None => None
    Some(m) => Shape::of_str(m)
  }
}

///|
/// How many lane literals a constant of this shape takes.
pub fn const_arity(s : Shape) -> Int {
  s.lane_count()
}

///|
/// Is this the name of a free intrinsic -- a constant, or bitselect?
pub fn is_free_intrinsic(name : String) -> Bool {
  name == bitselect_name || const_shape_of_name(name) is Some(_)
}

///|
/// Every free intrinsic's member name, for completion after `v128_`.
pub let free_member_names : Array[String] = {
  let out = all_shapes.map(s => s.to_str())
  out.push("bitselect")
  out
}