Repository: bytecodealliance/wasmtime
Pull request #8935 (2024-07-10 21:31:13)

ISLE: Make block codegen non-recursive
In #8919 we learned that `Codegen::emit_block` can overrun the stack when generating Rust source for deeply-nested blocks. Stack usage can be worse than one might expect because ISLE is called from a build script, and those are always built in debug mode.

This commit avoids the problem by using an explicit heap-allocated stack instead of recursion.

I recommend turning on the "ignore whitespace" option in your diff tool of choice when reviewing this commit.

The amount of stack space that this recursive function uses is affected by the version of rustc used to compile it, and the amount of stack space available depends on the host platform. As a result, this was only observed on Windows with a recent nightly version of rustc, but it could eventually affect other platforms or compiler versions, especially as ISLE rules grow in complexity.

Note that there are several other places in ISLE which recurse over the user-provided rules. This commit does not change those, but they could also become a problem in the future.

github-actions[bot]: #### Subscribe to Label Action

cc @cfallin, @fitzgen

<details>
This issue or pull request has been labeled: "cranelift", "isle"

Thus the following users have been cc'd because of the following labels:

* cfallin: isle
* fitzgen: isle

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>

Patch:
diff --git a/cranelift/isle/isle/src/codegen.rs b/cranelift/isle/isle/src/codegen.rs
index 5230d96e0..7deb391b1 100644
--- a/cranelift/isle/isle/src/codegen.rs
+++ b/cranelift/isle/isle/src/codegen.rs
@@ -5,6 +5,7 @@ use crate::serialize::{Block, ControlFlow, EvalStep, MatchArm};
 use crate::stablemapset::StableSet;
 use crate::trie_again::{Binding, BindingId, Constraint, RuleSet};
 use std::fmt::Write;
+use std::slice::Iter;
 
 /// Options for code generation.
 #[derive(Clone, Debug, Default)]
@@ -31,6 +32,11 @@ struct Codegen<'a> {
     terms: &'a [(TermId, RuleSet)],
 }
 
+enum Nested<'a> {
+    Cases(Iter<'a, EvalStep>),
+    Arms(BindingId, Iter<'a, MatchArm>),
+}
+
 struct BodyContext<'a, W> {
     out: &'a mut W,
     ruleset: &'a RuleSet,
@@ -60,7 +66,10 @@ impl<'a, W: Write> BodyContext<'a, W> {
         writeln!(self.out, " {{")
     }
 
-    fn end_block(&mut self, scope: StableSet<BindingId>) -> std::fmt::Result {
+    fn end_block(&mut self, last_line: &str, scope: StableSet<BindingId>) -> std::fmt::Result {
+        if !last_line.is_empty() {
+            writeln!(self.out, "{}{}", &self.indent, last_line)?;
+        }
         self.is_bound = scope;
         self.end_block_without_newline()?;
         writeln!(self.out)
@@ -433,37 +442,29 @@ impl<L: Length, C> Length for ContextIterWrapper<L, C> {{
                 ReturnKind::Plain => write!(ctx.out, "{}", ret)?,
             };
 
-            let scope = ctx.enter_scope();
-            ctx.begin_block()?;
-
-            self.emit_block(&mut ctx, &root, sig.ret_kind)?;
-
-            match (sig.ret_kind, root.steps.last()) {
-                    (ReturnKind::Iterator, _) => {
-                        writeln!(
-                            ctx.out,
-                            "{}return;",
-                            &ctx.indent
-                        )?;
-                    }
-                    (_, Some(EvalStep { check: ControlFlow::Return { .. }, .. })) => {
-                        // If there's an outermost fallback, no need for another `return` statement.
-                    }
-                    (ReturnKind::Option, _) => {
-                        writeln!(ctx.out, "{}None", &ctx.indent)?
-                    }
-                    (ReturnKind::Plain, _) => {
-                        writeln!(ctx.out,
-                                "unreachable!(\"no rule matched for term {{}} at {{}}; should it be partial?\", {:?}, {:?})",
-                                term_name,
-                                termdata
-                                    .decl_pos
-                                    .pretty_print_line(&self.typeenv.filenames[..])
-                        )?
-                    }
+            let last_expr = if let Some(EvalStep {
+                check: ControlFlow::Return { .. },
+                ..
+            }) = root.steps.last()
+            {
+                // If there's an outermost fallback, no need for another `return` statement.
+                String::new()
+            } else {
+                match sig.ret_kind {
+                    ReturnKind::Iterator => String::new(),
+                    ReturnKind::Option => "None".to_string(),
+                    ReturnKind::Plain => format!(
+                        "unreachable!(\"no rule matched for term {{}} at {{}}; should it be partial?\", {:?}, {:?})",
+                        term_name,
+                        termdata
+                            .decl_pos
+                            .pretty_print_line(&self.typeenv.filenames[..])
+                    ),
                 }
+            };
 
-            ctx.end_block(scope)?;
+            let scope = ctx.enter_scope();
+            self.emit_block(&mut ctx, &root, sig.ret_kind, &last_expr, scope)?;
         }
         Ok(())
     }
@@ -475,12 +476,7 @@ impl<L: Length, C> Length for ContextIterWrapper<L, C> {{
         }
     }
 
-    fn emit_block<W: Write>(
-        &self,
-        ctx: &mut BodyContext<W>,
-        block: &Block,
-        ret_kind: ReturnKind,
-    ) -> std::fmt::Result {
+    fn validate_block(ret_kind: ReturnKind, block: &Block) -> Nested {
         if !matches!(ret_kind, ReturnKind::Iterator) {
             // Loops are only allowed if we're returning an iterator.
             assert!(!block
@@ -499,167 +495,202 @@ impl<L: Length, C> Length for ContextIterWrapper<L, C> {{
             }
         }
 
-        for case in block.steps.iter() {
-            for &expr in case.bind_order.iter() {
-                let iter_return = match &ctx.ruleset.bindings[expr.index()] {
-                    Binding::Extractor { term, .. } => {
-                        let termdata = &self.termenv.terms[term.index()];
-                        let sig = termdata.extractor_sig(self.typeenv).unwrap();
-                        if sig.ret_kind == ReturnKind::Iterator {
-                            if termdata.has_external_extractor() {
-                                Some(format!("C::{}_returns", sig.func_name))
-                            } else {
-                                Some(format!("ContextIterWrapper::<ConstructorVec<_>, _>"))
+        Nested::Cases(block.steps.iter())
+    }
+
+    fn emit_block<W: Write>(
+        &self,
+        ctx: &mut BodyContext<W>,
+        block: &Block,
+        ret_kind: ReturnKind,
+        last_expr: &str,
+        scope: StableSet<BindingId>,
+    ) -> std::fmt::Result {
+        let mut stack = Vec::new();
+        ctx.begin_block()?;
+        stack.push((Self::validate_block(ret_kind, block), last_expr, scope));
+
+        while let Some((mut nested, last_line, scope)) = stack.pop() {
+            match &mut nested {
+                Nested::Cases(cases) => {
+                    let Some(case) = cases.next() else {
+                        ctx.end_block(last_line, scope)?;
+                        continue;
+                    };
+                    // Iterator isn't done, put it back on the stack.
+                    stack.push((nested, last_line, scope));
+
+                    for &expr in case.bind_order.iter() {
+                        let iter_return = match &ctx.ruleset.bindings[expr.index()] {
+                            Binding::Extractor { term, .. } => {
+                                let termdata = &self.termenv.terms[term.index()];
+                                let sig = termdata.extractor_sig(self.typeenv).unwrap();
+                                if sig.ret_kind == ReturnKind::Iterator {
+                                    if termdata.has_external_extractor() {
+                                        Some(format!("C::{}_returns", sig.func_name))
+                                    } else {
+                                        Some(format!("ContextIterWrapper::<ConstructorVec<_>, _>"))
+                                    }
+                                } else {
+                                    None
+                                }
+                            }
+                            Binding::Constructor { term, .. } => {
+                                let termdata = &self.termenv.terms[term.index()];
+                                let sig = termdata.constructor_sig(self.typeenv).unwrap();
+                                if sig.ret_kind == ReturnKind::Iterator {
+                                    if termdata.has_external_constructor() {
+                                        Some(format!("C::{}_returns", sig.func_name))
+                                    } else {
+                                        Some(format!("ContextIterWrapper::<ConstructorVec<_>, _>"))
+                                    }
+                                } else {
+                                    None
+                                }
                             }
+                            _ => None,
+                        };
+                        if let Some(ty) = iter_return {
+                            writeln!(
+                                ctx.out,
+                                "{}let mut v{} = {}::default();",
+                                &ctx.indent,
+                                expr.index(),
+                                ty
+                            )?;
+                            write!(ctx.out, "{}", &ctx.indent)?;
                         } else {
-                            None
+                            write!(ctx.out, "{}let v{} = ", &ctx.indent, expr.index())?;
                         }
+                        self.emit_expr(ctx, expr)?;
+                        writeln!(ctx.out, ";")?;
+                        ctx.is_bound.insert(expr);
                     }
-                    Binding::Constructor { term, .. } => {
-                        let termdata = &self.termenv.terms[term.index()];
-                        let sig = termdata.constructor_sig(self.typeenv).unwrap();
-                        if sig.ret_kind == ReturnKind::Iterator {
-                            if termdata.has_external_constructor() {
-                                Some(format!("C::{}_returns", sig.func_name))
-                            } else {
-                                Some(format!("ContextIterWrapper::<ConstructorVec<_>, _>"))
+
+                    match &case.check {
+                        // Use a shorthand notation if there's only one match arm.
+                        ControlFlow::Match { source, arms } if arms.len() == 1 => {
+                            let arm = &arms[0];
+                            let scope = ctx.enter_scope();
+                            match arm.constraint {
+                                Constraint::ConstInt { .. } | Constraint::ConstPrim { .. } => {
+                                    write!(ctx.out, "{}if ", &ctx.indent)?;
+                                    self.emit_expr(ctx, *source)?;
+                                    write!(ctx.out, " == ")?;
+                                    self.emit_constraint(ctx, *source, arm)?;
+                                }
+                                Constraint::Variant { .. } | Constraint::Some => {
+                                    write!(ctx.out, "{}if let ", &ctx.indent)?;
+                                    self.emit_constraint(ctx, *source, arm)?;
+                                    write!(ctx.out, " = ")?;
+                                    self.emit_source(ctx, *source, arm.constraint)?;
+                                }
                             }
-                        } else {
-                            None
+                            ctx.begin_block()?;
+                            stack.push((Self::validate_block(ret_kind, &arm.body), "", scope));
                         }
-                    }
-                    _ => None,
-                };
-                if let Some(ty) = iter_return {
-                    writeln!(
-                        ctx.out,
-                        "{}let mut v{} = {}::default();",
-                        &ctx.indent,
-                        expr.index(),
-                        ty
-                    )?;
-                    write!(ctx.out, "{}", &ctx.indent)?;
-                } else {
-                    write!(ctx.out, "{}let v{} = ", &ctx.indent, expr.index())?;
-                }
-                self.emit_expr(ctx, expr)?;
-                writeln!(ctx.out, ";")?;
-                ctx.is_bound.insert(expr);
-            }
 
-            match &case.check {
-                // Use a shorthand notation if there's only one match arm.
-                ControlFlow::Match { source, arms } if arms.len() == 1 => {
-                    let arm = &arms[0];
-                    let scope = ctx.enter_scope();
-                    match arm.constraint {
-                        Constraint::ConstInt { .. } | Constraint::ConstPrim { .. } => {
+                        ControlFlow::Match { source, arms } => {
+                            let scope = ctx.enter_scope();
+                            write!(ctx.out, "{}match ", &ctx.indent)?;
+                            self.emit_source(ctx, *source, arms[0].constraint)?;
+                            ctx.begin_block()?;
+
+                            // Always add a catchall arm, because we
+                            // don't do exhaustiveness checking on the
+                            // match arms.
+                            stack.push((Nested::Arms(*source, arms.iter()), "_ => {}", scope));
+                        }
+
+                        ControlFlow::Equal { a, b, body } => {
+                            let scope = ctx.enter_scope();
                             write!(ctx.out, "{}if ", &ctx.indent)?;
-                            self.emit_expr(ctx, *source)?;
+                            self.emit_expr(ctx, *a)?;
                             write!(ctx.out, " == ")?;
-                            self.emit_constraint(ctx, *source, arm)?;
+                            self.emit_expr(ctx, *b)?;
+                            ctx.begin_block()?;
+                            stack.push((Self::validate_block(ret_kind, body), "", scope));
                         }
-                        Constraint::Variant { .. } | Constraint::Some => {
-                            write!(ctx.out, "{}if let ", &ctx.indent)?;
-                            self.emit_constraint(ctx, *source, arm)?;
-                            write!(ctx.out, " = ")?;
-                            self.emit_source(ctx, *source, arm.constraint)?;
-                        }
-                    }
-                    ctx.begin_block()?;
-                    self.emit_block(ctx, &arm.body, ret_kind)?;
-                    ctx.end_block(scope)?;
-                }
 
-                ControlFlow::Match { source, arms } => {
-                    let scope = ctx.enter_scope();
-                    write!(ctx.out, "{}match ", &ctx.indent)?;
-                    self.emit_source(ctx, *source, arms[0].constraint)?;
-                    ctx.begin_block()?;
-                    for arm in arms.iter() {
-                        let scope = ctx.enter_scope();
-                        write!(ctx.out, "{}", &ctx.indent)?;
-                        self.emit_constraint(ctx, *source, arm)?;
-                        write!(ctx.out, " =>")?;
-                        ctx.begin_block()?;
-                        self.emit_block(ctx, &arm.body, ret_kind)?;
-                        ctx.end_block(scope)?;
-                    }
-                    // Always add a catchall, because we don't do exhaustiveness checking on the
-                    // match arms.
-                    writeln!(ctx.out, "{}_ => {{}}", &ctx.indent)?;
-                    ctx.end_block(scope)?;
-                }
+                        ControlFlow::Loop { result, body } => {
+                            let source = match &ctx.ruleset.bindings[result.index()] {
+                                Binding::Iterator { source } => source,
+                                _ => unreachable!("Loop from a non-Iterator"),
+                            };
+                            let scope = ctx.enter_scope();
 
-                ControlFlow::Equal { a, b, body } => {
-                    let scope = ctx.enter_scope();
-                    write!(ctx.out, "{}if ", &ctx.indent)?;
-                    self.emit_expr(ctx, *a)?;
-                    write!(ctx.out, " == ")?;
-                    self.emit_expr(ctx, *b)?;
-                    ctx.begin_block()?;
-                    self.emit_block(ctx, body, ret_kind)?;
-                    ctx.end_block(scope)?;
-                }
-
-                ControlFlow::Loop { result, body } => {
-                    let source = match &ctx.ruleset.bindings[result.index()] {
-                        Binding::Iterator { source } => source,
-                        _ => unreachable!("Loop from a non-Iterator"),
-                    };
-                    let scope = ctx.enter_scope();
+                            writeln!(
+                                ctx.out,
+                                "{}let mut v{} = v{}.into_context_iter();",
+                                &ctx.indent,
+                                source.index(),
+                                source.index(),
+                            )?;
 
-                    writeln!(
-                        ctx.out,
-                        "{}let mut v{} = v{}.into_context_iter();",
-                        &ctx.indent,
-                        source.index(),
-                        source.index(),
-                    )?;
-
-                    write!(
-                        ctx.out,
-                        "{}while let Some(v{}) = v{}.next(ctx)",
-                        &ctx.indent,
-                        result.index(),
-                        source.index()
-                    )?;
-                    ctx.is_bound.insert(*result);
-                    ctx.begin_block()?;
-                    self.emit_block(ctx, body, ret_kind)?;
-                    ctx.end_block(scope)?;
-                }
+                            write!(
+                                ctx.out,
+                                "{}while let Some(v{}) = v{}.next(ctx)",
+                                &ctx.indent,
+                                result.index(),
+                                source.index()
+                            )?;
+                            ctx.is_bound.insert(*result);
+                            ctx.begin_block()?;
+                            stack.push((Self::validate_block(ret_kind, body), "", scope));
+                        }
 
-                &ControlFlow::Return { pos, result } => {
-                    writeln!(
-                        ctx.out,
-                        "{}// Rule at {}.",
-                        &ctx.indent,
-                        pos.pretty_print_line(&self.typeenv.filenames)
-                    )?;
-                    write!(ctx.out, "{}", &ctx.indent)?;
-                    match ret_kind {
-                        ReturnKind::Plain | ReturnKind::Option => write!(ctx.out, "return ")?,
-                        ReturnKind::Iterator => write!(ctx.out, "returns.extend(Some(")?,
-                    }
-                    self.emit_expr(ctx, result)?;
-                    if ctx.is_ref.contains(&result) {
-                        write!(ctx.out, ".clone()")?;
-                    }
-                    match ret_kind {
-                        ReturnKind::Plain | ReturnKind::Option => writeln!(ctx.out, ";")?,
-                        ReturnKind::Iterator => {
-                            writeln!(ctx.out, "));")?;
+                        &ControlFlow::Return { pos, result } => {
                             writeln!(
                                 ctx.out,
-                                "{}if returns.len() >= MAX_ISLE_RETURNS {{ return; }}",
-                                ctx.indent
+                                "{}// Rule at {}.",
+                                &ctx.indent,
+                                pos.pretty_print_line(&self.typeenv.filenames)
                             )?;
+                            write!(ctx.out, "{}", &ctx.indent)?;
+                            match ret_kind {
+                                ReturnKind::Plain | ReturnKind::Option => {
+                                    write!(ctx.out, "return ")?
+                                }
+                                ReturnKind::Iterator => write!(ctx.out, "returns.extend(Some(")?,
+                            }
+                            self.emit_expr(ctx, result)?;
+                            if ctx.is_ref.contains(&result) {
+                                write!(ctx.out, ".clone()")?;
+                            }
+                            match ret_kind {
+                                ReturnKind::Plain | ReturnKind::Option => writeln!(ctx.out, ";")?,
+                                ReturnKind::Iterator => {
+                                    writeln!(ctx.out, "));")?;
+                                    writeln!(
+                                        ctx.out,
+                                        "{}if returns.len() >= MAX_ISLE_RETURNS {{ return; }}",
+                                        ctx.indent
+                                    )?;
+                                }
+                            }
                         }
                     }
                 }
+
+                Nested::Arms(source, arms) => {
+                    let Some(arm) = arms.next() else {
+                        ctx.end_block(last_line, scope)?;
+                        continue;
+                    };
+                    let source = *source;
+                    // Iterator isn't done, put it back on the stack.
+                    stack.push((nested, last_line, scope));
+
+                    let scope = ctx.enter_scope();
+                    write!(ctx.out, "{}", &ctx.indent)?;
+                    self.emit_constraint(ctx, source, arm)?;
+                    write!(ctx.out, " =>")?;
+                    ctx.begin_block()?;
+                    stack.push((Self::validate_block(ret_kind, &arm.body), "", scope));
+                }
             }
         }
+
         Ok(())
     }
 
diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs
index 944cca644..68be7fa7d 100644
--- a/crates/wasmtime/src/runtime/store.rs
+++ b/crates/wasmtime/src/runtime/store.rs
@@ -915,7 +915,7 @@ impl<T> Store<T> {
     /// yield and then continue.
     ///
     /// This deadline is always set relative to the current epoch:
-    /// `delta_beyond_current` ticks in the future. The deadline can
+    /// `ticks_beyond_current` ticks in the future. The deadline can
     /// be set explicitly via this method, or refilled automatically
     /// on a yield if configured via
     /// [`epoch_deadline_async_yield_and_update()`](Store::epoch_deadline_async_yield_and_update). After


Test patch:
diff --git a/tests/all/main.rs b/tests/all/main.rs
index b9f2c4b66..d4532d63c 100644
--- a/tests/all/main.rs
+++ b/tests/all/main.rs
@@ -42,9 +42,6 @@ mod traps;
 mod types;
 mod wait_notify;
 mod wasi_testsuite;
-// Currently Winch is only supported in x86_64.
-#[cfg(all(target_arch = "x86_64"))]
-mod winch;
 
 /// A helper to compile a module in a new store with reference types enabled.
 pub(crate) fn ref_types_module(
diff --git a/tests/all/memory.rs b/tests/all/memory.rs
index 7120d9556..538b43828 100644
--- a/tests/all/memory.rs
+++ b/tests/all/memory.rs
@@ -2,6 +2,7 @@ use rayon::prelude::*;
 use std::sync::atomic::{AtomicU32, Ordering::SeqCst};
 use std::time::Duration;
 use wasmtime::*;
+use wasmtime_test_macros::wasmtime_test;
 
 fn module(engine: &Engine) -> Result<Module> {
     let mut wat = format!("(module\n");
@@ -90,9 +91,9 @@ fn test_traps(store: &mut Store<()>, funcs: &[TestFunc], addr: u32, mem: &Memory
     }
 }
 
-#[test]
+#[wasmtime_test(wasm_features(simd))]
 #[cfg_attr(miri, ignore)]
-fn offsets_static_dynamic_oh_my() -> Result<()> {
+fn offsets_static_dynamic_oh_my(config: &mut Config) -> Result<()> {
     const GB: u64 = 1 << 30;
 
     let mut engines = Vec::new();
@@ -100,8 +101,6 @@ fn offsets_static_dynamic_oh_my() -> Result<()> {
     for &static_memory_maximum_size in sizes.iter() {
         for &guard_size in sizes.iter() {
             for &guard_before_linear_memory in [true, false].iter() {
-                let mut config = Config::new();
-                config.wasm_simd(true);
                 config.static_memory_maximum_size(static_memory_maximum_size);
                 config.dynamic_memory_guard_size(guard_size);
                 config.static_memory_guard_size(guard_size);
@@ -185,16 +184,15 @@ fn guards_present() -> Result<()> {
     Ok(())
 }
 
-#[test]
+#[wasmtime_test]
 #[cfg_attr(miri, ignore)]
-fn guards_present_pooling() -> Result<()> {
+fn guards_present_pooling(config: &mut Config) -> Result<()> {
     const GUARD_SIZE: u64 = 65536;
 
     let mut pool = crate::small_pool_config();
     pool.total_memories(2)
         .max_memory_size(10 << 16)
         .memory_protection_keys(MpkEnabled::Disable);
-    let mut config = Config::new();
     config.static_memory_maximum_size(1 << 20);
     config.dynamic_memory_guard_size(GUARD_SIZE);
     config.static_memory_guard_size(GUARD_SIZE);
@@ -243,9 +241,9 @@ fn guards_present_pooling() -> Result<()> {
     Ok(())
 }
 
-#[test]
+#[wasmtime_test]
 #[cfg_attr(miri, ignore)]
-fn guards_present_pooling_mpk() -> Result<()> {
+fn guards_present_pooling_mpk(config: &mut Config) -> Result<()> {
     if !wasmtime::PoolingAllocationConfig::are_memory_protection_keys_available() {
         println!("skipping `guards_present_pooling_mpk` test; mpk is not supported");
         return Ok(());
@@ -257,7 +255,6 @@ fn guards_present_pooling_mpk() -> Result<()> {
         .max_memory_size(10 << 16)
         .memory_protection_keys(MpkEnabled::Enable)
         .max_memory_protection_keys(2);
-    let mut config = Config::new();
     config.static_memory_maximum_size(1 << 20);
     config.dynamic_memory_guard_size(GUARD_SIZE);
     config.static_memory_guard_size(GUARD_SIZE);
@@ -388,14 +385,13 @@ fn massive_64_bit_still_limited() -> Result<()> {
     }
 }
 
-#[test]
+#[wasmtime_test]
 #[cfg_attr(miri, ignore)]
-fn tiny_static_heap() -> Result<()> {
+fn tiny_static_heap(config: &mut Config) -> Result<()> {
     // The size of the memory in the module below is the exact same size as
     // the static memory size limit in the configuration. This is intended to
     // specifically test that a load of all the valid addresses of the memory
     // all pass bounds-checks in cranelift to help weed out any off-by-one bugs.
-    let mut config = Config::new();
     config.static_memory_maximum_size(1 << 16);
     let engine = Engine::new(&config)?;
     let mut store = Store::new(&engine, ());
@@ -440,10 +436,9 @@ fn static_forced_max() -> Result<()> {
     Ok(())
 }
 
-#[test]
-fn dynamic_extra_growth_unchanged_pointer() -> Result<()> {
+#[wasmtime_test]
+fn dynamic_extra_growth_unchanged_pointer(config: &mut Config) -> Result<()> {
     const EXTRA_PAGES: u64 = 5;
-    let mut config = Config::new();
     config.static_memory_maximum_size(0);
     // 5 wasm pages extra
     config.dynamic_memory_reserved_for_growth(EXTRA_PAGES * (1 << 16));
@@ -499,9 +494,8 @@ fn dynamic_extra_growth_unchanged_pointer() -> Result<()> {
 // size of `1 << 48` pages. This should always fail but in the process of
 // determining this failure we shouldn't hit any overflows or anything like that
 // (checked via debug-mode tests).
-#[test]
-fn memory64_maximum_minimum() -> Result<()> {
-    let mut config = Config::new();
+#[wasmtime_test]
+fn memory64_maximum_minimum(config: &mut Config) -> Result<()> {
     config.wasm_memory64(true);
     let engine = Engine::new(&config)?;
     let mut store = Store::new(&engine, ());
@@ -663,9 +657,9 @@ fn shared_memory_wait_notify() -> Result<()> {
     Ok(())
 }
 
-#[test]
+#[wasmtime_test]
 #[cfg_attr(miri, ignore)]
-fn init_with_negative_segment() -> Result<()> {
+fn init_with_negative_segment(_: &mut Config) -> Result<()> {
     let engine = Engine::default();
     let module = Module::new(
         &engine,
@@ -718,10 +712,9 @@ fn new_memory_with_custom_page_size() -> Result<()> {
     Ok(())
 }
 
-#[test]
+#[wasmtime_test]
 #[cfg_attr(miri, ignore)]
-fn get_memory_type_with_custom_page_size_from_wasm() -> Result<()> {
-    let mut config = Config::new();
+fn get_memory_type_with_custom_page_size_from_wasm(config: &mut Config) -> Result<()> {
     config.wasm_custom_page_sizes(true);
     let engine = Engine::new(&config)?;
     let mut store = Store::new(&engine, ());
diff --git a/tests/all/winch.rs b/tests/all/winch.rs
deleted file mode 100644
index c839b2820..000000000
--- a/tests/all/winch.rs
+++ /dev/null
@@ -1,156 +0,0 @@
-use wasmtime::*;
-
-#[test]
-#[cfg_attr(miri, ignore)]
-fn dynamic_heap() -> Result<()> {
-    let mut c = Config::new();
-
-    c.strategy(Strategy::Winch);
-    c.static_memory_maximum_size(0);
-    c.static_memory_guard_size(0);
-    c.guard_before_linear_memory(false);
-    c.dynamic_memory_guard_size(0);
-
-    let engine = Engine::new(&c)?;
-    let wat = r#"
-        (module
-          (type (;0;) (func (result i32)))
-          (func (;0;) (type 0) (result i32)
-            (local i32 i64)
-            global.get 0
-            i32.eqz
-            if ;; label = @1
-              unreachable
-            end
-            global.get 0
-            i32.const 1
-            i32.sub
-            global.set 0
-            memory.size
-            local.set 0
-            block ;; label = @1
-              block ;; label = @2
-                memory.size
-                i32.const 65536
-                i32.mul
-                i32.const 65511
-                local.get 0
-                i32.add
-                i32.le_u
-                br_if 0 (;@2;)
-                local.get 0
-                i32.const 0
-                i32.le_s
-                br_if 0 (;@2;)
-                local.get 0
-                i64.load8_s offset=65503
-                local.set 1
-                br 1 (;@1;)
-              end
-              i64.const 0
-              local.set 1
-            end
-            local.get 1
-            drop
-            i32.const 0
-          )
-          (memory (;0;) 1 3)
-          (global (;0;) (mut i32) i32.const 1000)
-          (export " " (func 0))
-          (export "" (memory 0))
-        )
-    "#;
-    let mut store = Store::new(&engine, ());
-    let module = Module::new(&engine, wat)?;
-    let instance = Instance::new(&mut store, &module, &[])?;
-    let f = instance.get_typed_func::<(), i32>(&mut store, " ")?;
-    let result = f.call(&mut store, ())?;
-
-    assert!(result == 0);
-
-    Ok(())
-}
-
-#[test]
-#[cfg_attr(miri, ignore)]
-#[cfg_attr(windows, ignore)]
-fn static_oob() -> Result<()> {
-    let mut c = Config::new();
-    c.static_memory_maximum_size(65536);
-    c.strategy(Strategy::Winch);
-    let engine = Engine::new(&c)?;
-    let wat = r#"
-        (module
-          (memory 0 1)
-          (func (export "") (result i32)
-            (i32.const 0)
-            (i32.const 1)
-            (i32.store offset=726020653)
-            (i32.const 1)
-            (memory.grow)
-          )
-        )
-    "#;
-    let mut store = Store::new(&engine, ());
-    let module = Module::new(&engine, wat)?;
-    let instance = Instance::new(&mut store, &module, &[])?;
-    let f = instance.get_typed_func::<(), i32>(&mut store, "")?;
-    let result = f.call(&mut store, ()).unwrap_err();
-    assert!(result.downcast_ref::<WasmBacktrace>().is_some());
-
-    Ok(())
-}
-
-#[test]
-#[cfg_attr(miri, ignore)]
-#[cfg_attr(windows, ignore)]
-fn dynamic_heap_with_zero_max_size() -> Result<()> {
-    let mut c = Config::new();
-    c.static_memory_maximum_size(0);
-    c.strategy(Strategy::Winch);
-    let engine = Engine::new(&c)?;
-    let wat = r#"
-        (module
-          (type (;0;) (func (result i64)))
-          (type (;1;) (func (param f32)))
-          (func (;0;) (type 0) (result i64)
-            (local i32 i32 i32 i32 i32 i32 f32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
-            global.get 0
-            i32.eqz
-            if ;; label = @1
-              unreachable
-            end
-            global.get 0
-            i32.const 1
-            i32.sub
-            global.set 0
-            memory.size
-            f64.load offset=3598476644
-            loop (result i32) ;; label = @1
-              global.get 0
-              i32.eqz
-              if ;; label = @2
-                unreachable
-              end
-              global.get 0
-              i32.const 1
-              i32.sub
-              global.set 0
-              unreachable
-            end
-            unreachable
-          )
-          (memory (;0;) 0 8)
-          (global (;0;) (mut i32) i32.const 1000)
-          (export "" (func 0))
-        )
-    "#;
-    let mut store = Store::new(&engine, ());
-    let module = Module::new(&engine, wat)?;
-    let instance = Instance::new(&mut store, &module, &[])?;
-    let f = instance.get_typed_func::<(), i64>(&mut store, "")?;
-    let result = f.call(&mut store, ()).unwrap_err();
-    assert!(result.downcast_ref::<WasmBacktrace>().is_some());
-
-    Ok(())
-}