Skip to content

Commit 29f10d6

Browse files
refactor: unify numeric operations and remove libm
Signed-off-by: Henry <mail@henrygressmann.de>
1 parent eb833bc commit 29f10d6

21 files changed

Lines changed: 617 additions & 414 deletions

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1414
- Added an experimental C API in `tinywasm-c-api`.
1515
- Added parse-time resource limits and configurable fuel checkpoints.
1616
- Added `nightly` to enable all supported nightly optimizations, with automatic compiler capability detection.
17+
- Added nightly float intrinsics and portable Wasm math for `no_std`, removing the `libm` dependency.
1718

1819
### Breaking Changes
1920

‎Cargo.lock‎

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ wat = "1.263"
3939

4040
criterion = { version = "0.8", default-features = false, features = ["cargo_bench_support", "rayon"] }
4141

42-
[workspace.lints.cargo]
43-
unused_dependencies = "allow" # false-positive on libm
44-
4542
[workspace.lints.rust]
4643
unexpected_cfgs = { level = "warn", check-cfg = ["cfg(rust_analyzer)"] }
4744

‎README.md‎

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,21 +47,19 @@ See the [examples](./examples) directory and [documentation](https://docs.rs/tin
4747

4848
- **`full`:** Enables `archive`, `debug`, `parallel-parser`, `parser`, `state`, and `validate`. Enabled by default.
4949
- **`std`:** Enables parsing from files and streams, shared memory handles, and blocking atomic wait/notify across stores. Enabled by default.
50+
- **`nightly`:** Enables nightly-only optimizations (if the compiler supports them). Enabled by default.
51+
- **`simd-intrinsics`:** Enables architecture-specific SIMD implementations where available, using `unsafe` internally. Enabled by default.
5052
- **`parser`:** Enables `tinywasm-parser` and top-level parse helpers. Enabled by default.
5153
- **`validate`:** Enables WebAssembly validation while parsing. Enabled by default and configurable through [`ParserOptions`](https://docs.rs/tinywasm/latest/tinywasm/parser/struct.ParserOptions.html).
5254
- **`parallel-parser`:** Parallelizes function parsing when `std` is enabled. Enabled by default.
5355
- **`archive`:** Enables serialization and deserialization of the internal `twasm` format. Enabled by default.
54-
- **`log`:** Enables integration with the `log` crate.
55-
- **`send`:** Makes stores and store-local handles movable across threads.
5656
- **`state`:** Adds store-owned host state for access from host callbacks. Enabled by default.
57+
- **`debug`:** Derives `Debug` for runtime types. Enabled by default.
58+
- **`send`:** Makes stores and store-local handles movable across threads.
5759
- **`portable-atomic`:** Supports targets without native atomic compare-and-swap.
5860
- **`canonicalize-nans`:** Uses a [canonical NaN](https://en.wikipedia.org/wiki/NaN#Canonical_NaN) for normalized NaN results.
59-
- **`debug`:** Derives `Debug` for runtime types. Enabled by default.
6061
- **`guest-debug`:** Exposes module-internal by-index inspection APIs (`*_by_index`).
61-
- **`nightly`:** Enables all supported nightly-only optimizations. Enabled by default, with compiler capability detection and a stable fallback.
62-
- **`simd-intrinsics`:** Enables architecture-specific SIMD implementations where available, using `unsafe` internally. Enabled by default. Safe Wasm SIMD remains automatic.
63-
64-
With default features disabled, `tinywasm` supports `no_std + alloc` and its runtime depends only on `libm`.
62+
- **`log`:** Enables integration with the `log` crate.
6563

6664
Use [`Engine`](https://docs.rs/tinywasm/latest/tinywasm/engine/struct.Engine.html) and [`engine::Config`](https://docs.rs/tinywasm/latest/tinywasm/engine/struct.Config.html) for non-default fuel accounting, stack sizing, or GC collection thresholds. A configured `ResourceLimiter` can allow, reject, or trap memory and table allocation or growth requests, and GC object allocations.
6765

@@ -106,8 +104,9 @@ TinyWasm generally implements non-JavaScript core proposals at [phase 4](https:/
106104
| [**Garbage Collection**](https://github.com/WebAssembly/gc) | 🟢 | 0.11.0 |
107105
| [**Exception Handling**](https://github.com/WebAssembly/exception-handling) | 🟢 | 0.11.0 |
108106
| [**Compact Import Section**](https://github.com/WebAssembly/compact-import-section) | 🟢 | 0.11.0 |
109-
| [**Threads**](https://github.com/WebAssembly/threads) | 🚧 | next |
110-
| [**Stack Switching**](https://github.com/WebAssembly/stack-switching) | 🌑 | - |
107+
| [**Threads**](https://github.com/WebAssembly/threads)[^threads] | 🟢 | next |
108+
109+
[^threads]: Shared memory and blocking atomic wait/notify require `std`.
111110

112111
**Legend**\
113112
🌑 -- not available\

‎crates/cli/Cargo.toml‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ tinywasm = { workspace = true, features = [
3838
"log",
3939
"std",
4040
] }
41-
tinywasm-wasi.workspace = true
4241
wasm-testsuite = { workspace = true, optional = true }
4342
wast = { workspace = true, optional = true }
4443
wat = { workspace = true, optional = true }

‎crates/tinywasm/Cargo.toml‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,6 @@ harness = false
8181
name = "test-wasm-wide-arithmetic"
8282

8383
[dependencies]
84-
libm = { version = "0.2", default-features = false }
8584
log = { workspace = true, optional = true }
8685
portable-atomic = { version = "1.15.0", default-features = false, optional = true }
8786
tinywasm-parser = { workspace = true, optional = true }
@@ -133,11 +132,14 @@ debug = ["tinywasm-types/debug"]
133132
guest-debug = []
134133

135134
# enable all supported nightly-only optimizations
136-
nightly = ["nightly-abort-immediate", "nightly-rust-preserve-none", "nightly-tail-calls"]
135+
nightly = ["nightly-abort-immediate", "nightly-core-float-math", "nightly-rust-preserve-none", "nightly-tail-calls"]
137136

138137
# abort in place on internal invariant violations in release builds when supported by the compiler
139138
nightly-abort-immediate = []
140139

140+
# use compiler floating-point operations in no_std builds when supported
141+
nightly-core-float-math = []
142+
141143
# use explicit tail calls for interpreter dispatch when supported by the compiler
142144
nightly-tail-calls = []
143145

‎crates/tinywasm/build.rs‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,21 @@ const ABORT_IMMEDIATE_PROBE: &str = r#"
2727
pub fn trap() -> ! { core::process::abort_immediate() }
2828
"#;
2929

30+
const CORE_FLOAT_MATH_PROBE: &str = r#"
31+
#![no_std]
32+
#![feature(core_float_math)]
33+
34+
pub fn f32_ops(x: f32) -> [f32; 5] {
35+
use core::f32::math;
36+
[math::ceil(x), math::floor(x), math::trunc(x), math::round_ties_even(x), math::sqrt(x)]
37+
}
38+
39+
pub fn f64_ops(x: f64) -> [f64; 5] {
40+
use core::f64::math;
41+
[math::ceil(x), math::floor(x), math::trunc(x), math::round_ties_even(x), math::sqrt(x)]
42+
}
43+
"#;
44+
3045
fn main() {
3146
println!("cargo::rerun-if-changed=build.rs");
3247
println!("cargo::rerun-if-env-changed=RUSTC_BOOTSTRAP");
@@ -52,12 +67,18 @@ fn configure_simd() {
5267

5368
fn configure_nightly() {
5469
println!("cargo::rustc-check-cfg=cfg(tw_abort_immediate)");
70+
println!("cargo::rustc-check-cfg=cfg(tw_core_float_math)");
5571
println!("cargo::rustc-check-cfg=cfg(tw_tail_calls)");
5672
println!(
5773
"cargo::rustc-check-cfg=cfg(tw_tail_abi, values(\"Rust\", \"C-unwind\", \"sysv64-unwind\", \"rust-preserve-none\"))"
5874
);
5975
let mut compiler = autocfg::new();
6076
compiler.set_edition(Some("2024".into()));
77+
if env::var_os("CARGO_FEATURE_NIGHTLY_CORE_FLOAT_MATH").is_some()
78+
&& compiler.probe_raw(CORE_FLOAT_MATH_PROBE).is_ok()
79+
{
80+
println!("cargo::rustc-cfg=tw_core_float_math");
81+
}
6182
if env::var_os("CARGO_FEATURE_NIGHTLY_ABORT_IMMEDIATE").is_some()
6283
&& compiler.probe_raw(ABORT_IMMEDIATE_PROBE).is_ok()
6384
{

‎crates/tinywasm/src/interpreter/executor/instructions.rs‎

Lines changed: 47 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
11
macro_rules! exec_op {
2+
($executor:ident; unary_fallible $from:ty => $to:ty, |$v:ident| $expr:expr) => {{
3+
#[inline(always)]
4+
fn exec_unary_fallible(value_stack: &mut ValueStack) -> Result<(), Trap> {
5+
let $v = <$from>::stack_pop(value_stack);
6+
<$to>::stack_push(value_stack, $expr?);
7+
Ok(())
8+
}
9+
exec_unary_fallible(&mut $executor.value_stack)?;
10+
}};
211
($executor:ident; binary_fallible $ty:ty, |$lhs:ident, $rhs:ident| $expr:expr) => {{
312
fn exec_binary_fallible(value_stack: &mut ValueStack) -> Result<(), Trap> {
413
let $rhs = <$ty>::stack_pop(value_stack);
@@ -280,14 +289,14 @@ macro_rules! instruction_handlers {
280289
I64Mul => exec_op!(executor; binary i64 => i64, |a, b| a.wrapping_mul(b)),
281290
F32Mul => exec_op!(executor; binary f32 => f32, |a, b| a * b),
282291
F64Mul => exec_op!(executor; binary f64 => f64, |a, b| a * b),
283-
I32DivS => exec_op!(executor; binary_fallible i32, |a, b| a.tw_checked_div(b)),
284-
I64DivS => exec_op!(executor; binary_fallible i64, |a, b| a.tw_checked_div(b)),
285-
I32DivU => exec_op!(executor; binary_fallible u32, |a, b| a.checked_div(b).ok_or(Trap::DivisionByZero)),
286-
I64DivU => exec_op!(executor; binary_fallible u64, |a, b| a.checked_div(b).ok_or(Trap::DivisionByZero)),
287-
I32RemS => exec_op!(executor; binary_fallible i32, |a, b| a.tw_checked_wrapping_rem(b)),
288-
I64RemS => exec_op!(executor; binary_fallible i64, |a, b| a.tw_checked_wrapping_rem(b)),
289-
I32RemU => exec_op!(executor; binary_fallible u32, |a, b| a.tw_checked_wrapping_rem(b)),
290-
I64RemU => exec_op!(executor; binary_fallible u64, |a, b| a.tw_checked_wrapping_rem(b)),
292+
I32DivS => exec_op!(executor; binary_fallible i32, |a, b| num::i32::div(a, b)),
293+
I64DivS => exec_op!(executor; binary_fallible i64, |a, b| num::i64::div(a, b)),
294+
I32DivU => exec_op!(executor; binary_fallible u32, |a, b| num::u32::div(a, b)),
295+
I64DivU => exec_op!(executor; binary_fallible u64, |a, b| num::u64::div(a, b)),
296+
I32RemS => exec_op!(executor; binary_fallible i32, |a, b| num::i32::rem(a, b)),
297+
I64RemS => exec_op!(executor; binary_fallible i64, |a, b| num::i64::rem(a, b)),
298+
I32RemU => exec_op!(executor; binary_fallible u32, |a, b| num::u32::rem(a, b)),
299+
I64RemU => exec_op!(executor; binary_fallible u64, |a, b| num::u64::rem(a, b)),
291300
I32And => exec_op!(executor; binary i32 => i32, |a, b| a & b),
292301
I64And => exec_op!(executor; binary i64 => i64, |a, b| a & b),
293302
I32Or => exec_op!(executor; binary i32 => i32, |a, b| a | b),
@@ -423,40 +432,40 @@ macro_rules! instruction_handlers {
423432
F64Abs => exec_op!(executor; unary f64 => f64, |v| v.abs()),
424433
F32Neg => exec_op!(executor; unary f32 => f32, |v| -v),
425434
F64Neg => exec_op!(executor; unary f64 => f64, |v| -v),
426-
F32Ceil => exec_op!(executor; unary f32 => f32, |v| v.ceil()),
427-
F64Ceil => exec_op!(executor; unary f64 => f64, |v| v.ceil()),
428-
F32Floor => exec_op!(executor; unary f32 => f32, |v| v.floor()),
429-
F64Floor => exec_op!(executor; unary f64 => f64, |v| v.floor()),
430-
F32Trunc => exec_op!(executor; unary f32 => f32, |v| v.trunc()),
431-
F64Trunc => exec_op!(executor; unary f64 => f64, |v| v.trunc()),
432-
F32Nearest => exec_op!(executor; unary f32 => f32, |v| v.tw_nearest()),
433-
F64Nearest => exec_op!(executor; unary f64 => f64, |v| v.tw_nearest()),
434-
F32Sqrt => exec_op!(executor; unary f32 => f32, |v| v.sqrt()),
435-
F64Sqrt => exec_op!(executor; unary f64 => f64, |v| v.sqrt()),
436-
F32Min => exec_op!(executor; binary f32 => f32, |a, b| a.tw_minimum(b)),
437-
F64Min => exec_op!(executor; binary f64 => f64, |a, b| a.tw_minimum(b)),
438-
F32Max => exec_op!(executor; binary f32 => f32, |a, b| a.tw_maximum(b)),
439-
F64Max => exec_op!(executor; binary f64 => f64, |a, b| a.tw_maximum(b)),
435+
F32Ceil => exec_op!(executor; unary f32 => f32, |v| num::f32::ceil(v)),
436+
F64Ceil => exec_op!(executor; unary f64 => f64, |v| num::f64::ceil(v)),
437+
F32Floor => exec_op!(executor; unary f32 => f32, |v| num::f32::floor(v)),
438+
F64Floor => exec_op!(executor; unary f64 => f64, |v| num::f64::floor(v)),
439+
F32Trunc => exec_op!(executor; unary f32 => f32, |v| num::f32::trunc(v)),
440+
F64Trunc => exec_op!(executor; unary f64 => f64, |v| num::f64::trunc(v)),
441+
F32Nearest => exec_op!(executor; unary f32 => f32, |v| num::f32::nearest(v)),
442+
F64Nearest => exec_op!(executor; unary f64 => f64, |v| num::f64::nearest(v)),
443+
F32Sqrt => exec_op!(executor; unary f32 => f32, |v| num::f32::sqrt(v)),
444+
F64Sqrt => exec_op!(executor; unary f64 => f64, |v| num::f64::sqrt(v)),
445+
F32Min => exec_op!(executor; binary f32 => f32, |a, b| num::f32::minimum(a, b)),
446+
F64Min => exec_op!(executor; binary f64 => f64, |a, b| num::f64::minimum(a, b)),
447+
F32Max => exec_op!(executor; binary f32 => f32, |a, b| num::f32::maximum(a, b)),
448+
F64Max => exec_op!(executor; binary f64 => f64, |a, b| num::f64::maximum(a, b)),
440449
F32Copysign => exec_op!(executor; binary f32 => f32, |a, b| a.copysign(b)),
441450
F64Copysign => exec_op!(executor; binary f64 => f64, |a, b| a.copysign(b)),
442-
I32TruncF32S => checked_conv_float!(f32, i32, executor),
443-
I32TruncF64S => checked_conv_float!(f64, i32, executor),
444-
I32TruncF32U => checked_conv_float!(f32, u32, i32, executor),
445-
I32TruncF64U => checked_conv_float!(f64, u32, i32, executor),
446-
I64TruncF32S => checked_conv_float!(f32, i64, executor),
447-
I64TruncF64S => checked_conv_float!(f64, i64, executor),
448-
I64TruncF32U => checked_conv_float!(f32, u64, i64, executor),
449-
I64TruncF64U => checked_conv_float!(f64, u64, i64, executor),
451+
I32TruncF32S => exec_op!(executor; unary_fallible f32 => i32, |v| num::f32::trunc_to_i32(v)),
452+
I32TruncF64S => exec_op!(executor; unary_fallible f64 => i32, |v| num::f64::trunc_to_i32(v)),
453+
I32TruncF32U => exec_op!(executor; unary_fallible f32 => u32, |v| num::f32::trunc_to_u32(v)),
454+
I32TruncF64U => exec_op!(executor; unary_fallible f64 => u32, |v| num::f64::trunc_to_u32(v)),
455+
I64TruncF32S => exec_op!(executor; unary_fallible f32 => i64, |v| num::f32::trunc_to_i64(v)),
456+
I64TruncF64S => exec_op!(executor; unary_fallible f64 => i64, |v| num::f64::trunc_to_i64(v)),
457+
I64TruncF32U => exec_op!(executor; unary_fallible f32 => u64, |v| num::f32::trunc_to_u64(v)),
458+
I64TruncF64U => exec_op!(executor; unary_fallible f64 => u64, |v| num::f64::trunc_to_u64(v)),
450459

451460
// Non-trapping float-to-int conversions
452-
I32TruncSatF32S => exec_op!(executor; unary f32 => i32, |v| v.trunc() as i32),
453-
I32TruncSatF32U => exec_op!(executor; unary f32 => u32, |v| v.trunc() as u32),
454-
I32TruncSatF64S => exec_op!(executor; unary f64 => i32, |v| v.trunc() as i32),
455-
I32TruncSatF64U => exec_op!(executor; unary f64 => u32, |v| v.trunc() as u32),
456-
I64TruncSatF32S => exec_op!(executor; unary f32 => i64, |v| v.trunc() as i64),
457-
I64TruncSatF32U => exec_op!(executor; unary f32 => u64, |v| v.trunc() as u64),
458-
I64TruncSatF64S => exec_op!(executor; unary f64 => i64, |v| v.trunc() as i64),
459-
I64TruncSatF64U => exec_op!(executor; unary f64 => u64, |v| v.trunc() as u64),
461+
I32TruncSatF32S => exec_op!(executor; unary f32 => i32, |v| v as i32),
462+
I32TruncSatF32U => exec_op!(executor; unary f32 => u32, |v| v as u32),
463+
I32TruncSatF64S => exec_op!(executor; unary f64 => i32, |v| v as i32),
464+
I32TruncSatF64U => exec_op!(executor; unary f64 => u32, |v| v as u32),
465+
I64TruncSatF32S => exec_op!(executor; unary f32 => i64, |v| v as i64),
466+
I64TruncSatF32U => exec_op!(executor; unary f32 => u64, |v| v as u64),
467+
I64TruncSatF64S => exec_op!(executor; unary f64 => i64, |v| v as i64),
468+
I64TruncSatF64U => exec_op!(executor; unary f64 => u64, |v| v as u64),
460469

461470
// SIMD extension
462471
V128Not => exec_op!(executor; unary Value128 => Value128, |v| v.v128_not()),

‎crates/tinywasm/src/interpreter/executor/mod.rs‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
use core::convert::identity;
22

3-
#[cfg(not(feature = "std"))]
4-
#[allow(unused_imports)]
5-
use super::no_std_floats::NoStdFloatExt;
6-
73
use alloc::boxed::Box;
84
use alloc::vec::Vec;
95

106
use interpreter::stack::{CallFrame, CallStack, ValueStack};
117
use tinywasm_types::*;
128

13-
use super::num_helpers::*;
9+
use super::num;
1410
use super::values::*;
1511
use crate::engine::{Config, FuelPolicy};
1612
use crate::interpreter::Value128;

‎crates/tinywasm/src/interpreter/mod.rs‎

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
pub(crate) mod executor;
2-
pub(crate) mod num_helpers;
2+
pub(crate) mod num;
33
pub(crate) mod simd;
44
pub(crate) mod stack;
55
pub(crate) mod values;
66

7-
#[cfg(not(feature = "std"))]
8-
mod no_std_floats;
9-
107
use crate::{Error, Result, Store, interpreter::stack::CallFrame};
118
use executor::{Budget, ExecOutcome, Executor};
129
pub(crate) use simd::*;

0 commit comments

Comments
 (0)