Repository: bytecodealliance/wasmtime
Pull request #8324 (2024-04-10 20:03:31)

winch: Remove stale testing docs
This commit is a follow-up to https://github.com/bytecodealliance/wasmtime/pull/8248, in which the `winch-tools`  tool was removed. 

It's in my todo list to follow-up with a PR with more in-depth documentation about Winch.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/crates/component-macro/src/bindgen.rs b/crates/component-macro/src/bindgen.rs
index 5bfd968b3..d0aee83a0 100644
--- a/crates/component-macro/src/bindgen.rs
+++ b/crates/component-macro/src/bindgen.rs
@@ -7,7 +7,7 @@ use std::sync::atomic::{AtomicUsize, Ordering::Relaxed};
 use syn::parse::{Error, Parse, ParseStream, Result};
 use syn::punctuated::Punctuated;
 use syn::{braced, token, Token};
-use wasmtime_wit_bindgen::{AsyncConfig, Opts, Ownership, TrappableError};
+use wasmtime_wit_bindgen::{AsyncConfig, Opts, Ownership, TrappableError, TrappableImports};
 use wit_parser::{PackageId, Resolve, UnresolvedPackage, WorldId};
 
 pub struct Config {
@@ -107,6 +107,7 @@ impl Parse for Config {
                         opts.async_ = val;
                     }
                     Opt::TrappableErrorType(val) => opts.trappable_error_type = val,
+                    Opt::TrappableImports(val) => opts.trappable_imports = val,
                     Opt::Ownership(val) => opts.ownership = val,
                     Opt::Interfaces(s) => {
                         if inline.is_some() {
@@ -201,6 +202,7 @@ mod kw {
     syn::custom_keyword!(with);
     syn::custom_keyword!(except_imports);
     syn::custom_keyword!(only_imports);
+    syn::custom_keyword!(trappable_imports);
 }
 
 enum Opt {
@@ -213,6 +215,7 @@ enum Opt {
     Ownership(Ownership),
     Interfaces(syn::LitStr),
     With(HashMap<String, String>),
+    TrappableImports(TrappableImports),
 }
 
 impl Parse for Opt {
@@ -330,6 +333,22 @@ impl Parse for Opt {
             let fields: Punctuated<(String, String), Token![,]> =
                 contents.parse_terminated(with_field_parse, Token![,])?;
             Ok(Opt::With(HashMap::from_iter(fields)))
+        } else if l.peek(kw::trappable_imports) {
+            input.parse::<kw::trappable_imports>()?;
+            input.parse::<Token![:]>()?;
+            let config = if input.peek(syn::LitBool) {
+                match input.parse::<syn::LitBool>()?.value {
+                    true => TrappableImports::All,
+                    false => TrappableImports::None,
+                }
+            } else {
+                let contents;
+                syn::bracketed!(contents in input);
+                let fields: Punctuated<syn::LitStr, Token![,]> =
+                    contents.parse_terminated(Parse::parse, Token![,])?;
+                TrappableImports::Only(fields.iter().map(|s| s.value()).collect())
+            };
+            Ok(Opt::TrappableImports(config))
         } else {
             Err(l.error())
         }
diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs
index 3334b1736..873105b34 100644
--- a/crates/environ/src/component/translate.rs
+++ b/crates/environ/src/component/translate.rs
@@ -4,6 +4,7 @@ use crate::{
     EntityIndex, ModuleEnvironment, ModuleTranslation, ModuleTypesBuilder, PrimaryMap, Tunables,
     TypeConvert, WasmHeapType, WasmValType,
 };
+use anyhow::anyhow;
 use anyhow::{bail, Result};
 use indexmap::IndexMap;
 use std::collections::HashMap;
@@ -534,7 +535,18 @@ impl<'a, 'data> Translator<'a, 'data> {
                     self.validator,
                     self.types.module_types_builder(),
                 )
-                .translate(parser, &component[range.start..range.end])?;
+                .translate(
+                    parser,
+                    component.get(range.start..range.end).ok_or_else(|| {
+                        anyhow!(
+                            "section range {}..{} is out of bounds (bound = {})",
+                            range.start,
+                            range.end,
+                            component.len()
+                        )
+                        .context("wasm component contains an invalid module section")
+                    })?,
+                )?;
                 let static_idx = self.static_modules.push(translation);
                 self.result
                     .initializers
diff --git a/crates/wasi-http/src/lib.rs b/crates/wasi-http/src/lib.rs
index 29419d745..da72cecfa 100644
--- a/crates/wasi-http/src/lib.rs
+++ b/crates/wasi-http/src/lib.rs
@@ -20,6 +20,7 @@ pub mod bindings {
         ",
         tracing: true,
         async: false,
+        trappable_imports: true,
         with: {
             // Upstream package dependencies
             "wasi:io": wasmtime_wasi::bindings::io,
diff --git a/crates/wasi-nn/src/wit.rs b/crates/wasi-nn/src/wit.rs
index 95bfd1dac..dd9c9cc08 100644
--- a/crates/wasi-nn/src/wit.rs
+++ b/crates/wasi-nn/src/wit.rs
@@ -20,7 +20,11 @@ use std::{error::Error, fmt, hash::Hash, str::FromStr};
 
 /// Generate the traits and types from the `wasi-nn` WIT specification.
 mod gen_ {
-    wasmtime::component::bindgen!("ml" in "spec/wit/wasi-nn.wit");
+    wasmtime::component::bindgen!({
+        world: "ml",
+        path: "spec/wit/wasi-nn.wit",
+        trappable_imports: true,
+    });
 }
 use gen_::wasi::nn as gen; // Shortcut to the module containing the types we need.
 
diff --git a/crates/wasi/src/bindings.rs b/crates/wasi/src/bindings.rs
index 57c08329a..737301403 100644
--- a/crates/wasi/src/bindings.rs
+++ b/crates/wasi/src/bindings.rs
@@ -14,6 +14,7 @@ pub mod sync {
                 "wasi:io/streams/stream-error" => StreamError,
                 "wasi:filesystem/types/error-code" => FsError,
             },
+            trappable_imports: true,
             with: {
                 // These interfaces come from the outer module, as it's
                 // sync/async agnostic.
@@ -118,6 +119,7 @@ mod async_io {
         path: "wit",
         world: "wasi:cli/command",
         tracing: true,
+        trappable_imports: true,
         async: {
             // Only these functions are `async` and everything else is sync
             // meaning that it basically doesn't need to block. These functions
diff --git a/crates/wasmtime/src/runtime/component/mod.rs b/crates/wasmtime/src/runtime/component/mod.rs
index 9418ede3b..43ac2f710 100644
--- a/crates/wasmtime/src/runtime/component/mod.rs
+++ b/crates/wasmtime/src/runtime/component/mod.rs
@@ -209,8 +209,8 @@ pub(crate) use self::store::ComponentStoreData;
 /// impl HelloWorldImports for MyState {
 ///     // Note the `Result` return value here where `Ok` is returned back to
 ///     // the component and `Err` will raise a trap.
-///     fn name(&mut self) -> wasmtime::Result<String> {
-///         Ok(self.name.clone())
+///     fn name(&mut self) -> String {
+///         self.name.clone()
 ///     }
 /// }
 ///
@@ -315,13 +315,13 @@ pub(crate) use self::store::ComponentStoreData;
 ///
 /// // Note that the trait here is per-interface and within a submodule now.
 /// impl Host for MyState {
-///     fn gen_random_integer(&mut self) -> wasmtime::Result<u32> {
+///     fn gen_random_integer(&mut self) -> u32 {
 /// #       panic!();
 /// #       #[cfg(FALSE)]
-///         Ok(rand::thread_rng().gen())
+///         rand::thread_rng().gen()
 ///     }
 ///
-///     fn sha256(&mut self, bytes: Vec<u8>) -> wasmtime::Result<String> {
+///     fn sha256(&mut self, bytes: Vec<u8>) -> String {
 ///         // ...
 /// #       panic!()
 ///     }
@@ -432,6 +432,21 @@ pub(crate) use self::store::ComponentStoreData;
 ///         only_imports: ["foo", "bar"],
 ///     },
 ///
+///     // This option is used to indicate whether imports can trap.
+///     //
+///     // Imports that may trap have their return types wrapped in
+///     // `wasmtime::Result<T>` where the `Err` variant indicates that a
+///     // trap will be raised in the guest.
+///     //
+///     // By default imports cannot trap and the return value is the return
+///     // value from the WIT bindings itself. This value can be set to `true`
+///     // to indicate that any import can trap. This value can also be set to
+///     // an array-of-strings to indicate that only a set list of imports
+///     // can trap.
+///     trappable_imports: false,             // no imports can trap (default)
+///     // trappable_imports: true,           // all imports can trap
+///     // trappable_imports: ["foo", "bar"], // only these can trap
+///
 ///     // This can be used to translate WIT return values of the form
 ///     // `result<T, error-type>` into `Result<T, RustErrorType>` in Rust.
 ///     // Users must define `RustErrorType` and the `Host` trait for the
@@ -440,7 +455,8 @@ pub(crate) use self::store::ComponentStoreData;
 ///     // into `wasmtime::Result<ErrorType>`. This conversion can either
 ///     // return the raw WIT error (`ErrorType` here) or a trap.
 ///     //
-///     // By default this option is not specified.
+///     // By default this option is not specified. This option only takes
+///     // effect when `trappable_imports` is set for some imports.
 ///     trappable_error_type: {
 ///         "wasi:io/streams/stream-error" => RustErrorType,
 ///     },
diff --git a/crates/wit-bindgen/src/lib.rs b/crates/wit-bindgen/src/lib.rs
index b07ac4c4d..5d51c583b 100644
--- a/crates/wit-bindgen/src/lib.rs
+++ b/crates/wit-bindgen/src/lib.rs
@@ -104,6 +104,9 @@ pub struct Opts {
     /// Whether or not to generate code for only the interfaces of this wit file or not.
     pub only_interfaces: bool,
 
+    /// Configuration of which imports are allowed to generate a trap.
+    pub trappable_imports: TrappableImports,
+
     /// Remapping of interface names to rust module names.
     /// TODO: is there a better type to use for the value of this map?
     pub with: HashMap<String, String>,
@@ -154,6 +157,27 @@ impl AsyncConfig {
     }
 }
 
+#[derive(Default, Debug, Clone)]
+pub enum TrappableImports {
+    /// No imports are allowed to trap.
+    #[default]
+    None,
+    /// All imports may trap.
+    All,
+    /// Only the specified set of functions may trap.
+    Only(HashSet<String>),
+}
+
+impl TrappableImports {
+    fn can_trap(&self, f: &Function) -> bool {
+        match self {
+            TrappableImports::None => false,
+            TrappableImports::All => true,
+            TrappableImports::Only(set) => set.contains(&f.name),
+        }
+    }
+}
+
 impl Opts {
     pub fn generate(&self, resolve: &Resolve, world: WorldId) -> String {
         let mut r = Wasmtime::default();
@@ -1853,7 +1877,13 @@ impl<'a> InterfaceGenerator<'a> {
             );
         }
 
-        if let Some((_, err, _)) = self.special_case_trappable_error(&func.results) {
+        if !self.gen.opts.trappable_imports.can_trap(&func) {
+            if func.results.iter_types().len() == 1 {
+                uwrite!(self.src, "Ok((r,))\n");
+            } else {
+                uwrite!(self.src, "Ok(r)\n");
+            }
+        } else if let Some((_, err, _)) = self.special_case_trappable_error(&func.results) {
             let err = &self.resolve.types[resolve_type_definition_id(self.resolve, err)];
             let err_name = err.name.as_ref().unwrap();
             let owner = match err.owner {
@@ -1905,7 +1935,11 @@ impl<'a> InterfaceGenerator<'a> {
         self.push_str(")");
         self.push_str(" -> ");
 
-        if let Some((r, _id, error_typename)) = self.special_case_trappable_error(&func.results) {
+        if !self.gen.opts.trappable_imports.can_trap(func) {
+            self.print_result_ty(&func.results, TypeMode::Owned);
+        } else if let Some((r, _id, error_typename)) =
+            self.special_case_trappable_error(&func.results)
+        {
             // Functions which have a single result `result<ok,err>` get special
             // cased to use the host_wasmtime_rust::Error<err>, making it possible
             // for them to trap or use `?` to propogate their errors
diff --git a/examples/component/main.rs b/examples/component/main.rs
index 459764664..f70fc3819 100644
--- a/examples/component/main.rs
+++ b/examples/component/main.rs
@@ -13,8 +13,8 @@ struct HostComponent;
 
 // Implmentation of the host interface defined in the wit file.
 impl host::Host for HostComponent {
-    fn multiply(&mut self, a: f32, b: f32) -> wasmtime::Result<f32> {
-        Ok(a * b)
+    fn multiply(&mut self, a: f32, b: f32) -> f32 {
+        a * b
     }
 }
 


Test patch:
diff --git a/crates/component-macro/tests/codegen.rs b/crates/component-macro/tests/codegen.rs
index a292d8004..76e228203 100644
--- a/crates/component-macro/tests/codegen.rs
+++ b/crates/component-macro/tests/codegen.rs
@@ -254,7 +254,7 @@ mod with_works_with_hierarchy {
         struct X;
 
         impl FooImports for X {
-            fn x(&mut self) -> wasmtime::Result<super::bindings::foo::foo::a::T> {
+            fn x(&mut self) -> super::bindings::foo::foo::a::T {
                 loop {}
             }
         }
@@ -294,7 +294,7 @@ mod with_works_with_hierarchy {
         struct X;
 
         impl FooImports for X {
-            fn x(&mut self) -> wasmtime::Result<super::bindings::foo::foo::a::T> {
+            fn x(&mut self) -> super::bindings::foo::foo::a::T {
                 loop {}
             }
         }
@@ -334,9 +334,189 @@ mod with_works_with_hierarchy {
         struct X;
 
         impl FooImports for X {
-            fn x(&mut self) -> wasmtime::Result<super::bindings::foo::foo::a::T> {
+            fn x(&mut self) -> super::bindings::foo::foo::a::T {
                 loop {}
             }
         }
     }
 }
+
+mod trappable_imports {
+    mod none {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                world foo {
+                    import foo: func();
+                }
+            ",
+            trappable_imports: false,
+        });
+        struct X;
+
+        impl FooImports for X {
+            fn foo(&mut self) {}
+        }
+    }
+
+    mod all {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                world foo {
+                    import foo: func();
+                }
+            ",
+            trappable_imports: true,
+        });
+        struct X;
+
+        impl FooImports for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+        }
+    }
+
+    mod some {
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                world foo {
+                    import foo: func();
+                    import bar: func();
+                }
+            ",
+            trappable_imports: ["foo"],
+        });
+        struct X;
+
+        impl FooImports for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) {}
+        }
+    }
+
+    mod across_interfaces {
+        use wasmtime::component::Resource;
+
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                interface a {
+                    foo: func();
+                    bar: func();
+
+                    resource r {
+                        constructor();
+                        foo: func();
+                        bar: static func();
+                    }
+                }
+
+                world foo {
+                    import a;
+                    import foo: func();
+                    import bar: func();
+                    import i: interface {
+                        foo: func();
+                        bar: func();
+                    }
+
+                }
+            ",
+            trappable_imports: ["foo"],
+            with: { "foo:foo/a/r": R },
+        });
+
+        struct X;
+        pub struct R;
+
+        impl FooImports for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) {}
+        }
+
+        impl i::Host for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) {}
+        }
+
+        impl foo::foo::a::Host for X {
+            fn foo(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) {}
+        }
+
+        impl foo::foo::a::HostR for X {
+            fn new(&mut self) -> Resource<R> {
+                loop {}
+            }
+            fn foo(&mut self, _: Resource<R>) {}
+            fn bar(&mut self) {}
+            fn drop(&mut self, _: Resource<R>) -> wasmtime::Result<()> {
+                Ok(())
+            }
+        }
+    }
+
+    mod resources {
+        use wasmtime::component::Resource;
+
+        wasmtime::component::bindgen!({
+            inline: "
+                package foo:foo;
+
+                interface a {
+                    resource r {
+                        constructor();
+                        foo: func();
+                        bar: static func();
+                    }
+                }
+
+                world foo {
+                    import a;
+
+                }
+            ",
+            trappable_imports: [
+                "[constructor]r",
+                "[method]r.foo",
+                "[static]r.bar",
+            ],
+            with: { "foo:foo/a/r": R },
+        });
+
+        struct X;
+        pub struct R;
+
+        impl foo::foo::a::Host for X {}
+
+        impl foo::foo::a::HostR for X {
+            fn new(&mut self) -> wasmtime::Result<Resource<R>> {
+                loop {}
+            }
+            fn foo(&mut self, _: Resource<R>) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn bar(&mut self) -> wasmtime::Result<()> {
+                Ok(())
+            }
+            fn drop(&mut self, _: Resource<R>) -> wasmtime::Result<()> {
+                Ok(())
+            }
+        }
+    }
+}
diff --git a/tests/all/component_model/aot.rs b/tests/all/component_model/aot.rs
index b94e8f085..676de39d1 100644
--- a/tests/all/component_model/aot.rs
+++ b/tests/all/component_model/aot.rs
@@ -164,3 +164,46 @@ fn reflect_resource_import() -> Result<()> {
 
     Ok(())
 }
+
+#[test]
+#[cfg_attr(miri, ignore)]
+fn truncated_component_binaries_dont_panic() -> Result<()> {
+    let engine = super::engine();
+
+    let binary = wat::parse_str(
+        r#"
+        (component
+            (import "a" (core module $m0
+                (import "" "" (func))
+            ))
+
+            (core module $m1
+                (func (export ""))
+            )
+            (core instance $i1 (instantiate (module $m1)))
+            (func $f (canon lift (core func $i1 "f")))
+
+            (component $c1
+                (import "f" (func))
+                (core module $m2
+                    (func (export "g"))
+                )
+                (core instance $i2 (instantiate $m2))
+                (func (export "g")
+                    (canon lift (core func $i2 "g"))
+                )
+            )
+            (instance $i3 (instantiate $c1 (with "f" (func $f))))
+            (func (export "g") (alias export $i3 "g"))
+        )
+        "#,
+    )?;
+
+    // Check that if we feed each truncation of the component binary into
+    // `Component::new` we don't get any panics.
+    for i in 1..binary.len() - 1 {
+        let _ = Component::from_binary(&engine, &binary[0..i]);
+    }
+
+    Ok(())
+}
diff --git a/tests/all/component_model/bindgen.rs b/tests/all/component_model/bindgen.rs
index 1c0b713a1..764836491 100644
--- a/tests/all/component_model/bindgen.rs
+++ b/tests/all/component_model/bindgen.rs
@@ -105,9 +105,8 @@ mod one_import {
         }
 
         impl foo::Host for MyImports {
-            fn foo(&mut self) -> Result<()> {
+            fn foo(&mut self) {
                 self.hit = true;
-                Ok(())
             }
         }
 
@@ -181,9 +180,9 @@ mod resources_at_world_level {
         }
 
         impl HostX for MyImports {
-            fn new(&mut self) -> Result<Resource<X>> {
+            fn new(&mut self) -> Resource<X> {
                 self.ctor_hit = true;
-                Ok(Resource::new_own(80))
+                Resource::new_own(80)
             }
 
             fn drop(&mut self, val: Resource<X>) -> Result<()> {
@@ -286,9 +285,9 @@ mod resources_at_interface_level {
         use foo::foo::def::X;
 
         impl foo::foo::def::HostX for MyImports {
-            fn new(&mut self) -> Result<Resource<X>> {
+            fn new(&mut self) -> Resource<X> {
                 self.ctor_hit = true;
-                Ok(Resource::new_own(80))
+                Resource::new_own(80)
             }
 
             fn drop(&mut self, val: Resource<X>) -> Result<()> {
@@ -337,13 +336,9 @@ mod async_config {
 
     #[async_trait::async_trait]
     impl T1Imports for T {
-        async fn x(&mut self) -> Result<()> {
-            Ok(())
-        }
+        async fn x(&mut self) {}
 
-        async fn y(&mut self) -> Result<()> {
-            Ok(())
-        }
+        async fn y(&mut self) {}
     }
 
     async fn _test_t1(t1: &T1, store: &mut Store<()>) {
@@ -367,13 +362,9 @@ mod async_config {
 
     #[async_trait::async_trait]
     impl T2Imports for T {
-        fn x(&mut self) -> Result<()> {
-            Ok(())
-        }
+        fn x(&mut self) {}
 
-        async fn y(&mut self) -> Result<()> {
-            Ok(())
-        }
+        async fn y(&mut self) {}
     }
 
     async fn _test_t2(t2: &T2, store: &mut Store<()>) {
@@ -397,13 +388,9 @@ mod async_config {
 
     #[async_trait::async_trait]
     impl T3Imports for T {
-        async fn x(&mut self) -> Result<()> {
-            Ok(())
-        }
+        async fn x(&mut self) {}
 
-        fn y(&mut self) -> Result<()> {
-            Ok(())
-        }
+        fn y(&mut self) {}
     }
 
     async fn _test_t3(t3: &T3, store: &mut Store<()>) {
@@ -468,11 +455,11 @@ mod exported_resources {
     }
 
     impl a::HostX for MyImports {
-        fn new(&mut self) -> Result<Resource<a::X>> {
+        fn new(&mut self) -> Resource<a::X> {
             let rep = self.next_a_x;
             self.next_a_x += 1;
             self.hostcalls.push(Hostcall::NewA);
-            Ok(Resource::new_own(rep))
+            Resource::new_own(rep)
         }
 
         fn drop(&mut self, val: Resource<a::X>) -> Result<()> {
diff --git a/tests/all/component_model/bindgen/results.rs b/tests/all/component_model/bindgen/results.rs
index 29f60fe8b..dfb361523 100644
--- a/tests/all/component_model/bindgen/results.rs
+++ b/tests/all/component_model/bindgen/results.rs
@@ -17,6 +17,7 @@ mod empty_error {
 
             export empty-error: func(a: float64) -> result<float64>;
         }",
+        trappable_imports: true,
     });
 
     #[test]
@@ -118,6 +119,7 @@ mod string_error {
 
             export string-error: func(a: float64) -> result<float64, string>;
         }",
+        trappable_imports: true,
     });
 
     #[test]
@@ -238,7 +240,8 @@ mod enum_error {
                 enum-error: func(a: float64) -> result<float64, e1>;
             }
         }",
-        trappable_error_type: { "inline:inline/imports/e1" => TrappableE1 }
+        trappable_error_type: { "inline:inline/imports/e1" => TrappableE1 },
+        trappable_imports: true,
     });
 
     // You can create concrete trap types which make it all the way out to the
@@ -418,7 +421,8 @@ mod record_error {
                 record-error: func(a: float64) -> result<float64, e2>;
             }
         }",
-        trappable_error_type: { "inline:inline/imports/e2" => TrappableE2 }
+        trappable_error_type: { "inline:inline/imports/e2" => TrappableE2 },
+        trappable_imports: true,
     });
 
     pub enum TrappableE2 {
@@ -589,7 +593,8 @@ mod variant_error {
                 variant-error: func(a: float64) -> result<float64, e3>;
             }
         }",
-        trappable_error_type: { "inline:inline/imports/e3" => TrappableE3 }
+        trappable_error_type: { "inline:inline/imports/e3" => TrappableE3 },
+        trappable_imports: true,
     });
 
     pub enum TrappableE3 {
@@ -784,7 +789,8 @@ mod multiple_interfaces_error {
                 enum-error: func(a: float64) -> result<float64, e1>;
             }
         }",
-        trappable_error_type: { "inline:inline/types/e1" => TrappableE1 }
+        trappable_error_type: { "inline:inline/types/e1" => TrappableE1 },
+        trappable_imports: true,
     });
 
     pub enum TrappableE1 {
@@ -967,6 +973,7 @@ mod with_remapping {
             import imports: interface {
                 empty-error: func(a: float64) -> result<float64>;
             }",
+            trappable_imports: true,
         });
     }
 
@@ -983,6 +990,7 @@ mod with_remapping {
         with: {
             "imports": interfaces::imports,
         },
+        trappable_imports: true,
     });
 
     #[test]
diff --git a/winch/docs/testing.md b/winch/docs/testing.md
deleted file mode 100644
index 859db45b7..000000000
--- a/winch/docs/testing.md
+++ /dev/null
@@ -1,63 +0,0 @@
-# Testing Winch
-
-Winch is tested through integration testing using the `winch-filetests` crate
-and manual exploratory testing. A CLI is available to run these tests
-conveniently. To add the `winch-tools` binary to your `PATH`, run `cargo install
---path winch` from the root of `wasmtime`. The CLI provides two commands: `test`
-and `compile`. To see the help text for each command, run `winch-tools test
---help` or `winch-tools compile --help`.
-
-## Integration Testing (`winch-tools test`)
-
-The `test` command will run a suite of tests that validates Winch output for a
-WebAssembly module is consistent with our expectations.
-
-### Running `test`
-
-Running `winch-tools test` will run all integration tests in the
-`winch-filetests` crate. All arguments following two dashes (`--`) will be
-passed directly to `cargo test -p winch-filetests`. This will allow you to
-configure the tests to run based on your requirements. All tests in the
-`winch-filetests` crate get named in the following convention:
-`winch_filetests_${filepath}`. This makes it possible to filter if you don't
-want to run the entire suite.
-
-If the output of Winch changes for a test in a run due to code updates, the test
-will fail and the difference between the two outputs will be shown. If the new
-output is expected, the tests can be re-run with an `WINCH_TEST_BLESS`
-environment variable set to `1`.
-
-### Adding a test
-
-To add new tests, create a `.wat` file in the `winch/filetests/filetests` folder
-in the following format:
-
-```wat
-;;! target = "x86_64"
-(module
-  (func (result i32)
-    (i32.const 42)
-  )
-)
-```
-
-It is encouraged to use folders to organize tests. For example, tests targeting
-the x86_64 architecture can be placed in the `winch/filetests/filetests/x64`.
-
-The first block of comments are a TOML compatible configuration passed to Winch
-during compilation with a `!` at the start of each line. The body of the file
-will be the subject of the test. A final block of comments is reserved for the
-output of the compilation, and it will be used to compare the output of the
-current run with the output of previous runs.
-
-## Manual Exploratory Tests (`winch-tools compile`)
-
-The `compile` command will run Winch for particular architecture against
-provided input file, and print the disassembled output to the console. Only
-`.wat` files are supported.
-
-### Running `compile`
-
-```bash
-winch-tools compile $wat_file --target $target_triple
-```