// 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
}
}