// Choosing an arithmetic opcode from the type the checker inferred.
//
// Ported from the binop/unop tables of wax/src/lib-conversion/to_wasm.ml.
//
// This is the narrow interface the whole port turns on. `a + b` is one token in
// the source and four instructions here -- `i32.add`, `i64.add`, `f32.add`,
// `f64.add` -- and nothing in the syntax says which. The annotation the checker
// left on the node does, and it is the only thing that does.

///|
/// The instruction a binary operator becomes at this value type.
///
/// `None` when the operator has no form at that type: comparing two `v128`s with
/// `<` has no opcode, and neither does a signed shift of a float. The caller
/// raises rather than guessing, since a plausible substitute would be a module
/// that validates and computes something else.
fn binop_instruction(
  op : @ast.BinOp,
  ty : @wasm_types.ValType[Int],
) -> @wasm_bin.Instruction? {
  match ty {
    I32 => i32_binop(op)
    I64 => i64_binop(op)
    F32 => f32_binop(op)
    F64 => f64_binop(op)
    V128 | Ref(_) => None
  }
}

///|
fn i32_binop(op : @ast.BinOp) -> @wasm_bin.Instruction? {
  match op {
    Add => Some(I32Add)
    Sub => Some(I32Sub)
    Mul => Some(I32Mul)
    Div(Some(Signed)) => Some(I32DivS)
    Div(Some(Unsigned)) => Some(I32DivU)
    Rem(Signed) => Some(I32RemS)
    Rem(Unsigned) => Some(I32RemU)
    And => Some(I32And)
    Or => Some(I32Or)
    Xor => Some(I32Xor)
    Shl => Some(I32Shl)
    Shr(Signed) => Some(I32ShrS)
    Shr(Unsigned) => Some(I32ShrU)
    Eq => Some(I32Eq)
    Ne => Some(I32Ne)
    Lt(Some(Signed)) => Some(I32LtS)
    Lt(Some(Unsigned)) => Some(I32LtU)
    Gt(Some(Signed)) => Some(I32GtS)
    Gt(Some(Unsigned)) => Some(I32GtU)
    Le(Some(Signed)) => Some(I32LeS)
    Le(Some(Unsigned)) => Some(I32LeU)
    Ge(Some(Signed)) => Some(I32GeS)
    Ge(Some(Unsigned)) => Some(I32GeU)
    // An unsigned-less comparison or division is the FLOAT spelling; on an
    // integer it means the source said `/` where it had to say `/s` or `/u`,
    // which the checker has already reported.
    Div(None) | Lt(None) | Gt(None) | Le(None) | Ge(None) => None
  }
}

///|
fn i64_binop(op : @ast.BinOp) -> @wasm_bin.Instruction? {
  match op {
    Add => Some(I64Add)
    Sub => Some(I64Sub)
    Mul => Some(I64Mul)
    Div(Some(Signed)) => Some(I64DivS)
    Div(Some(Unsigned)) => Some(I64DivU)
    Rem(Signed) => Some(I64RemS)
    Rem(Unsigned) => Some(I64RemU)
    And => Some(I64And)
    Or => Some(I64Or)
    Xor => Some(I64Xor)
    Shl => Some(I64Shl)
    Shr(Signed) => Some(I64ShrS)
    Shr(Unsigned) => Some(I64ShrU)
    Eq => Some(I64Eq)
    Ne => Some(I64Ne)
    Lt(Some(Signed)) => Some(I64LtS)
    Lt(Some(Unsigned)) => Some(I64LtU)
    Gt(Some(Signed)) => Some(I64GtS)
    Gt(Some(Unsigned)) => Some(I64GtU)
    Le(Some(Signed)) => Some(I64LeS)
    Le(Some(Unsigned)) => Some(I64LeU)
    Ge(Some(Signed)) => Some(I64GeS)
    Ge(Some(Unsigned)) => Some(I64GeU)
    Div(None) | Lt(None) | Gt(None) | Le(None) | Ge(None) => None
  }
}

///|
fn f32_binop(op : @ast.BinOp) -> @wasm_bin.Instruction? {
  match op {
    Add => Some(F32Add)
    Sub => Some(F32Sub)
    Mul => Some(F32Mul)
    Div(None) => Some(F32Div)
    Eq => Some(F32Eq)
    Ne => Some(F32Ne)
    Lt(None) => Some(F32Lt)
    Gt(None) => Some(F32Gt)
    Le(None) => Some(F32Le)
    Ge(None) => Some(F32Ge)
    // A float has no signed form of anything, and no remainder or bitwise
    // operator at all.
    _ => None
  }
}

///|
fn f64_binop(op : @ast.BinOp) -> @wasm_bin.Instruction? {
  match op {
    Add => Some(F64Add)
    Sub => Some(F64Sub)
    Mul => Some(F64Mul)
    Div(None) => Some(F64Div)
    Eq => Some(F64Eq)
    Ne => Some(F64Ne)
    Lt(None) => Some(F64Lt)
    Gt(None) => Some(F64Gt)
    Le(None) => Some(F64Le)
    Ge(None) => Some(F64Ge)
    _ => None
  }
}

///|
/// The instruction a no-argument method becomes at its receiver's type.
///
/// The METHOD fixes the family and the RECEIVER fixes the width -- `clz` is one
/// name over `i32.clz` and `i64.clz`, and only the receiver's type says which.
fn unary_instruction(
  meth : String,
  recv : @wasm_types.ValType[Int]?,
) -> @wasm_bin.Instruction? {
  guard recv is Some(ty) else { return None }
  match (meth, ty) {
    ("clz", I32) => Some(I32Clz)
    ("clz", I64) => Some(I64Clz)
    ("ctz", I32) => Some(I32Ctz)
    ("ctz", I64) => Some(I64Ctz)
    ("popcnt", I32) => Some(I32Popcnt)
    ("popcnt", I64) => Some(I64Popcnt)
    ("extend8_s", I32) => Some(I32Extend8S)
    ("extend8_s", I64) => Some(I64Extend8S)
    ("extend16_s", I32) => Some(I32Extend16S)
    ("extend16_s", I64) => Some(I64Extend16S)
    ("extend32_s", I64) => Some(I64Extend32S)
    ("abs", F32) => Some(F32Abs)
    ("abs", F64) => Some(F64Abs)
    ("neg", F32) => Some(F32Neg)
    ("neg", F64) => Some(F64Neg)
    ("ceil", F32) => Some(F32Ceil)
    ("ceil", F64) => Some(F64Ceil)
    ("floor", F32) => Some(F32Floor)
    ("floor", F64) => Some(F64Floor)
    ("trunc", F32) => Some(F32Trunc)
    ("trunc", F64) => Some(F64Trunc)
    ("nearest", F32) => Some(F32Nearest)
    ("nearest", F64) => Some(F64Nearest)
    ("sqrt", F32) => Some(F32Sqrt)
    ("sqrt", F64) => Some(F64Sqrt)
    // The reinterprets change family without changing bits, so the receiver
    // decides the direction as well as the width.
    ("to_bits", F32) => Some(I32ReinterpretF32)
    ("to_bits", F64) => Some(I64ReinterpretF64)
    ("from_bits", I32) => Some(F32ReinterpretI32)
    ("from_bits", I64) => Some(F64ReinterpretI64)
    _ => None
  }
}

///|
/// The instruction a two-operand scalar method becomes.
fn binary_intrinsic(
  meth : String,
  recv : @wasm_types.ValType[Int]?,
) -> @wasm_bin.Instruction? {
  guard recv is Some(ty) else { return None }
  match (meth, ty) {
    ("rotl", I32) => Some(I32Rotl)
    ("rotl", I64) => Some(I64Rotl)
    ("rotr", I32) => Some(I32Rotr)
    ("rotr", I64) => Some(I64Rotr)
    ("min", F32) => Some(F32Min)
    ("min", F64) => Some(F64Min)
    ("max", F32) => Some(F32Max)
    ("max", F64) => Some(F64Max)
    ("copysign", F32) => Some(F32Copysign)
    ("copysign", F64) => Some(F64Copysign)
    _ => None
  }
}