Repository: bytecodealliance/wasm-tools
Pull request #1450 (2024-03-13 01:52:22)

Replace custom "test harnesses" with `libtest-mimic`
There's a number of tests suites in this repository which are based on "drop a file here and it's a test now". Up to now these have been powered by a custom "test harness" copied around which implements at filtering mechanism but little else. The `libtest-mimic` crate provides a `test`-crate-lookalike API which enables dynamically building the list of tests at program start time (aka read the filesystem) and then afterwards looks and feels similar to the `test` crate's CLI and UX.

This enables a nicer dev experience when running these tests and additionally enables using runners like `cargo nextest` with this repository.


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5b8afd99..13ddc798 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -616,6 +616,15 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "escape8259"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba4f4911e3666fcd7826997b4745c8224295a6f3072f1418c3067b97a67557ee"
+dependencies = [
+ "rustversion",
+]
+
 [[package]]
 name = "fallible-iterator"
 version = "0.2.0"
@@ -926,6 +935,18 @@ dependencies = [
  "once_cell",
 ]
 
+[[package]]
+name = "libtest-mimic"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f0f4c6f44ecfd52e8b443f2ad18f2b996540135771561283c2352ce56a1c70b"
+dependencies = [
+ "clap 4.5.2",
+ "escape8259",
+ "termcolor",
+ "threadpool",
+]
+
 [[package]]
 name = "linux-raw-sys"
 version = "0.4.13"
@@ -1273,6 +1294,12 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
+
 [[package]]
 name = "ruzstd"
 version = "0.5.0"
@@ -1498,6 +1525,15 @@ dependencies = [
  "syn 2.0.52",
 ]
 
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
 [[package]]
 name = "tinytemplate"
 version = "1.2.1"
@@ -1830,6 +1866,7 @@ dependencies = [
  "env_logger",
  "gimli 0.28.1",
  "is_executable",
+ "libtest-mimic",
  "log",
  "pretty_assertions",
  "rayon",
@@ -2212,8 +2249,8 @@ dependencies = [
  "anyhow",
  "bumpalo",
  "leb128",
+ "libtest-mimic",
  "memchr",
- "rayon",
  "unicode-width",
  "wasm-encoder 0.201.0",
  "wasmparser 0.201.0",
@@ -2464,9 +2501,9 @@ dependencies = [
  "env_logger",
  "id-arena",
  "indexmap 2.2.5",
+ "libtest-mimic",
  "log",
  "pretty_assertions",
- "rayon",
  "semver",
  "serde",
  "serde_derive",
diff --git a/Cargo.toml b/Cargo.toml
index 89b890a6..dc237146 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -56,6 +56,7 @@ url = "2.0.0"
 pretty_assertions = "1.3.0"
 semver = "1.0.0"
 smallvec = "1.11.1"
+libtest-mimic = "0.7.0"
 
 wasm-compose = { version = "0.201.0", path = "crates/wasm-compose" }
 wasm-encoder = { version = "0.201.0", path = "crates/wasm-encoder" }
@@ -136,6 +137,7 @@ tempfile = "3.1"
 diff = "0.1"
 wast = { path = 'crates/wast' }
 pretty_assertions = { workspace = true }
+libtest-mimic = { workspace = true }
 
 [[test]]
 name = "cli"
diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs
index 43dbecce..a01bc074 100644
--- a/crates/wasm-smith/src/config.rs
+++ b/crates/wasm-smith/src/config.rs
@@ -681,6 +681,7 @@ impl<'a> Arbitrary<'a> for Config {
             },
             table_max_size_required: u.arbitrary()?,
             max_table_elements: u.int_in_range(0..=1_000_000)?,
+            disallow_traps: u.arbitrary()?,
 
             // These fields, unlike the ones above, are less useful to set.
             // They either make weird inputs or are for features not widely
@@ -712,7 +713,6 @@ impl<'a> Arbitrary<'a> for Config {
             exports: None,
             threads_enabled: false,
             export_everything: false,
-            disallow_traps: false,
             tail_call_enabled: false,
             gc_enabled: false,
             generate_custom_sections: false,
diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs
index 194d2101..48e6d578 100644
--- a/crates/wasm-smith/src/core.rs
+++ b/crates/wasm-smith/src/core.rs
@@ -146,6 +146,11 @@ pub struct Module {
 
     /// What the maximum type index that can be referenced is.
     max_type_limit: MaxTypeLimit,
+
+    /// Some known-interesting values, such as powers of two, values just before
+    /// or just after a memory size, etc...
+    interesting_values32: Vec<u32>,
+    interesting_values64: Vec<u64>,
 }
 
 impl<'a> Arbitrary<'a> for Module {
@@ -232,6 +237,8 @@ impl Module {
             export_names: HashSet::new(),
             const_expr_choices: Vec::new(),
             max_type_limit: MaxTypeLimit::ModuleTypes,
+            interesting_values32: Vec::new(),
+            interesting_values64: Vec::new(),
         }
     }
 }
@@ -2039,6 +2046,8 @@ impl Module {
     }
 
     fn arbitrary_code(&mut self, u: &mut Unstructured) -> Result<()> {
+        self.compute_interesting_values();
+
         self.code.reserve(self.num_defined_funcs);
         let mut allocs = CodeBuilderAllocations::new(self, self.config.exports.is_some());
         for (_, ty) in self.funcs[self.funcs.len() - self.num_defined_funcs..].iter() {
@@ -2224,6 +2233,170 @@ impl Module {
                 }
             })
     }
+
+    fn compute_interesting_values(&mut self) {
+        debug_assert!(self.interesting_values32.is_empty());
+        debug_assert!(self.interesting_values64.is_empty());
+
+        let mut interesting_values32 = HashSet::new();
+        let mut interesting_values64 = HashSet::new();
+
+        let mut interesting = |val: u64| {
+            interesting_values32.insert(val as u32);
+            interesting_values64.insert(val);
+        };
+
+        // Zero is always interesting.
+        interesting(0);
+
+        // Max values are always interesting.
+        interesting(u8::MAX as _);
+        interesting(u16::MAX as _);
+        interesting(u32::MAX as _);
+        interesting(u64::MAX);
+
+        // Min values are always interesting.
+        interesting(i8::MIN as _);
+        interesting(i16::MIN as _);
+        interesting(i32::MIN as _);
+        interesting(i64::MIN as _);
+
+        for i in 0..64 {
+            // Powers of two.
+            interesting(1 << i);
+
+            // Inverted powers of two.
+            interesting(!(1 << i));
+
+            // Powers of two minus one, AKA high bits unset and low bits set.
+            interesting((1 << i) - 1);
+
+            // Negative powers of two, AKA high bits set and low bits unset.
+            interesting(((1_i64 << 63) >> i) as _);
+        }
+
+        // Some repeating bit patterns.
+        for pattern in [0b01010101, 0b00010001, 0b00010001, 0b00000001] {
+            for b in [pattern, !pattern] {
+                interesting(u64::from_ne_bytes([b, b, b, b, b, b, b, b]));
+            }
+        }
+
+        // Interesting float values.
+        let mut interesting_f64 = |x: f64| interesting(x.to_bits());
+        interesting_f64(0.0);
+        interesting_f64(-0.0);
+        interesting_f64(f64::INFINITY);
+        interesting_f64(f64::NEG_INFINITY);
+        interesting_f64(f64::EPSILON);
+        interesting_f64(-f64::EPSILON);
+        interesting_f64(f64::MIN);
+        interesting_f64(f64::MIN_POSITIVE);
+        interesting_f64(f64::MAX);
+        interesting_f64(f64::NAN);
+        let mut interesting_f32 = |x: f32| interesting(x.to_bits() as _);
+        interesting_f32(0.0);
+        interesting_f32(-0.0);
+        interesting_f32(f32::INFINITY);
+        interesting_f32(f32::NEG_INFINITY);
+        interesting_f32(f32::EPSILON);
+        interesting_f32(-f32::EPSILON);
+        interesting_f32(f32::MIN);
+        interesting_f32(f32::MIN_POSITIVE);
+        interesting_f32(f32::MAX);
+        interesting_f32(f32::NAN);
+
+        // Interesting values related to table bounds.
+        for t in self.tables.iter() {
+            interesting(t.minimum as _);
+            if let Some(x) = t.minimum.checked_add(1) {
+                interesting(x as _);
+            }
+
+            if let Some(x) = t.maximum {
+                interesting(x as _);
+                if let Some(y) = x.checked_add(1) {
+                    interesting(y as _);
+                }
+            }
+        }
+
+        // Interesting values related to memory bounds.
+        for m in self.memories.iter() {
+            let min = m.minimum.saturating_mul(crate::WASM_PAGE_SIZE);
+            interesting(min);
+            for i in 0..5 {
+                if let Some(x) = min.checked_add(1 << i) {
+                    interesting(x);
+                }
+                if let Some(x) = min.checked_sub(1 << i) {
+                    interesting(x);
+                }
+            }
+
+            if let Some(max) = m.maximum {
+                let max = max.saturating_mul(crate::WASM_PAGE_SIZE);
+                interesting(max);
+                for i in 0..5 {
+                    if let Some(x) = max.checked_add(1 << i) {
+                        interesting(x);
+                    }
+                    if let Some(x) = max.checked_sub(1 << i) {
+                        interesting(x);
+                    }
+                }
+            }
+        }
+
+        self.interesting_values32.extend(interesting_values32);
+        self.interesting_values64.extend(interesting_values64);
+
+        // Sort for determinism.
+        self.interesting_values32.sort();
+        self.interesting_values64.sort();
+    }
+
+    fn arbitrary_const_instruction(
+        &self,
+        ty: ValType,
+        u: &mut Unstructured<'_>,
+    ) -> Result<Instruction> {
+        debug_assert!(self.interesting_values32.len() > 0);
+        debug_assert!(self.interesting_values64.len() > 0);
+        match ty {
+            ValType::I32 => Ok(Instruction::I32Const(if u.arbitrary()? {
+                *u.choose(&self.interesting_values32)? as i32
+            } else {
+                u.arbitrary()?
+            })),
+            ValType::I64 => Ok(Instruction::I64Const(if u.arbitrary()? {
+                *u.choose(&self.interesting_values64)? as i64
+            } else {
+                u.arbitrary()?
+            })),
+            ValType::F32 => Ok(Instruction::F32Const(if u.arbitrary()? {
+                f32::from_bits(*u.choose(&self.interesting_values32)?)
+            } else {
+                u.arbitrary()?
+            })),
+            ValType::F64 => Ok(Instruction::F64Const(if u.arbitrary()? {
+                f64::from_bits(*u.choose(&self.interesting_values64)?)
+            } else {
+                u.arbitrary()?
+            })),
+            ValType::V128 => Ok(Instruction::V128Const(if u.arbitrary()? {
+                let upper = (*u.choose(&self.interesting_values64)? as i128) << 64;
+                let lower = *u.choose(&self.interesting_values64)? as i128;
+                upper | lower
+            } else {
+                u.arbitrary()?
+            })),
+            ValType::Ref(ty) => {
+                assert!(ty.nullable);
+                Ok(Instruction::RefNull(ty.heap_type))
+            }
+        }
+    }
 }
 
 pub(crate) fn arbitrary_limits32(
diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs
index c4cc515e..3156a859 100644
--- a/crates/wasm-smith/src/core/code_builder.rs
+++ b/crates/wasm-smith/src/core/code_builder.rs
@@ -1435,7 +1435,7 @@ impl CodeBuilder<'_> {
                 }
                 operands = &[];
             }
-            instructions.push(arbitrary_val(*expected, u));
+            instructions.push(module.arbitrary_const_instruction(*expected, u)?);
         }
         Ok(())
     }
@@ -1544,20 +1544,6 @@ impl CodeBuilder<'_> {
     }
 }
 
-fn arbitrary_val(ty: ValType, u: &mut Unstructured<'_>) -> Instruction {
-    match ty {
-        ValType::I32 => Instruction::I32Const(u.arbitrary().unwrap_or(0)),
-        ValType::I64 => Instruction::I64Const(u.arbitrary().unwrap_or(0)),
-        ValType::F32 => Instruction::F32Const(u.arbitrary().unwrap_or(0.0)),
-        ValType::F64 => Instruction::F64Const(u.arbitrary().unwrap_or(0.0)),
-        ValType::V128 => Instruction::V128Const(u.arbitrary().unwrap_or(0)),
-        ValType::Ref(ty) => {
-            assert!(ty.nullable);
-            Instruction::RefNull(ty.heap_type)
-        }
-    }
-}
-
 #[inline]
 fn unreachable_valid(module: &Module, _: &mut CodeBuilder) -> bool {
     !module.config.disallow_traps
@@ -3419,49 +3405,45 @@ fn data_drop(
 
 fn i32_const(
     u: &mut Unstructured,
-    _module: &Module,
+    module: &Module,
     builder: &mut CodeBuilder,
     instructions: &mut Vec<Instruction>,
 ) -> Result<()> {
-    let x = u.arbitrary()?;
     builder.push_operands(&[ValType::I32]);
-    instructions.push(Instruction::I32Const(x));
+    instructions.push(module.arbitrary_const_instruction(ValType::I32, u)?);
     Ok(())
 }
 
 fn i64_const(
     u: &mut Unstructured,
-    _module: &Module,
+    module: &Module,
     builder: &mut CodeBuilder,
     instructions: &mut Vec<Instruction>,
 ) -> Result<()> {
-    let x = u.arbitrary()?;
     builder.push_operands(&[ValType::I64]);
-    instructions.push(Instruction::I64Const(x));
+    instructions.push(module.arbitrary_const_instruction(ValType::I64, u)?);
     Ok(())
 }
 
 fn f32_const(
     u: &mut Unstructured,
-    _module: &Module,
+    module: &Module,
     builder: &mut CodeBuilder,
     instructions: &mut Vec<Instruction>,
 ) -> Result<()> {
-    let x = u.arbitrary()?;
     builder.push_operands(&[ValType::F32]);
-    instructions.push(Instruction::F32Const(x));
+    instructions.push(module.arbitrary_const_instruction(ValType::F32, u)?);
     Ok(())
 }
 
 fn f64_const(
     u: &mut Unstructured,
-    _module: &Module,
+    module: &Module,
     builder: &mut CodeBuilder,
     instructions: &mut Vec<Instruction>,
 ) -> Result<()> {
-    let x = u.arbitrary()?;
     builder.push_operands(&[ValType::F64]);
-    instructions.push(Instruction::F64Const(x));
+    instructions.push(module.arbitrary_const_instruction(ValType::F64, u)?);
     Ok(())
 }
 
@@ -5220,10 +5202,10 @@ fn memory_offset(u: &mut Unstructured, module: &Module, memory_index: u32) -> Re
     assert!(a + b + c != 0);
 
     let memory_type = &module.memories[memory_index as usize];
-    let min = memory_type.minimum.saturating_mul(65536);
+    let min = memory_type.minimum.saturating_mul(crate::WASM_PAGE_SIZE);
     let max = memory_type
         .maximum
-        .map(|max| max.saturating_mul(65536))
+        .map(|max| max.saturating_mul(crate::WASM_PAGE_SIZE))
         .unwrap_or(u64::MAX);
 
     let (min, max, true_max) = match (memory_type.memory64, module.config.disallow_traps) {
diff --git a/crates/wasm-smith/src/lib.rs b/crates/wasm-smith/src/lib.rs
index b985791a..c987a95d 100644
--- a/crates/wasm-smith/src/lib.rs
+++ b/crates/wasm-smith/src/lib.rs
@@ -67,6 +67,8 @@ use std::{collections::HashSet, fmt::Write, str};
 #[cfg(feature = "_internal_cli")]
 pub use config::InternalOptionalConfig;
 
+const WASM_PAGE_SIZE: u64 = 65_536;
+
 /// Do something an arbitrary number of times.
 ///
 /// The callback can return `false` to exit the loop early.
diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml
index c173450b..b7db2768 100644
--- a/crates/wast/Cargo.toml
+++ b/crates/wast/Cargo.toml
@@ -24,7 +24,7 @@ bumpalo = "3.14.0"
 
 [dev-dependencies]
 anyhow = { workspace = true }
-rayon = { workspace = true }
+libtest-mimic = { workspace = true }
 wasmparser = { path = "../wasmparser" }
 wat = { path = "../wat" }
 
diff --git a/crates/wit-parser/Cargo.toml b/crates/wit-parser/Cargo.toml
index b0ad5eaa..34573d42 100644
--- a/crates/wit-parser/Cargo.toml
+++ b/crates/wit-parser/Cargo.toml
@@ -44,11 +44,11 @@ decoding = ['dep:wasmparser']
 wat = ['decoding', 'dep:wat']
 
 [dev-dependencies]
-rayon = "1"
 env_logger = { workspace = true }
 pretty_assertions = { workspace = true }
 serde_json = { workspace = true }
 wit-parser = { path = '.', features = ['serde', 'wat'] }
+libtest-mimic = { workspace = true }
 
 [[test]]
 name = "all"
diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs
index bd7218a3..3b753fce 100644
--- a/src/bin/wasm-tools/component.rs
+++ b/src/bin/wasm-tools/component.rs
@@ -101,7 +101,7 @@ pub struct NewOpts {
     /// ABI, for example, to one that uses the component model. The first
     /// `[NAME=]` specified in the argument is inferred from the name of file
     /// specified by `MODULE` if not present and is the name of the import
-    /// module that's being implemented (e.g. `wasi_snapshot_preview1.wasm`.
+    /// module that's being implemented (e.g. `wasi_snapshot_preview1.wasm`).
     ///
     /// The second part of this argument, optionally specified, is the interface
     /// that this adapter module imports. If not specified then the interface
@@ -317,7 +317,7 @@ pub struct LinkOpts {
     /// ABI, for example, to one that uses the component model. The first
     /// `[NAME=]` specified in the argument is inferred from the name of file
     /// specified by `MODULE` if not present and is the name of the import
-    /// module that's being implemented (e.g. `wasi_snapshot_preview1.wasm`.
+    /// module that's being implemented (e.g. `wasi_snapshot_preview1.wasm`).
     ///
     /// The second part of this argument, optionally specified, is the interface
     /// that this adapter module imports. If not specified then the interface


Test patch:
diff --git a/crates/wast/tests/parse-fail.rs b/crates/wast/tests/parse-fail.rs
index 3ae1817a..a8d4f344 100644
--- a/crates/wast/tests/parse-fail.rs
+++ b/crates/wast/tests/parse-fail.rs
@@ -4,46 +4,28 @@
 //! Use `BLESS=1` in the environment to auto-update `*.err` files. Be sure to
 //! look at the diff!
 
-use rayon::prelude::*;
+use libtest_mimic::{Arguments, Trial};
 use std::env;
 use std::path::{Path, PathBuf};
 
 fn main() {
     let mut tests = Vec::new();
     find_tests("tests/parse-fail".as_ref(), &mut tests);
-    let filter = std::env::args().nth(1);
-
     let bless = env::var("BLESS").is_ok();
-    let tests = tests
-        .iter()
-        .filter(|test| {
-            if let Some(filter) = &filter {
-                if let Some(s) = test.file_name().and_then(|s| s.to_str()) {
-                    if !s.contains(filter) {
-                        return false;
-                    }
-                }
-            }
-            true
-        })
-        .collect::<Vec<_>>();
-
-    println!("running {} tests\n", tests.len());
-
-    let errors = tests
-        .par_iter()
-        .filter_map(|test| run_test(test, bless).err())
-        .collect::<Vec<_>>();
 
-    if !errors.is_empty() {
-        for msg in errors.iter() {
-            eprintln!("{}", msg);
-        }
-
-        panic!("{} tests failed", errors.len())
+    let mut trials = Vec::new();
+    for test in tests {
+        let trial = Trial::test(format!("{test:?}"), move || {
+            run_test(&test, bless).map_err(|e| format!("{e:?}").into())
+        });
+        trials.push(trial);
     }
 
-    println!("test result: ok. {} passed\n", tests.len());
+    let mut args = Arguments::from_args();
+    if cfg!(target_family = "wasm") && !cfg!(target_feature = "atomics") {
+        args.test_threads = Some(1);
+    }
+    libtest_mimic::run(&args, trials).exit();
 }
 
 fn run_test(test: &Path, bless: bool) -> anyhow::Result<()> {
diff --git a/crates/wit-parser/tests/all.rs b/crates/wit-parser/tests/all.rs
index 096192d7..bdb66321 100644
--- a/crates/wit-parser/tests/all.rs
+++ b/crates/wit-parser/tests/all.rs
@@ -8,61 +8,36 @@
 //!     cargo test --test all foo.wit
 
 use anyhow::{bail, Context, Result};
+use libtest_mimic::{Arguments, Trial};
 use pretty_assertions::StrComparison;
-use rayon::prelude::*;
 use std::env;
 use std::ffi::OsStr;
 use std::fs;
 use std::io;
 use std::path::{Path, PathBuf};
 use std::str;
-use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
 use wit_parser::*;
 
 fn main() {
     env_logger::init();
     let tests = find_tests();
-    let filter = std::env::args().nth(1);
 
-    let tests = tests
-        .par_iter()
-        .filter_map(|test| {
-            if let Some(filter) = &filter {
-                if let Some(s) = test.to_str() {
-                    if !s.contains(filter) {
-                        return None;
-                    }
-                }
-            }
-            Some(test)
-        })
-        .collect::<Vec<_>>();
-
-    println!("running {} test files\n", tests.len());
-
-    let ntests = AtomicUsize::new(0);
-    let errors = tests
-        .par_iter()
-        .filter_map(|test| {
-            Runner { ntests: &ntests }
-                .run(test)
+    let mut trials = Vec::new();
+    for test in tests {
+        let trial = Trial::test(format!("{test:?}"), move || {
+            Runner {}
+                .run(&test)
                 .context(format!("test {:?} failed", test))
-                .err()
-        })
-        .collect::<Vec<_>>();
-
-    if !errors.is_empty() {
-        for msg in errors.iter() {
-            eprintln!("{:?}", msg);
-        }
-
-        panic!("{} tests failed", errors.len())
+                .map_err(|e| format!("{e:?}").into())
+        });
+        trials.push(trial);
     }
 
-    println!(
-        "test result: ok. {} directives passed\n",
-        ntests.load(SeqCst)
-    );
+    let mut args = Arguments::from_args();
+    if cfg!(target_family = "wasm") && !cfg!(target_feature = "atomics") {
+        args.test_threads = Some(1);
+    }
+    libtest_mimic::run(&args, trials).exit();
 }
 
 /// Recursively finds all tests in a whitelisted set of directories which we
@@ -95,11 +70,9 @@ fn find_tests() -> Vec<PathBuf> {
     }
 }
 
-struct Runner<'a> {
-    ntests: &'a AtomicUsize,
-}
+struct Runner {}
 
-impl Runner<'_> {
+impl Runner {
     fn run(&mut self, test: &Path) -> Result<()> {
         let mut resolve = Resolve::new();
         let result = resolve.push_path(test);
@@ -164,13 +137,8 @@ impl Runner<'_> {
                 );
             }
         }
-        self.bump_ntests();
         Ok(())
     }
-
-    fn bump_ntests(&self) {
-        self.ntests.fetch_add(1, SeqCst);
-    }
 }
 
 fn normalize(s: &str, extension: &str) -> String {
diff --git a/tests/cli.rs b/tests/cli.rs
index ed9f4a64..3c8d2cfd 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -26,59 +26,36 @@
 //! to look at the diff!
 
 use anyhow::{anyhow, bail, Context, Result};
+use libtest_mimic::{Arguments, Trial};
 use pretty_assertions::StrComparison;
-use rayon::prelude::*;
 use std::env;
 use std::io::Write;
 use std::path::{Path, PathBuf};
 use std::process::{Command, Output, Stdio};
 
 fn main() {
-    // This test suite can't run on wasm since it involves spawning
-    // subprocesses.
-    if cfg!(target_family = "wasm") {
-        return;
-    }
-
     let mut tests = Vec::new();
     find_tests("tests/cli".as_ref(), &mut tests);
-    let filter = std::env::args().nth(1);
-
     let bless = env::var("BLESS").is_ok();
-    let tests = tests
-        .iter()
-        .filter(|test| {
-            if let Some(filter) = &filter {
-                if let Some(s) = test.file_name().and_then(|s| s.to_str()) {
-                    if !s.contains(filter) {
-                        return false;
-                    }
-                }
-            }
-            true
-        })
-        .collect::<Vec<_>>();
-
-    println!("running {} tests\n", tests.len());
 
-    let errors = tests
-        .par_iter()
-        .filter_map(|test| {
-            run_test(test, bless)
+    let mut trials = Vec::new();
+    for test in tests {
+        let trial = Trial::test(format!("{test:?}"), move || {
+            run_test(&test, bless)
                 .with_context(|| format!("failed test {test:?}"))
-                .err()
+                .map_err(|e| format!("{e:?}").into())
         })
-        .collect::<Vec<_>>();
-
-    if !errors.is_empty() {
-        for msg in errors.iter() {
-            eprintln!("{:?}", msg);
-        }
-
-        panic!("{} tests failed", errors.len())
+        // This test suite can't run on wasm since it involves spawning
+        // subprocesses.
+        .with_ignored_flag(cfg!(target_family = "wasm"));
+        trials.push(trial);
     }
 
-    println!("test result: ok. {} passed\n", tests.len());
+    let mut args = Arguments::from_args();
+    if cfg!(target_family = "wasm") && !cfg!(target_feature = "atomics") {
+        args.test_threads = Some(1);
+    }
+    libtest_mimic::run(&args, trials).exit();
 }
 
 fn wasm_tools_exe() -> Command {
diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs
index cef492df..c626ba6d 100644
--- a/tests/roundtrip.rs
+++ b/tests/roundtrip.rs
@@ -22,12 +22,14 @@
 //!     cargo test --test roundtrip local/ref.wat
 
 use anyhow::{anyhow, bail, Context, Result};
+use libtest_mimic::{Arguments, FormatSetting, Trial};
 use rayon::prelude::*;
 use std::io::{Read, Write};
 use std::path::{Path, PathBuf};
 use std::process::{Command, Stdio};
 use std::str;
 use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
+use std::sync::Arc;
 use wasmparser::*;
 use wast::core::{Module, ModuleKind};
 use wast::lexer::Lexer;
@@ -38,58 +40,36 @@ fn main() {
     env_logger::init();
 
     let tests = find_tests();
-    let filter = std::env::args().nth(1);
     let bless = std::env::var_os("BLESS").is_some();
-    if bless {
-        drop(std::fs::remove_dir_all("tests/snapshots"));
-    }
 
-    let tests = tests
-        .par_iter()
-        .filter_map(|test| {
-            if let Some(filter) = &filter {
-                if let Some(s) = test.to_str() {
-                    if !s.contains(filter) {
-                        return None;
-                    }
-                }
-            }
-            let contents = std::fs::read(test).unwrap();
-            if skip_test(&test, &contents) {
-                None
-            } else {
-                Some((test, contents))
-            }
-        })
-        .collect::<Vec<_>>();
-
-    println!("running {} test files\n", tests.len());
-
-    let state = TestState::default();
-    let errors = tests
-        .par_iter()
-        .filter_map(|(test, contents)| {
-            let start = std::time::Instant::now();
-            let result = state.run_test(test, contents).err();
-            if start.elapsed().as_secs() > 2 {
-                println!("{test:?} SLOW");
+    let state = Arc::new(TestState::default());
+    let mut trials = Vec::new();
+    for test in tests {
+        let contents = std::fs::read(&test).unwrap();
+        let skip = skip_test(&test, &contents);
+        let trial = Trial::test(format!("{test:?}"), {
+            let state = state.clone();
+            move || {
+                state
+                    .run_test(&test, &contents)
+                    .map_err(|e| format!("{e:?}").into())
             }
-            result
         })
-        .collect::<Vec<_>>();
-
-    if !errors.is_empty() {
-        for msg in errors.iter() {
-            eprintln!("{:?}", msg);
-        }
-
-        panic!("{} tests failed", errors.len())
+        .with_ignored_flag(skip);
+        trials.push(trial);
     }
 
-    println!(
-        "test result: ok. {} directives passed\n",
-        state.ntests.load(SeqCst)
-    );
+    let mut args = Arguments::from_args();
+    if args.format.is_none() {
+        args.format = Some(FormatSetting::Terse);
+    }
+    if cfg!(target_family = "wasm") && !cfg!(target_feature = "atomics") {
+        args.test_threads = Some(1);
+    }
+    if bless && !args.list {
+        drop(std::fs::remove_dir_all("tests/snapshots"));
+    }
+    libtest_mimic::run(&args, trials).exit();
 }
 
 /// Recursively finds all tests in a whitelisted set of directories which we
diff --git a/tests/snapshots/testsuite/elem.wast/63.print b/tests/snapshots/testsuite/elem.wast/62.print
similarity index 100%
rename from tests/snapshots/testsuite/elem.wast/63.print
rename to tests/snapshots/testsuite/elem.wast/62.print
diff --git a/tests/snapshots/testsuite/elem.wast/65.print b/tests/snapshots/testsuite/elem.wast/64.print
similarity index 100%
rename from tests/snapshots/testsuite/elem.wast/65.print
rename to tests/snapshots/testsuite/elem.wast/64.print
diff --git a/tests/snapshots/testsuite/elem.wast/67.print b/tests/snapshots/testsuite/elem.wast/66.print
similarity index 100%
rename from tests/snapshots/testsuite/elem.wast/67.print
rename to tests/snapshots/testsuite/elem.wast/66.print
diff --git a/tests/snapshots/testsuite/elem.wast/72.print b/tests/snapshots/testsuite/elem.wast/71.print
similarity index 100%
rename from tests/snapshots/testsuite/elem.wast/72.print
rename to tests/snapshots/testsuite/elem.wast/71.print
diff --git a/tests/snapshots/testsuite/elem.wast/76.print b/tests/snapshots/testsuite/elem.wast/75.print
similarity index 100%
rename from tests/snapshots/testsuite/elem.wast/76.print
rename to tests/snapshots/testsuite/elem.wast/75.print
diff --git a/tests/snapshots/testsuite/elem.wast/84.print b/tests/snapshots/testsuite/elem.wast/83.print
similarity index 100%
rename from tests/snapshots/testsuite/elem.wast/84.print
rename to tests/snapshots/testsuite/elem.wast/83.print
diff --git a/tests/snapshots/testsuite/elem.wast/92.print b/tests/snapshots/testsuite/elem.wast/91.print
similarity index 100%
rename from tests/snapshots/testsuite/elem.wast/92.print
rename to tests/snapshots/testsuite/elem.wast/91.print
diff --git a/tests/snapshots/testsuite/elem.wast/95.print b/tests/snapshots/testsuite/elem.wast/94.print
similarity index 100%
rename from tests/snapshots/testsuite/elem.wast/95.print
rename to tests/snapshots/testsuite/elem.wast/94.print
diff --git a/tests/snapshots/testsuite/elem.wast/97.print b/tests/snapshots/testsuite/elem.wast/96.print
similarity index 100%
rename from tests/snapshots/testsuite/elem.wast/97.print
rename to tests/snapshots/testsuite/elem.wast/96.print
diff --git a/tests/snapshots/testsuite/float_literals.wast/0.print b/tests/snapshots/testsuite/float_literals.wast/0.print
index 6464f7db..ab8e54d3 100644
--- a/tests/snapshots/testsuite/float_literals.wast/0.print
+++ b/tests/snapshots/testsuite/float_literals.wast/0.print
@@ -88,227 +88,291 @@
     i32.reinterpret_f32
   )
   (func (;21;) (type 0) (result i32)
-    f32.const 0x0p+0 (;=0;)
+    f32.const 0x1.2345p+16 (;=74565;)
     i32.reinterpret_f32
   )
   (func (;22;) (type 0) (result i32)
-    f32.const 0x0p+0 (;=0;)
+    f32.const 0x1p+80 (;=1208925800000000000000000;)
     i32.reinterpret_f32
   )
   (func (;23;) (type 0) (result i32)
-    f32.const -0x0p+0 (;=-0;)
+    f32.const -0x1p+31 (;=-2147483600;)
     i32.reinterpret_f32
   )
   (func (;24;) (type 0) (result i32)
-    f32.const 0x1.921fb6p+2 (;=6.2831855;)
+    f32.const -0x1p+63 (;=-9223372000000000000;)
     i32.reinterpret_f32
   )
   (func (;25;) (type 0) (result i32)
-    f32.const 0x1.p-149 (;=0.000000000000000000000000000000000000000000001;)
+    f32.const 0x0p+0 (;=0;)
     i32.reinterpret_f32
   )
   (func (;26;) (type 0) (result i32)
-    f32.const 0x1p-126 (;=0.000000000000000000000000000000000000011754944;)
+    f32.const 0x0p+0 (;=0;)
     i32.reinterpret_f32
   )
   (func (;27;) (type 0) (result i32)
-    f32.const 0x1.fffffcp-127 (;=0.000000000000000000000000000000000000011754942;)
+    f32.const -0x0p+0 (;=-0;)
     i32.reinterpret_f32
   )
   (func (;28;) (type 0) (result i32)
-    f32.const 0x1.fffffep+127 (;=340282350000000000000000000000000000000;)
+    f32.const 0x1.921fb6p+2 (;=6.2831855;)
     i32.reinterpret_f32
   )
   (func (;29;) (type 0) (result i32)
-    f32.const 0x1.2a05f2p+33 (;=10000000000;)
+    f32.const 0x1.p-149 (;=0.000000000000000000000000000000000000000000001;)
     i32.reinterpret_f32
   )
   (func (;30;) (type 0) (result i32)
+    f32.const 0x1p-126 (;=0.000000000000000000000000000000000000011754944;)
+    i32.reinterpret_f32
+  )
+  (func (;31;) (type 0) (result i32)
+    f32.const 0x1.fffffcp-127 (;=0.000000000000000000000000000000000000011754942;)
+    i32.reinterpret_f32
+  )
+  (func (;32;) (type 0) (result i32)
+    f32.const 0x1.fffffep+127 (;=340282350000000000000000000000000000000;)
+    i32.reinterpret_f32
+  )
+  (func (;33;) (type 0) (result i32)
+    f32.const 0x1.2a05f2p+33 (;=10000000000;)
+    i32.reinterpret_f32
+  )
+  (func (;34;) (type 0) (result i32)
+    f32.const 0x1.81c8p+13 (;=12345;)
+    i32.reinterpret_f32
+  )
+  (func (;35;) (type 0) (result i32)
+    f32.const 0x1.5af1d8p+66 (;=100000000000000000000;)
+    i32.reinterpret_f32
+  )
+  (func (;36;) (type 0) (result i32)
+    f32.const -0x1p+31 (;=-2147483600;)
+    i32.reinterpret_f32
+  )
+  (func (;37;) (type 0) (result i32)
+    f32.const -0x1p+63 (;=-9223372000000000000;)
+    i32.reinterpret_f32
+  )
+  (func (;38;) (type 0) (result i32)
     f32.const 0x1.000002p+0 (;=1.0000001;)
     i32.reinterpret_f32
   )
-  (func (;31;) (type 1) (result i64)
+  (func (;39;) (type 1) (result i64)
     f64.const nan (;=NaN;)
     i64.reinterpret_f64
   )
-  (func (;32;) (type 1) (result i64)
+  (func (;40;) (type 1) (result i64)
     f64.const nan (;=NaN;)
     i64.reinterpret_f64
   )
-  (func (;33;) (type 1) (result i64)
+  (func (;41;) (type 1) (result i64)
     f64.const -nan (;=NaN;)
     i64.reinterpret_f64
   )
-  (func (;34;) (type 1) (result i64)
+  (func (;42;) (type 1) (result i64)
     f64.const nan (;=NaN;)
     i64.reinterpret_f64
   )
-  (func (;35;) (type 1) (result i64)
+  (func (;43;) (type 1) (result i64)
     f64.const nan:0x4000000000000 (;=NaN;)
     i64.reinterpret_f64
   )
-  (func (;36;) (type 1) (result i64)
+  (func (;44;) (type 1) (result i64)
     f64.const -nan:0xfffffffffffff (;=NaN;)
     i64.reinterpret_f64
   )
-  (func (;37;) (type 1) (result i64)
+  (func (;45;) (type 1) (result i64)
     f64.const nan:0x123456789abc (;=NaN;)
     i64.reinterpret_f64
   )
-  (func (;38;) (type 1) (result i64)
+  (func (;46;) (type 1) (result i64)
     f64.const nan:0x3040506070809 (;=NaN;)
     i64.reinterpret_f64
   )
-  (func (;39;) (type 1) (result i64)
+  (func (;47;) (type 1) (result i64)
     f64.const -nan:0x2abcdef012345 (;=NaN;)
     i64.reinterpret_f64
   )
-  (func (;40;) (type 1) (result i64)
+  (func (;48;) (type 1) (result i64)
     f64.const inf (;=inf;)
     i64.reinterpret_f64
   )
-  (func (;41;) (type 1) (result i64)
+  (func (;49;) (type 1) (result i64)
     f64.const inf (;=inf;)
     i64.reinterpret_f64
   )
-  (func (;42;) (type 1) (result i64)
+  (func (;50;) (type 1) (result i64)
     f64.const -inf (;=-inf;)
     i64.reinterpret_f64
   )
-  (func (;43;) (type 1) (result i64)
+  (func (;51;) (type 1) (result i64)
     f64.const 0x0p+0 (;=0;)
     i64.reinterpret_f64
   )
-  (func (;44;) (type 1) (result i64)
+  (func (;52;) (type 1) (result i64)
     f64.const 0x0p+0 (;=0;)
     i64.reinterpret_f64
   )
-  (func (;45;) (type 1) (result i64)
+  (func (;53;) (type 1) (result i64)
     f64.const -0x0p+0 (;=-0;)
     i64.reinterpret_f64
   )
-  (func (;46;) (type 1) (result i64)
+  (func (;54;) (type 1) (result i64)
     f64.const 0x1.921fb54442d18p+2 (;=6.283185307179586;)
     i64.reinterpret_f64
   )
-  (func (;47;) (type 1) (result i64)
+  (func (;55;) (type 1) (result i64)
     f64.const 0x1.p-1074 (;=0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005;)
     i64.reinterpret_f64
   )
-  (func (;48;) (type 1) (result i64)
+  (func (;56;) (type 1) (result i64)
     f64.const 0x1p-1022 (;=0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014;)
     i64.reinterpret_f64
   )
-  (func (;49;) (type 1) (result i64)
+  (func (;57;) (type 1) (result i64)
     f64.const 0x1.ffffffffffffep-1023 (;=0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201;)
     i64.reinterpret_f64
   )
-  (func (;50;) (type 1) (result i64)
+  (func (;58;) (type 1) (result i64)
     f64.const 0x1.fffffffffffffp+1023 (;=179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;)
     i64.reinterpret_f64
   )
-  (func (;51;) (type 1) (result i64)
+  (func (;59;) (type 1) (result i64)
     f64.const 0x1p+100 (;=1267650600228229400000000000000;)
     i64.reinterpret_f64
   )
-  (func (;52;) (type 1) (result i64)
+  (func (;60;) (type 1) (result i64)
+    f64.const 0x1.2345p+16 (;=74565;)
+    i64.reinterpret_f64
+  )
+  (func (;61;) (type 1) (result i64)
+    f64.const 0x1p+80 (;=1208925819614629200000000;)
+    i64.reinterpret_f64
+  )
+  (func (;62;) (type 1) (result i64)
+    f64.const -0x1p+31 (;=-2147483648;)
+    i64.reinterpret_f64
+  )
+  (func (;63;) (type 1) (result i64)
+    f64.const -0x1p+63 (;=-9223372036854776000;)
+    i64.reinterpret_f64
+  )
+  (func (;64;) (type 1) (result i64)
     f64.const 0x0p+0 (;=0;)
     i64.reinterpret_f64
   )
-  (func (;53;) (type 1) (result i64)
+  (func (;65;) (type 1) (result i64)
     f64.const 0x0p+0 (;=0;)
     i64.reinterpret_f64
   )
-  (func (;54;) (type 1) (result i64)
+  (func (;66;) (type 1) (result i64)
     f64.const -0x0p+0 (;=-0;)
     i64.reinterpret_f64
   )
-  (func (;55;) (type 1) (result i64)
+  (func (;67;) (type 1) (result i64)
     f64.const 0x1.921fb54442d18p+2 (;=6.283185307179586;)
     i64.reinterpret_f64
   )
-  (func (;56;) (type 1) (result i64)
+  (func (;68;) (type 1) (result i64)
     f64.const 0x1.p-1074 (;=0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005;)
     i64.reinterpret_f64
   )
-  (func (;57;) (type 1) (result i64)
+  (func (;69;) (type 1) (result i64)
     f64.const 0x1p-1022 (;=0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014;)
     i64.reinterpret_f64
   )
-  (func (;58;) (type 1) (result i64)
+  (func (;70;) (type 1) (result i64)
     f64.const 0x1.ffffffffffffep-1023 (;=0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002225073858507201;)
     i64.reinterpret_f64
   )
-  (func (;59;) (type 1) (result i64)
+  (func (;71;) (type 1) (result i64)
     f64.const 0x1.fffffffffffffp+1023 (;=179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;)
     i64.reinterpret_f64
   )
-  (func (;60;) (type 1) (result i64)
+  (func (;72;) (type 1) (result i64)
     f64.const 0x1.249ad2594c37dp+332 (;=10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000;)
     i64.reinterpret_f64
   )
-  (func (;61;) (type 1) (result i64)
+  (func (;73;) (type 1) (result i64)
+    f64.const 0x1.81c8p+13 (;=12345;)
+    i64.reinterpret_f64
+  )
+  (func (;74;) (type 1) (result i64)
+    f64.const 0x1.5af1d78b58c4p+66 (;=100000000000000000000;)
+    i64.reinterpret_f64
+  )
+  (func (;75;) (type 1) (result i64)
+    f64.const -0x1p+31 (;=-2147483648;)
+    i64.reinterpret_f64
+  )
+  (func (;76;) (type 1) (result i64)
+    f64.const -0x1p+63 (;=-9223372036854776000;)
+    i64.reinterpret_f64
+  )
+  (func (;77;) (type 1) (result i64)
     f64.const 0x1.000001ff19e24p+0 (;=1.000000119;)
     i64.reinterpret_f64
   )
-  (func (;62;) (type 2) (result f32)
+  (func (;78;) (type 2) (result f32)
     f32.const 0x1.e848p+19 (;=1000000;)
   )
-  (func (;63;) (type 2) (result f32)
+  (func (;79;) (type 2) (result f32)
     f32.const 0x1.f4p+9 (;=1000;)
   )
-  (func (;64;) (type 2) (result f32)
+  (func (;80;) (type 2) (result f32)
     f32.const 0x1.f5922p+9 (;=1003.1416;)
   )
-  (func (;65;) (type 2) (result f32)
+  (func (;81;) (type 2) (result f32)
     f32.const 0x1.c2332cp+49 (;=990000000000000;)
   )
-  (func (;66;) (type 2) (result f32)
+  (func (;82;) (type 2) (result f32)
     f32.const 0x1.3b5ce8p+93 (;=12200012000000000000000000000;)
   )
-  (func (;67;) (type 2) (result f32)
+  (func (;83;) (type 2) (result f32)
     f32.const 0x1.41e014p+27 (;=168755360;)
   )
-  (func (;68;) (type 2) (result f32)
+  (func (;84;) (type 2) (result f32)
     f32.const 0x1.aa0fp+16 (;=109071;)
   )
-  (func (;69;) (type 2) (result f32)
+  (func (;85;) (type 2) (result f32)
     f32.const 0x1.41ffe2p+15 (;=41215.94;)
   )
-  (func (;70;) (type 2) (result f32)
+  (func (;86;) (type 2) (result f32)
     f32.const 0x1.ep+20 (;=1966080;)
   )
-  (func (;71;) (type 2) (result f32)
+  (func (;87;) (type 2) (result f32)
     f32.const 0x1.57805p+44 (;=23605224000000;)
   )
-  (func (;72;) (type 3) (result f64)
+  (func (;88;) (type 3) (result f64)
     f64.const 0x1.e848p+19 (;=1000000;)
   )
-  (func (;73;) (type 3) (result f64)
+  (func (;89;) (type 3) (result f64)
     f64.const 0x1.f4p+9 (;=1000;)
   )
-  (func (;74;) (type 3) (result f64)
+  (func (;90;) (type 3) (result f64)
     f64.const 0x1.f5921fafc8bp+9 (;=1003.141592;)
   )
-  (func (;75;) (type 3) (result f64)
+  (func (;91;) (type 3) (result f64)
     f64.const 0x1.05c735bb7cc45p-402 (;=0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000099;)
   )
-  (func (;76;) (type 3) (result f64)
+  (func (;92;) (type 3) (result f64)
     f64.const 0x1.3b5ce725bde9cp+93 (;=12200011354000000000000000000;)
   )
-  (func (;77;) (type 3) (result f64)
+  (func (;93;) (type 3) (result f64)
     f64.const 0x1.5e01e00013332p+51 (;=3078696982321561;)
   )
-  (func (;78;) (type 3) (result f64)
+  (func (;94;) (type 3) (result f64)
     f64.const 0x1.aa0fp+16 (;=109071;)
   )
-  (func (;79;) (type 3) (result f64)
+  (func (;95;) (type 3) (result f64)
     f64.const 0x1.41ffe2834b34p+15 (;=41215.94240794191;)
   )
-  (func (;80;) (type 3) (result f64)
+  (func (;96;) (type 3) (result f64)
     f64.const 0x1.ep+20 (;=1966080;)
   )
-  (func (;81;) (type 3) (result f64)
+  (func (;97;) (type 3) (result f64)
     f64.const 0x1.578050f9f7p+44 (;=23605225168752;)
   )
   (export "f32.nan" (func 0))
@@ -332,65 +396,81 @@
   (export "f32.max_finite" (func 18))
   (export "f32.max_subnormal" (func 19))
   (export "f32.trailing_dot" (func 20))
-  (export "f32_dec.zero" (func 21))
-  (export "f32_dec.positive_zero" (func 22))
-  (export "f32_dec.negative_zero" (func 23))
-  (export "f32_dec.misc" (func 24))
-  (export "f32_dec.min_positive" (func 25))
-  (export "f32_dec.min_normal" (func 26))
-  (export "f32_dec.max_subnormal" (func 27))
-  (export "f32_dec.max_finite" (func 28))
-  (export "f32_dec.trailing_dot" (func 29))
-  (export "f32_dec.root_beer_float" (func 30))
-  (export "f64.nan" (func 31))
-  (export "f64.positive_nan" (func 32))
-  (export "f64.negative_nan" (func 33))
-  (export "f64.plain_nan" (func 34))
-  (export "f64.informally_known_as_plain_snan" (func 35))
-  (export "f64.all_ones_nan" (func 36))
-  (export "f64.misc_nan" (func 37))
-  (export "f64.misc_positive_nan" (func 38))
-  (export "f64.misc_negative_nan" (func 39))
-  (export "f64.infinity" (func 40))
-  (export "f64.positive_infinity" (func 41))
-  (export "f64.negative_infinity" (func 42))
-  (export "f64.zero" (func 43))
-  (export "f64.positive_zero" (func 44))
-  (export "f64.negative_zero" (func 45))
-  (export "f64.misc" (func 46))
-  (export "f64.min_positive" (func 47))
-  (export "f64.min_normal" (func 48))
-  (export "f64.max_subnormal" (func 49))
-  (export "f64.max_finite" (func 50))
-  (export "f64.trailing_dot" (func 51))
-  (export "f64_dec.zero" (func 52))
-  (export "f64_dec.positive_zero" (func 53))
-  (export "f64_dec.negative_zero" (func 54))
-  (export "f64_dec.misc" (func 55))
-  (export "f64_dec.min_positive" (func 56))
-  (export "f64_dec.min_normal" (func 57))
-  (export "f64_dec.max_subnormal" (func 58))
-  (export "f64_dec.max_finite" (func 59))
-  (export "f64_dec.trailing_dot" (func 60))
-  (export "f64_dec.root_beer_float" (func 61))
-  (export "f32-dec-sep1" (func 62))
-  (export "f32-dec-sep2" (func 63))
-  (export "f32-dec-sep3" (func 64))
-  (export "f32-dec-sep4" (func 65))
-  (export "f32-dec-sep5" (func 66))
-  (export "f32-hex-sep1" (func 67))
-  (export "f32-hex-sep2" (func 68))
-  (export "f32-hex-sep3" (func 69))
-  (export "f32-hex-sep4" (func 70))
-  (export "f32-hex-sep5" (func 71))
-  (export "f64-dec-sep1" (func 72))
-  (export "f64-dec-sep2" (func 73))
-  (export "f64-dec-sep3" (func 74))
-  (export "f64-dec-sep4" (func 75))
-  (export "f64-dec-sep5" (func 76))
-  (export "f64-hex-sep1" (func 77))
-  (export "f64-hex-sep2" (func 78))
-  (export "f64-hex-sep3" (func 79))
-  (export "f64-hex-sep4" (func 80))
-  (export "f64-hex-sep5" (func 81))
+  (export "f32.misc_int" (func 21))
+  (export "f32.large_int" (func 22))
+  (export "f32.min_int32" (func 23))
+  (export "f32.min_int64" (func 24))
+  (export "f32_dec.zero" (func 25))
+  (export "f32_dec.positive_zero" (func 26))
+  (export "f32_dec.negative_zero" (func 27))
+  (export "f32_dec.misc" (func 28))
+  (export "f32_dec.min_positive" (func 29))
+  (export "f32_dec.min_normal" (func 30))
+  (export "f32_dec.max_subnormal" (func 31))
+  (export "f32_dec.max_finite" (func 32))
+  (export "f32_dec.trailing_dot" (func 33))
+  (export "f32_dec.misc_int" (func 34))
+  (export "f32_dec.large_int" (func 35))
+  (export "f32_dec.min_int32" (func 36))
+  (export "f32_dec.min_int64" (func 37))
+  (export "f32_dec.root_beer_float" (func 38))
+  (export "f64.nan" (func 39))
+  (export "f64.positive_nan" (func 40))
+  (export "f64.negative_nan" (func 41))
+  (export "f64.plain_nan" (func 42))
+  (export "f64.informally_known_as_plain_snan" (func 43))
+  (export "f64.all_ones_nan" (func 44))
+  (export "f64.misc_nan" (func 45))
+  (export "f64.misc_positive_nan" (func 46))
+  (export "f64.misc_negative_nan" (func 47))
+  (export "f64.infinity" (func 48))
+  (export "f64.positive_infinity" (func 49))
+  (export "f64.negative_infinity" (func 50))
+  (export "f64.zero" (func 51))
+  (export "f64.positive_zero" (func 52))
+  (export "f64.negative_zero" (func 53))
+  (export "f64.misc" (func 54))
+  (export "f64.min_positive" (func 55))
+  (export "f64.min_normal" (func 56))
+  (export "f64.max_subnormal" (func 57))
+  (export "f64.max_finite" (func 58))
+  (export "f64.trailing_dot" (func 59))
+  (export "f64.misc_int" (func 60))
+  (export "f64.large_int" (func 61))
+  (export "f64.min_int32" (func 62))
+  (export "f64.min_int64" (func 63))
+  (export "f64_dec.zero" (func 64))
+  (export "f64_dec.positive_zero" (func 65))
+  (export "f64_dec.negative_zero" (func 66))
+  (export "f64_dec.misc" (func 67))
+  (export "f64_dec.min_positive" (func 68))
+  (export "f64_dec.min_normal" (func 69))
+  (export "f64_dec.max_subnormal" (func 70))
+  (export "f64_dec.max_finite" (func 71))
+  (export "f64_dec.trailing_dot" (func 72))
+  (export "f64_dec.misc_int" (func 73))
+  (export "f64_dec.large_int" (func 74))
+  (export "f64_dec.min_int32" (func 75))
+  (export "f64_dec.min_int64" (func 76))
+  (export "f64_dec.root_beer_float" (func 77))
+  (export "f32-dec-sep1" (func 78))
+  (export "f32-dec-sep2" (func 79))
+  (export "f32-dec-sep3" (func 80))
+  (export "f32-dec-sep4" (func 81))
+  (export "f32-dec-sep5" (func 82))
+  (export "f32-hex-sep1" (func 83))
+  (export "f32-hex-sep2" (func 84))
+  (export "f32-hex-sep3" (func 85))
+  (export "f32-hex-sep4" (func 86))
+  (export "f32-hex-sep5" (func 87))
+  (export "f64-dec-sep1" (func 88))
+  (export "f64-dec-sep2" (func 89))
+  (export "f64-dec-sep3" (func 90))
+  (export "f64-dec-sep4" (func 91))
+  (export "f64-dec-sep5" (func 92))
+  (export "f64-hex-sep1" (func 93))
+  (export "f64-hex-sep2" (func 94))
+  (export "f64-hex-sep3" (func 95))
+  (export "f64-hex-sep4" (func 96))
+  (export "f64-hex-sep5" (func 97))
 )
diff --git a/tests/snapshots/testsuite/float_literals.wast/83.print b/tests/snapshots/testsuite/float_literals.wast/99.print
similarity index 100%
rename from tests/snapshots/testsuite/float_literals.wast/83.print
rename to tests/snapshots/testsuite/float_literals.wast/99.print
diff --git a/tests/testsuite b/tests/testsuite
index 139fc876..16a839d5 160000
--- a/tests/testsuite
+++ b/tests/testsuite
@@ -1,1 +1,1 @@
-Subproject commit 139fc876068a2d37f0c4bdfaf05173c3c3702faf
+Subproject commit 16a839d5601c283541a84572b47637f035b51437