From 3ec924f0794a6fc5fd6dbd8bd8060e75734ecdb1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 12 Dec 2024 12:49:35 -0700 Subject: [PATCH] pulley: Implement float<->int conversions (#9804) * pulley: Implement float<->int conversions Gets the `conversions.wast` test running along with a few other misc ones. cc #9783 * Fix pulley's no_std build * One more conversion to a workspace dep --- Cargo.lock | 1 + Cargo.toml | 1 + .../codegen/src/isa/pulley_shared/lower.isle | 88 ++++++++ cranelift/interpreter/Cargo.toml | 2 +- crates/wasmtime/Cargo.toml | 2 +- crates/wasmtime/src/runtime/vm/interpreter.rs | 1 + crates/wast-util/src/lib.rs | 3 - pulley/Cargo.toml | 3 +- pulley/src/interp.rs | 189 ++++++++++++++++++ pulley/src/interp/float_ext.rs | 18 ++ pulley/src/lib.rs | 56 ++++++ 11 files changed, 358 insertions(+), 6 deletions(-) create mode 100644 pulley/src/interp/float_ext.rs diff --git a/Cargo.lock b/Cargo.lock index 882e30c199b1..4ebf78b0307a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2485,6 +2485,7 @@ dependencies = [ "arbitrary", "cranelift-bitset", "env_logger 0.11.5", + "libm", "log", "object", "sptr", diff --git a/Cargo.toml b/Cargo.toml index 58f1a3226261..09537181daf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -353,6 +353,7 @@ rustc-hash = "2.0.0" libtest-mimic = "0.7.0" semver = { version = "1.0.17", default-features = false } ittapi = "0.4.0" +libm = "0.2.7" # ============================================================================= # diff --git a/cranelift/codegen/src/isa/pulley_shared/lower.isle b/cranelift/codegen/src/isa/pulley_shared/lower.isle index 0163055f2442..157ba189f328 100644 --- a/cranelift/codegen/src/isa/pulley_shared/lower.isle +++ b/cranelift/codegen/src/isa/pulley_shared/lower.isle @@ -495,3 +495,91 @@ (rule (lower (has_type $I64 (bitcast _flags val @ (value_type $F64)))) (pulley_bitcast_int_from_float_64 val)) + +;;;; Rules for `fcvt_to_{u,s}int` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type $I32 (fcvt_to_uint val @ (value_type $F32)))) + (pulley_x32_from_f32_u val)) + +(rule (lower (has_type $I32 (fcvt_to_uint val @ (value_type $F64)))) + (pulley_x32_from_f64_u val)) + +(rule (lower (has_type $I64 (fcvt_to_uint val @ (value_type $F32)))) + (pulley_x64_from_f32_u val)) + +(rule (lower (has_type $I64 (fcvt_to_uint val @ (value_type $F64)))) + (pulley_x64_from_f64_u val)) + +(rule (lower (has_type $I32 (fcvt_to_sint val @ (value_type $F32)))) + (pulley_x32_from_f32_s val)) + +(rule (lower (has_type $I32 (fcvt_to_sint val @ (value_type $F64)))) + (pulley_x32_from_f64_s val)) + +(rule (lower (has_type $I64 (fcvt_to_sint val @ (value_type $F32)))) + (pulley_x64_from_f32_s val)) + +(rule (lower (has_type $I64 (fcvt_to_sint val @ (value_type $F64)))) + (pulley_x64_from_f64_s val)) + +;;;; Rules for `fcvt_from_{u,s}int` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type $F32 (fcvt_from_uint val @ (value_type $I32)))) + (pulley_f32_from_x32_u val)) + +(rule (lower (has_type $F32 (fcvt_from_uint val @ (value_type $I64)))) + (pulley_f32_from_x64_u val)) + +(rule (lower (has_type $F64 (fcvt_from_uint val @ (value_type $I32)))) + (pulley_f64_from_x32_u val)) + +(rule (lower (has_type $F64 (fcvt_from_uint val @ (value_type $I64)))) + (pulley_f64_from_x64_u val)) + +(rule (lower (has_type $F32 (fcvt_from_sint val @ (value_type $I32)))) + (pulley_f32_from_x32_s val)) + +(rule (lower (has_type $F32 (fcvt_from_sint val @ (value_type $I64)))) + (pulley_f32_from_x64_s val)) + +(rule (lower (has_type $F64 (fcvt_from_sint val @ (value_type $I32)))) + (pulley_f64_from_x32_s val)) + +(rule (lower (has_type $F64 (fcvt_from_sint val @ (value_type $I64)))) + (pulley_f64_from_x64_s val)) + +;;;; Rules for `fcvt_to_{u,s}int_sat` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type $I32 (fcvt_to_uint_sat val @ (value_type $F32)))) + (pulley_x32_from_f32_u_sat val)) + +(rule (lower (has_type $I32 (fcvt_to_uint_sat val @ (value_type $F64)))) + (pulley_x32_from_f64_u_sat val)) + +(rule (lower (has_type $I64 (fcvt_to_uint_sat val @ (value_type $F32)))) + (pulley_x64_from_f32_u_sat val)) + +(rule (lower (has_type $I64 (fcvt_to_uint_sat val @ (value_type $F64)))) + (pulley_x64_from_f64_u_sat val)) + +(rule (lower (has_type $I32 (fcvt_to_sint_sat val @ (value_type $F32)))) + (pulley_x32_from_f32_s_sat val)) + +(rule (lower (has_type $I32 (fcvt_to_sint_sat val @ (value_type $F64)))) + (pulley_x32_from_f64_s_sat val)) + +(rule (lower (has_type $I64 (fcvt_to_sint_sat val @ (value_type $F32)))) + (pulley_x64_from_f32_s_sat val)) + +(rule (lower (has_type $I64 (fcvt_to_sint_sat val @ (value_type $F64)))) + (pulley_x64_from_f64_s_sat val)) + +;;;; Rules for `fdemote` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type $F32 (fdemote val @ (value_type $F64)))) + (pulley_f32_from_f64 val)) + +;;;; Rules for `fpromote` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(rule (lower (has_type $F64 (fpromote val @ (value_type $F32)))) + (pulley_f64_from_f32 val)) diff --git a/cranelift/interpreter/Cargo.toml b/cranelift/interpreter/Cargo.toml index 96c2055011c6..e7b3659c1604 100644 --- a/cranelift/interpreter/Cargo.toml +++ b/cranelift/interpreter/Cargo.toml @@ -22,7 +22,7 @@ smallvec = { workspace = true } thiserror = { workspace = true } [target.x86_64-pc-windows-gnu.dependencies] -libm = "0.2.4" +libm = { workspace = true } [dev-dependencies] cranelift-frontend = { workspace = true } diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index 0641b29e4339..2eee207f1cca 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -59,7 +59,7 @@ addr2line = { workspace = true, optional = true } semver = { workspace = true, optional = true } smallvec = { workspace = true, optional = true } hashbrown = { workspace = true, features = ["ahash"] } -libm = "0.2.7" +libm = { workspace = true } bitflags = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies.windows-sys] diff --git a/crates/wasmtime/src/runtime/vm/interpreter.rs b/crates/wasmtime/src/runtime/vm/interpreter.rs index dfb353ef13c1..a0ec7a8e196a 100644 --- a/crates/wasmtime/src/runtime/vm/interpreter.rs +++ b/crates/wasmtime/src/runtime/vm/interpreter.rs @@ -137,6 +137,7 @@ impl InterpreterRef<'_> { let trap = match kind { TrapKind::IntegerOverflow => Trap::IntegerOverflow, TrapKind::DivideByZero => Trap::IntegerDivisionByZero, + TrapKind::BadConversionToInteger => Trap::BadConversionToInteger, }; s.set_jit_trap(regs, None, trap); } diff --git a/crates/wast-util/src/lib.rs b/crates/wast-util/src/lib.rs index c4257d0586c8..0b94d96809bc 100644 --- a/crates/wast-util/src/lib.rs +++ b/crates/wast-util/src/lib.rs @@ -401,7 +401,6 @@ impl WastTest { "misc_testsuite/embenchen_primes.wast", "misc_testsuite/float-round-doesnt-load-too-much.wast", "misc_testsuite/int-to-float-splat.wast", - "misc_testsuite/issue4840.wast", "misc_testsuite/issue4890.wast", "misc_testsuite/issue6562.wast", "misc_testsuite/memory-combos.wast", @@ -433,7 +432,6 @@ impl WastTest { "misc_testsuite/winch/_simd_store.wast", "spec_testsuite/call.wast", "spec_testsuite/call_indirect.wast", - "spec_testsuite/conversions.wast", "spec_testsuite/f32.wast", "spec_testsuite/f32_bitwise.wast", "spec_testsuite/f32_cmp.wast", @@ -518,7 +516,6 @@ impl WastTest { "spec_testsuite/simd_store64_lane.wast", "spec_testsuite/simd_store8_lane.wast", "spec_testsuite/switch.wast", - "spec_testsuite/traps.wast", ]; if unsupported.iter().any(|part| self.path.ends_with(part)) { diff --git a/pulley/Cargo.toml b/pulley/Cargo.toml index d5c2c132bc7e..f26ad6254f52 100644 --- a/pulley/Cargo.toml +++ b/pulley/Cargo.toml @@ -17,6 +17,7 @@ arbitrary = { workspace = true, optional = true } cranelift-bitset = { workspace = true } log = { workspace = true } sptr = { workspace = true } +libm = { workspace = true, optional = true } [dev-dependencies] env_logger = { workspace = true } @@ -29,7 +30,7 @@ arbitrary = ["dep:arbitrary", "arbitrary/derive", "std", "cranelift-bitset/arbit encode = [] decode = [] disas = ["decode"] -interp = ["decode", "encode"] +interp = ["decode", "encode", "dep:libm"] [package.metadata.docs.rs] all-features = true diff --git a/pulley/src/interp.rs b/pulley/src/interp.rs index efe95b633a2b..9ddff068e30c 100644 --- a/pulley/src/interp.rs +++ b/pulley/src/interp.rs @@ -19,6 +19,11 @@ mod match_loop; #[cfg(any(pulley_tail_calls, pulley_assume_llvm_makes_tail_calls))] mod tail_loop; +#[cfg(not(feature = "std"))] +mod float_ext; +#[cfg(not(feature = "std"))] +use self::float_ext::FloatExt; + const DEFAULT_STACK_SIZE: usize = 1 << 20; // 1 MiB /// A virtual machine for interpreting Pulley bytecode. @@ -706,6 +711,7 @@ mod done { pub enum TrapKind { DivideByZero, IntegerOverflow, + BadConversionToInteger, } impl MachineState { @@ -851,6 +857,17 @@ impl Interpreter<'_> { .byte_offset(offset as isize) .write_unaligned(val) } + + fn check_xnn_from_fnn(&mut self, val: f64, lo: f64, hi: f64) -> ControlFlow { + if val != val { + return self.done_trap_kind::(Some(TrapKind::BadConversionToInteger)); + } + let val = val.trunc(); + if val <= lo || val >= hi { + return self.done_trap_kind::(Some(TrapKind::IntegerOverflow)); + } + ControlFlow::Continue(()) + } } #[test] @@ -1947,6 +1964,178 @@ impl OpVisitor for Interpreter<'_> { self.state[dst].set_f64(result); ControlFlow::Continue(()) } + + fn f32_from_x32_s(&mut self, dst: FReg, src: XReg) -> ControlFlow { + let a = self.state[src].get_i32(); + self.state[dst].set_f32(a as f32); + ControlFlow::Continue(()) + } + + fn f32_from_x32_u(&mut self, dst: FReg, src: XReg) -> ControlFlow { + let a = self.state[src].get_u32(); + self.state[dst].set_f32(a as f32); + ControlFlow::Continue(()) + } + + fn f32_from_x64_s(&mut self, dst: FReg, src: XReg) -> ControlFlow { + let a = self.state[src].get_i64(); + self.state[dst].set_f32(a as f32); + ControlFlow::Continue(()) + } + + fn f32_from_x64_u(&mut self, dst: FReg, src: XReg) -> ControlFlow { + let a = self.state[src].get_u64(); + self.state[dst].set_f32(a as f32); + ControlFlow::Continue(()) + } + + fn f64_from_x32_s(&mut self, dst: FReg, src: XReg) -> ControlFlow { + let a = self.state[src].get_i32(); + self.state[dst].set_f64(a as f64); + ControlFlow::Continue(()) + } + + fn f64_from_x32_u(&mut self, dst: FReg, src: XReg) -> ControlFlow { + let a = self.state[src].get_u32(); + self.state[dst].set_f64(a as f64); + ControlFlow::Continue(()) + } + + fn f64_from_x64_s(&mut self, dst: FReg, src: XReg) -> ControlFlow { + let a = self.state[src].get_i64(); + self.state[dst].set_f64(a as f64); + ControlFlow::Continue(()) + } + + fn f64_from_x64_u(&mut self, dst: FReg, src: XReg) -> ControlFlow { + let a = self.state[src].get_u64(); + self.state[dst].set_f64(a as f64); + ControlFlow::Continue(()) + } + + fn x32_from_f32_s(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f32(); + self.check_xnn_from_fnn::(a.into(), -2147483649.0, 2147483648.0)?; + self.state[dst].set_i32(a as i32); + ControlFlow::Continue(()) + } + + fn x32_from_f32_u(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f32(); + self.check_xnn_from_fnn::(a.into(), -1.0, 4294967296.0)?; + self.state[dst].set_u32(a as u32); + ControlFlow::Continue(()) + } + + fn x64_from_f32_s(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f32(); + self.check_xnn_from_fnn::( + a.into(), + -9223372036854777856.0, + 9223372036854775808.0, + )?; + self.state[dst].set_i64(a as i64); + ControlFlow::Continue(()) + } + + fn x64_from_f32_u(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f32(); + self.check_xnn_from_fnn::(a.into(), -1.0, 18446744073709551616.0)?; + self.state[dst].set_u64(a as u64); + ControlFlow::Continue(()) + } + + fn x32_from_f64_s(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f64(); + self.check_xnn_from_fnn::(a, -2147483649.0, 2147483648.0)?; + self.state[dst].set_i32(a as i32); + ControlFlow::Continue(()) + } + + fn x32_from_f64_u(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f64(); + self.check_xnn_from_fnn::(a, -1.0, 4294967296.0)?; + self.state[dst].set_u32(a as u32); + ControlFlow::Continue(()) + } + + fn x64_from_f64_s(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f64(); + self.check_xnn_from_fnn::( + a, + -9223372036854777856.0, + 9223372036854775808.0, + )?; + self.state[dst].set_i64(a as i64); + ControlFlow::Continue(()) + } + + fn x64_from_f64_u(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f64(); + self.check_xnn_from_fnn::(a, -1.0, 18446744073709551616.0)?; + self.state[dst].set_u64(a as u64); + ControlFlow::Continue(()) + } + + fn x32_from_f32_s_sat(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f32(); + self.state[dst].set_i32(a as i32); + ControlFlow::Continue(()) + } + + fn x32_from_f32_u_sat(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f32(); + self.state[dst].set_u32(a as u32); + ControlFlow::Continue(()) + } + + fn x64_from_f32_s_sat(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f32(); + self.state[dst].set_i64(a as i64); + ControlFlow::Continue(()) + } + + fn x64_from_f32_u_sat(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f32(); + self.state[dst].set_u64(a as u64); + ControlFlow::Continue(()) + } + + fn x32_from_f64_s_sat(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f64(); + self.state[dst].set_i32(a as i32); + ControlFlow::Continue(()) + } + + fn x32_from_f64_u_sat(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f64(); + self.state[dst].set_u32(a as u32); + ControlFlow::Continue(()) + } + + fn x64_from_f64_s_sat(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f64(); + self.state[dst].set_i64(a as i64); + ControlFlow::Continue(()) + } + + fn x64_from_f64_u_sat(&mut self, dst: XReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f64(); + self.state[dst].set_u64(a as u64); + ControlFlow::Continue(()) + } + + fn f32_from_f64(&mut self, dst: FReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f64(); + self.state[dst].set_f32(a as f32); + ControlFlow::Continue(()) + } + + fn f64_from_f32(&mut self, dst: FReg, src: FReg) -> ControlFlow { + let a = self.state[src].get_f32(); + self.state[dst].set_f64(a.into()); + ControlFlow::Continue(()) + } } impl ExtendedOpVisitor for Interpreter<'_> { diff --git a/pulley/src/interp/float_ext.rs b/pulley/src/interp/float_ext.rs new file mode 100644 index 000000000000..914fb8033b05 --- /dev/null +++ b/pulley/src/interp/float_ext.rs @@ -0,0 +1,18 @@ +//! Adapters for float methods to get routed to the `libm` dependency when the +//! `std` feature is disabled and these functions are otherwise not available. + +pub trait FloatExt { + fn trunc(self) -> Self; +} + +impl FloatExt for f32 { + fn trunc(self) -> f32 { + libm::truncf(self) + } +} + +impl FloatExt for f64 { + fn trunc(self) -> f64 { + libm::trunc(self) + } +} diff --git a/pulley/src/lib.rs b/pulley/src/lib.rs index bffb05c3484a..2781c793f3ed 100644 --- a/pulley/src/lib.rs +++ b/pulley/src/lib.rs @@ -421,6 +421,62 @@ macro_rules! for_each_op { fselect32 = FSelect32 { dst: FReg, cond: XReg, if_nonzero: FReg, if_zero: FReg }; /// `dst = low32(cond) ? if_nonzero : if_zero` fselect64 = FSelect64 { dst: FReg, cond: XReg, if_nonzero: FReg, if_zero: FReg }; + + /// `low32(dst) = checked_f32_from_signed(low32(src))` + f32_from_x32_s = F32FromX32S { dst: FReg, src: XReg }; + /// `low32(dst) = checked_f32_from_unsigned(low32(src))` + f32_from_x32_u = F32FromX32U { dst: FReg, src: XReg }; + /// `low32(dst) = checked_f32_from_signed(src)` + f32_from_x64_s = F32FromX64S { dst: FReg, src: XReg }; + /// `low32(dst) = checked_f32_from_unsigned(src)` + f32_from_x64_u = F32FromX64U { dst: FReg, src: XReg }; + /// `dst = checked_f64_from_signed(low32(src))` + f64_from_x32_s = F64FromX32S { dst: FReg, src: XReg }; + /// `dst = checked_f64_from_unsigned(low32(src))` + f64_from_x32_u = F64FromX32U { dst: FReg, src: XReg }; + /// `dst = checked_f64_from_signed(src)` + f64_from_x64_s = F64FromX64S { dst: FReg, src: XReg }; + /// `dst = checked_f64_from_unsigned(src)` + f64_from_x64_u = F64FromX64U { dst: FReg, src: XReg }; + + /// `low32(dst) = checked_signed_from_f32(low32(src))` + x32_from_f32_s = X32FromF32S { dst: XReg, src: FReg }; + /// `low32(dst) = checked_unsigned_from_f32(low32(src))` + x32_from_f32_u = X32FromF32U { dst: XReg, src: FReg }; + /// `low32(dst) = checked_signed_from_f64(src)` + x32_from_f64_s = X32FromF64S { dst: XReg, src: FReg }; + /// `low32(dst) = checked_unsigned_from_f64(src)` + x32_from_f64_u = X32FromF64U { dst: XReg, src: FReg }; + /// `dst = checked_signed_from_f32(low32(src))` + x64_from_f32_s = X64FromF32S { dst: XReg, src: FReg }; + /// `dst = checked_unsigned_from_f32(low32(src))` + x64_from_f32_u = X64FromF32U { dst: XReg, src: FReg }; + /// `dst = checked_signed_from_f64(src)` + x64_from_f64_s = X64FromF64S { dst: XReg, src: FReg }; + /// `dst = checked_unsigned_from_f64(src)` + x64_from_f64_u = X64FromF64U { dst: XReg, src: FReg }; + + /// `low32(dst) = saturating_signed_from_f32(low32(src))` + x32_from_f32_s_sat = X32FromF32SSat { dst: XReg, src: FReg }; + /// `low32(dst) = saturating_unsigned_from_f32(low32(src))` + x32_from_f32_u_sat = X32FromF32USat { dst: XReg, src: FReg }; + /// `low32(dst) = saturating_signed_from_f64(src)` + x32_from_f64_s_sat = X32FromF64SSat { dst: XReg, src: FReg }; + /// `low32(dst) = saturating_unsigned_from_f64(src)` + x32_from_f64_u_sat = X32FromF64USat { dst: XReg, src: FReg }; + /// `dst = saturating_signed_from_f32(low32(src))` + x64_from_f32_s_sat = X64FromF32SSat { dst: XReg, src: FReg }; + /// `dst = saturating_unsigned_from_f32(low32(src))` + x64_from_f32_u_sat = X64FromF32USat { dst: XReg, src: FReg }; + /// `dst = saturating_signed_from_f64(src)` + x64_from_f64_s_sat = X64FromF64SSat { dst: XReg, src: FReg }; + /// `dst = saturating_unsigned_from_f64(src)` + x64_from_f64_u_sat = X64FromF64USat { dst: XReg, src: FReg }; + + /// `low32(dst) = demote(src)` + f32_from_f64 = F32FromF64 { dst: FReg, src: FReg }; + /// `(st) = promote(low32(src))` + f64_from_f32 = F64FromF32 { dst: FReg, src: FReg }; } }; }