Fix some float issues
This commit is contained in:
154
README.md
154
README.md
@@ -165,88 +165,88 @@ Now obviously I'm not advocating for replacing FireFox's optimising compiler wit
|
|||||||
|
|
||||||
## Specification compliance
|
## Specification compliance
|
||||||
|
|
||||||
It's hard to judge, since each test in the spec testsuite covers a wide range of features (to check their interactions), but currently 42 out of 76 of the spec suite tests pass when run in Wasmtime with Lightbeam as a backend. Here's the full test output:
|
It's hard to judge, since each test in the spec testsuite covers a wide range of features (to check their interactions), but currently 50 out of 76 of the spec suite tests pass when run in Wasmtime with Lightbeam as a backend. Here's the full test output:
|
||||||
|
|
||||||
```
|
```
|
||||||
running 76 tests
|
running 76 tests
|
||||||
test misc_testsuite::stack_overflow ... ok
|
test misc_testsuite::stack_overflow ... ok
|
||||||
test misc_testsuite::misc_traps ... ok
|
test spec_testsuite::binary ... ok
|
||||||
test spec_testsuite::binary ... ok
|
test misc_testsuite::misc_traps ... ok
|
||||||
test spec_testsuite::align ... FAILED
|
test spec_testsuite::align ... FAILED
|
||||||
test spec_testsuite::address ... FAILED
|
test spec_testsuite::br_if ... FAILED
|
||||||
test spec_testsuite::block ... ok
|
test spec_testsuite::address ... FAILED
|
||||||
test spec_testsuite::break_drop ... ok
|
test spec_testsuite::break_drop ... ok
|
||||||
test spec_testsuite::br ... ok
|
test spec_testsuite::block ... ok
|
||||||
test spec_testsuite::call ... FAILED
|
test spec_testsuite::br ... ok
|
||||||
test spec_testsuite::comments ... ok
|
test spec_testsuite::comments ... ok
|
||||||
test spec_testsuite::call_indirect ... FAILED
|
test spec_testsuite::const_ ... ok
|
||||||
test spec_testsuite::const_ ... ok
|
test spec_testsuite::conversions ... FAILED
|
||||||
test spec_testsuite::custom ... ok
|
test spec_testsuite::custom ... ok
|
||||||
test spec_testsuite::custom_section ... ok
|
test spec_testsuite::custom_section ... ok
|
||||||
test spec_testsuite::br_if ... ok
|
test spec_testsuite::data ... ok
|
||||||
test spec_testsuite::data ... ok
|
test spec_testsuite::call ... ok
|
||||||
test spec_testsuite::conversions ... FAILED
|
test spec_testsuite::endianness ... FAILED
|
||||||
test spec_testsuite::endianness ... FAILED
|
test spec_testsuite::br_table ... FAILED
|
||||||
test spec_testsuite::exports ... ok
|
test spec_testsuite::elem ... FAILED
|
||||||
test spec_testsuite::elem ... FAILED
|
test spec_testsuite::exports ... ok
|
||||||
test spec_testsuite::f32_bitwise ... FAILED
|
test spec_testsuite::f32_bitwise ... ok
|
||||||
test spec_testsuite::br_table ... FAILED
|
test spec_testsuite::call_indirect ... ok
|
||||||
test spec_testsuite::f64_bitwise ... FAILED
|
test spec_testsuite::f64_bitwise ... ok
|
||||||
test spec_testsuite::f32 ... FAILED
|
test spec_testsuite::f32_cmp ... ok
|
||||||
test spec_testsuite::fac ... ok
|
test spec_testsuite::f32 ... ok
|
||||||
test spec_testsuite::f64 ... FAILED
|
test spec_testsuite::fac ... ok
|
||||||
test spec_testsuite::f32_cmp ... ok
|
test spec_testsuite::f64 ... ok
|
||||||
test spec_testsuite::float_memory ... ok
|
test spec_testsuite::float_memory ... ok
|
||||||
test spec_testsuite::f64_cmp ... ok
|
test spec_testsuite::f64_cmp ... ok
|
||||||
test spec_testsuite::forward ... ok
|
test spec_testsuite::forward ... ok
|
||||||
test spec_testsuite::float_misc ... FAILED
|
test spec_testsuite::float_literals ... ok
|
||||||
test spec_testsuite::func_ptrs ... FAILED
|
test spec_testsuite::func_ptrs ... FAILED
|
||||||
test spec_testsuite::get_local ... FAILED
|
test spec_testsuite::get_local ... FAILED
|
||||||
test spec_testsuite::float_literals ... ok
|
test spec_testsuite::float_misc ... ok
|
||||||
test spec_testsuite::float_exprs ... FAILED
|
test spec_testsuite::float_exprs ... FAILED
|
||||||
test spec_testsuite::globals ... ok
|
test spec_testsuite::globals ... ok
|
||||||
test spec_testsuite::func ... ok
|
test spec_testsuite::func ... ok
|
||||||
test spec_testsuite::if_ ... FAILED
|
test spec_testsuite::imports ... FAILED
|
||||||
test spec_testsuite::inline_module ... ok
|
test spec_testsuite::inline_module ... ok
|
||||||
test spec_testsuite::imports ... FAILED
|
test spec_testsuite::if_ ... FAILED
|
||||||
test spec_testsuite::i32 ... ok
|
test spec_testsuite::i32 ... ok
|
||||||
test spec_testsuite::int_literals ... ok
|
test spec_testsuite::i64 ... ok
|
||||||
test spec_testsuite::labels ... ok
|
test spec_testsuite::labels ... ok
|
||||||
test spec_testsuite::linking ... FAILED
|
test spec_testsuite::linking ... FAILED
|
||||||
test spec_testsuite::i64 ... ok
|
test spec_testsuite::int_literals ... ok
|
||||||
test spec_testsuite::left_to_right ... FAILED
|
test spec_testsuite::loop_ ... FAILED
|
||||||
test spec_testsuite::loop_ ... FAILED
|
test spec_testsuite::memory_grow ... FAILED
|
||||||
test spec_testsuite::memory ... FAILED
|
test spec_testsuite::memory_redundancy ... ok
|
||||||
test spec_testsuite::memory_grow ... FAILED
|
test spec_testsuite::memory_trap ... FAILED
|
||||||
test spec_testsuite::memory_redundancy ... ok
|
test spec_testsuite::int_exprs ... ok
|
||||||
test spec_testsuite::memory_trap ... FAILED
|
test spec_testsuite::nop ... FAILED
|
||||||
test spec_testsuite::resizing ... FAILED
|
test spec_testsuite::resizing ... FAILED
|
||||||
test spec_testsuite::nop ... FAILED
|
test spec_testsuite::left_to_right ... ok
|
||||||
test spec_testsuite::return_minimal ... ok
|
test spec_testsuite::memory ... ok
|
||||||
test spec_testsuite::int_exprs ... ok
|
test spec_testsuite::return_minimal ... ok
|
||||||
test spec_testsuite::set_local ... FAILED
|
test spec_testsuite::set_local ... FAILED
|
||||||
test spec_testsuite::select ... FAILED
|
test spec_testsuite::skip_stack_guard_page ... FAILED
|
||||||
test spec_testsuite::skip_stack_guard_page ... FAILED
|
test spec_testsuite::select ... FAILED
|
||||||
test spec_testsuite::stack ... ok
|
test spec_testsuite::stack ... ok
|
||||||
test spec_testsuite::store_retval ... ok
|
test spec_testsuite::store_retval ... ok
|
||||||
test spec_testsuite::start ... FAILED
|
test spec_testsuite::start ... FAILED
|
||||||
test spec_testsuite::tee_local ... FAILED
|
test spec_testsuite::tee_local ... FAILED
|
||||||
test spec_testsuite::token ... ok
|
test spec_testsuite::token ... ok
|
||||||
test spec_testsuite::switch ... ok
|
test spec_testsuite::switch ... ok
|
||||||
test spec_testsuite::type_ ... ok
|
test spec_testsuite::type_ ... ok
|
||||||
test spec_testsuite::typecheck ... ok
|
test spec_testsuite::typecheck ... ok
|
||||||
test spec_testsuite::traps ... FAILED
|
test spec_testsuite::traps ... FAILED
|
||||||
test spec_testsuite::return_ ... ok
|
test spec_testsuite::unreached_invalid ... ok
|
||||||
test spec_testsuite::unreached_invalid ... ok
|
test spec_testsuite::unwind ... FAILED
|
||||||
test spec_testsuite::unwind ... FAILED
|
|
||||||
test spec_testsuite::utf8_custom_section_id ... ok
|
test spec_testsuite::utf8_custom_section_id ... ok
|
||||||
test spec_testsuite::utf8_import_field ... ok
|
test spec_testsuite::return_ ... ok
|
||||||
test spec_testsuite::utf8_import_module ... ok
|
test spec_testsuite::utf8_import_field ... ok
|
||||||
test spec_testsuite::utf8_invalid_encoding ... ok
|
test spec_testsuite::utf8_import_module ... ok
|
||||||
test spec_testsuite::unreachable ... ok
|
test spec_testsuite::utf8_invalid_encoding ... ok
|
||||||
test spec_testsuite::names ... FAILED
|
test spec_testsuite::unreachable ... ok
|
||||||
|
test spec_testsuite::names ... FAILED
|
||||||
|
|
||||||
test result: FAILED. 42 passed; 34 failed; 0 ignored; 0 measured; 0 filtered out
|
test result: FAILED. 50 passed; 26 failed; 0 ignored; 0 measured; 0 filtered out
|
||||||
```
|
```
|
||||||
|
|
||||||
## Getting involved
|
## Getting involved
|
||||||
|
|||||||
397
src/backend.rs
397
src/backend.rs
@@ -225,6 +225,11 @@ pub mod registers {
|
|||||||
pub const NUM_GPRS: u8 = 16;
|
pub const NUM_GPRS: u8 = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SIGN_MASK_F64: u64 = 0b1000000000000000000000000000000000000000000000000000000000000000;
|
||||||
|
const REST_MASK_F64: u64 = 0b0111111111111111111111111111111111111111111111111111111111111111;
|
||||||
|
const SIGN_MASK_F32: u32 = 0b10000000000000000000000000000000;
|
||||||
|
const REST_MASK_F32: u32 = 0b01111111111111111111111111111111;
|
||||||
|
|
||||||
extern "sysv64" fn println(len: u64, args: *const u8) {
|
extern "sysv64" fn println(len: u64, args: *const u8) {
|
||||||
println!("{}", unsafe {
|
println!("{}", unsafe {
|
||||||
std::str::from_utf8_unchecked(std::slice::from_raw_parts(args, len as usize))
|
std::str::from_utf8_unchecked(std::slice::from_raw_parts(args, len as usize))
|
||||||
@@ -612,36 +617,6 @@ impl TranslatedCodeSection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A value on the logical stack. The logical stack is the value stack as it
|
|
||||||
/// is visible to the WebAssembly, whereas the physical stack is the stack as
|
|
||||||
/// it exists on the machine (i.e. as offsets in memory relative to `rsp`).
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
enum StackValue {
|
|
||||||
/// This value has a "real" location, either in a register, on the stack,
|
|
||||||
/// in an immediate, etc.
|
|
||||||
Value(ValueLocation),
|
|
||||||
/// This value is on the physical stack and so should be accessed
|
|
||||||
/// with the `pop` instruction.
|
|
||||||
// TODO: This complicates a lot of our code, it'd be great if we could get rid of it.
|
|
||||||
Pop,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StackValue {
|
|
||||||
/// Returns either the location that this value can be accessed at
|
|
||||||
/// if possible. If this value is `Pop`, you can only access it by
|
|
||||||
/// popping the physical stack and so this function returns `None`.
|
|
||||||
///
|
|
||||||
/// Of course, we could calculate the location of the value on the
|
|
||||||
/// physical stack, but that would be unncessary computation for
|
|
||||||
/// our usecases.
|
|
||||||
fn location(&self) -> Option<ValueLocation> {
|
|
||||||
match *self {
|
|
||||||
StackValue::Value(loc) => Some(loc),
|
|
||||||
StackValue::Pop => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct BlockState {
|
pub struct BlockState {
|
||||||
stack: Stack,
|
stack: Stack,
|
||||||
@@ -706,7 +681,6 @@ impl<T> From<T> for Pending<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: We can share one trap/constant for all functions by reusing this struct
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Labels {
|
struct Labels {
|
||||||
trap: Option<Pending<Label>>,
|
trap: Option<Pending<Label>>,
|
||||||
@@ -1292,6 +1266,58 @@ macro_rules! eq_float {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! minmax_float {
|
||||||
|
(
|
||||||
|
$name:ident,
|
||||||
|
$instr:ident,
|
||||||
|
$cmpinstr:ident,
|
||||||
|
$addinstr:ident,
|
||||||
|
$combineinstr:ident,
|
||||||
|
$imm_fn:ident,
|
||||||
|
$const_fallback:expr
|
||||||
|
) => {
|
||||||
|
pub fn $name(&mut self) {
|
||||||
|
let right = self.pop();
|
||||||
|
let left = self.pop();
|
||||||
|
|
||||||
|
if let Some(right) = right.immediate() {
|
||||||
|
if let Some(left) = left.immediate() {
|
||||||
|
self.push(ValueLocation::Immediate(
|
||||||
|
$const_fallback(left.$imm_fn().unwrap(), right.$imm_fn().unwrap()).into()
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (left, right) = match left {
|
||||||
|
ValueLocation::Reg(r) if self.block_state.regs.num_usages(r) <= 1 => (left, right),
|
||||||
|
_ => (right, left)
|
||||||
|
};
|
||||||
|
|
||||||
|
let left = self.into_temp_reg(GPRType::Rx, left);
|
||||||
|
let right = self.into_reg(GPRType::Rx, right);
|
||||||
|
|
||||||
|
dynasm!(self.asm
|
||||||
|
; $cmpinstr Rx(left.rx().unwrap()), Rx(right.rx().unwrap())
|
||||||
|
; je >equal
|
||||||
|
; $instr Rx(left.rx().unwrap()), Rx(right.rx().unwrap())
|
||||||
|
; jmp >ret
|
||||||
|
; equal:
|
||||||
|
; jnp >equal_but_not_parity
|
||||||
|
; $addinstr Rx(left.rx().unwrap()), Rx(right.rx().unwrap())
|
||||||
|
; jmp >ret
|
||||||
|
; equal_but_not_parity:
|
||||||
|
; $combineinstr Rx(left.rx().unwrap()), Rx(right.rx().unwrap())
|
||||||
|
; ret:
|
||||||
|
);
|
||||||
|
|
||||||
|
self.push(ValueLocation::Reg(left));
|
||||||
|
self.free_value(ValueLocation::Reg(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! cmp_f64 {
|
macro_rules! cmp_f64 {
|
||||||
($name:ident, $reverse_name:ident, $instr:ident, $const_fallback:expr) => {
|
($name:ident, $reverse_name:ident, $instr:ident, $const_fallback:expr) => {
|
||||||
cmp_float!(
|
cmp_float!(
|
||||||
@@ -2438,8 +2464,8 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
fn push_physical(&mut self, value: ValueLocation) -> ValueLocation {
|
fn push_physical(&mut self, value: ValueLocation) -> ValueLocation {
|
||||||
self.block_state.depth.reserve(1);
|
self.block_state.depth.reserve(1);
|
||||||
match value {
|
match value {
|
||||||
ValueLocation::Reg(gpr) => {
|
value @ ValueLocation::Reg(_) | value @ ValueLocation::Immediate(_) => {
|
||||||
// TODO: Proper stack allocation scheme
|
let gpr = self.into_reg(GPRType::Rq, value);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; push Rq(gpr.rq().unwrap())
|
; push Rq(gpr.rq().unwrap())
|
||||||
);
|
);
|
||||||
@@ -2451,14 +2477,6 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
; push QWORD [rsp + offset]
|
; push QWORD [rsp + offset]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ValueLocation::Immediate(imm) => {
|
|
||||||
let gpr = self.block_state.regs.take(I64);
|
|
||||||
dynasm!(self.asm
|
|
||||||
; mov Rq(gpr.rq().unwrap()), QWORD imm.as_bytes()
|
|
||||||
; push Rq(gpr.rq().unwrap())
|
|
||||||
);
|
|
||||||
self.block_state.regs.release(gpr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ValueLocation::Stack(-(self.block_state.depth.0 as i32))
|
ValueLocation::Stack(-(self.block_state.depth.0 as i32))
|
||||||
}
|
}
|
||||||
@@ -2675,21 +2693,17 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
|
|
||||||
let out = if let (Some(left), Some(right)) = (left.imm_f32(), right.imm_f32()) {
|
let out = if let (Some(left), Some(right)) = (left.imm_f32(), right.imm_f32()) {
|
||||||
ValueLocation::Immediate(
|
ValueLocation::Immediate(
|
||||||
wasmparser::Ieee32(
|
wasmparser::Ieee32((left.bits() & REST_MASK_F32) | (right.bits() & SIGN_MASK_F32))
|
||||||
f32::from_bits(left.bits())
|
.into(),
|
||||||
.copysign(f32::from_bits(right.bits()))
|
|
||||||
.to_bits(),
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let left = self.into_temp_reg(GPRType::Rx, left);
|
let left = self.into_temp_reg(GPRType::Rx, left);
|
||||||
let right = self.into_reg(GPRType::Rx, right);
|
let right = self.into_reg(GPRType::Rx, right);
|
||||||
let (neg_zero, nan) = self.copysign_consts_f32_labels();
|
let (sign_mask, rest_mask) = self.copysign_consts_f32_labels();
|
||||||
|
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; andps Rx(right.rx().unwrap()), [=>neg_zero.0]
|
; andps Rx(right.rx().unwrap()), [=>sign_mask.0]
|
||||||
; andps Rx(left.rx().unwrap()), [=>nan.0]
|
; andps Rx(left.rx().unwrap()), [=>rest_mask.0]
|
||||||
; orps Rx(left.rx().unwrap()), Rx(right.rx().unwrap())
|
; orps Rx(left.rx().unwrap()), Rx(right.rx().unwrap())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2707,21 +2721,17 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
|
|
||||||
let out = if let (Some(left), Some(right)) = (left.imm_f64(), right.imm_f64()) {
|
let out = if let (Some(left), Some(right)) = (left.imm_f64(), right.imm_f64()) {
|
||||||
ValueLocation::Immediate(
|
ValueLocation::Immediate(
|
||||||
wasmparser::Ieee64(
|
wasmparser::Ieee64((left.bits() & REST_MASK_F64) | (right.bits() & SIGN_MASK_F64))
|
||||||
f64::from_bits(left.bits())
|
.into(),
|
||||||
.copysign(f64::from_bits(right.bits()))
|
|
||||||
.to_bits(),
|
|
||||||
)
|
|
||||||
.into(),
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let left = self.into_temp_reg(GPRType::Rx, left);
|
let left = self.into_temp_reg(GPRType::Rx, left);
|
||||||
let right = self.into_reg(GPRType::Rx, right);
|
let right = self.into_reg(GPRType::Rx, right);
|
||||||
let (neg_zero, nan) = self.copysign_consts_f64_labels();
|
let (sign_mask, rest_mask) = self.copysign_consts_f64_labels();
|
||||||
|
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; andpd Rx(right.rx().unwrap()), [=>neg_zero.0]
|
; andpd Rx(right.rx().unwrap()), [=>sign_mask.0]
|
||||||
; andpd Rx(left.rx().unwrap()), [=>nan.0]
|
; andpd Rx(left.rx().unwrap()), [=>rest_mask.0]
|
||||||
; orpd Rx(left.rx().unwrap()), Rx(right.rx().unwrap())
|
; orpd Rx(left.rx().unwrap()), Rx(right.rx().unwrap())
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2884,8 +2894,28 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
|
|
||||||
commutative_binop_f32!(f32_add, addss, |a, b| a + b);
|
commutative_binop_f32!(f32_add, addss, |a, b| a + b);
|
||||||
commutative_binop_f32!(f32_mul, mulss, |a, b| a * b);
|
commutative_binop_f32!(f32_mul, mulss, |a, b| a * b);
|
||||||
commutative_binop_f32!(f32_min, minss, f32::min);
|
minmax_float!(
|
||||||
commutative_binop_f32!(f32_max, maxss, f32::max);
|
f32_min,
|
||||||
|
minss,
|
||||||
|
ucomiss,
|
||||||
|
addss,
|
||||||
|
orps,
|
||||||
|
as_f32,
|
||||||
|
|a: wasmparser::Ieee32, b: wasmparser::Ieee32| wasmparser::Ieee32(
|
||||||
|
f32::from_bits(a.0).min(f32::from_bits(b.0)).to_bits()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
minmax_float!(
|
||||||
|
f32_max,
|
||||||
|
maxss,
|
||||||
|
ucomiss,
|
||||||
|
addss,
|
||||||
|
andps,
|
||||||
|
as_f32,
|
||||||
|
|a: wasmparser::Ieee32, b: wasmparser::Ieee32| wasmparser::Ieee32(
|
||||||
|
f32::from_bits(a.0).max(f32::from_bits(b.0)).to_bits()
|
||||||
|
)
|
||||||
|
);
|
||||||
binop_f32!(f32_sub, subss, |a, b| a - b);
|
binop_f32!(f32_sub, subss, |a, b| a - b);
|
||||||
binop_f32!(f32_div, divss, |a, b| a / b);
|
binop_f32!(f32_div, divss, |a, b| a / b);
|
||||||
|
|
||||||
@@ -2913,10 +2943,38 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn f32_trunc(&mut self) {
|
||||||
|
self.relocated_function_call(
|
||||||
|
&ir::ExternalName::LibCall(ir::LibCall::TruncF32),
|
||||||
|
iter::once(F32),
|
||||||
|
iter::once(F32),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
commutative_binop_f64!(f64_add, addsd, |a, b| a + b);
|
commutative_binop_f64!(f64_add, addsd, |a, b| a + b);
|
||||||
commutative_binop_f64!(f64_mul, mulsd, |a, b| a * b);
|
commutative_binop_f64!(f64_mul, mulsd, |a, b| a * b);
|
||||||
commutative_binop_f64!(f64_min, minsd, f64::min);
|
minmax_float!(
|
||||||
commutative_binop_f64!(f64_max, maxsd, f64::max);
|
f64_min,
|
||||||
|
minsd,
|
||||||
|
ucomisd,
|
||||||
|
addsd,
|
||||||
|
orpd,
|
||||||
|
as_f64,
|
||||||
|
|a: wasmparser::Ieee64, b: wasmparser::Ieee64| wasmparser::Ieee64(
|
||||||
|
f64::from_bits(a.0).min(f64::from_bits(b.0)).to_bits()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
minmax_float!(
|
||||||
|
f64_max,
|
||||||
|
maxsd,
|
||||||
|
ucomisd,
|
||||||
|
addsd,
|
||||||
|
andpd,
|
||||||
|
as_f64,
|
||||||
|
|a: wasmparser::Ieee64, b: wasmparser::Ieee64| wasmparser::Ieee64(
|
||||||
|
f64::from_bits(a.0).max(f64::from_bits(b.0)).to_bits()
|
||||||
|
)
|
||||||
|
);
|
||||||
binop_f64!(f64_sub, subsd, |a, b| a - b);
|
binop_f64!(f64_sub, subsd, |a, b| a - b);
|
||||||
binop_f64!(f64_div, divsd, |a, b| a / b);
|
binop_f64!(f64_div, divsd, |a, b| a / b);
|
||||||
|
|
||||||
@@ -2944,6 +3002,14 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn f64_trunc(&mut self) {
|
||||||
|
self.relocated_function_call(
|
||||||
|
&ir::ExternalName::LibCall(ir::LibCall::TruncF64),
|
||||||
|
iter::once(F64),
|
||||||
|
iter::once(F64),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
shift!(
|
shift!(
|
||||||
i32_shl,
|
i32_shl,
|
||||||
Rd,
|
Rd,
|
||||||
@@ -3359,52 +3425,113 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
let else_ = self.pop();
|
let else_ = self.pop();
|
||||||
let then = self.pop();
|
let then = self.pop();
|
||||||
|
|
||||||
match cond {
|
if let ValueLocation::Immediate(i) = cond {
|
||||||
ValueLocation::Immediate(i) => {
|
if i.as_i32().unwrap() == 0 {
|
||||||
if i.as_i32().unwrap() == 0 {
|
self.push(else_);
|
||||||
self.push(else_);
|
} else {
|
||||||
} else {
|
self.push(then);
|
||||||
self.push(then);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
other => {
|
|
||||||
let reg = self.into_reg(I32, other);
|
|
||||||
|
|
||||||
dynasm!(self.asm
|
return;
|
||||||
; test Rd(reg.rq().unwrap()), Rd(reg.rq().unwrap())
|
|
||||||
);
|
|
||||||
|
|
||||||
self.block_state.regs.release(reg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let out_gpr = self.block_state.regs.take(GPRType::Rq);
|
let cond_reg = self.into_reg(I32, cond);
|
||||||
|
let else_ = if let ValueLocation::Stack(_) = else_ {
|
||||||
|
else_
|
||||||
|
} else {
|
||||||
|
ValueLocation::Reg(self.into_reg(I32, else_))
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: Can do this better for variables on stack
|
let then = if let ValueLocation::Stack(_) = then {
|
||||||
macro_rules! cmov_helper {
|
then
|
||||||
($instr:ident, $val:expr) => {
|
} else {
|
||||||
match $val {
|
ValueLocation::Reg(self.into_reg(I32, then))
|
||||||
|
};
|
||||||
|
|
||||||
|
dynasm!(self.asm
|
||||||
|
; test Rd(cond_reg.rq().unwrap()), Rd(cond_reg.rq().unwrap())
|
||||||
|
);
|
||||||
|
|
||||||
|
self.block_state.regs.release(cond_reg);
|
||||||
|
|
||||||
|
let out_gpr = match (then, else_) {
|
||||||
|
(ValueLocation::Reg(then_reg), else_) if self.block_state.regs.num_usages(then_reg) <= 1 => {
|
||||||
|
match else_ {
|
||||||
|
ValueLocation::Reg(reg) => {
|
||||||
|
dynasm!(self.asm
|
||||||
|
; cmovz Rq(then_reg.rq().unwrap()), Rq(reg.rq().unwrap())
|
||||||
|
);
|
||||||
|
}
|
||||||
ValueLocation::Stack(offset) => {
|
ValueLocation::Stack(offset) => {
|
||||||
let offset = self.adjusted_offset(offset);
|
let offset = self.adjusted_offset(offset);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; $instr Rq(out_gpr.rq().unwrap()), [rsp + offset]
|
; cmovz Rq(then_reg.rq().unwrap()), [rsp + offset]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
other => {
|
_ => unreachable!(),
|
||||||
let scratch = self.into_reg(GPRType::Rq, other);
|
|
||||||
dynasm!(self.asm
|
|
||||||
; $instr Rq(out_gpr.rq().unwrap()), Rq(scratch.rq().unwrap())
|
|
||||||
);
|
|
||||||
self.block_state.regs.release(scratch);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmov_helper!(cmovz, else_);
|
self.free_value(else_);
|
||||||
cmov_helper!(cmovnz, then);
|
|
||||||
|
then_reg
|
||||||
|
}
|
||||||
|
(then, ValueLocation::Reg(else_reg)) if self.block_state.regs.num_usages(else_reg) <= 1 => {
|
||||||
|
match then {
|
||||||
|
ValueLocation::Reg(reg) => {
|
||||||
|
dynasm!(self.asm
|
||||||
|
; cmovnz Rq(else_reg.rq().unwrap()), Rq(reg.rq().unwrap())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ValueLocation::Stack(offset) => {
|
||||||
|
let offset = self.adjusted_offset(offset);
|
||||||
|
dynasm!(self.asm
|
||||||
|
; cmovnz Rq(else_reg.rq().unwrap()), [rsp + offset]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.free_value(then);
|
||||||
|
|
||||||
|
else_reg
|
||||||
|
}
|
||||||
|
(then, else_) => {
|
||||||
|
let out = self.block_state.regs.take(GPRType::Rq);
|
||||||
|
match else_ {
|
||||||
|
ValueLocation::Reg(reg) => {
|
||||||
|
dynasm!(self.asm
|
||||||
|
; cmovz Rq(out.rq().unwrap()), Rq(reg.rq().unwrap())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ValueLocation::Stack(offset) => {
|
||||||
|
let offset = self.adjusted_offset(offset);
|
||||||
|
dynasm!(self.asm
|
||||||
|
; cmovz Rq(out.rq().unwrap()), [rsp + offset]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
match then {
|
||||||
|
ValueLocation::Reg(reg) => {
|
||||||
|
dynasm!(self.asm
|
||||||
|
; cmovnz Rq(out.rq().unwrap()), Rq(reg.rq().unwrap())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ValueLocation::Stack(offset) => {
|
||||||
|
let offset = self.adjusted_offset(offset);
|
||||||
|
dynasm!(self.asm
|
||||||
|
; cmovnz Rq(out.rq().unwrap()), [rsp + offset]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.free_value(then);
|
||||||
|
self.free_value(else_);
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
self.push(ValueLocation::Reg(out_gpr));
|
self.push(ValueLocation::Reg(out_gpr));
|
||||||
}
|
}
|
||||||
@@ -3572,6 +3699,7 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
self.free_value(val);
|
self.free_value(val);
|
||||||
} else if self.block_state.regs.is_free(r) {
|
} else if self.block_state.regs.is_free(r) {
|
||||||
self.copy_value(&val, &mut loc.into());
|
self.copy_value(&val, &mut loc.into());
|
||||||
|
self.block_state.regs.mark_used(r);
|
||||||
self.free_value(val);
|
self.free_value(val);
|
||||||
} else {
|
} else {
|
||||||
pending.push((val, loc.into()));
|
pending.push((val, loc.into()));
|
||||||
@@ -3594,6 +3722,8 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
pending.push((src, dst));
|
pending.push((src, dst));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.block_state.regs.mark_used(r);
|
||||||
}
|
}
|
||||||
self.copy_value(&src, &mut { dst });
|
self.copy_value(&src, &mut { dst });
|
||||||
self.free_value(src);
|
self.free_value(src);
|
||||||
@@ -3603,7 +3733,6 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
self.set_stack_depth(StackDepth(depth));
|
self.set_stack_depth(StackDepth(depth));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Multiple returns
|
|
||||||
fn push_function_returns(&mut self, returns: impl IntoIterator<Item = SignlessType>) {
|
fn push_function_returns(&mut self, returns: impl IntoIterator<Item = SignlessType>) {
|
||||||
for loc in ret_locs(returns) {
|
for loc in ret_locs(returns) {
|
||||||
if let CCLoc::Reg(reg) = loc {
|
if let CCLoc::Reg(reg) = loc {
|
||||||
@@ -3614,7 +3743,6 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Do return types properly
|
|
||||||
pub fn call_indirect(
|
pub fn call_indirect(
|
||||||
&mut self,
|
&mut self,
|
||||||
type_id: u32,
|
type_id: u32,
|
||||||
@@ -3644,7 +3772,6 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
|
|
||||||
let fail = self.trap_label().0;
|
let fail = self.trap_label().0;
|
||||||
|
|
||||||
// TODO: Consider generating a single trap function and jumping to that instead.
|
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; cmp Rd(callee.rq().unwrap()), [
|
; cmp Rd(callee.rq().unwrap()), [
|
||||||
Rq(VMCTX) +
|
Rq(VMCTX) +
|
||||||
@@ -3683,6 +3810,10 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
self.block_state.regs.release(temp1);
|
self.block_state.regs.release(temp1);
|
||||||
self.block_state.regs.release(callee);
|
self.block_state.regs.release(callee);
|
||||||
|
|
||||||
|
for i in locs {
|
||||||
|
self.free_value(i.into());
|
||||||
|
}
|
||||||
|
|
||||||
self.push_function_returns(return_types);
|
self.push_function_returns(return_types);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3698,19 +3829,23 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
arg_types: impl IntoIterator<Item = SignlessType>,
|
arg_types: impl IntoIterator<Item = SignlessType>,
|
||||||
return_types: impl IntoIterator<Item = SignlessType>,
|
return_types: impl IntoIterator<Item = SignlessType>,
|
||||||
) {
|
) {
|
||||||
self.pass_outgoing_args(&arg_locs(arg_types));
|
let locs = arg_locs(arg_types);
|
||||||
|
self.pass_outgoing_args(&locs);
|
||||||
|
|
||||||
let label = &self.func_starts[index as usize].1;
|
let label = &self.func_starts[index as usize].1;
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; call =>*label
|
; call =>*label
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for i in locs {
|
||||||
|
self.free_value(i.into());
|
||||||
|
}
|
||||||
|
|
||||||
self.push_function_returns(return_types);
|
self.push_function_returns(return_types);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Reserve space to store RBX, RBP, and R12..R15 so we can use them
|
// TODO: Reserve space to store RBX, RBP, and R12..R15 so we can use them
|
||||||
// as scratch registers
|
// as scratch registers
|
||||||
// TODO: Allow use of unused argument registers as scratch registers.
|
|
||||||
/// Writes the function prologue and stores the arguments as locals
|
/// Writes the function prologue and stores the arguments as locals
|
||||||
pub fn start_function(&mut self, params: impl IntoIterator<Item = SignlessType>) {
|
pub fn start_function(&mut self, params: impl IntoIterator<Item = SignlessType>) {
|
||||||
let locs = Vec::from_iter(arg_locs(params));
|
let locs = Vec::from_iter(arg_locs(params));
|
||||||
@@ -3732,7 +3867,6 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
/// Writes the function epilogue (right now all this does is add the trap label that the
|
/// Writes the function epilogue (right now all this does is add the trap label that the
|
||||||
/// conditional traps in `call_indirect` use)
|
/// conditional traps in `call_indirect` use)
|
||||||
pub fn epilogue(&mut self) {
|
pub fn epilogue(&mut self) {
|
||||||
// TODO: We don't want to redefine this label if we're sharing it between functions
|
|
||||||
if let Some(l) = self.labels.trap.as_ref().and_then(Pending::as_undefined) {
|
if let Some(l) = self.labels.trap.as_ref().and_then(Pending::as_undefined) {
|
||||||
self.define_label(l);
|
self.define_label(l);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
@@ -3759,9 +3893,6 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
self.define_label(l);
|
self.define_label(l);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; .dword -2147483648
|
; .dword -2147483648
|
||||||
; .dword 0
|
|
||||||
; .dword 0
|
|
||||||
; .dword 0
|
|
||||||
);
|
);
|
||||||
self.labels.neg_const_f32 = Some(Pending::defined(l));
|
self.labels.neg_const_f32 = Some(Pending::defined(l));
|
||||||
}
|
}
|
||||||
@@ -3777,8 +3908,6 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; .dword 0
|
; .dword 0
|
||||||
; .dword -2147483648
|
; .dword -2147483648
|
||||||
; .dword 0
|
|
||||||
; .dword 0
|
|
||||||
);
|
);
|
||||||
self.labels.neg_const_f64 = Some(Pending::defined(l));
|
self.labels.neg_const_f64 = Some(Pending::defined(l));
|
||||||
}
|
}
|
||||||
@@ -3793,9 +3922,6 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
self.define_label(l);
|
self.define_label(l);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; .dword 2147483647
|
; .dword 2147483647
|
||||||
; .dword 2147483647
|
|
||||||
; .dword 2147483647
|
|
||||||
; .dword 2147483647
|
|
||||||
);
|
);
|
||||||
self.labels.abs_const_f32 = Some(Pending::defined(l));
|
self.labels.abs_const_f32 = Some(Pending::defined(l));
|
||||||
}
|
}
|
||||||
@@ -3810,53 +3936,46 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
self.define_label(l);
|
self.define_label(l);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; .qword 9223372036854775807
|
; .qword 9223372036854775807
|
||||||
; .qword 9223372036854775807
|
|
||||||
);
|
);
|
||||||
self.labels.abs_const_f64 = Some(Pending::defined(l));
|
self.labels.abs_const_f64 = Some(Pending::defined(l));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((neg_zero, nan)) = self
|
if let Some((sign_mask, rest_mask)) = self
|
||||||
.labels
|
.labels
|
||||||
.copysign_consts_f32
|
.copysign_consts_f32
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(Pending::as_undefined)
|
.and_then(Pending::as_undefined)
|
||||||
{
|
{
|
||||||
self.align(16);
|
self.align(16);
|
||||||
self.define_label(neg_zero);
|
self.define_label(sign_mask);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; .dword 2147483648u32 as i32
|
; .dword SIGN_MASK_F32 as i32
|
||||||
; .dword 2147483648u32 as i32
|
|
||||||
; .dword 2147483648u32 as i32
|
|
||||||
; .dword 2147483648u32 as i32
|
|
||||||
);
|
);
|
||||||
self.define_label(nan);
|
self.align(16);
|
||||||
|
self.define_label(rest_mask);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; .dword 2147483647
|
; .dword REST_MASK_F32 as i32
|
||||||
; .dword 2147483647
|
|
||||||
; .dword 2147483647
|
|
||||||
; .dword 2147483647
|
|
||||||
);
|
);
|
||||||
self.labels.copysign_consts_f32 = Some(Pending::defined((neg_zero, nan)));
|
self.labels.copysign_consts_f32 = Some(Pending::defined((sign_mask, rest_mask)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((neg_zero, nan)) = self
|
if let Some((sign_mask, rest_mask)) = self
|
||||||
.labels
|
.labels
|
||||||
.copysign_consts_f64
|
.copysign_consts_f64
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(Pending::as_undefined)
|
.and_then(Pending::as_undefined)
|
||||||
{
|
{
|
||||||
self.align(16);
|
self.align(16);
|
||||||
self.define_label(neg_zero);
|
self.define_label(sign_mask);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; .qword -9223372036854775808
|
; .qword SIGN_MASK_F64 as i64
|
||||||
; .qword -9223372036854775808
|
|
||||||
);
|
);
|
||||||
self.define_label(nan);
|
self.align(16);
|
||||||
|
self.define_label(rest_mask);
|
||||||
dynasm!(self.asm
|
dynasm!(self.asm
|
||||||
; .qword 9223372036854775807
|
; .qword REST_MASK_F64 as i64
|
||||||
; .qword 9223372036854775807
|
|
||||||
);
|
);
|
||||||
self.labels.copysign_consts_f64 = Some(Pending::defined((neg_zero, nan)));
|
self.labels.copysign_consts_f64 = Some(Pending::defined((sign_mask, rest_mask)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3945,9 +4064,9 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
return l.label;
|
return l.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
let neg_zero = self.create_label();
|
let sign_mask = self.create_label();
|
||||||
let nan = self.create_label();
|
let rest_mask = self.create_label();
|
||||||
let labels = (neg_zero, nan);
|
let labels = (sign_mask, rest_mask);
|
||||||
self.labels.copysign_consts_f32 = Some(labels.into());
|
self.labels.copysign_consts_f32 = Some(labels.into());
|
||||||
labels
|
labels
|
||||||
}
|
}
|
||||||
@@ -3958,10 +4077,10 @@ impl<'module, M: ModuleContext> Context<'module, M> {
|
|||||||
return l.label;
|
return l.label;
|
||||||
}
|
}
|
||||||
|
|
||||||
let neg_zero = self.create_label();
|
let sign_mask = self.create_label();
|
||||||
let nan = self.create_label();
|
let rest_mask = self.create_label();
|
||||||
let labels = (neg_zero, nan);
|
let labels = (sign_mask, rest_mask);
|
||||||
self.labels.copysign_consts_f32 = Some(labels.into());
|
self.labels.copysign_consts_f64 = Some(labels.into());
|
||||||
labels
|
labels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -466,6 +466,7 @@ where
|
|||||||
Operator::Floor(Size::_32) => ctx.f32_floor(),
|
Operator::Floor(Size::_32) => ctx.f32_floor(),
|
||||||
Operator::Ceil(Size::_32) => ctx.f32_ceil(),
|
Operator::Ceil(Size::_32) => ctx.f32_ceil(),
|
||||||
Operator::Nearest(Size::_32) => ctx.f32_nearest(),
|
Operator::Nearest(Size::_32) => ctx.f32_nearest(),
|
||||||
|
Operator::Trunc(Size::_32) => ctx.f32_trunc(),
|
||||||
Operator::Eq(F32) => ctx.f32_eq(),
|
Operator::Eq(F32) => ctx.f32_eq(),
|
||||||
Operator::Ne(F32) => ctx.f32_ne(),
|
Operator::Ne(F32) => ctx.f32_ne(),
|
||||||
Operator::Gt(SF32) => ctx.f32_gt(),
|
Operator::Gt(SF32) => ctx.f32_gt(),
|
||||||
@@ -485,6 +486,7 @@ where
|
|||||||
Operator::Floor(Size::_64) => ctx.f64_floor(),
|
Operator::Floor(Size::_64) => ctx.f64_floor(),
|
||||||
Operator::Ceil(Size::_64) => ctx.f64_ceil(),
|
Operator::Ceil(Size::_64) => ctx.f64_ceil(),
|
||||||
Operator::Nearest(Size::_64) => ctx.f64_nearest(),
|
Operator::Nearest(Size::_64) => ctx.f64_nearest(),
|
||||||
|
Operator::Trunc(Size::_64) => ctx.f64_trunc(),
|
||||||
Operator::Eq(F64) => ctx.f64_eq(),
|
Operator::Eq(F64) => ctx.f64_eq(),
|
||||||
Operator::Ne(F64) => ctx.f64_ne(),
|
Operator::Ne(F64) => ctx.f64_ne(),
|
||||||
Operator::Gt(SF64) => ctx.f64_gt(),
|
Operator::Gt(SF64) => ctx.f64_gt(),
|
||||||
|
|||||||
@@ -801,6 +801,8 @@ where
|
|||||||
input_ty,
|
input_ty,
|
||||||
Type::Float::<Int>(*output_ty)
|
Type::Float::<Int>(*output_ty)
|
||||||
),
|
),
|
||||||
|
Operator::GetGlobal(index) => write!(f, "global.get {}", index),
|
||||||
|
Operator::SetGlobal(index) => write!(f, "global.set {}", index),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1244,14 +1246,14 @@ where
|
|||||||
| WasmOperator::F32Lt
|
| WasmOperator::F32Lt
|
||||||
| WasmOperator::F32Gt
|
| WasmOperator::F32Gt
|
||||||
| WasmOperator::F32Le
|
| WasmOperator::F32Le
|
||||||
| WasmOperator::F32Ge => sig!((F32) -> (I32)),
|
| WasmOperator::F32Ge => sig!((F32, F32) -> (I32)),
|
||||||
|
|
||||||
WasmOperator::F64Eq
|
WasmOperator::F64Eq
|
||||||
| WasmOperator::F64Ne
|
| WasmOperator::F64Ne
|
||||||
| WasmOperator::F64Lt
|
| WasmOperator::F64Lt
|
||||||
| WasmOperator::F64Gt
|
| WasmOperator::F64Gt
|
||||||
| WasmOperator::F64Le
|
| WasmOperator::F64Le
|
||||||
| WasmOperator::F64Ge => sig!((F64) -> (I32)),
|
| WasmOperator::F64Ge => sig!((F64, F64) -> (I32)),
|
||||||
|
|
||||||
WasmOperator::I32Clz | WasmOperator::I32Ctz | WasmOperator::I32Popcnt => {
|
WasmOperator::I32Clz | WasmOperator::I32Ctz | WasmOperator::I32Popcnt => {
|
||||||
sig!((I32) -> (I32))
|
sig!((I32) -> (I32))
|
||||||
|
|||||||
Reference in New Issue
Block a user