Repository: bytecodealliance/wasmtime
Pull request #9401 (2024-10-08 21:20:09)

Implement the `ref.test` Wasm GC instruction
This commit implements the `ref.test` instruction, which tests whether a reference is of a given type.

We implement inline fast paths for abstract types, but currently rely on an out-of-line libcall for concrete types in the general case. This is known to be suboptimal. (FWIW, we also emit a fast path in front of the libcall where we first check the actual type and expected type for equality and skip the libcall if they are equal.)

This implementation is expected to be improved in the future by exposing a module's types' supertypes arrays to Wasm, so that the Wasm can do the O(1) subtype checks inline. This will make the vast majority of all `ref.test`s inlinable. After that, the only remaining case that would require out-of-line libcalls would be when a module is given an instance of a type that it did not itself define (but which could be a subtype of a type it defined, for example, and which itself might not even have been defined until after this module's instance was created!)

<!--
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/Cargo.lock b/Cargo.lock
index 9d110b50d..2bc6313f6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2897,7 +2897,7 @@ dependencies = [
  "cargo_metadata",
  "heck 0.5.0",
  "wasmtime",
- "wit-component",
+ "wit-component 0.219.0",
 ]
 
 [[package]]
@@ -3258,7 +3258,7 @@ name = "verify-component-adapter"
 version = "27.0.0"
 dependencies = [
  "anyhow",
- "wasmparser",
+ "wasmparser 0.219.0",
  "wat",
 ]
 
@@ -3351,7 +3351,7 @@ dependencies = [
  "byte-array-literals",
  "object",
  "wasi",
- "wasm-encoder",
+ "wasm-encoder 0.219.0",
  "wit-bindgen-rust-macro",
 ]
 
@@ -3416,7 +3416,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "22b896fa8ceb71091ace9bcb81e853f54043183a1c9667cf93422c40252ffa0a"
 dependencies = [
  "leb128",
- "wasmparser",
+ "wasmparser 0.218.0",
+]
+
+[[package]]
+name = "wasm-encoder"
+version = "0.219.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2b1b95711b3ad655656a341e301cc64e33cbee94de9a99a1c5a2ab88efab79d"
+dependencies = [
+ "leb128",
+ "wasmparser 0.219.0",
 ]
 
 [[package]]
@@ -3431,36 +3441,52 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "spdx",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.218.0",
+ "wasmparser 0.218.0",
+]
+
+[[package]]
+name = "wasm-metadata"
+version = "0.219.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96132fe00dd17d092d2be289eeed5a0a68ad3cf30b68e8875bc953b96f55f0be"
+dependencies = [
+ "anyhow",
+ "indexmap 2.2.6",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "spdx",
+ "wasm-encoder 0.219.0",
+ "wasmparser 0.219.0",
 ]
 
 [[package]]
 name = "wasm-mutate"
-version = "0.218.0"
+version = "0.219.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17197857afe3d1569d6d42bfb52cf7999ee70090630213f73e035bec2e5922e4"
+checksum = "94295e8974bd5b547492f028851a9c9aae45df8075b40424d4ef72f7edf10605"
 dependencies = [
  "egg",
  "log",
  "rand",
  "thiserror",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.219.0",
+ "wasmparser 0.219.0",
 ]
 
 [[package]]
 name = "wasm-smith"
-version = "0.218.0"
+version = "0.219.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b533994f2242064bf512b31d47380e4ea9acc85ffec09fc07db61300261793f7"
+checksum = "318c50953daf236696ce547c6cf3c499137a3e46ab5b74c1a248e333c5d1d517"
 dependencies = [
  "anyhow",
  "arbitrary",
  "flagset",
  "indexmap 2.2.6",
  "leb128",
- "wasm-encoder",
+ "wasm-encoder 0.219.0",
 ]
 
 [[package]]
@@ -3508,6 +3534,19 @@ name = "wasmparser"
 version = "0.218.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b09e46c7fceceaa72b2dd1a8a137ea7fd8f93dfaa69806010a709918e496c5dc"
+dependencies = [
+ "ahash",
+ "bitflags 2.6.0",
+ "hashbrown 0.14.3",
+ "indexmap 2.2.6",
+ "semver",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.219.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "324b4e56d24439495b88cd81439dad5e97f3c7b1eedc3c7e10455ed1e045e9a2"
 dependencies = [
  "ahash",
  "bitflags 2.6.0",
@@ -3528,13 +3567,13 @@ dependencies = [
 
 [[package]]
 name = "wasmprinter"
-version = "0.218.0"
+version = "0.219.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ace089155491837b75f474bf47c99073246d1b737393fe722d6dee311595ddc"
+checksum = "22cdac6eee8166b6bc319166483ea8f68ffaf9d378da5fff1dd4ce0af76b597a"
 dependencies = [
  "anyhow",
  "termcolor",
- "wasmparser",
+ "wasmparser 0.219.0",
 ]
 
 [[package]]
@@ -3579,8 +3618,8 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "wasi-common",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.219.0",
+ "wasmparser 0.219.0",
  "wasmtime-asm-macros",
  "wasmtime-cache",
  "wasmtime-component-macro",
@@ -3724,7 +3763,7 @@ dependencies = [
  "tracing",
  "walkdir",
  "wasi-common",
- "wasmparser",
+ "wasmparser 0.219.0",
  "wasmtime",
  "wasmtime-cache",
  "wasmtime-cli-flags",
@@ -3740,10 +3779,10 @@ dependencies = [
  "wasmtime-wasi-runtime-config",
  "wasmtime-wasi-threads",
  "wasmtime-wast",
- "wast 218.0.0",
+ "wast 219.0.0",
  "wat",
  "windows-sys 0.59.0",
- "wit-component",
+ "wit-component 0.219.0",
 ]
 
 [[package]]
@@ -3776,7 +3815,7 @@ dependencies = [
  "wasmtime",
  "wasmtime-component-util",
  "wasmtime-wit-bindgen",
- "wit-parser",
+ "wit-parser 0.219.0",
 ]
 
 [[package]]
@@ -3801,7 +3840,7 @@ dependencies = [
  "smallvec",
  "target-lexicon",
  "thiserror",
- "wasmparser",
+ "wasmparser 0.219.0",
  "wasmtime-environ",
  "wasmtime-versioned-export-macros",
 ]
@@ -3827,8 +3866,8 @@ dependencies = [
  "serde_derive",
  "smallvec",
  "target-lexicon",
- "wasm-encoder",
- "wasmparser",
+ "wasm-encoder 0.219.0",
+ "wasmparser 0.219.0",
  "wasmprinter",
  "wasmtime-component-util",
  "wat",
@@ -3842,7 +3881,7 @@ dependencies = [
  "component-fuzz-util",
  "env_logger 0.11.5",
  "libfuzzer-sys",
- "wasmparser",
+ "wasmparser 0.219.0",
  "wasmprinter",
  "wasmtime-environ",
  "wat",
@@ -3902,7 +3941,7 @@ dependencies = [
  "rand",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.219.0",
  "wasmtime",
  "wasmtime-fuzzing",
 ]
@@ -3923,12 +3962,12 @@ dependencies = [
  "target-lexicon",
  "tempfile",
  "v8",
- "wasm-encoder",
+ "wasm-encoder 0.219.0",
  "wasm-mutate",
  "wasm-smith",
  "wasm-spec-interpreter",
  "wasmi",
- "wasmparser",
+ "wasmparser 0.219.0",
  "wasmprinter",
  "wasmtime",
  "wasmtime-wast",
@@ -4098,7 +4137,7 @@ dependencies = [
  "anyhow",
  "log",
  "wasmtime",
- "wast 218.0.0",
+ "wast 219.0.0",
 ]
 
 [[package]]
@@ -4110,7 +4149,7 @@ dependencies = [
  "gimli",
  "object",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.219.0",
  "wasmtime-cranelift",
  "wasmtime-environ",
  "winch-codegen",
@@ -4123,7 +4162,7 @@ dependencies = [
  "anyhow",
  "heck 0.5.0",
  "indexmap 2.2.6",
- "wit-parser",
+ "wit-parser 0.219.0",
 ]
 
 [[package]]
@@ -4141,24 +4180,24 @@ dependencies = [
 
 [[package]]
 name = "wast"
-version = "218.0.0"
+version = "219.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a53cd1f0fa505df97557e36a58bddb8296e2fcdcd089529545ebfdb18a1b9d7"
+checksum = "06880ecb25662bc21db6a83f4fcc27c41f71fbcba4f1980b650c88ada92728e1"
 dependencies = [
  "bumpalo",
  "leb128",
  "memchr",
  "unicode-width",
- "wasm-encoder",
+ "wasm-encoder 0.219.0",
 ]
 
 [[package]]
 name = "wat"
-version = "1.218.0"
+version = "1.219.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f87f8e14e776762e07927c27c2054d2cf678aab9aae2d431a79b3e31e4dd391"
+checksum = "11e56dbf9fc89111b0d97c91e683d7895b1a6e5633a729f2ccad2303724005b6"
 dependencies = [
- "wast 218.0.0",
+ "wast 219.0.0",
 ]
 
 [[package]]
@@ -4299,7 +4338,7 @@ dependencies = [
  "regalloc2",
  "smallvec",
  "target-lexicon",
- "wasmparser",
+ "wasmparser 0.219.0",
  "wasmtime-cranelift",
  "wasmtime-environ",
 ]
@@ -4538,7 +4577,7 @@ checksum = "4b885a00e1c428fd12b7b7c4bccc4bad8b2a3ca0abe8eaf1e0f90adabb4c7ac7"
 dependencies = [
  "anyhow",
  "heck 0.5.0",
- "wit-parser",
+ "wit-parser 0.218.0",
 ]
 
 [[package]]
@@ -4561,9 +4600,9 @@ dependencies = [
  "indexmap 2.2.6",
  "prettyplease",
  "syn 2.0.60",
- "wasm-metadata",
+ "wasm-metadata 0.218.0",
  "wit-bindgen-core",
- "wit-component",
+ "wit-component 0.218.0",
 ]
 
 [[package]]
@@ -4594,10 +4633,29 @@ dependencies = [
  "serde",
  "serde_derive",
  "serde_json",
- "wasm-encoder",
- "wasm-metadata",
- "wasmparser",
- "wit-parser",
+ "wasm-encoder 0.218.0",
+ "wasm-metadata 0.218.0",
+ "wasmparser 0.218.0",
+ "wit-parser 0.218.0",
+]
+
+[[package]]
+name = "wit-component"
+version = "0.219.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99a76111c20444a814019de20499d30940ecd219b9512ee296f034a5edb18a2d"
+dependencies = [
+ "anyhow",
+ "bitflags 2.6.0",
+ "indexmap 2.2.6",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder 0.219.0",
+ "wasm-metadata 0.219.0",
+ "wasmparser 0.219.0",
+ "wit-parser 0.219.0",
 ]
 
 [[package]]
@@ -4615,7 +4673,25 @@ dependencies = [
  "serde_derive",
  "serde_json",
  "unicode-xid",
- "wasmparser",
+ "wasmparser 0.218.0",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.219.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23102e180c0c464f36e293d31a27b524e3ece930d7b5527d2f33f9d2c963de64"
+dependencies = [
+ "anyhow",
+ "id-arena",
+ "indexmap 2.2.6",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid",
+ "wasmparser 0.219.0",
 ]
 
 [[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 8fa098ae0..d80e7f55b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -275,15 +275,15 @@ wit-bindgen = { version = "0.33.0", default-features = false }
 wit-bindgen-rust-macro = { version = "0.33.0", default-features = false }
 
 # wasm-tools family:
-wasmparser = { version = "0.218.0", default-features = false }
-wat = "1.218.0"
-wast = "218.0.0"
-wasmprinter = "0.218.0"
-wasm-encoder = "0.218.0"
-wasm-smith = "0.218.0"
-wasm-mutate = "0.218.0"
-wit-parser = "0.218.0"
-wit-component = "0.218.0"
+wasmparser = { version = "0.219.0", default-features = false }
+wat = "1.219.0"
+wast = "219.0.0"
+wasmprinter = "0.219.0"
+wasm-encoder = "0.219.0"
+wasm-smith = "0.219.0"
+wasm-mutate = "0.219.0"
+wit-parser = "0.219.0"
+wit-component = "0.219.0"
 
 # Non-Bytecode Alliance maintained dependencies:
 # --------------------------
diff --git a/crates/c-api/include/wasmtime/config.h b/crates/c-api/include/wasmtime/config.h
index 0cd970ccc..e7f8ecc91 100644
--- a/crates/c-api/include/wasmtime/config.h
+++ b/crates/c-api/include/wasmtime/config.h
@@ -240,6 +240,14 @@ WASMTIME_CONFIG_PROP(void, wasm_multi_memory, bool)
  */
 WASMTIME_CONFIG_PROP(void, wasm_memory64, bool)
 
+/**
+ * \brief Configures whether the WebAssembly wide-arithmetic proposal is
+ * enabled.
+ *
+ * This setting is `false` by default.
+ */
+WASMTIME_CONFIG_PROP(void, wasm_wide_arithmetic, bool)
+
 #ifdef WASMTIME_FEATURE_COMPILER
 
 /**
diff --git a/crates/c-api/src/config.rs b/crates/c-api/src/config.rs
index 4ce46768a..27035283e 100644
--- a/crates/c-api/src/config.rs
+++ b/crates/c-api/src/config.rs
@@ -460,3 +460,8 @@ pub unsafe extern "C" fn wasmtime_config_host_memory_creator_set(
 pub extern "C" fn wasmtime_config_memory_init_cow_set(c: &mut wasm_config_t, enable: bool) {
     c.config.memory_init_cow(enable);
 }
+
+#[no_mangle]
+pub extern "C" fn wasmtime_config_wasm_wide_arithmetic_set(c: &mut wasm_config_t, enable: bool) {
+    c.config.wasm_wide_arithmetic(enable);
+}
diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs
index 990601495..015a9b678 100644
--- a/crates/cli-flags/src/lib.rs
+++ b/crates/cli-flags/src/lib.rs
@@ -281,6 +281,8 @@ wasmtime_option_group! {
         pub gc: Option<bool>,
         /// Configure support for the custom-page-sizes proposal.
         pub custom_page_sizes: Option<bool>,
+        /// Configure support for the wide-arithmetic proposal.
+        pub wide_arithmetic: Option<bool>,
     }
 
     enum Wasm {
@@ -724,6 +726,9 @@ impl CommonOptions {
         if let Some(enable) = self.wasm.custom_page_sizes.or(all) {
             config.wasm_custom_page_sizes(enable);
         }
+        if let Some(enable) = self.wasm.wide_arithmetic.or(all) {
+            config.wasm_wide_arithmetic(enable);
+        }
 
         macro_rules! handle_conditionally_compiled {
             ($(($feature:tt, $field:tt, $method:tt))*) => ($(
diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs
index db0a4225e..e1fcb8c4f 100644
--- a/crates/cranelift/src/func_environ.rs
+++ b/crates/cranelift/src/func_environ.rs
@@ -20,10 +20,10 @@ use std::mem;
 use wasmparser::Operator;
 use wasmtime_environ::{
     BuiltinFunctionIndex, DataIndex, ElemIndex, EngineOrModuleTypeIndex, FuncIndex, GlobalIndex,
-    IndexType, Memory, MemoryIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation,
-    ModuleTypesBuilder, PtrSize, Table, TableIndex, TableStyle, Tunables, TypeConvert, TypeIndex,
-    VMOffsets, WasmCompositeType, WasmFuncType, WasmHeapTopType, WasmHeapType, WasmResult,
-    WasmValType,
+    IndexType, Memory, MemoryIndex, MemoryPlan, MemoryStyle, Module, ModuleInternedTypeIndex,
+    ModuleTranslation, ModuleTypesBuilder, PtrSize, Table, TableIndex, TableStyle, Tunables,
+    TypeConvert, TypeIndex, VMOffsets, WasmCompositeType, WasmFuncType, WasmHeapTopType,
+    WasmHeapType, WasmRefType, WasmResult, WasmValType,
 };
 use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK};
 
@@ -1129,6 +1129,57 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
         let call = builder.ins().call(libcall, &[vmctx, val]);
         *builder.func.dfg.inst_results(call).first().unwrap()
     }
+
+    fn vmshared_type_index_ty(&self) -> Type {
+        Type::int_with_byte_size(self.offsets.size_of_vmshared_type_index().into()).unwrap()
+    }
+
+    /// Given a `ModuleInternedTypeIndex`, emit code to get the corresponding
+    /// `VMSharedTypeIndex` at runtime.
+    pub(crate) fn module_interned_to_shared_ty(
+        &mut self,
+        pos: &mut FuncCursor,
+        interned_ty: ModuleInternedTypeIndex,
+    ) -> ir::Value {
+        let vmctx = self.vmctx_val(pos);
+        let pointer_type = self.pointer_type();
+        let mem_flags = ir::MemFlags::trusted().with_readonly();
+
+        // Load the base pointer of the array of `VMSharedTypeIndex`es.
+        let shared_indices = pos.ins().load(
+            pointer_type,
+            mem_flags,
+            vmctx,
+            i32::from(self.offsets.ptr.vmctx_type_ids_array()),
+        );
+
+        // Calculate the offset in that array for this type's entry.
+        let ty = self.vmshared_type_index_ty();
+        let offset = i32::try_from(interned_ty.as_u32().checked_mul(ty.bytes()).unwrap()).unwrap();
+
+        // Load the`VMSharedTypeIndex` that this `ModuleInternedTypeIndex` is
+        // associated with at runtime from the array.
+        pos.ins().load(ty, mem_flags, shared_indices, offset)
+    }
+
+    /// Load the associated `VMSharedTypeIndex` from inside a `*const VMFuncRef`.
+    ///
+    /// Does not check for null; just assumes that the `funcref` is a valid
+    /// pointer.
+    pub(crate) fn load_funcref_type_index(
+        &mut self,
+        pos: &mut FuncCursor,
+        mem_flags: ir::MemFlags,
+        funcref: ir::Value,
+    ) -> ir::Value {
+        let ty = self.vmshared_type_index_ty();
+        pos.ins().load(
+            ty,
+            mem_flags,
+            funcref,
+            i32::from(self.offsets.ptr.vm_func_ref_type_index()),
+        )
+    }
 }
 
 struct Call<'a, 'func, 'module_env> {
@@ -1320,7 +1371,6 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
         ty_index: TypeIndex,
         funcref_ptr: ir::Value,
     ) -> CheckIndirectCallTypeSignature {
-        let pointer_type = self.env.pointer_type();
         let table = &self.env.module.table_plans[table_index];
         let sig_id_size = self.env.offsets.size_of_vmshared_type_index();
         let sig_id_type = Type::int(u16::from(sig_id_size) * 8).unwrap();
@@ -1409,28 +1459,13 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
             }
         }
 
-        let vmctx = self.env.vmctx(self.builder.func);
-        let base = self.builder.ins().global_value(pointer_type, vmctx);
-
-        // Load the caller ID. This requires loading the `*mut VMFuncRef` base
-        // pointer from `VMContext` and then loading, based on `SignatureIndex`,
-        // the corresponding entry.
-        let mem_flags = ir::MemFlags::trusted().with_readonly();
-        let signatures = self.builder.ins().load(
-            pointer_type,
-            mem_flags,
-            base,
-            i32::from(self.env.offsets.ptr.vmctx_type_ids_array()),
-        );
-        let sig_index = self.env.module.types[ty_index];
-        let offset =
-            i32::try_from(sig_index.as_u32().checked_mul(sig_id_type.bytes()).unwrap()).unwrap();
+        // Load the caller's `VMSharedTypeIndex.
+        let interned_ty = self.env.module.types[ty_index];
         let caller_sig_id = self
-            .builder
-            .ins()
-            .load(sig_id_type, mem_flags, signatures, offset);
+            .env
+            .module_interned_to_shared_ty(&mut self.builder.cursor(), interned_ty);
 
-        // Load the callee ID.
+        // Load the callee's `VMSharedTypeIndex`.
         //
         // Note that the callee may be null in which case this load may
         // trap. If so use the `TRAP_INDIRECT_CALL_TO_NULL` trap code.
@@ -1441,12 +1476,9 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
             self.env
                 .trapz(self.builder, funcref_ptr, crate::TRAP_INDIRECT_CALL_TO_NULL);
         }
-        let callee_sig_id = self.builder.ins().load(
-            sig_id_type,
-            mem_flags,
-            funcref_ptr,
-            i32::from(self.env.offsets.ptr.vm_func_ref_type_index()),
-        );
+        let callee_sig_id =
+            self.env
+                .load_funcref_type_index(&mut self.builder.cursor(), mem_flags, funcref_ptr);
 
         // Check that they match.
         let cmp = self
@@ -2162,6 +2194,15 @@ impl<'module_environment> crate::translate::FuncEnvironment
         gc::translate_array_set(self, builder, array_type_index, array, index, value)
     }
 
+    fn translate_ref_test(
+        &mut self,
+        builder: &mut FunctionBuilder<'_>,
+        ref_ty: WasmRefType,
+        gc_ref: ir::Value,
+    ) -> WasmResult<ir::Value> {
+        gc::translate_ref_test(self, builder, ref_ty, gc_ref)
+    }
+
     fn translate_ref_null(
         &mut self,
         mut pos: cranelift_codegen::cursor::FuncCursor,
diff --git a/crates/cranelift/src/gc/disabled.rs b/crates/cranelift/src/gc/disabled.rs
index c6a66617b..ebdc46b48 100644
--- a/crates/cranelift/src/gc/disabled.rs
+++ b/crates/cranelift/src/gc/disabled.rs
@@ -4,7 +4,7 @@ use super::GcCompiler;
 use crate::func_environ::FuncEnvironment;
 use cranelift_codegen::ir;
 use cranelift_frontend::FunctionBuilder;
-use wasmtime_environ::{wasm_unsupported, TypeIndex, WasmResult};
+use wasmtime_environ::{wasm_unsupported, TypeIndex, WasmRefType, WasmResult};
 
 fn disabled<T>() -> WasmResult<T> {
     Err(wasm_unsupported!(
@@ -164,3 +164,12 @@ pub fn translate_array_set(
 ) -> WasmResult<()> {
     disabled()
 }
+
+pub fn translate_ref_test(
+    _func_env: &mut FuncEnvironment<'_>,
+    _builder: &mut FunctionBuilder<'_>,
+    _ref_ty: WasmRefType,
+    _gc_ref: ir::Value,
+) -> WasmResult<ir::Value> {
+    disabled()
+}
diff --git a/crates/cranelift/src/gc/enabled.rs b/crates/cranelift/src/gc/enabled.rs
index 833f7a839..c756406c2 100644
--- a/crates/cranelift/src/gc/enabled.rs
+++ b/crates/cranelift/src/gc/enabled.rs
@@ -10,7 +10,7 @@ use cranelift_codegen::{
 use cranelift_entity::packed_option::ReservedValue;
 use cranelift_frontend::FunctionBuilder;
 use wasmtime_environ::{
-    GcArrayLayout, GcLayout, GcStructLayout, ModuleInternedTypeIndex, PtrSize, TypeIndex,
+    GcArrayLayout, GcLayout, GcStructLayout, ModuleInternedTypeIndex, PtrSize, TypeIndex, VMGcKind,
     WasmCompositeType, WasmHeapTopType, WasmHeapType, WasmRefType, WasmResult, WasmStorageType,
     WasmValType, I31_DISCRIMINANT, NON_NULL_NON_I31_MASK,
 };
@@ -826,6 +826,227 @@ pub fn translate_array_set(
     write_field_at_addr(func_env, builder, elem_ty, elem_addr, value)
 }
 
+pub fn translate_ref_test(
+    func_env: &mut FuncEnvironment<'_>,
+    builder: &mut FunctionBuilder<'_>,
+    ref_ty: WasmRefType,
+    val: ir::Value,
+) -> WasmResult<ir::Value> {
+    use crate::translate::FuncEnvironment as _;
+
+    // First special case: testing for references to bottom types.
+    if ref_ty.heap_type.is_bottom() {
+        let result = if ref_ty.nullable {
+            // All null references (within the same type hierarchy) match null
+            // references to the bottom type.
+            func_env.translate_ref_is_null(builder.cursor(), val)?
+        } else {
+            // `ref.test` is always false for non-nullable bottom types, as the
+            // bottom types are uninhabited.
+            builder.ins().iconst(ir::types::I32, 0)
+        };
+        return Ok(result);
+    }
+
+    // And because `ref.test heap_ty` is only valid on operands whose type is in
+    // the same type hierarchy as `heap_ty`, if `heap_ty` is its hierarchy's top
+    // type, we only need to worry about whether we are testing for nullability
+    // or not.
+    if ref_ty.heap_type.is_top() {
+        let result = if ref_ty.nullable {
+            builder.ins().iconst(ir::types::I32, 1)
+        } else {
+            let is_null = func_env.translate_ref_is_null(builder.cursor(), val)?;
+            let zero = builder.ins().iconst(ir::types::I32, 0);
+            let one = builder.ins().iconst(ir::types::I32, 1);
+            builder.ins().select(is_null, zero, one)
+        };
+        return Ok(result);
+    }
+
+    // `i31ref`s are a little interesting because they don't point to GC
+    // objects; we test the bit pattern of the reference itself.
+    if ref_ty.heap_type == WasmHeapType::I31 {
+        let i31_mask = builder.ins().iconst(
+            ir::types::I32,
+            i64::try_from(wasmtime_environ::I31_DISCRIMINANT).unwrap(),
+        );
+        let is_i31 = builder.ins().band(val, i31_mask);
+        let result = if ref_ty.nullable {
+            let is_null = func_env.translate_ref_is_null(builder.cursor(), val)?;
+            builder.ins().bor(is_null, is_i31)
+        } else {
+            is_i31
+        };
+        return Ok(result);
+    }
+
+    // Otherwise, in the general case, we need to inspect our given object's
+    // actual type, which also requires null-checking and i31-checking it.
+
+    let is_any_hierarchy = ref_ty.heap_type.top() == WasmHeapTopType::Any;
+
+    let non_null_block = builder.create_block();
+    let non_null_non_i31_block = builder.create_block();
+    let continue_block = builder.create_block();
+
+    // Current block: check if the reference is null and branch appropriately.
+    let is_null = func_env.translate_ref_is_null(builder.cursor(), val)?;
+    let is_null_result = if ref_ty.nullable {
+        is_null
+    } else {
+        let zero = builder.ins().iconst(ir::types::I32, 0);
+        let one = builder.ins().iconst(ir::types::I32, 1);
+        builder.ins().select(is_null, zero, one)
+    };
+    builder.ins().brif(
+        is_null,
+        continue_block,
+        &[is_null_result],
+        non_null_block,
+        &[],
+    );
+
+    // Non-null block: We know the GC ref is non-null, but we need to also check
+    // for `i31` references that don't point to GC objects.
+    builder.switch_to_block(non_null_block);
+    if is_any_hierarchy {
+        let i31_mask = builder.ins().iconst(
+            ir::types::I32,
+            i64::try_from(wasmtime_environ::I31_DISCRIMINANT).unwrap(),
+        );
+        let is_i31 = builder.ins().band(val, i31_mask);
+        // If it is an `i31`, then create the result value based on whether we
+        // want `i31`s to pass the test or not.
+        let is_i31_result = if ref_ty.heap_type == WasmHeapType::Eq {
+            is_i31
+        } else {
+            let zero = builder.ins().iconst(ir::types::I32, 0);
+            let one = builder.ins().iconst(ir::types::I32, 1);
+            builder.ins().select(is_i31, zero, one)
+        };
+        builder.ins().brif(
+            is_i31,
+            continue_block,
+            &[is_i31_result],
+            non_null_non_i31_block,
+            &[],
+        );
+    } else {
+        // If we aren't testing the `any` hierarchy, the reference cannot be an
+        // `i31ref`. Jump directly to the non-null and non-i31 block; rely on
+        // branch folding during lowering to clean this up.
+        builder.ins().jump(non_null_non_i31_block, &[]);
+    }
+
+    // Non-null and non-i31 block: Read the actual `VMGcKind` or
+    // `VMSharedTypeIndex` out of the object's header and check whether it
+    // matches the expected type.
+    builder.switch_to_block(non_null_non_i31_block);
+    let check_header_kind = |func_env: &mut FuncEnvironment<'_>,
+                             builder: &mut FunctionBuilder,
+                             val: ir::Value,
+                             expected_kind: VMGcKind|
+     -> ir::Value {
+        let header_size = builder.ins().iconst(
+            ir::types::I32,
+            i64::from(wasmtime_environ::VM_GC_HEADER_SIZE),
+        );
+        let kind_addr = func_env.prepare_gc_ref_access(
+            builder,
+            val,
+            Offset::Static(wasmtime_environ::VM_GC_HEADER_KIND_OFFSET),
+            BoundsCheck::Object(header_size),
+        );
+        let actual_kind = builder.ins().load(
+            ir::types::I32,
+            ir::MemFlags::trusted().with_readonly(),
+            kind_addr,
+            0,
+        );
+        let expected_kind = builder
+            .ins()
+            .iconst(ir::types::I32, i64::from(expected_kind.as_u32()));
+        // Inline version of `VMGcKind::matches`.
+        let and = builder.ins().band(actual_kind, expected_kind);
+        let kind_matches = builder
+            .ins()
+            .icmp(ir::condcodes::IntCC::Equal, and, expected_kind);
+        builder.ins().uextend(ir::types::I32, kind_matches)
+    };
+    let result = match ref_ty.heap_type {
+        WasmHeapType::Any
+        | WasmHeapType::None
+        | WasmHeapType::Extern
+        | WasmHeapType::NoExtern
+        | WasmHeapType::Func
+        | WasmHeapType::NoFunc
+        | WasmHeapType::I31 => unreachable!("handled top, bottom, and i31 types above"),
+
+        // For these abstract but non-top and non-bottom types, we check the
+        // `VMGcKind` that is in the object's header.
+        WasmHeapType::Eq => check_header_kind(func_env, builder, val, VMGcKind::EqRef),
+        WasmHeapType::Struct => check_header_kind(func_env, builder, val, VMGcKind::StructRef),
+        WasmHeapType::Array => check_header_kind(func_env, builder, val, VMGcKind::ArrayRef),
+
+        // For concrete types, we need to do a full subtype check between the
+        // `VMSharedTypeIndex` in the object's header and the
+        // `ModuleInternedTypeIndex` we have here.
+        //
+        // TODO: This check should ideally be done inline, but we don't have a
+        // good way to access the `TypeRegistry`'s supertypes arrays from Wasm
+        // code at the moment.
+        WasmHeapType::ConcreteArray(ty) | WasmHeapType::ConcreteStruct(ty) => {
+            let expected_interned_ty = ty.unwrap_module_type_index();
+            let expected_shared_ty =
+                func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty);
+
+            let ty_addr = func_env.prepare_gc_ref_access(
+                builder,
+                val,
+                Offset::Static(wasmtime_environ::VM_GC_HEADER_TYPE_INDEX_OFFSET),
+                BoundsCheck::Access(wasmtime_environ::VM_GC_HEADER_SIZE),
+            );
+            let actual_shared_ty = builder.ins().load(
+                ir::types::I32,
+                ir::MemFlags::trusted().with_readonly(),
+                ty_addr,
+                0,
+            );
+
+            func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)
+        }
+
+        // Same as for concrete arrays and structs except that a `VMFuncRef`
+        // doesn't begin with a `VMGcHeader` and is a raw pointer rather than GC
+        // heap index.
+        WasmHeapType::ConcreteFunc(ty) => {
+            let expected_interned_ty = ty.unwrap_module_type_index();
+            let expected_shared_ty =
+                func_env.module_interned_to_shared_ty(&mut builder.cursor(), expected_interned_ty);
+
+            let actual_shared_ty = func_env.load_funcref_type_index(
+                &mut builder.cursor(),
+                ir::MemFlags::trusted().with_readonly(),
+                val,
+            );
+
+            func_env.is_subtype(builder, actual_shared_ty, expected_shared_ty)
+        }
+    };
+    builder.ins().jump(continue_block, &[result]);
+
+    // Control flow join point with the result.
+    builder.switch_to_block(continue_block);
+    let result = builder.append_block_param(continue_block, ir::types::I32);
+
+    builder.seal_block(non_null_block);
+    builder.seal_block(non_null_non_i31_block);
+    builder.seal_block(continue_block);
+
+    Ok(result)
+}
+
 /// A static or dynamic offset from a GC reference.
 enum Offset {
     /// A static offset from a GC reference.
@@ -1065,4 +1286,39 @@ impl FuncEnvironment<'_> {
             }
         }
     }
+
+    // Emit code to check whether `a <: b` for two `VMSharedTypeIndex`es.
+    pub(crate) fn is_subtype(
+        &mut self,
+        builder: &mut FunctionBuilder<'_>,
+        a: ir::Value,
+        b: ir::Value,
+    ) -> ir::Value {
+        let diff_tys_block = builder.create_block();
+        let continue_block = builder.create_block();
+
+        // Current block: fast path for when `a == b`.
+        let same_ty = builder.ins().icmp(IntCC::Equal, a, b);
+        let same_ty = builder.ins().uextend(ir::types::I32, same_ty);
+        builder
+            .ins()
+            .brif(same_ty, continue_block, &[same_ty], diff_tys_block, &[]);
+
+        // Different types block: fall back to the `is_subtype` libcall.
+        builder.switch_to_block(diff_tys_block);
+        let is_subtype = self.builtin_functions.is_subtype(builder.func);
+        let vmctx = self.vmctx_val(&mut builder.cursor());
+        let call_inst = builder.ins().call(is_subtype, &[vmctx, a, b]);
+        let result = builder.func.dfg.first_result(call_inst);
+        builder.ins().jump(continue_block, &[result]);
+
+        // Continue block: join point for the result.
+        builder.switch_to_block(continue_block);
+        let result = builder.append_block_param(continue_block, ir::types::I32);
+
+        builder.seal_block(diff_tys_block);
+        builder.seal_block(continue_block);
+
+        result
+    }
 }
diff --git a/crates/cranelift/src/translate/code_translator.rs b/crates/cranelift/src/translate/code_translator.rs
index c332edb22..e69b30e15 100644
--- a/crates/cranelift/src/translate/code_translator.rs
+++ b/crates/cranelift/src/translate/code_translator.rs
@@ -94,7 +94,7 @@ use std::vec::Vec;
 use wasmparser::{FuncValidator, MemArg, Operator, WasmModuleResources};
 use wasmtime_environ::{
     wasm_unsupported, DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex,
-    TypeIndex, WasmResult,
+    TypeIndex, WasmRefType, WasmResult,
 };
 
 /// Given a `Reachability<T>`, unwrap the inner `T` or, when unreachable, set
@@ -2729,10 +2729,34 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
             let eq = builder.ins().uextend(ir::types::I32, eq);
             state.push1(eq);
         }
+        Operator::RefTestNonNull { hty } => {
+            let r = state.pop1();
+            let heap_type = environ.convert_heap_type(*hty);
+            let result = environ.translate_ref_test(
+                builder,
+                WasmRefType {
+                    heap_type,
+                    nullable: false,
+                },
+                r,
+            )?;
+            state.push1(result);
+        }
+        Operator::RefTestNullable { hty } => {
+            let r = state.pop1();
+            let heap_type = environ.convert_heap_type(*hty);
+            let result = environ.translate_ref_test(
+                builder,
+                WasmRefType {
+                    heap_type,
+                    nullable: true,
+                },
+                r,
+            )?;
+            state.push1(result);
+        }
 
-        Operator::RefTestNonNull { .. }
-        | Operator::RefTestNullable { .. }
-        | Operator::RefCastNonNull { .. }
+        Operator::RefCastNonNull { .. }
         | Operator::RefCastNullable { .. }
         | Operator::BrOnCast { .. }
         | Operator::BrOnCastFail { .. }
@@ -2792,6 +2816,39 @@ pub fn translate_operator<FE: FuncEnvironment + ?Sized>(
                 "stack-switching operators are not yet implemented"
             ));
         }
+
+        Operator::I64MulWideS => {
+            let (arg1, arg2) = state.pop2();
+            let arg1 = builder.ins().sextend(I128, arg1);
+            let arg2 = builder.ins().sextend(I128, arg2);
+            let result = builder.ins().imul(arg1, arg2);
+            let (lo, hi) = builder.ins().isplit(result);
+            state.push2(lo, hi);
+        }
+        Operator::I64MulWideU => {
+            let (arg1, arg2) = state.pop2();
+            let arg1 = builder.ins().uextend(I128, arg1);
+            let arg2 = builder.ins().uextend(I128, arg2);
+            let result = builder.ins().imul(arg1, arg2);
+            let (lo, hi) = builder.ins().isplit(result);
+            state.push2(lo, hi);
+        }
+        Operator::I64Add128 => {
+            let (arg1, arg2, arg3, arg4) = state.pop4();
+            let arg1 = builder.ins().iconcat(arg1, arg2);
+            let arg2 = builder.ins().iconcat(arg3, arg4);
+            let result = builder.ins().iadd(arg1, arg2);
+            let (res1, res2) = builder.ins().isplit(result);
+            state.push2(res1, res2);
+        }
+        Operator::I64Sub128 => {
+            let (arg1, arg2, arg3, arg4) = state.pop4();
+            let arg1 = builder.ins().iconcat(arg1, arg2);
+            let arg2 = builder.ins().iconcat(arg3, arg4);
+            let result = builder.ins().isub(arg1, arg2);
+            let (res1, res2) = builder.ins().isplit(result);
+            state.push2(res1, res2);
+        }
     };
     Ok(())
 }
diff --git a/crates/cranelift/src/translate/environ/spec.rs b/crates/cranelift/src/translate/environ/spec.rs
index a4803e16f..2225e4890 100644
--- a/crates/cranelift/src/translate/environ/spec.rs
+++ b/crates/cranelift/src/translate/environ/spec.rs
@@ -18,7 +18,7 @@ use smallvec::SmallVec;
 use wasmparser::Operator;
 use wasmtime_environ::{
     DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TypeConvert, TypeIndex,
-    WasmHeapType, WasmResult,
+    WasmHeapType, WasmRefType, WasmResult,
 };
 
 /// The value of a WebAssembly global variable.
@@ -701,6 +701,14 @@ pub trait FuncEnvironment: TargetEnvironment {
         value: ir::Value,
     ) -> WasmResult<()>;
 
+    /// Translate a `ref.test` instruction.
+    fn translate_ref_test(
+        &mut self,
+        builder: &mut FunctionBuilder<'_>,
+        ref_ty: WasmRefType,
+        gc_ref: ir::Value,
+    ) -> WasmResult<ir::Value>;
+
     /// Emit code at the beginning of every wasm loop.
     ///
     /// This can be used to insert explicit interrupt or safepoint checking at
diff --git a/crates/cranelift/src/translate/state.rs b/crates/cranelift/src/translate/state.rs
index 5f67f9009..155369e5e 100644
--- a/crates/cranelift/src/translate/state.rs
+++ b/crates/cranelift/src/translate/state.rs
@@ -295,6 +295,12 @@ impl FuncTranslationState {
         self.stack.push(val);
     }
 
+    /// Push two values.
+    pub(crate) fn push2(&mut self, val1: Value, val2: Value) {
+        self.stack.push(val1);
+        self.stack.push(val2);
+    }
+
     /// Push multiple values.
     pub(crate) fn pushn(&mut self, vals: &[Value]) {
         self.stack.extend_from_slice(vals);
diff --git a/crates/environ/Cargo.toml b/crates/environ/Cargo.toml
index b7d83c13c..c12593ec0 100644
--- a/crates/environ/Cargo.toml
+++ b/crates/environ/Cargo.toml
@@ -48,7 +48,11 @@ name = "factc"
 required-features = ['component-model', 'compile']
 
 [features]
-component-model = ["dep:wasmtime-component-util", "dep:semver"]
+component-model = [
+  "dep:wasmtime-component-util",
+  "dep:semver",
+  "wasmparser/component-model",
+]
 demangle = ['std', 'dep:rustc-demangle', 'dep:cpp_demangle']
 gc = []
 compile = [
diff --git a/crates/environ/examples/factc.rs b/crates/environ/examples/factc.rs
index ac55c4296..2c692a926 100644
--- a/crates/environ/examples/factc.rs
+++ b/crates/environ/examples/factc.rs
@@ -127,7 +127,7 @@ impl Factc {
         let wasm_types = wasm_types.as_ref();
         for i in 0..wasm_types.component_type_count() {
             let ty = match wasm_types.component_any_type_at(i) {
-                wasmparser::types::ComponentAnyTypeId::Func(id) => id,
+                wasmparser::component_types::ComponentAnyTypeId::Func(id) => id,
                 _ => continue,
             };
             let ty = types.convert_component_func_type(wasm_types, ty)?;
diff --git a/crates/environ/fuzz/fuzz_targets/fact-valid-module.rs b/crates/environ/fuzz/fuzz_targets/fact-valid-module.rs
index cb7d85e02..2995aab75 100644
--- a/crates/environ/fuzz/fuzz_targets/fact-valid-module.rs
+++ b/crates/environ/fuzz/fuzz_targets/fact-valid-module.rs
@@ -12,7 +12,7 @@
 use arbitrary::Unstructured;
 use component_fuzz_util::{TestCase, Type, MAX_TYPE_DEPTH};
 use libfuzzer_sys::fuzz_target;
-use wasmparser::types::ComponentAnyTypeId;
+use wasmparser::component_types::ComponentAnyTypeId;
 use wasmparser::{Parser, Payload, Validator, WasmFeatures};
 use wasmtime_environ::component::*;
 use wasmtime_environ::fact::Module;
diff --git a/crates/environ/src/builtin.rs b/crates/environ/src/builtin.rs
index 65e035135..104c716d5 100644
--- a/crates/environ/src/builtin.rs
+++ b/crates/environ/src/builtin.rs
@@ -179,6 +179,15 @@ macro_rules! foreach_builtin_function {
                 len: i32
             );
 
+            // Returns whether `actual_engine_type` is a subtype of
+            // `expected_engine_type`.
+            #[cfg(feature = "gc")]
+            is_subtype(
+                vmctx: vmctx,
+                actual_engine_type: i32,
+                expected_engine_type: i32
+            ) -> i32;
+
             // Returns an index for Wasm's `table.grow` instruction for GC references.
             #[cfg(feature = "gc")]
             table_grow_gc_ref(vmctx: vmctx, table: i32, delta: i64, init: reference) -> pointer;
diff --git a/crates/environ/src/compile/module_environ.rs b/crates/environ/src/compile/module_environ.rs
index 416384b04..6a9783635 100644
--- a/crates/environ/src/compile/module_environ.rs
+++ b/crates/environ/src/compile/module_environ.rs
@@ -239,7 +239,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
             Payload::TypeSection(types) => {
                 self.validator.type_section(&types)?;
 
-                let count = self.validator.types(0).unwrap().core_type_count();
+                let count = self.validator.types(0).unwrap().core_type_count_in_module();
                 log::trace!("interning {count} Wasm types");
 
                 let capacity = usize::try_from(count).unwrap();
@@ -262,7 +262,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> {
                     // Get the rec group for the current type index, which is
                     // always the first type defined in a rec group.
                     log::trace!("looking up wasmparser type for index {type_index}");
-                    let core_type_id = validator_types.core_type_at(type_index).unwrap_sub();
+                    let core_type_id = validator_types.core_type_at_in_module(type_index);
                     log::trace!(
                         "  --> {core_type_id:?} = {:?}",
                         validator_types[core_type_id],
diff --git a/crates/environ/src/component/dfg.rs b/crates/environ/src/component/dfg.rs
index 1bf88819f..a2a78c564 100644
--- a/crates/environ/src/component/dfg.rs
+++ b/crates/environ/src/component/dfg.rs
@@ -35,7 +35,7 @@ use indexmap::IndexMap;
 use std::collections::HashMap;
 use std::hash::Hash;
 use std::ops::Index;
-use wasmparser::types::ComponentCoreModuleTypeId;
+use wasmparser::component_types::ComponentCoreModuleTypeId;
 
 #[derive(Default)]
 #[allow(missing_docs)]
diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs
index ee7b9b89e..3b8b92cc1 100644
--- a/crates/environ/src/component/translate.rs
+++ b/crates/environ/src/component/translate.rs
@@ -12,10 +12,11 @@ use anyhow::{bail, Result};
 use indexmap::IndexMap;
 use std::collections::HashMap;
 use std::mem;
-use wasmparser::types::{
+use wasmparser::component_types::{
     AliasableResourceId, ComponentCoreModuleTypeId, ComponentEntityType, ComponentFuncTypeId,
-    ComponentInstanceTypeId, Types,
+    ComponentInstanceTypeId,
 };
+use wasmparser::types::Types;
 use wasmparser::{Chunk, ComponentImportName, Encoding, Parser, Payload, Validator};
 
 mod adapt;
diff --git a/crates/environ/src/component/translate/inline.rs b/crates/environ/src/component/translate/inline.rs
index 6953199bb..b75b0509a 100644
--- a/crates/environ/src/component/translate/inline.rs
+++ b/crates/environ/src/component/translate/inline.rs
@@ -48,7 +48,7 @@
 use crate::component::translate::*;
 use crate::{EntityType, IndexType};
 use std::borrow::Cow;
-use wasmparser::types::{ComponentAnyTypeId, ComponentCoreModuleTypeId};
+use wasmparser::component_types::{ComponentAnyTypeId, ComponentCoreModuleTypeId};
 
 pub(super) fn run(
     types: &mut ComponentTypesBuilder,
diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs
index 8d53d9c09..6d99e1f55 100644
--- a/crates/environ/src/component/types.rs
+++ b/crates/environ/src/component/types.rs
@@ -4,7 +4,7 @@ use crate::{EntityType, ModuleInternedTypeIndex, ModuleTypes, PrimaryMap};
 use core::hash::{Hash, Hasher};
 use core::ops::Index;
 use serde_derive::{Deserialize, Serialize};
-use wasmparser::types;
+use wasmparser::component_types::ComponentAnyTypeId;
 use wasmtime_component_util::{DiscriminantSize, FlagsSize};
 
 pub use crate::StaticModuleIndex;
@@ -214,7 +214,7 @@ pub enum ComponentItem {
     Module(ModuleIndex),
     Component(ComponentIndex),
     ComponentInstance(ComponentInstanceIndex),
-    Type(types::ComponentAnyTypeId),
+    Type(ComponentAnyTypeId),
 }
 
 /// Runtime information about the type information contained within a component.
diff --git a/crates/environ/src/component/types_builder.rs b/crates/environ/src/component/types_builder.rs
index a26c8ffc5..c43b65616 100644
--- a/crates/environ/src/component/types_builder.rs
+++ b/crates/environ/src/component/types_builder.rs
@@ -9,8 +9,14 @@ use cranelift_entity::EntityRef;
 use std::collections::HashMap;
 use std::hash::Hash;
 use std::ops::Index;
+use wasmparser::component_types::{
+    ComponentAnyTypeId, ComponentCoreModuleTypeId, ComponentDefinedType, ComponentDefinedTypeId,
+    ComponentEntityType, ComponentFuncTypeId, ComponentInstanceTypeId, ComponentTypeId,
+    ComponentValType, RecordType, ResourceId, TupleType, VariantType,
+};
 use wasmparser::names::KebabString;
-use wasmparser::{types, Validator};
+use wasmparser::types::TypesRef;
+use wasmparser::Validator;
 use wasmtime_component_util::FlagsSize;
 
 mod resources;
@@ -193,8 +199,8 @@ impl ComponentTypesBuilder {
     /// representation.
     pub fn convert_component_func_type(
         &mut self,
-        types: types::TypesRef<'_>,
-        id: types::ComponentFuncTypeId,
+        types: TypesRef<'_>,
+        id: ComponentFuncTypeId,
     ) -> Result<TypeFuncIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let ty = &types[id];
@@ -219,66 +225,56 @@ impl ComponentTypesBuilder {
     /// representation.
     pub fn convert_component_entity_type(
         &mut self,
-        types: types::TypesRef<'_>,
-        ty: types::ComponentEntityType,
+        types: TypesRef<'_>,
+        ty: ComponentEntityType,
     ) -> Result<TypeDef> {
         assert_eq!(types.id(), self.module_types.validator_id());
         Ok(match ty {
-            types::ComponentEntityType::Module(id) => {
-                TypeDef::Module(self.convert_module(types, id)?)
-            }
-            types::ComponentEntityType::Component(id) => {
+            ComponentEntityType::Module(id) => TypeDef::Module(self.convert_module(types, id)?),
+            ComponentEntityType::Component(id) => {
                 TypeDef::Component(self.convert_component(types, id)?)
             }
-            types::ComponentEntityType::Instance(id) => {
+            ComponentEntityType::Instance(id) => {
                 TypeDef::ComponentInstance(self.convert_instance(types, id)?)
             }
-            types::ComponentEntityType::Func(id) => {
+            ComponentEntityType::Func(id) => {
                 TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?)
             }
-            types::ComponentEntityType::Type { created, .. } => match created {
-                types::ComponentAnyTypeId::Defined(id) => {
+            ComponentEntityType::Type { created, .. } => match created {
+                ComponentAnyTypeId::Defined(id) => {
                     TypeDef::Interface(self.defined_type(types, id)?)
                 }
-                types::ComponentAnyTypeId::Resource(id) => {
+                ComponentAnyTypeId::Resource(id) => {
                     TypeDef::Resource(self.resource_id(id.resource()))
                 }
                 _ => bail!("unsupported type export"),
             },
-            types::ComponentEntityType::Value(_) => bail!("values not supported"),
+            ComponentEntityType::Value(_) => bail!("values not supported"),
         })
     }
 
     /// Converts a wasmparser `Type` into Wasmtime's type representation.
-    pub fn convert_type(
-        &mut self,
-        types: types::TypesRef<'_>,
-        id: types::ComponentAnyTypeId,
-    ) -> Result<TypeDef> {
+    pub fn convert_type(&mut self, types: TypesRef<'_>, id: ComponentAnyTypeId) -> Result<TypeDef> {
         assert_eq!(types.id(), self.module_types.validator_id());
         Ok(match id {
-            types::ComponentAnyTypeId::Defined(id) => {
-                TypeDef::Interface(self.defined_type(types, id)?)
-            }
-            types::ComponentAnyTypeId::Component(id) => {
+            ComponentAnyTypeId::Defined(id) => TypeDef::Interface(self.defined_type(types, id)?),
+            ComponentAnyTypeId::Component(id) => {
                 TypeDef::Component(self.convert_component(types, id)?)
             }
-            types::ComponentAnyTypeId::Instance(id) => {
+            ComponentAnyTypeId::Instance(id) => {
                 TypeDef::ComponentInstance(self.convert_instance(types, id)?)
             }
-            types::ComponentAnyTypeId::Func(id) => {
+            ComponentAnyTypeId::Func(id) => {
                 TypeDef::ComponentFunc(self.convert_component_func_type(types, id)?)
             }
-            types::ComponentAnyTypeId::Resource(id) => {
-                TypeDef::Resource(self.resource_id(id.resource()))
-            }
+            ComponentAnyTypeId::Resource(id) => TypeDef::Resource(self.resource_id(id.resource())),
         })
     }
 
     fn convert_component(
         &mut self,
-        types: types::TypesRef<'_>,
-        id: types::ComponentTypeId,
+        types: TypesRef<'_>,
+        id: ComponentTypeId,
     ) -> Result<TypeComponentIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let ty = &types[id];
@@ -300,8 +296,8 @@ impl ComponentTypesBuilder {
 
     pub(crate) fn convert_instance(
         &mut self,
-        types: types::TypesRef<'_>,
-        id: types::ComponentInstanceTypeId,
+        types: TypesRef<'_>,
+        id: ComponentInstanceTypeId,
     ) -> Result<TypeComponentInstanceIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let ty = &types[id];
@@ -317,8 +313,8 @@ impl ComponentTypesBuilder {
 
     pub(crate) fn convert_module(
         &mut self,
-        types: types::TypesRef<'_>,
-        id: types::ComponentCoreModuleTypeId,
+        types: TypesRef<'_>,
+        id: ComponentCoreModuleTypeId,
     ) -> Result<TypeModuleIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let ty = &types[id];
@@ -339,54 +335,48 @@ impl ComponentTypesBuilder {
 
     fn entity_type(
         &mut self,
-        types: types::TypesRef<'_>,
-        ty: &types::EntityType,
+        types: TypesRef<'_>,
+        ty: &wasmparser::types::EntityType,
     ) -> Result<EntityType> {
+        use wasmparser::types::EntityType::*;
+
         assert_eq!(types.id(), self.module_types.validator_id());
         Ok(match ty {
-            types::EntityType::Func(id) => EntityType::Function({
+            Func(id) => EntityType::Function({
                 let module = Module::default();
                 self.module_types_builder_mut()
                     .intern_type(&module, types, *id)?
                     .into()
             }),
-            types::EntityType::Table(ty) => EntityType::Table(self.convert_table_type(ty)?),
-            types::EntityType::Memory(ty) => EntityType::Memory((*ty).into()),
-            types::EntityType::Global(ty) => EntityType::Global(self.convert_global_type(ty)),
-            types::EntityType::Tag(_) => bail!("exceptions proposal not implemented"),
+            Table(ty) => EntityType::Table(self.convert_table_type(ty)?),
+            Memory(ty) => EntityType::Memory((*ty).into()),
+            Global(ty) => EntityType::Global(self.convert_global_type(ty)),
+            Tag(_) => bail!("exceptions proposal not implemented"),
         })
     }
 
     fn defined_type(
         &mut self,
-        types: types::TypesRef<'_>,
-        id: types::ComponentDefinedTypeId,
+        types: TypesRef<'_>,
+        id: ComponentDefinedTypeId,
     ) -> Result<InterfaceType> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let ret = match &types[id] {
-            types::ComponentDefinedType::Primitive(ty) => ty.into(),
-            types::ComponentDefinedType::Record(e) => {
-                InterfaceType::Record(self.record_type(types, e)?)
-            }
-            types::ComponentDefinedType::Variant(e) => {
+            ComponentDefinedType::Primitive(ty) => ty.into(),
+            ComponentDefinedType::Record(e) => InterfaceType::Record(self.record_type(types, e)?),
+            ComponentDefinedType::Variant(e) => {
                 InterfaceType::Variant(self.variant_type(types, e)?)
             }
-            types::ComponentDefinedType::List(e) => InterfaceType::List(self.list_type(types, e)?),
-            types::ComponentDefinedType::Tuple(e) => {
-                InterfaceType::Tuple(self.tuple_type(types, e)?)
-            }
-            types::ComponentDefinedType::Flags(e) => InterfaceType::Flags(self.flags_type(e)),
-            types::ComponentDefinedType::Enum(e) => InterfaceType::Enum(self.enum_type(e)),
-            types::ComponentDefinedType::Option(e) => {
-                InterfaceType::Option(self.option_type(types, e)?)
-            }
-            types::ComponentDefinedType::Result { ok, err } => {
+            ComponentDefinedType::List(e) => InterfaceType::List(self.list_type(types, e)?),
+            ComponentDefinedType::Tuple(e) => InterfaceType::Tuple(self.tuple_type(types, e)?),
+            ComponentDefinedType::Flags(e) => InterfaceType::Flags(self.flags_type(e)),
+            ComponentDefinedType::Enum(e) => InterfaceType::Enum(self.enum_type(e)),
+            ComponentDefinedType::Option(e) => InterfaceType::Option(self.option_type(types, e)?),
+            ComponentDefinedType::Result { ok, err } => {
                 InterfaceType::Result(self.result_type(types, ok, err)?)
             }
-            types::ComponentDefinedType::Own(r) => {
-                InterfaceType::Own(self.resource_id(r.resource()))
-            }
-            types::ComponentDefinedType::Borrow(r) => {
+            ComponentDefinedType::Own(r) => InterfaceType::Own(self.resource_id(r.resource())),
+            ComponentDefinedType::Borrow(r) => {
                 InterfaceType::Borrow(self.resource_id(r.resource()))
             }
         };
@@ -397,23 +387,15 @@ impl ComponentTypesBuilder {
         Ok(ret)
     }
 
-    fn valtype(
-        &mut self,
-        types: types::TypesRef<'_>,
-        ty: &types::ComponentValType,
-    ) -> Result<InterfaceType> {
+    fn valtype(&mut self, types: TypesRef<'_>, ty: &ComponentValType) -> Result<InterfaceType> {
         assert_eq!(types.id(), self.module_types.validator_id());
         match ty {
-            types::ComponentValType::Primitive(p) => Ok(p.into()),
-            types::ComponentValType::Type(id) => self.defined_type(types, *id),
+            ComponentValType::Primitive(p) => Ok(p.into()),
+            ComponentValType::Type(id) => self.defined_type(types, *id),
         }
     }
 
-    fn record_type(
-        &mut self,
-        types: types::TypesRef<'_>,
-        ty: &types::RecordType,
-    ) -> Result<TypeRecordIndex> {
+    fn record_type(&mut self, types: TypesRef<'_>, ty: &RecordType) -> Result<TypeRecordIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let fields = ty
             .fields
@@ -433,11 +415,7 @@ impl ComponentTypesBuilder {
         Ok(self.add_record_type(TypeRecord { fields, abi }))
     }
 
-    fn variant_type(
-        &mut self,
-        types: types::TypesRef<'_>,
-        ty: &types::VariantType,
-    ) -> Result<TypeVariantIndex> {
+    fn variant_type(&mut self, types: TypesRef<'_>, ty: &VariantType) -> Result<TypeVariantIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let cases = ty
             .cases
@@ -465,11 +443,7 @@ impl ComponentTypesBuilder {
         Ok(self.add_variant_type(TypeVariant { cases, abi, info }))
     }
 
-    fn tuple_type(
-        &mut self,
-        types: types::TypesRef<'_>,
-        ty: &types::TupleType,
-    ) -> Result<TypeTupleIndex> {
+    fn tuple_type(&mut self, types: TypesRef<'_>, ty: &TupleType) -> Result<TypeTupleIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let types = ty
             .types
@@ -507,8 +481,8 @@ impl ComponentTypesBuilder {
 
     fn option_type(
         &mut self,
-        types: types::TypesRef<'_>,
-        ty: &types::ComponentValType,
+        types: TypesRef<'_>,
+        ty: &ComponentValType,
     ) -> Result<TypeOptionIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let ty = self.valtype(types, ty)?;
@@ -518,9 +492,9 @@ impl ComponentTypesBuilder {
 
     fn result_type(
         &mut self,
-        types: types::TypesRef<'_>,
-        ok: &Option<types::ComponentValType>,
-        err: &Option<types::ComponentValType>,
+        types: TypesRef<'_>,
+        ok: &Option<ComponentValType>,
+        err: &Option<ComponentValType>,
     ) -> Result<TypeResultIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let ok = match ok {
@@ -538,11 +512,7 @@ impl ComponentTypesBuilder {
         Ok(self.add_result_type(TypeResult { ok, err, abi, info }))
     }
 
-    fn list_type(
-        &mut self,
-        types: types::TypesRef<'_>,
-        ty: &types::ComponentValType,
-    ) -> Result<TypeListIndex> {
+    fn list_type(&mut self, types: TypesRef<'_>, ty: &ComponentValType) -> Result<TypeListIndex> {
         assert_eq!(types.id(), self.module_types.validator_id());
         let element = self.valtype(types, ty)?;
         Ok(self.add_list_type(TypeList { element }))
@@ -550,7 +520,7 @@ impl ComponentTypesBuilder {
 
     /// Converts a wasmparser `id`, which must point to a resource, to its
     /// corresponding `TypeResourceTableIndex`.
-    pub fn resource_id(&mut self, id: types::ResourceId) -> TypeResourceTableIndex {
+    pub fn resource_id(&mut self, id: ResourceId) -> TypeResourceTableIndex {
         self.resources.convert(id, &mut self.component_types)
     }
 
diff --git a/crates/environ/src/component/types_builder/resources.rs b/crates/environ/src/component/types_builder/resources.rs
index fc9e07605..d02426d49 100644
--- a/crates/environ/src/component/types_builder/resources.rs
+++ b/crates/environ/src/component/types_builder/resources.rs
@@ -73,7 +73,8 @@ use crate::component::{
 };
 use crate::prelude::*;
 use std::collections::HashMap;
-use wasmparser::types;
+use wasmparser::component_types::{ComponentAnyTypeId, ComponentEntityType, ResourceId};
+use wasmparser::types::TypesRef;
 
 /// Builder state used to translate wasmparser's `ResourceId` types to
 /// Wasmtime's `TypeResourceTableIndex` type.
@@ -111,7 +112,7 @@ pub struct ResourcesBuilder {
     /// A cache of previously visited `ResourceId` items and which table they
     /// correspond to. This is lazily populated as resources are visited and is
     /// exclusively used by the `convert` function below.
-    resource_id_to_table_index: HashMap<types::ResourceId, TypeResourceTableIndex>,
+    resource_id_to_table_index: HashMap<ResourceId, TypeResourceTableIndex>,
 
     /// A cache of the origin resource type behind a `ResourceId`.
     ///
@@ -121,7 +122,7 @@ pub struct ResourcesBuilder {
     /// phase. This is used to record the actual underlying type of a resource
     /// and where it originally comes from. When a resource is later referred to
     /// then a table is injected to be referred to.
-    resource_id_to_resource_index: HashMap<types::ResourceId, ResourceIndex>,
+    resource_id_to_resource_index: HashMap<ResourceId, ResourceIndex>,
 
     /// The current instance index that's being visited. This is updated as
     /// inliner frames are processed and components are instantiated.
@@ -144,7 +145,7 @@ impl ResourcesBuilder {
     /// any component it's assigned a new table, which is exactly what we want.
     pub fn convert(
         &mut self,
-        id: types::ResourceId,
+        id: ResourceId,
         types: &mut ComponentTypes,
     ) -> TypeResourceTableIndex {
         *self
@@ -174,8 +175,8 @@ impl ResourcesBuilder {
     /// resource type is expected.
     pub fn register_component_entity_type<'a>(
         &mut self,
-        types: &'a types::TypesRef<'_>,
-        ty: types::ComponentEntityType,
+        types: &'a TypesRef<'_>,
+        ty: ComponentEntityType,
         path: &mut Vec<&'a str>,
         register: &mut dyn FnMut(&[&'a str]) -> ResourceIndex,
     ) {
@@ -185,8 +186,8 @@ impl ResourcesBuilder {
             // with the current path and that's inserted in to
             // `resource_id_to_resource_index` if the resource hasn't been seen
             // yet.
-            types::ComponentEntityType::Type {
-                created: types::ComponentAnyTypeId::Resource(id),
+            ComponentEntityType::Type {
+                created: ComponentAnyTypeId::Resource(id),
                 ..
             } => {
                 self.resource_id_to_resource_index
@@ -197,7 +198,7 @@ impl ResourcesBuilder {
             // Resources can be imported/defined through exports of instances so
             // all instance exports are walked here. Note the management of
             // `path` which is used for the recursive invocation of this method.
-            types::ComponentEntityType::Instance(id) => {
+            ComponentEntityType::Instance(id) => {
                 let ty = &types[id];
                 for (name, ty) in ty.exports.iter() {
                     path.push(name);
@@ -208,11 +209,11 @@ impl ResourcesBuilder {
 
             // None of these items can introduce a new component type, so
             // there's no need to recurse over these.
-            types::ComponentEntityType::Func(_)
-            | types::ComponentEntityType::Type { .. }
-            | types::ComponentEntityType::Module(_)
-            | types::ComponentEntityType::Component(_)
-            | types::ComponentEntityType::Value(_) => {}
+            ComponentEntityType::Func(_)
+            | ComponentEntityType::Type { .. }
+            | ComponentEntityType::Module(_)
+            | ComponentEntityType::Component(_)
+            | ComponentEntityType::Value(_) => {}
         }
     }
 
@@ -220,7 +221,7 @@ impl ResourcesBuilder {
     /// defined by the `ty` provided.
     ///
     /// This is used when a local resource is defined within a component for example.
-    pub fn register_resource(&mut self, id: types::ResourceId, ty: ResourceIndex) {
+    pub fn register_resource(&mut self, id: ResourceId, ty: ResourceIndex) {
         let prev = self.resource_id_to_resource_index.insert(id, ty);
         assert!(prev.is_none());
     }
diff --git a/crates/environ/src/gc.rs b/crates/environ/src/gc.rs
index a89c0d33b..fad280759 100644
--- a/crates/environ/src/gc.rs
+++ b/crates/environ/src/gc.rs
@@ -23,6 +23,18 @@ pub const I31_DISCRIMINANT: u64 = 1;
 /// with a single bitwise-and operation.
 pub const NON_NULL_NON_I31_MASK: u64 = !I31_DISCRIMINANT;
 
+/// The size of the `VMGcHeader` in bytes.
+pub const VM_GC_HEADER_SIZE: u32 = 8;
+
+/// The minimum alignment of the `VMGcHeader` in bytes.
+pub const VM_GC_HEADER_ALIGN: u32 = 8;
+
+/// The offset of the `VMGcKind` field in the `VMGcHeader`.
+pub const VM_GC_HEADER_KIND_OFFSET: u32 = 0;
+
+/// The offset of the `VMSharedTypeIndex` field in the `VMGcHeader`.
+pub const VM_GC_HEADER_TYPE_INDEX_OFFSET: u32 = 4;
+
 /// Get the byte size of the given Wasm type when it is stored inside the GC
 /// heap.
 pub fn byte_size_of_wasm_ty_in_gc_heap(ty: &WasmStorageType) -> u32 {
diff --git a/crates/environ/src/types.rs b/crates/environ/src/types.rs
index 5d0aca7c8..0b7d64a35 100644
--- a/crates/environ/src/types.rs
+++ b/crates/environ/src/types.rs
@@ -438,6 +438,17 @@ impl From<WasmHeapTopType> for WasmHeapType {
     }
 }
 
+impl From<WasmHeapBottomType> for WasmHeapType {
+    #[inline]
+    fn from(value: WasmHeapBottomType) -> Self {
+        match value {
+            WasmHeapBottomType::NoExtern => Self::NoExtern,
+            WasmHeapBottomType::None => Self::None,
+            WasmHeapBottomType::NoFunc => Self::NoFunc,
+        }
+    }
+}
+
 impl fmt::Display for WasmHeapType {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
@@ -508,6 +519,12 @@ impl WasmHeapType {
         self.is_vmgcref_type() && *self != Self::I31
     }
 
+    /// Is this heap type the top of its type hierarchy?
+    #[inline]
+    pub fn is_top(&self) -> bool {
+        *self == Self::from(self.top())
+    }
+
     /// Get this type's top type.
     #[inline]
     pub fn top(&self) -> WasmHeapTopType {
@@ -528,6 +545,33 @@ impl WasmHeapType {
             | WasmHeapType::None => WasmHeapTopType::Any,
         }
     }
+
+    /// Is this heap type the bottom of its type hierarchy?
+    #[inline]
+    pub fn is_bottom(&self) -> bool {
+        *self == Self::from(self.bottom())
+    }
+
+    /// Get this type's bottom type.
+    #[inline]
+    pub fn bottom(&self) -> WasmHeapBottomType {
+        match self {
+            WasmHeapType::Extern | WasmHeapType::NoExtern => WasmHeapBottomType::NoExtern,
+
+            WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
+                WasmHeapBottomType::NoFunc
+            }
+
+            WasmHeapType::Any
+            | WasmHeapType::Eq
+            | WasmHeapType::I31
+            | WasmHeapType::Array
+            | WasmHeapType::ConcreteArray(_)
+            | WasmHeapType::Struct
+            | WasmHeapType::ConcreteStruct(_)
+            | WasmHeapType::None => WasmHeapBottomType::None,
+        }
+    }
 }
 
 /// A top heap type.
@@ -541,6 +585,17 @@ pub enum WasmHeapTopType {
     Func,
 }
 
+/// A bottom heap type.
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub enum WasmHeapBottomType {
+    /// The common subtype of all external references.
+    NoExtern,
+    /// The common subtype of all internal references.
+    None,
+    /// The common subtype of all function references.
+    NoFunc,
+}
+
 /// WebAssembly function type -- equivalent of `wasmparser`'s FuncType.
 #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
 pub struct WasmFuncType {
diff --git a/crates/fuzzing/src/generators/config.rs b/crates/fuzzing/src/generators/config.rs
index b1355a5b0..512912a13 100644
--- a/crates/fuzzing/src/generators/config.rs
+++ b/crates/fuzzing/src/generators/config.rs
@@ -170,6 +170,7 @@ impl Config {
             .wasm_threads(self.module_config.config.threads_enabled)
             .wasm_function_references(self.module_config.config.gc_enabled)
             .wasm_gc(self.module_config.config.gc_enabled)
+            .wasm_wide_arithmetic(self.module_config.config.wide_arithmetic_enabled)
             .native_unwind_info(cfg!(target_os = "windows") || self.wasmtime.native_unwind_info)
             .cranelift_nan_canonicalization(self.wasmtime.canonicalize_nans)
             .cranelift_opt_level(self.wasmtime.opt_level.to_wasmtime())
@@ -474,6 +475,10 @@ impl WasmtimeConfig {
         // Not fully implemented in Wasmtime and fuzzing.
         config.gc_enabled = false;
 
+        // Off-by-default in wasm-smith but implemented in wasmtime, so give the
+        // fuzzers a chance to run it.
+        config.wide_arithmetic_enabled = u.arbitrary()?;
+
         // Winch doesn't support the same set of wasm proposal as Cranelift at
         // this time, so if winch is selected be sure to disable wasm proposals
         // in `Config` to ensure that Winch can compile the module that
@@ -485,6 +490,7 @@ impl WasmtimeConfig {
             config.threads_enabled = false;
             config.tail_call_enabled = false;
             config.reference_types_enabled = false;
+            config.wide_arithmetic_enabled = false;
 
             // Winch requires host trap handlers to be enabled at this time.
             self.signals_based_traps = true;
diff --git a/crates/fuzzing/src/generators/single_inst_module.rs b/crates/fuzzing/src/generators/single_inst_module.rs
index dcad77ee1..6177d0342 100644
--- a/crates/fuzzing/src/generators/single_inst_module.rs
+++ b/crates/fuzzing/src/generators/single_inst_module.rs
@@ -163,36 +163,38 @@ impl<'a> SingleInstModule<'a> {
 // instructions compactly and allow for easier changes to the Rust code (e.g.,
 // `SingleInstModule`).
 
-macro_rules! valtype {
-    (i32) => {
+macro_rules! valtypes {
+    (@list ($($ty:tt),*)) => {&[$(valtypes!(@one $ty)),*]};
+    (@list $ty:tt) => {&[valtypes!(@one $ty)]};
+    (@one i32) => {
         ValType::I32
     };
-    (i64) => {
+    (@one i64) => {
         ValType::I64
     };
-    (f32) => {
+    (@one f32) => {
         ValType::F32
     };
-    (f64) => {
+    (@one f64) => {
         ValType::F64
     };
-    (v128) => {
+    (@one v128) => {
         ValType::V128
     };
 }
 
 macro_rules! inst {
-    ($inst:ident, ($($arguments_ty:tt),*) -> $result_ty:tt) => {
-        inst! { $inst, ($($arguments_ty),*) -> $result_ty, |_| true }
+    ($inst:ident, $arguments:tt -> $results:tt) => {
+        inst! { $inst, $arguments -> $results, |_| true }
     };
-    ($inst:ident, ($($arguments_ty:tt),*) -> $result_ty:tt, $feature:expr) => {
-        inst! { $inst, ($($arguments_ty),*) -> $result_ty, $feature, None }
+    ($inst:ident, $arguments:tt -> $results:tt, $feature:expr) => {
+        inst! { $inst, $arguments -> $results, $feature, None }
     };
-    ($inst:ident, ($($arguments_ty:tt),*) -> $result_ty:tt, $feature:expr, $nan:expr) => {
+    ($inst:ident, $arguments:tt -> $results:tt, $feature:expr, $nan:expr) => {
         SingleInstModule {
             instruction: Instruction::$inst,
-            parameters: &[$(valtype!($arguments_ty)),*],
-            results: &[valtype!($result_ty)],
+            parameters: valtypes!(@list $arguments),
+            results: valtypes!(@list $results),
             feature: $feature,
             canonicalize_nan: $nan,
         }
@@ -568,6 +570,11 @@ static INSTRUCTIONS: &[SingleInstModule] = &[
     inst!(F64x2ConvertLowI32x4U, (v128) -> v128, |c| c.config.simd_enabled),
     inst!(F32x4DemoteF64x2Zero, (v128) -> v128, |c| c.config.simd_enabled),
     inst!(F64x2PromoteLowF32x4, (v128) -> v128, |c| c.config.simd_enabled),
+    // wide arithmetic
+    inst!(I64Add128, (i64, i64, i64, i64) -> (i64, i64), |c| c.config.wide_arithmetic_enabled && c.config.multi_value_enabled),
+    inst!(I64Sub128, (i64, i64, i64, i64) -> (i64, i64), |c| c.config.wide_arithmetic_enabled && c.config.multi_value_enabled),
+    inst!(I64MulWideS, (i64, i64) -> (i64, i64), |c| c.config.wide_arithmetic_enabled && c.config.multi_value_enabled),
+    inst!(I64MulWideU, (i64, i64) -> (i64, i64), |c| c.config.wide_arithmetic_enabled && c.config.multi_value_enabled),
 ];
 
 #[cfg(test)]
diff --git a/crates/fuzzing/src/oracles/diff_spec.rs b/crates/fuzzing/src/oracles/diff_spec.rs
index df4b4f314..4a22c8cb9 100644
--- a/crates/fuzzing/src/oracles/diff_spec.rs
+++ b/crates/fuzzing/src/oracles/diff_spec.rs
@@ -25,6 +25,7 @@ impl SpecInterpreter {
         config.reference_types_enabled = false;
         config.tail_call_enabled = false;
         config.relaxed_simd_enabled = false;
+        config.wide_arithmetic_enabled = false;
 
         Self
     }
diff --git a/crates/fuzzing/src/oracles/diff_v8.rs b/crates/fuzzing/src/oracles/diff_v8.rs
index 609b388ec..c2697b906 100644
--- a/crates/fuzzing/src/oracles/diff_v8.rs
+++ b/crates/fuzzing/src/oracles/diff_v8.rs
@@ -32,6 +32,7 @@ impl V8Engine {
         config.min_memories = config.min_memories.min(1);
         config.max_memories = config.max_memories.min(1);
         config.memory64_enabled = false;
+        config.wide_arithmetic_enabled = false;
 
         Self {
             isolate: Rc::new(RefCell::new(v8::Isolate::new(Default::default()))),
diff --git a/crates/fuzzing/src/oracles/diff_wasmi.rs b/crates/fuzzing/src/oracles/diff_wasmi.rs
index 37143c77d..0c8a3d95e 100644
--- a/crates/fuzzing/src/oracles/diff_wasmi.rs
+++ b/crates/fuzzing/src/oracles/diff_wasmi.rs
@@ -20,6 +20,7 @@ impl WasmiEngine {
         config.threads_enabled = false;
         config.exceptions_enabled = false;
         config.gc_enabled = false;
+        config.wide_arithmetic_enabled = false;
         config.max_memories = config.max_memories.min(1);
         config.min_memories = config.min_memories.min(1);
 
diff --git a/crates/wasi-preview1-component-adapter/verify/src/main.rs b/crates/wasi-preview1-component-adapter/verify/src/main.rs
index d013fe1fb..f074b61f0 100644
--- a/crates/wasi-preview1-component-adapter/verify/src/main.rs
+++ b/crates/wasi-preview1-component-adapter/verify/src/main.rs
@@ -56,29 +56,9 @@ fn main() -> Result<()> {
 
             // sections that shouldn't appear in the specially-crafted core wasm
             // adapter self we're processing
-            Payload::DataCountSection { .. }
-            | Payload::ElementSection(_)
-            | Payload::DataSection(_)
-            | Payload::StartSection { .. }
-            | Payload::TagSection(_)
-            | Payload::UnknownSection { .. } => {
+            _ => {
                 bail!("unsupported section {payload:?} found in preview1.wasm")
             }
-
-            // component-model related things that shouldn't show up
-            Payload::ModuleSection { .. }
-            | Payload::ComponentSection { .. }
-            | Payload::InstanceSection(_)
-            | Payload::ComponentInstanceSection(_)
-            | Payload::ComponentAliasSection(_)
-            | Payload::ComponentCanonicalSection(_)
-            | Payload::ComponentStartSection { .. }
-            | Payload::ComponentImportSection(_)
-            | Payload::CoreTypeSection(_)
-            | Payload::ComponentExportSection(_)
-            | Payload::ComponentTypeSection(_) => {
-                bail!("component section found in preview1.wasm")
-            }
         }
     }
 
diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs
index 84d7e7367..88138bec2 100644
--- a/crates/wasmtime/src/config.rs
+++ b/crates/wasmtime/src/config.rs
@@ -818,6 +818,17 @@ impl Config {
         self
     }
 
+    /// Configures whether the [WebAssembly wide-arithmetic][proposal] will be
+    /// enabled for compilation.
+    ///
+    /// This feature is `false` by default.
+    ///
+    /// [proposal]: https://github.com/WebAssembly/wide-arithmetic
+    pub fn wasm_wide_arithmetic(&mut self, enable: bool) -> &mut Self {
+        self.wasm_feature(WasmFeatures::WIDE_ARITHMETIC, enable);
+        self
+    }
+
     /// Configures whether the [WebAssembly Garbage Collection
     /// proposal][proposal] will be enabled for compilation.
     ///
diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs
index d530c6d28..885acbcde 100644
--- a/crates/wasmtime/src/engine/serialization.rs
+++ b/crates/wasmtime/src/engine/serialization.rs
@@ -205,6 +205,7 @@ struct WasmFeatures {
     component_model_more_flags: bool,
     component_model_multiple_returns: bool,
     gc_types: bool,
+    wide_arithmetic: bool,
 }
 
 impl Metadata<'_> {
@@ -235,6 +236,7 @@ impl Metadata<'_> {
             legacy_exceptions,
             gc_types,
             stack_switching,
+            wide_arithmetic,
 
             // Always on; we don't currently have knobs for these.
             mutable_global: _,
@@ -277,6 +279,7 @@ impl Metadata<'_> {
                 component_model_more_flags,
                 component_model_multiple_returns,
                 gc_types,
+                wide_arithmetic,
             },
         }
     }
@@ -488,6 +491,7 @@ impl Metadata<'_> {
             component_model_more_flags,
             component_model_multiple_returns,
             gc_types,
+            wide_arithmetic,
         } = self.features;
 
         use wasmparser::WasmFeatures as F;
@@ -579,6 +583,11 @@ impl Metadata<'_> {
             other.contains(F::GC_TYPES),
             "support for WebAssembly gc types",
         )?;
+        Self::check_bool(
+            wide_arithmetic,
+            other.contains(F::WIDE_ARITHMETIC),
+            "WebAssembly wide-arithmetic support",
+        )?;
 
         Ok(())
     }
diff --git a/crates/wasmtime/src/runtime/type_registry.rs b/crates/wasmtime/src/runtime/type_registry.rs
index c18388ca3..95a23deaf 100644
--- a/crates/wasmtime/src/runtime/type_registry.rs
+++ b/crates/wasmtime/src/runtime/type_registry.rs
@@ -145,7 +145,9 @@ impl TypeCollection {
     /// Gets the shared type index given a module type index.
     #[inline]
     pub fn shared_type(&self, index: ModuleInternedTypeIndex) -> Option<VMSharedTypeIndex> {
-        self.types.get(index).copied()
+        let shared_ty = self.types.get(index).copied();
+        log::trace!("TypeCollection::shared_type({index:?}) -> {shared_ty:?}");
+        shared_ty
     }
 
     /// Get the module-level type index of the trampoline type for the given
diff --git a/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs b/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs
index 885dde0f8..aaf89035c 100644
--- a/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs
+++ b/crates/wasmtime/src/runtime/vm/gc/gc_ref.rs
@@ -28,9 +28,16 @@ use wasmtime_environ::{VMGcKind, VMSharedTypeIndex};
 ///     ty: Option<VMSharedTypeIndex>,
 /// }
 /// ```
-#[repr(align(8))]
+#[repr(C, align(8))]
 #[derive(Debug, Clone, Copy)]
-pub struct VMGcHeader(u64);
+pub struct VMGcHeader {
+    /// The object's `VMGcKind` and 26 bits of space reserved for however the GC
+    /// sees fit to use it.
+    kind: u32,
+
+    /// The object's type index.
+    ty: VMSharedTypeIndex,
+}
 
 unsafe impl GcHeapObject for VMGcHeader {
     #[inline]
@@ -40,34 +47,29 @@ unsafe impl GcHeapObject for VMGcHeader {
 }
 
 const _: () = {
-    assert!((VMGcHeader::HEADER_SIZE as usize) == core::mem::size_of::<VMGcHeader>());
-    assert!((VMGcHeader::HEADER_ALIGN as usize) == core::mem::align_of::<VMGcHeader>());
+    use core::mem::offset_of;
+    use wasmtime_environ::*;
+    assert!((VM_GC_HEADER_SIZE as usize) == core::mem::size_of::<VMGcHeader>());
+    assert!((VM_GC_HEADER_ALIGN as usize) == core::mem::align_of::<VMGcHeader>());
+    assert!((VM_GC_HEADER_KIND_OFFSET as usize) == offset_of!(VMGcHeader, kind));
+    assert!((VM_GC_HEADER_TYPE_INDEX_OFFSET as usize) == offset_of!(VMGcHeader, ty));
 };
 
 impl VMGcHeader {
-    /// The size of this type on all architectures.
-    pub const HEADER_SIZE: u32 = 8;
-
-    /// The alignment of this type on all architectures.
-    pub const HEADER_ALIGN: u32 = 8;
-
     /// Create the header for an `externref`.
     pub fn externref() -> Self {
         Self::from_kind_and_index(VMGcKind::ExternRef, VMSharedTypeIndex::reserved_value())
     }
 
     /// Create the header for the given kind and type index.
-    pub fn from_kind_and_index(kind: VMGcKind, index: VMSharedTypeIndex) -> Self {
-        let upper = kind as u32;
-        let upper = u64::from(upper) << 32;
-        let lower = u64::from(index.bits());
-        Self(upper | lower)
+    pub fn from_kind_and_index(kind: VMGcKind, ty: VMSharedTypeIndex) -> Self {
+        let kind = kind.as_u32();
+        Self { kind, ty }
     }
 
     /// Get the kind of GC object that this is.
     pub fn kind(&self) -> VMGcKind {
-        let upper = u32::try_from(self.0 >> 32).unwrap();
-        VMGcKind::from_high_bits_of_u32(upper)
+        VMGcKind::from_high_bits_of_u32(self.kind)
     }
 
     /// Get the reserved 26 bits in this header.
@@ -75,8 +77,7 @@ impl VMGcHeader {
     /// These are bits are reserved for `GcRuntime` implementations to make use
     /// of however they see fit.
     pub fn reserved_u26(&self) -> u32 {
-        let upper = u32::try_from(self.0 >> 32).unwrap();
-        upper & VMGcKind::UNUSED_MASK
+        self.kind & VMGcKind::UNUSED_MASK
     }
 
     /// Set the 26-bit reserved value.
@@ -89,7 +90,7 @@ impl VMGcHeader {
             VMGcKind::value_fits_in_unused_bits(value),
             "VMGcHeader::set_reserved_u26 with value using more than 26 bits"
         );
-        self.0 |= u64::from(value) << 32;
+        self.kind |= value;
     }
 
     /// Set the 26-bit reserved value.
@@ -100,33 +101,19 @@ impl VMGcHeader {
     /// be unset.
     pub unsafe fn unchecked_set_reserved_u26(&mut self, value: u32) {
         debug_assert_eq!(value & VMGcKind::MASK, 0);
-        self.0 |= u64::from(value) << 32;
+        self.kind |= value;
     }
 
     /// Get this object's specific concrete type.
     pub fn ty(&self) -> Option<VMSharedTypeIndex> {
-        let lower_mask = u64::from(u32::MAX);
-        let lower = u32::try_from(self.0 & lower_mask).unwrap();
-        if lower == u32::MAX {
+        if self.ty.is_reserved_value() {
             None
         } else {
-            Some(VMSharedTypeIndex::new(lower))
+            Some(self.ty)
         }
     }
 }
 
-#[cfg(test)]
-mod vm_gc_header_tests {
-    use super::*;
-    use std::mem;
-
-    #[test]
-    fn size_align() {
-        assert_eq!(mem::size_of::<VMGcHeader>(), 8);
-        assert_eq!(mem::align_of::<VMGcHeader>(), 8);
-    }
-}
-
 /// A raw, unrooted GC reference.
 ///
 /// A `VMGcRef` is either:
diff --git a/crates/wasmtime/src/runtime/vm/libcalls.rs b/crates/wasmtime/src/runtime/vm/libcalls.rs
index 15189d37e..bbe86f3b9 100644
--- a/crates/wasmtime/src/runtime/vm/libcalls.rs
+++ b/crates/wasmtime/src/runtime/vm/libcalls.rs
@@ -182,6 +182,13 @@ pub mod raw {
             self
         }
     }
+
+    impl LibcallResult for bool {
+        type Abi = u32;
+        unsafe fn convert(self) -> u32 {
+            self as u32
+        }
+    }
 }
 
 fn memory32_grow(
@@ -994,6 +1001,24 @@ unsafe fn array_copy(
     Ok(())
 }
 
+#[cfg(feature = "gc")]
+unsafe fn is_subtype(
+    instance: &mut Instance,
+    actual_engine_type: u32,
+    expected_engine_type: u32,
+) -> bool {
+    use wasmtime_environ::VMSharedTypeIndex;
+
+    let actual = VMSharedTypeIndex::from_u32(actual_engine_type);
+    let expected = VMSharedTypeIndex::from_u32(expected_engine_type);
+
+    (*instance.store())
+        .engine()
+        .signatures()
+        .is_subtype(actual, expected)
+        .into()
+}
+
 // Implementation of `memory.atomic.notify` for locally defined memories.
 #[cfg(feature = "threads")]
 fn memory_atomic_notify(
diff --git a/docs/stability-tiers.md b/docs/stability-tiers.md
index f19b4842a..7fedfa984 100644
--- a/docs/stability-tiers.md
+++ b/docs/stability-tiers.md
@@ -73,6 +73,7 @@ For explanations of what each tier means see below.
 | Target               | Support for `#![no_std]`   | Support beyond CI checks    |
 | WebAssembly Proposal | [`memory64`]               | Unstable wasm proposal      |
 | WebAssembly Proposal | [`function-references`]    | Unstable wasm proposal      |
+| WebAssembly Proposal | [`wide-arithmetic`]        | Unstable wasm proposal      |
 
 [`memory64`]: https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md
 [`multi-memory`]: https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md
@@ -80,6 +81,7 @@ For explanations of what each tier means see below.
 [`component-model`]: https://github.com/WebAssembly/component-model/blob/main/design/mvp/Explainer.md
 [`relaxed-simd`]: https://github.com/WebAssembly/relaxed-simd/blob/main/proposals/relaxed-simd/Overview.md
 [`function-references`]: https://github.com/WebAssembly/function-references/blob/main/proposals/function-references/Overview.md
+[`wide-arithmetic`]: https://github.com/WebAssembly/wide-arithmetic/blob/main/proposals/wide-arithmetic/Overview.md
 
 #### Tier 3
 
diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock
index 6d2a0f320..8204d0246 100644
--- a/supply-chain/imports.lock
+++ b/supply-chain/imports.lock
@@ -1909,6 +1909,12 @@ when = "2024-09-30"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasm-encoder]]
+version = "0.219.0"
+when = "2024-10-08"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasm-metadata]]
 version = "0.214.0"
 when = "2024-07-16"
@@ -1939,6 +1945,12 @@ when = "2024-09-30"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasm-metadata]]
+version = "0.219.0"
+when = "2024-10-08"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasmparser]]
 version = "0.214.0"
 when = "2024-07-16"
@@ -1969,6 +1981,12 @@ when = "2024-09-30"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasmparser]]
+version = "0.219.0"
+when = "2024-10-08"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasmprinter]]
 version = "0.214.0"
 when = "2024-07-16"
@@ -1999,6 +2017,12 @@ when = "2024-09-30"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wasmprinter]]
+version = "0.219.0"
+when = "2024-10-08"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wasmtime]]
 version = "23.0.1"
 when = "2024-07-22"
@@ -2449,6 +2473,12 @@ when = "2024-09-30"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wast]]
+version = "219.0.0"
+when = "2024-10-08"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wat]]
 version = "1.214.0"
 when = "2024-07-16"
@@ -2479,6 +2509,12 @@ when = "2024-09-30"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wat]]
+version = "1.219.0"
+when = "2024-10-08"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wiggle]]
 version = "23.0.1"
 when = "2024-07-22"
@@ -3006,6 +3042,12 @@ when = "2024-09-30"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-component]]
+version = "0.219.0"
+when = "2024-10-08"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.wit-parser]]
 version = "0.214.0"
 when = "2024-07-16"
@@ -3036,6 +3078,12 @@ when = "2024-09-30"
 user-id = 73222
 user-login = "wasmtime-publish"
 
+[[publisher.wit-parser]]
+version = "0.219.0"
+when = "2024-10-08"
+user-id = 73222
+user-login = "wasmtime-publish"
+
 [[publisher.witx]]
 version = "0.9.1"
 when = "2021-06-22"
diff --git a/winch/codegen/src/codegen/env.rs b/winch/codegen/src/codegen/env.rs
index baca13fc1..c7afe0e7f 100644
--- a/winch/codegen/src/codegen/env.rs
+++ b/winch/codegen/src/codegen/env.rs
@@ -334,6 +334,7 @@ impl<'a, 'translation, 'data, P: PtrSize> FuncEnv<'a, 'translation, 'data, P> {
         match callee {
             Callee::Local(idx) | Callee::Import(idx) => {
                 let types = self.translation.get_types();
+                let types = types.as_ref();
                 let ty = types[types.core_function_at(idx.as_u32())].unwrap_func();
                 let val = || {
                     let converter = TypeConverter::new(self.translation, self.types);


Test patch:
diff --git a/tests/disas/aarch64-wide-arithmetic.wat b/tests/disas/aarch64-wide-arithmetic.wat
new file mode 100644
index 000000000..c4e115422
--- /dev/null
+++ b/tests/disas/aarch64-wide-arithmetic.wat
@@ -0,0 +1,91 @@
+;;! target = "aarch64"
+;;! test = "compile"
+;;! flags = "-Wwide-arithmetic"
+
+(module
+  (func $add128 (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.add128)
+
+  (func $sub128 (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.sub128)
+
+  (func $signed (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s)
+
+  (func $unsigned (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u)
+
+  (func $signed_only_high (param i64 i64) (result i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s
+    local.set 0
+    drop
+    local.get 0)
+
+  (func $unsigned_only_high (param i64 i64) (result i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u
+    local.set 0
+    drop
+    local.get 0)
+)
+
+;; wasm[0]::function[0]::add128:
+;;       stp     x29, x30, [sp, #-0x10]!
+;;       mov     x29, sp
+;;       adds    x2, x4, x6
+;;       adc     x3, x5, x7
+;;       ldp     x29, x30, [sp], #0x10
+;;       ret
+;;
+;; wasm[0]::function[1]::sub128:
+;;       stp     x29, x30, [sp, #-0x10]!
+;;       mov     x29, sp
+;;       subs    x2, x4, x6
+;;       sbc     x3, x5, x7
+;;       ldp     x29, x30, [sp], #0x10
+;;       ret
+;;
+;; wasm[0]::function[2]::signed:
+;;       stp     x29, x30, [sp, #-0x10]!
+;;       mov     x29, sp
+;;       mul     x2, x4, x5
+;;       smulh   x3, x4, x5
+;;       ldp     x29, x30, [sp], #0x10
+;;       ret
+;;
+;; wasm[0]::function[3]::unsigned:
+;;       stp     x29, x30, [sp, #-0x10]!
+;;       mov     x29, sp
+;;       mul     x2, x4, x5
+;;       umulh   x3, x4, x5
+;;       ldp     x29, x30, [sp], #0x10
+;;       ret
+;;
+;; wasm[0]::function[4]::signed_only_high:
+;;       stp     x29, x30, [sp, #-0x10]!
+;;       mov     x29, sp
+;;       smulh   x2, x4, x5
+;;       ldp     x29, x30, [sp], #0x10
+;;       ret
+;;
+;; wasm[0]::function[5]::unsigned_only_high:
+;;       stp     x29, x30, [sp, #-0x10]!
+;;       mov     x29, sp
+;;       umulh   x2, x4, x5
+;;       ldp     x29, x30, [sp], #0x10
+;;       ret
diff --git a/tests/disas/gc/ref-test-any.wat b/tests/disas/gc/ref-test-any.wat
new file mode 100644
index 000000000..fd82efc73
--- /dev/null
+++ b/tests/disas/gc/ref-test-any.wat
@@ -0,0 +1,43 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (func (param anyref) (result i32)
+    (ref.test (ref any) (local.get 0))
+  )
+  (func (param (ref any)) (result i32)
+    (ref.test (ref any) (local.get 0))
+  )
+)
+;; function u0:0(i64 vmctx, i64, i32) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;; @0025                               jump block1
+;;
+;;                                 block1:
+;; @0022                               v7 = iconst.i32 1
+;;                                     v9 = iconst.i32 0
+;;                                     v14 = select v2, v7, v9  ; v7 = 1, v9 = 0
+;; @0025                               return v14
+;; }
+;;
+;; function u0:1(i64 vmctx, i64, i32) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;; @002d                               jump block1
+;;
+;;                                 block1:
+;; @002a                               v7 = iconst.i32 1
+;;                                     v9 = iconst.i32 0
+;;                                     v14 = select v2, v7, v9  ; v7 = 1, v9 = 0
+;; @002d                               return v14
+;; }
diff --git a/tests/disas/gc/ref-test-array.wat b/tests/disas/gc/ref-test-array.wat
new file mode 100644
index 000000000..627990cef
--- /dev/null
+++ b/tests/disas/gc/ref-test-array.wat
@@ -0,0 +1,55 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (func (param anyref) (result i32)
+    (ref.test (ref array) (local.get 0))
+  )
+)
+;; function u0:0(i64 vmctx, i64, i32) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;;                                     v31 = iconst.i32 0
+;; @001b                               v4 = icmp eq v2, v31  ; v31 = 0
+;; @001b                               v5 = uextend.i32 v4
+;; @001b                               v7 = iconst.i32 1
+;;                                     v36 = select v2, v7, v31  ; v7 = 1, v31 = 0
+;; @001b                               brif v5, block4(v36), block2
+;;
+;;                                 block2:
+;;                                     v44 = iconst.i32 1
+;;                                     v45 = band.i32 v2, v44  ; v44 = 1
+;;                                     v46 = iconst.i32 0
+;;                                     v47 = select v45, v46, v44  ; v46 = 0, v44 = 1
+;; @001b                               brif v45, block4(v47), block3
+;;
+;;                                 block3:
+;; @001b                               v18 = uextend.i64 v2
+;; @001b                               v19 = iconst.i64 0
+;; @001b                               v20 = uadd_overflow_trap v18, v19, user1  ; v19 = 0
+;;                                     v43 = iconst.i64 8
+;; @001b                               v22 = uadd_overflow_trap v18, v43, user1  ; v43 = 8
+;; @001b                               v17 = load.i64 notrap aligned readonly v0+48
+;; @001b                               v23 = icmp ule v22, v17
+;; @001b                               trapz v23, user1
+;; @001b                               v16 = load.i64 notrap aligned readonly v0+40
+;; @001b                               v24 = iadd v16, v20
+;; @001b                               v25 = load.i32 notrap aligned readonly v24
+;; @001b                               v26 = iconst.i32 -1543503872
+;; @001b                               v27 = band v25, v26  ; v26 = -1543503872
+;; @001b                               v28 = icmp eq v27, v26  ; v26 = -1543503872
+;; @001b                               v29 = uextend.i32 v28
+;; @001b                               jump block4(v29)
+;;
+;;                                 block4(v30: i32):
+;; @001e                               jump block1(v30)
+;;
+;;                                 block1(v3: i32):
+;; @001e                               return v3
+;; }
diff --git a/tests/disas/gc/ref-test-concrete-func-type.wat b/tests/disas/gc/ref-test-concrete-func-type.wat
new file mode 100644
index 000000000..936bcd25b
--- /dev/null
+++ b/tests/disas/gc/ref-test-concrete-func-type.wat
@@ -0,0 +1,52 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (type $f (func (param i32) (result i32)))
+  (func (param funcref) (result i32)
+    (ref.test (ref $f) (local.get 0))
+  )
+)
+;; function u0:0(i64 vmctx, i64, i64) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     sig0 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+;;     fn0 = colocated u1:35 sig0
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i64):
+;;                                     v19 = iconst.i64 0
+;; @0020                               v4 = icmp eq v2, v19  ; v19 = 0
+;; @0020                               v5 = uextend.i32 v4
+;; @0020                               v7 = iconst.i32 1
+;; @0020                               v6 = iconst.i32 0
+;;                                     v24 = select v2, v7, v6  ; v7 = 1, v6 = 0
+;; @0020                               brif v5, block4(v24), block2
+;;
+;;                                 block2:
+;; @0020                               jump block3
+;;
+;;                                 block3:
+;; @0020                               v12 = load.i32 notrap aligned readonly v2+16
+;; @0020                               v10 = load.i64 notrap aligned readonly v0+80
+;; @0020                               v11 = load.i32 notrap aligned readonly v10
+;; @0020                               v13 = icmp eq v12, v11
+;; @0020                               v14 = uextend.i32 v13
+;; @0020                               brif v14, block6(v14), block5
+;;
+;;                                 block5:
+;; @0020                               v16 = call fn0(v0, v12, v11)
+;; @0020                               jump block6(v16)
+;;
+;;                                 block6(v17: i32):
+;; @0020                               jump block4(v17)
+;;
+;;                                 block4(v18: i32):
+;; @0023                               jump block1(v18)
+;;
+;;                                 block1(v3: i32):
+;; @0023                               return v3
+;; }
diff --git a/tests/disas/gc/ref-test-concrete-type.wat b/tests/disas/gc/ref-test-concrete-type.wat
new file mode 100644
index 000000000..fd99ea0c7
--- /dev/null
+++ b/tests/disas/gc/ref-test-concrete-type.wat
@@ -0,0 +1,65 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (type $s (struct))
+  (func (param anyref) (result i32)
+    (ref.test (ref $s) (local.get 0))
+  )
+)
+;; function u0:0(i64 vmctx, i64, i32) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     sig0 = (i64 vmctx, i32 uext, i32 uext) -> i32 uext system_v
+;;     fn0 = colocated u1:35 sig0
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;;                                     v34 = iconst.i32 0
+;; @001d                               v4 = icmp eq v2, v34  ; v34 = 0
+;; @001d                               v5 = uextend.i32 v4
+;; @001d                               v7 = iconst.i32 1
+;;                                     v39 = select v2, v7, v34  ; v7 = 1, v34 = 0
+;; @001d                               brif v5, block4(v39), block2
+;;
+;;                                 block2:
+;;                                     v46 = iconst.i32 1
+;;                                     v47 = band.i32 v2, v46  ; v46 = 1
+;;                                     v48 = iconst.i32 0
+;;                                     v49 = select v47, v48, v46  ; v48 = 0, v46 = 1
+;; @001d                               brif v47, block4(v49), block3
+;;
+;;                                 block3:
+;; @001d                               v20 = uextend.i64 v2
+;; @001d                               v21 = iconst.i64 4
+;; @001d                               v22 = uadd_overflow_trap v20, v21, user1  ; v21 = 4
+;; @001d                               v23 = iconst.i64 8
+;; @001d                               v24 = uadd_overflow_trap v22, v23, user1  ; v23 = 8
+;; @001d                               v19 = load.i64 notrap aligned readonly v0+48
+;; @001d                               v25 = icmp ule v24, v19
+;; @001d                               trapz v25, user1
+;; @001d                               v18 = load.i64 notrap aligned readonly v0+40
+;; @001d                               v26 = iadd v18, v22
+;; @001d                               v27 = load.i32 notrap aligned readonly v26
+;; @001d                               v15 = load.i64 notrap aligned readonly v0+80
+;; @001d                               v16 = load.i32 notrap aligned readonly v15
+;; @001d                               v28 = icmp eq v27, v16
+;; @001d                               v29 = uextend.i32 v28
+;; @001d                               brif v29, block6(v29), block5
+;;
+;;                                 block5:
+;; @001d                               v31 = call fn0(v0, v27, v16)
+;; @001d                               jump block6(v31)
+;;
+;;                                 block6(v32: i32):
+;; @001d                               jump block4(v32)
+;;
+;;                                 block4(v33: i32):
+;; @0020                               jump block1(v33)
+;;
+;;                                 block1(v3: i32):
+;; @0020                               return v3
+;; }
diff --git a/tests/disas/gc/ref-test-eq.wat b/tests/disas/gc/ref-test-eq.wat
new file mode 100644
index 000000000..bab78fbcc
--- /dev/null
+++ b/tests/disas/gc/ref-test-eq.wat
@@ -0,0 +1,53 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (func (param anyref) (result i32)
+    (ref.test (ref eq) (local.get 0))
+  )
+)
+;; function u0:0(i64 vmctx, i64, i32) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;;                                     v28 = iconst.i32 0
+;; @001b                               v4 = icmp eq v2, v28  ; v28 = 0
+;; @001b                               v5 = uextend.i32 v4
+;; @001b                               v7 = iconst.i32 1
+;;                                     v33 = select v2, v7, v28  ; v7 = 1, v28 = 0
+;; @001b                               brif v5, block4(v33), block2
+;;
+;;                                 block2:
+;;                                     v41 = iconst.i32 1
+;;                                     v42 = band.i32 v2, v41  ; v41 = 1
+;; @001b                               brif v42, block4(v42), block3
+;;
+;;                                 block3:
+;; @001b                               v15 = uextend.i64 v2
+;; @001b                               v16 = iconst.i64 0
+;; @001b                               v17 = uadd_overflow_trap v15, v16, user1  ; v16 = 0
+;;                                     v40 = iconst.i64 8
+;; @001b                               v19 = uadd_overflow_trap v15, v40, user1  ; v40 = 8
+;; @001b                               v14 = load.i64 notrap aligned readonly v0+48
+;; @001b                               v20 = icmp ule v19, v14
+;; @001b                               trapz v20, user1
+;; @001b                               v13 = load.i64 notrap aligned readonly v0+40
+;; @001b                               v21 = iadd v13, v17
+;; @001b                               v22 = load.i32 notrap aligned readonly v21
+;; @001b                               v23 = iconst.i32 -1610612736
+;; @001b                               v24 = band v22, v23  ; v23 = -1610612736
+;; @001b                               v25 = icmp eq v24, v23  ; v23 = -1610612736
+;; @001b                               v26 = uextend.i32 v25
+;; @001b                               jump block4(v26)
+;;
+;;                                 block4(v27: i32):
+;; @001e                               jump block1(v27)
+;;
+;;                                 block1(v3: i32):
+;; @001e                               return v3
+;; }
diff --git a/tests/disas/gc/ref-test-i31.wat b/tests/disas/gc/ref-test-i31.wat
new file mode 100644
index 000000000..2517f283c
--- /dev/null
+++ b/tests/disas/gc/ref-test-i31.wat
@@ -0,0 +1,23 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (func (param anyref) (result i32)
+    (ref.test (ref i31) (local.get 0))
+  )
+)
+;; function u0:0(i64 vmctx, i64, i32) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;; @001e                               jump block1
+;;
+;;                                 block1:
+;; @001b                               v4 = iconst.i32 1
+;; @001b                               v5 = band.i32 v2, v4  ; v4 = 1
+;; @001e                               return v5
+;; }
diff --git a/tests/disas/gc/ref-test-none.wat b/tests/disas/gc/ref-test-none.wat
new file mode 100644
index 000000000..a1369671a
--- /dev/null
+++ b/tests/disas/gc/ref-test-none.wat
@@ -0,0 +1,41 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (func (param anyref) (result i32)
+    (ref.test (ref none) (local.get 0))
+  )
+  (func (param anyref) (result i32)
+    (ref.test (ref null none) (local.get 0))
+  )
+)
+;; function u0:0(i64 vmctx, i64, i32) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;; @001f                               jump block1
+;;
+;;                                 block1:
+;; @001c                               v4 = iconst.i32 0
+;; @001f                               return v4  ; v4 = 0
+;; }
+;;
+;; function u0:1(i64 vmctx, i64, i32) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;; @0027                               jump block1
+;;
+;;                                 block1:
+;;                                     v6 = iconst.i32 0
+;; @0024                               v4 = icmp.i32 eq v2, v6  ; v6 = 0
+;; @0024                               v5 = uextend.i32 v4
+;; @0027                               return v5
+;; }
diff --git a/tests/disas/gc/ref-test-struct.wat b/tests/disas/gc/ref-test-struct.wat
new file mode 100644
index 000000000..b7f154091
--- /dev/null
+++ b/tests/disas/gc/ref-test-struct.wat
@@ -0,0 +1,55 @@
+;;! target = "x86_64"
+;;! flags = "-W function-references,gc"
+;;! test = "optimize"
+
+(module
+  (func (param anyref) (result i32)
+    (ref.test (ref struct) (local.get 0))
+  )
+)
+;; function u0:0(i64 vmctx, i64, i32) -> i32 tail {
+;;     gv0 = vmctx
+;;     gv1 = load.i64 notrap aligned readonly gv0+8
+;;     gv2 = load.i64 notrap aligned gv1
+;;     gv3 = vmctx
+;;     stack_limit = gv2
+;;
+;;                                 block0(v0: i64, v1: i64, v2: i32):
+;;                                     v31 = iconst.i32 0
+;; @001b                               v4 = icmp eq v2, v31  ; v31 = 0
+;; @001b                               v5 = uextend.i32 v4
+;; @001b                               v7 = iconst.i32 1
+;;                                     v36 = select v2, v7, v31  ; v7 = 1, v31 = 0
+;; @001b                               brif v5, block4(v36), block2
+;;
+;;                                 block2:
+;;                                     v44 = iconst.i32 1
+;;                                     v45 = band.i32 v2, v44  ; v44 = 1
+;;                                     v46 = iconst.i32 0
+;;                                     v47 = select v45, v46, v44  ; v46 = 0, v44 = 1
+;; @001b                               brif v45, block4(v47), block3
+;;
+;;                                 block3:
+;; @001b                               v18 = uextend.i64 v2
+;; @001b                               v19 = iconst.i64 0
+;; @001b                               v20 = uadd_overflow_trap v18, v19, user1  ; v19 = 0
+;;                                     v43 = iconst.i64 8
+;; @001b                               v22 = uadd_overflow_trap v18, v43, user1  ; v43 = 8
+;; @001b                               v17 = load.i64 notrap aligned readonly v0+48
+;; @001b                               v23 = icmp ule v22, v17
+;; @001b                               trapz v23, user1
+;; @001b                               v16 = load.i64 notrap aligned readonly v0+40
+;; @001b                               v24 = iadd v16, v20
+;; @001b                               v25 = load.i32 notrap aligned readonly v24
+;; @001b                               v26 = iconst.i32 -1476395008
+;; @001b                               v27 = band v25, v26  ; v26 = -1476395008
+;; @001b                               v28 = icmp eq v27, v26  ; v26 = -1476395008
+;; @001b                               v29 = uextend.i32 v28
+;; @001b                               jump block4(v29)
+;;
+;;                                 block4(v30: i32):
+;; @001e                               jump block1(v30)
+;;
+;;                                 block1(v3: i32):
+;; @001e                               return v3
+;; }
diff --git a/tests/disas/riscv64-wide-arithmetic.wat b/tests/disas/riscv64-wide-arithmetic.wat
new file mode 100644
index 000000000..dc81d5a1d
--- /dev/null
+++ b/tests/disas/riscv64-wide-arithmetic.wat
@@ -0,0 +1,119 @@
+;;! target = "riscv64"
+;;! test = "compile"
+;;! flags = "-Wwide-arithmetic"
+
+(module
+  (func $add128 (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.add128)
+
+  (func $sub128 (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.sub128)
+
+  (func $signed (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s)
+
+  (func $unsigned (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u)
+
+  (func $signed_only_high (param i64 i64) (result i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s
+    local.set 0
+    drop
+    local.get 0)
+
+  (func $unsigned_only_high (param i64 i64) (result i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u
+    local.set 0
+    drop
+    local.get 0)
+)
+
+;; wasm[0]::function[0]::add128:
+;;       addi    sp, sp, -0x10
+;;       sd      ra, 8(sp)
+;;       sd      s0, 0(sp)
+;;       mv      s0, sp
+;;       add     a0, a2, a4
+;;       sltu    a1, a0, a4
+;;       add     a3, a3, a5
+;;       add     a1, a3, a1
+;;       ld      ra, 8(sp)
+;;       ld      s0, 0(sp)
+;;       addi    sp, sp, 0x10
+;;       ret
+;;
+;; wasm[0]::function[1]::sub128:
+;;       addi    sp, sp, -0x10
+;;       sd      ra, 8(sp)
+;;       sd      s0, 0(sp)
+;;       mv      s0, sp
+;;       sub     a0, a2, a4
+;;       sltu    a1, a2, a0
+;;       sub     a3, a3, a5
+;;       sub     a1, a3, a1
+;;       ld      ra, 8(sp)
+;;       ld      s0, 0(sp)
+;;       addi    sp, sp, 0x10
+;;       ret
+;;
+;; wasm[0]::function[2]::signed:
+;;       addi    sp, sp, -0x10
+;;       sd      ra, 8(sp)
+;;       sd      s0, 0(sp)
+;;       mv      s0, sp
+;;       mul     a0, a2, a3
+;;       mulh    a1, a2, a3
+;;       ld      ra, 8(sp)
+;;       ld      s0, 0(sp)
+;;       addi    sp, sp, 0x10
+;;       ret
+;;
+;; wasm[0]::function[3]::unsigned:
+;;       addi    sp, sp, -0x10
+;;       sd      ra, 8(sp)
+;;       sd      s0, 0(sp)
+;;       mv      s0, sp
+;;       mul     a0, a2, a3
+;;       mulhu   a1, a2, a3
+;;       ld      ra, 8(sp)
+;;       ld      s0, 0(sp)
+;;       addi    sp, sp, 0x10
+;;       ret
+;;
+;; wasm[0]::function[4]::signed_only_high:
+;;       addi    sp, sp, -0x10
+;;       sd      ra, 8(sp)
+;;       sd      s0, 0(sp)
+;;       mv      s0, sp
+;;       mulh    a0, a2, a3
+;;       ld      ra, 8(sp)
+;;       ld      s0, 0(sp)
+;;       addi    sp, sp, 0x10
+;;       ret
+;;
+;; wasm[0]::function[5]::unsigned_only_high:
+;;       addi    sp, sp, -0x10
+;;       sd      ra, 8(sp)
+;;       sd      s0, 0(sp)
+;;       mv      s0, sp
+;;       mulhu   a0, a2, a3
+;;       ld      ra, 8(sp)
+;;       ld      s0, 0(sp)
+;;       addi    sp, sp, 0x10
+;;       ret
diff --git a/tests/disas/s390x-wide-arithmetic.wat b/tests/disas/s390x-wide-arithmetic.wat
new file mode 100644
index 000000000..dfaf6ed83
--- /dev/null
+++ b/tests/disas/s390x-wide-arithmetic.wat
@@ -0,0 +1,145 @@
+;;! target = "s390x"
+;;! test = "compile"
+;;! flags = "-Wwide-arithmetic"
+
+(module
+  (func $add128 (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.add128)
+
+  (func $sub128 (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.sub128)
+
+  (func $signed (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s)
+
+  (func $unsigned (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u)
+
+  (func $signed_only_high (param i64 i64) (result i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s
+    local.set 0
+    drop
+    local.get 0)
+
+  (func $unsigned_only_high (param i64 i64) (result i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u
+    local.set 0
+    drop
+    local.get 0)
+)
+
+;; wasm[0]::function[0]::add128:
+;;       lg      %r1, 8(%r2)
+;;       lg      %r1, 0(%r1)
+;;       la      %r1, 0xa0(%r1)
+;;       clgrtle %r15, %r1
+;;       stmg    %r14, %r15, 0x70(%r15)
+;;       lgr     %r1, %r15
+;;       aghi    %r15, -0xa0
+;;       stg     %r1, 0(%r15)
+;;       vlvgp   %v17, %r5, %r4
+;;       vlvgp   %v18, %r7, %r6
+;;       vaq     %v17, %v17, %v18
+;;       vlgvg   %r3, %v17, 0
+;;       vlgvg   %r2, %v17, 1
+;;       lmg     %r14, %r15, 0x110(%r15)
+;;       br      %r14
+;;
+;; wasm[0]::function[1]::sub128:
+;;       lg      %r1, 8(%r2)
+;;       lg      %r1, 0(%r1)
+;;       la      %r1, 0xa0(%r1)
+;;       clgrtle %r15, %r1
+;;       stmg    %r14, %r15, 0x70(%r15)
+;;       lgr     %r1, %r15
+;;       aghi    %r15, -0xa0
+;;       stg     %r1, 0(%r15)
+;;       vlvgp   %v17, %r5, %r4
+;;       vlvgp   %v18, %r7, %r6
+;;       vsq     %v17, %v17, %v18
+;;       vlgvg   %r3, %v17, 0
+;;       vlgvg   %r2, %v17, 1
+;;       lmg     %r14, %r15, 0x110(%r15)
+;;       br      %r14
+;;
+;; wasm[0]::function[2]::signed:
+;;       lg      %r1, 8(%r2)
+;;       lg      %r1, 0(%r1)
+;;       la      %r1, 0xa0(%r1)
+;;       clgrtle %r15, %r1
+;;       stmg    %r14, %r15, 0x70(%r15)
+;;       lgr     %r1, %r15
+;;       aghi    %r15, -0xa0
+;;       stg     %r1, 0(%r15)
+;;       mgrk    %r2, %r4, %r5
+;;       vlvgp   %v16, %r2, %r3
+;;       vlgvg   %r3, %v16, 0
+;;       vlgvg   %r2, %v16, 1
+;;       lmg     %r14, %r15, 0x110(%r15)
+;;       br      %r14
+;;
+;; wasm[0]::function[3]::unsigned:
+;;       lg      %r1, 8(%r2)
+;;       lg      %r1, 0(%r1)
+;;       la      %r1, 0xa0(%r1)
+;;       clgrtle %r15, %r1
+;;       stmg    %r14, %r15, 0x70(%r15)
+;;       lgr     %r1, %r15
+;;       aghi    %r15, -0xa0
+;;       stg     %r1, 0(%r15)
+;;       lgr     %r3, %r4
+;;       mlgr    %r2, %r5
+;;       vlvgp   %v16, %r2, %r3
+;;       vlgvg   %r3, %v16, 0
+;;       vlgvg   %r2, %v16, 1
+;;       lmg     %r14, %r15, 0x110(%r15)
+;;       br      %r14
+;;
+;; wasm[0]::function[4]::signed_only_high:
+;;       lg      %r1, 8(%r2)
+;;       lg      %r1, 0(%r1)
+;;       la      %r1, 0xa0(%r1)
+;;       clgrtle %r15, %r1
+;;       stmg    %r14, %r15, 0x70(%r15)
+;;       lgr     %r1, %r15
+;;       aghi    %r15, -0xa0
+;;       stg     %r1, 0(%r15)
+;;       mgrk    %r2, %r4, %r5
+;;       vlvgp   %v16, %r2, %r3
+;;       vlgvg   %r2, %v16, 0
+;;       vlgvg   %r3, %v16, 1
+;;       lmg     %r14, %r15, 0x110(%r15)
+;;       br      %r14
+;;
+;; wasm[0]::function[5]::unsigned_only_high:
+;;       lg      %r1, 8(%r2)
+;;       lg      %r1, 0(%r1)
+;;       la      %r1, 0xa0(%r1)
+;;       clgrtle %r15, %r1
+;;       stmg    %r14, %r15, 0x70(%r15)
+;;       lgr     %r1, %r15
+;;       aghi    %r15, -0xa0
+;;       stg     %r1, 0(%r15)
+;;       lgr     %r3, %r4
+;;       mlgr    %r2, %r5
+;;       vlvgp   %v16, %r2, %r3
+;;       vlgvg   %r2, %v16, 0
+;;       vlgvg   %r3, %v16, 1
+;;       lmg     %r14, %r15, 0x110(%r15)
+;;       br      %r14
diff --git a/tests/disas/x64-wide-arithmetic.wat b/tests/disas/x64-wide-arithmetic.wat
new file mode 100644
index 000000000..cd15c69a3
--- /dev/null
+++ b/tests/disas/x64-wide-arithmetic.wat
@@ -0,0 +1,105 @@
+;;! target = "x86_64"
+;;! test = "compile"
+;;! flags = "-Wwide-arithmetic"
+
+(module
+  (func $add128 (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.add128)
+
+  (func $sub128 (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.sub128)
+
+  (func $signed (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s)
+
+  (func $unsigned (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u)
+
+  (func $signed_only_high (param i64 i64) (result i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s
+    local.set 0
+    drop
+    local.get 0)
+
+  (func $unsigned_only_high (param i64 i64) (result i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u
+    local.set 0
+    drop
+    local.get 0)
+)
+
+;; wasm[0]::function[0]::add128:
+;;       pushq   %rbp
+;;       movq    %rsp, %rbp
+;;       movq    %rdx, %rax
+;;       addq    %r8, %rax
+;;       adcq    %r9, %rcx
+;;       movq    %rbp, %rsp
+;;       popq    %rbp
+;;       retq
+;;
+;; wasm[0]::function[1]::sub128:
+;;       pushq   %rbp
+;;       movq    %rsp, %rbp
+;;       movq    %rdx, %rax
+;;       subq    %r8, %rax
+;;       sbbq    %r9, %rcx
+;;       movq    %rbp, %rsp
+;;       popq    %rbp
+;;       retq
+;;
+;; wasm[0]::function[2]::signed:
+;;       pushq   %rbp
+;;       movq    %rsp, %rbp
+;;       movq    %rdx, %rax
+;;       imulq   %rcx
+;;       movq    %rdx, %rcx
+;;       movq    %rbp, %rsp
+;;       popq    %rbp
+;;       retq
+;;
+;; wasm[0]::function[3]::unsigned:
+;;       pushq   %rbp
+;;       movq    %rsp, %rbp
+;;       movq    %rdx, %rax
+;;       mulq    %rcx
+;;       movq    %rdx, %rcx
+;;       movq    %rbp, %rsp
+;;       popq    %rbp
+;;       retq
+;;
+;; wasm[0]::function[4]::signed_only_high:
+;;       pushq   %rbp
+;;       movq    %rsp, %rbp
+;;       movq    %rdx, %rax
+;;       imulq   %rcx
+;;       movq    %rdx, %rax
+;;       movq    %rbp, %rsp
+;;       popq    %rbp
+;;       retq
+;;
+;; wasm[0]::function[5]::unsigned_only_high:
+;;       pushq   %rbp
+;;       movq    %rsp, %rbp
+;;       movq    %rdx, %rax
+;;       mulq    %rcx
+;;       movq    %rdx, %rax
+;;       movq    %rbp, %rsp
+;;       popq    %rbp
+;;       retq
diff --git a/tests/misc_testsuite/gc/ref-test.wast b/tests/misc_testsuite/gc/ref-test.wast
new file mode 100644
index 000000000..78a82d86d
--- /dev/null
+++ b/tests/misc_testsuite/gc/ref-test.wast
@@ -0,0 +1,603 @@
+(module
+  (func (export "nulls-to-nullable-tops") (result i32)
+    (ref.test anyref (ref.null any))
+    (ref.test anyref (ref.null none))
+    i32.and
+    (ref.test externref (ref.null extern))
+    i32.and
+    (ref.test externref (ref.null noextern))
+    i32.and
+    (ref.test funcref (ref.null func))
+    i32.and
+    (ref.test funcref (ref.null nofunc))
+    i32.and
+  )
+)
+
+(assert_return (invoke "nulls-to-nullable-tops") (i32.const 1))
+
+(module
+  (type $s (struct))
+  (func $f (export "non-nulls-to-nullable-tops") (param externref) (result i32)
+    (ref.test anyref (struct.new_default $s))
+    (ref.test anyref (ref.i31 (i32.const 42)))
+    i32.and
+    (ref.test externref (local.get 0))
+    i32.and
+    (ref.test funcref (ref.func $f))
+    i32.and
+  )
+)
+
+(assert_return (invoke "non-nulls-to-nullable-tops" (ref.extern 99)) (i32.const 1))
+
+(module
+  (func (export "nulls-to-non-nullable-tops") (result i32)
+    (ref.test (ref any) (ref.null any))
+    (ref.test (ref any) (ref.null none))
+    i32.or
+    (ref.test (ref extern) (ref.null extern))
+    i32.or
+    (ref.test (ref extern) (ref.null noextern))
+    i32.or
+    (ref.test (ref func) (ref.null func))
+    i32.or
+    (ref.test (ref func) (ref.null nofunc))
+    i32.or
+  )
+)
+
+(assert_return (invoke "nulls-to-non-nullable-tops") (i32.const 0))
+
+(module
+  (type $s (struct))
+  (func $f (export "non-nulls-to-non-nullable-tops") (param externref) (result i32)
+    (ref.test (ref any) (struct.new_default $s))
+    (ref.test (ref any) (ref.i31 (i32.const 42)))
+    i32.and
+    (ref.test (ref extern) (local.get 0))
+    i32.and
+    (ref.test (ref func) (ref.func $f))
+    i32.and
+  )
+)
+
+(assert_return (invoke "non-nulls-to-non-nullable-tops" (ref.extern 1)) (i32.const 1))
+
+(module
+  (func (export "null-to-nullable-i31") (result i32)
+    (ref.test i31ref (ref.null none))
+    (ref.test i31ref (ref.null i31))
+    i32.and
+    (ref.test i31ref (ref.null struct))
+    i32.and
+    (ref.test i31ref (ref.null array))
+    i32.and
+    (ref.test i31ref (ref.null eq))
+    i32.and
+    (ref.test i31ref (ref.null any))
+    i32.and
+  )
+)
+
+(assert_return (invoke "null-to-nullable-i31") (i32.const 1))
+
+(module
+  (func (export "truthy-non-null-to-nullable-i31") (result i32)
+    (ref.test i31ref (ref.i31 (i32.const 42)))
+  )
+)
+
+(assert_return (invoke "truthy-non-null-to-nullable-i31") (i32.const 1))
+
+(module
+  (type $s (struct))
+  (type $a (array i32))
+  (func (export "falsey-non-null-to-nullable-i31") (result i32)
+    (ref.test i31ref (struct.new_default $s))
+    (ref.test i31ref (array.new_default $a (i32.const 3)))
+    i32.or
+  )
+)
+
+(assert_return (invoke "falsey-non-null-to-nullable-i31") (i32.const 0))
+
+(module
+  (func (export "null-to-non-nullable-i31") (result i32)
+    (ref.test (ref i31) (ref.null none))
+    (ref.test (ref i31) (ref.null i31))
+    i32.or
+    (ref.test (ref i31) (ref.null struct))
+    i32.or
+    (ref.test (ref i31) (ref.null array))
+    i32.or
+    (ref.test (ref i31) (ref.null eq))
+    i32.or
+    (ref.test (ref i31) (ref.null any))
+    i32.or
+  )
+)
+
+(assert_return (invoke "null-to-non-nullable-i31") (i32.const 0))
+
+(module
+  (type $s (struct))
+  (type $a (array i32))
+  (func (export "falsey-non-null-to-non-nullable-i31") (result i32)
+    (ref.test (ref i31) (struct.new_default $s))
+    (ref.test (ref i31) (array.new_default $a (i32.const 3)))
+    i32.or
+  )
+)
+
+(assert_return (invoke "falsey-non-null-to-non-nullable-i31") (i32.const 0))
+
+(module
+  (func (export "truthy-non-null-to-non-nullable-i31") (result i32)
+    (ref.test (ref i31) (ref.i31 (i32.const 42)))
+  )
+)
+
+(assert_return (invoke "truthy-non-null-to-non-nullable-i31") (i32.const 1))
+
+(module
+  (func (export "null-to-nullable-middle-types") (result i32)
+    (ref.test structref (ref.null any))
+    (ref.test structref (ref.null eq))
+    i32.and
+    (ref.test structref (ref.null i31))
+    i32.and
+    (ref.test structref (ref.null struct))
+    i32.and
+    (ref.test structref (ref.null array))
+    i32.and
+    (ref.test structref (ref.null none))
+    i32.and
+    (ref.test arrayref (ref.null any))
+    i32.and
+    (ref.test arrayref (ref.null eq))
+    i32.and
+    (ref.test arrayref (ref.null i31))
+    i32.and
+    (ref.test arrayref (ref.null array))
+    i32.and
+    (ref.test arrayref (ref.null array))
+    i32.and
+    (ref.test arrayref (ref.null none))
+    i32.and
+  )
+)
+
+(assert_return (invoke "null-to-nullable-middle-types") (i32.const 1))
+
+(module
+  (type $s (struct))
+  (type $a (array i32))
+  (func (export "truthy-non-null-to-nullable-middle-types") (result i32)
+    (ref.test eqref (ref.i31 (i32.const 42)))
+    (ref.test eqref (struct.new_default $s))
+    i32.and
+    (ref.test eqref (array.new_default $a (i32.const 3)))
+    i32.and
+    (ref.test structref (struct.new_default $s))
+    i32.and
+    (ref.test arrayref (array.new_default $a (i32.const 3)))
+    i32.and
+  )
+)
+
+(assert_return (invoke "truthy-non-null-to-nullable-middle-types") (i32.const 1))
+
+(module
+  (type $s (struct))
+  (type $a (array i32))
+  (func (export "falsey-non-null-to-nullable-middle-types") (result i32)
+    (ref.test structref (ref.i31 (i32.const 42)))
+    (ref.test structref (array.new_default $a (i32.const 3)))
+    i32.or
+    (ref.test arrayref (ref.i31 (i32.const 42)))
+    i32.or
+    (ref.test arrayref (struct.new_default $s))
+    i32.or
+  )
+)
+
+(assert_return (invoke "falsey-non-null-to-nullable-middle-types") (i32.const 0))
+
+(module
+  (type $s0 (sub     (struct)))
+  (type $s1 (sub $s0 (struct (field i32))))
+  (type $a0 (sub     (array (ref null $s0))))
+  (type $a1 (sub $a0 (array (ref null $s1))))
+  (type $f0 (sub     (func)))
+  (type $f1 (sub $f0 (func)))
+  (func (export "null-to-nullable-concrete-types") (result i32)
+    (ref.test (ref null $s0) (ref.null any))
+    (ref.test (ref null $s0) (ref.null eq))
+    i32.and
+    (ref.test (ref null $s0) (ref.null i31))
+    i32.and
+    (ref.test (ref null $s0) (ref.null struct))
+    i32.and
+    (ref.test (ref null $s0) (ref.null array))
+    i32.and
+    (ref.test (ref null $s0) (ref.null $s0))
+    i32.and
+    (ref.test (ref null $s0) (ref.null $s1))
+    i32.and
+    (ref.test (ref null $s0) (ref.null $a0))
+    i32.and
+    (ref.test (ref null $s0) (ref.null $a1))
+    i32.and
+    (ref.test (ref null $s0) (ref.null none))
+    i32.and
+    (ref.test (ref null $s1) (ref.null any))
+    i32.and
+    (ref.test (ref null $s1) (ref.null eq))
+    i32.and
+    (ref.test (ref null $s1) (ref.null i31))
+    i32.and
+    (ref.test (ref null $s1) (ref.null struct))
+    i32.and
+    (ref.test (ref null $s1) (ref.null array))
+    i32.and
+    (ref.test (ref null $s1) (ref.null $s0))
+    i32.and
+    (ref.test (ref null $s1) (ref.null $s1))
+    i32.and
+    (ref.test (ref null $s1) (ref.null $a0))
+    i32.and
+    (ref.test (ref null $s1) (ref.null $a1))
+    i32.and
+    (ref.test (ref null $s1) (ref.null none))
+    i32.and
+    (ref.test (ref null $a0) (ref.null any))
+    i32.and
+    (ref.test (ref null $a0) (ref.null eq))
+    i32.and
+    (ref.test (ref null $a0) (ref.null i31))
+    i32.and
+    (ref.test (ref null $a0) (ref.null struct))
+    i32.and
+    (ref.test (ref null $a0) (ref.null array))
+    i32.and
+    (ref.test (ref null $a0) (ref.null $s0))
+    i32.and
+    (ref.test (ref null $a0) (ref.null $s1))
+    i32.and
+    (ref.test (ref null $a0) (ref.null $a0))
+    i32.and
+    (ref.test (ref null $a0) (ref.null $a1))
+    i32.and
+    (ref.test (ref null $a0) (ref.null none))
+    i32.and
+    (ref.test (ref null $a1) (ref.null any))
+    i32.and
+    (ref.test (ref null $a1) (ref.null eq))
+    i32.and
+    (ref.test (ref null $a1) (ref.null i31))
+    i32.and
+    (ref.test (ref null $a1) (ref.null struct))
+    i32.and
+    (ref.test (ref null $a1) (ref.null array))
+    i32.and
+    (ref.test (ref null $a1) (ref.null $s0))
+    i32.and
+    (ref.test (ref null $a1) (ref.null $s1))
+    i32.and
+    (ref.test (ref null $a1) (ref.null $a0))
+    i32.and
+    (ref.test (ref null $a1) (ref.null $a1))
+    i32.and
+    (ref.test (ref null $a1) (ref.null none))
+    i32.and
+    (ref.test (ref null $f0) (ref.null func))
+    i32.and
+    (ref.test (ref null $f0) (ref.null $f0))
+    i32.and
+    (ref.test (ref null $f0) (ref.null $f1))
+    i32.and
+    (ref.test (ref null $f0) (ref.null nofunc))
+    i32.and
+    (ref.test (ref null $f1) (ref.null func))
+    i32.and
+    (ref.test (ref null $f1) (ref.null $f0))
+    i32.and
+    (ref.test (ref null $f1) (ref.null $f1))
+    i32.and
+    (ref.test (ref null $f1) (ref.null nofunc))
+    i32.and
+  )
+)
+
+(assert_return (invoke "null-to-nullable-concrete-types") (i32.const 1))
+
+(module
+  (type $s0 (sub     (struct)))
+  (type $s1 (sub $s0 (struct (field i32))))
+  (type $a0 (sub     (array (ref null $s0))))
+  (type $a1 (sub $a0 (array (ref null $s1))))
+  (type $f0 (sub     (func)))
+  (type $f1 (sub $f0 (func)))
+
+  (func $g0 (export "g0") (type $f0) unreachable)
+  (func $g1 (export "g1") (type $f1) unreachable)
+
+  (func (export "truthy-non-null-to-nullable-concrete-types") (result i32)
+    (ref.test (ref null $s0) (struct.new_default $s0))
+    (ref.test (ref null $s0) (struct.new_default $s1))
+    i32.and
+    (ref.test (ref null $s1) (struct.new_default $s1))
+    i32.and
+    (ref.test (ref null $a0) (array.new_default $a0 (i32.const 3)))
+    i32.and
+    (ref.test (ref null $a0) (array.new_default $a1 (i32.const 3)))
+    i32.and
+    (ref.test (ref null $a1) (array.new_default $a1 (i32.const 3)))
+    i32.and
+    (ref.test (ref null $f0) (ref.func $g0))
+    i32.and
+    (ref.test (ref null $f0) (ref.func $g1))
+    i32.and
+    (ref.test (ref null $f1) (ref.func $g1))
+    i32.and
+  )
+)
+
+(assert_return (invoke "truthy-non-null-to-nullable-concrete-types") (i32.const 1))
+
+(module
+  (type $s0 (sub     (struct)))
+  (type $s1 (sub $s0 (struct (field i32))))
+  (type $a0 (sub     (array (ref null $s0))))
+  (type $a1 (sub $a0 (array (ref null $s1))))
+  (type $f0 (sub     (func)))
+  (type $f1 (sub $f0 (func)))
+
+  (func $g0 (export "g0") (type $f0) unreachable)
+  (func $g1 (export "g1") (type $f1) unreachable)
+
+  (func (export "falsey-non-null-to-nullable-concrete-types") (result i32)
+    (ref.test (ref null $s1) (struct.new_default $s0))
+    (ref.test (ref null $a1) (array.new_default $a0 (i32.const 3)))
+    i32.or
+    (ref.test (ref null $f1) (ref.func $g0))
+    i32.or
+  )
+)
+
+(assert_return (invoke "falsey-non-null-to-nullable-concrete-types") (i32.const 0))
+
+(module
+  (type $s0 (sub     (struct)))
+  (type $s1 (sub $s0 (struct (field i32))))
+  (type $a0 (sub     (array (ref null $s0))))
+  (type $a1 (sub $a0 (array (ref null $s1))))
+  (type $f0 (sub     (func)))
+  (type $f1 (sub $f0 (func)))
+
+  (func $g0 (export "g0") (type $f0) unreachable)
+  (func $g1 (export "g1") (type $f1) unreachable)
+
+  (func (export "null-to-non-nullable-concrete-types") (result i32)
+    (ref.test (ref $s0) (ref.null any))
+    (ref.test (ref $s0) (ref.null eq))
+    i32.or
+    (ref.test (ref $s0) (ref.null i31))
+    i32.or
+    (ref.test (ref $s0) (ref.null struct))
+    i32.or
+    (ref.test (ref $s0) (ref.null array))
+    i32.or
+    (ref.test (ref $s0) (ref.null $s0))
+    i32.or
+    (ref.test (ref $s0) (ref.null $s1))
+    i32.or
+    (ref.test (ref $s0) (ref.null $a0))
+    i32.or
+    (ref.test (ref $s0) (ref.null $a1))
+    i32.or
+    (ref.test (ref $s0) (ref.null none))
+    i32.or
+    (ref.test (ref $s1) (ref.null any))
+    i32.or
+    (ref.test (ref $s1) (ref.null eq))
+    i32.or
+    (ref.test (ref $s1) (ref.null i31))
+    i32.or
+    (ref.test (ref $s1) (ref.null struct))
+    i32.or
+    (ref.test (ref $s1) (ref.null array))
+    i32.or
+    (ref.test (ref $s1) (ref.null $s0))
+    i32.or
+    (ref.test (ref $s1) (ref.null $s1))
+    i32.or
+    (ref.test (ref $s1) (ref.null $a0))
+    i32.or
+    (ref.test (ref $s1) (ref.null $a1))
+    i32.or
+    (ref.test (ref $s1) (ref.null none))
+    i32.or
+    (ref.test (ref $a0) (ref.null any))
+    i32.or
+    (ref.test (ref $a0) (ref.null eq))
+    i32.or
+    (ref.test (ref $a0) (ref.null i31))
+    i32.or
+    (ref.test (ref $a0) (ref.null struct))
+    i32.or
+    (ref.test (ref $a0) (ref.null array))
+    i32.or
+    (ref.test (ref $a0) (ref.null $s0))
+    i32.or
+    (ref.test (ref $a0) (ref.null $s1))
+    i32.or
+    (ref.test (ref $a0) (ref.null $a0))
+    i32.or
+    (ref.test (ref $a0) (ref.null $a1))
+    i32.or
+    (ref.test (ref $a0) (ref.null none))
+    i32.or
+    (ref.test (ref $a1) (ref.null any))
+    i32.or
+    (ref.test (ref $a1) (ref.null eq))
+    i32.or
+    (ref.test (ref $a1) (ref.null i31))
+    i32.or
+    (ref.test (ref $a1) (ref.null struct))
+    i32.or
+    (ref.test (ref $a1) (ref.null array))
+    i32.or
+    (ref.test (ref $a1) (ref.null $s0))
+    i32.or
+    (ref.test (ref $a1) (ref.null $s1))
+    i32.or
+    (ref.test (ref $a1) (ref.null $a0))
+    i32.or
+    (ref.test (ref $a1) (ref.null $a1))
+    i32.or
+    (ref.test (ref $a1) (ref.null none))
+    i32.or
+    (ref.test (ref $f0) (ref.null nofunc))
+    i32.or
+    (ref.test (ref $f1) (ref.null nofunc))
+    i32.or
+  )
+)
+
+(assert_return (invoke "null-to-non-nullable-concrete-types") (i32.const 0))
+
+(module
+  (type $s0 (sub     (struct)))
+  (type $s1 (sub $s0 (struct (field i32))))
+  (type $a0 (sub     (array (ref null $s0))))
+  (type $a1 (sub $a0 (array (ref null $s1))))
+  (type $f0 (sub     (func)))
+  (type $f1 (sub $f0 (func)))
+
+  (func $g0 (export "g0") (type $f0) unreachable)
+  (func $g1 (export "g1") (type $f1) unreachable)
+
+  (func (export "truthy-non-null-to-non-nullable-concrete-types") (result i32)
+    (ref.test (ref $s0) (struct.new_default $s0))
+    (ref.test (ref $s0) (struct.new_default $s1))
+    i32.and
+    (ref.test (ref $s1) (struct.new_default $s1))
+    i32.and
+    (ref.test (ref $a0) (array.new_default $a0 (i32.const 3)))
+    i32.and
+    (ref.test (ref $a0) (array.new_default $a1 (i32.const 3)))
+    i32.and
+    (ref.test (ref $a1) (array.new_default $a1 (i32.const 3)))
+    i32.and
+    (ref.test (ref $f0) (ref.func $g0))
+    i32.and
+    (ref.test (ref $f0) (ref.func $g1))
+    i32.and
+    (ref.test (ref $f1) (ref.func $g1))
+    i32.and
+  )
+)
+
+(assert_return (invoke "truthy-non-null-to-non-nullable-concrete-types") (i32.const 1))
+
+(module
+  (type $s0 (sub     (struct)))
+  (type $s1 (sub $s0 (struct (field i32))))
+  (type $a0 (sub     (array (ref null $s0))))
+  (type $a1 (sub $a0 (array (ref null $s1))))
+  (type $f0 (sub     (func)))
+  (type $f1 (sub $f0 (func)))
+
+  (func $g0 (export "g0") (type $f0) unreachable)
+  (func $g1 (export "g1") (type $f1) unreachable)
+
+  (func (export "falsey-non-null-to-non-nullable-concrete-types") (result i32)
+    (ref.test (ref $s1) (struct.new_default $s0))
+    (ref.test (ref $a1) (array.new_default $a0 (i32.const 3)))
+    i32.or
+    (ref.test (ref $f1) (ref.func $g0))
+    i32.or
+  )
+)
+
+(assert_return (invoke "falsey-non-null-to-non-nullable-concrete-types") (i32.const 0))
+
+(module
+  (func (export "null-to-nullable-bottom-type") (result i32)
+    (ref.test nullref (ref.null any))
+    (ref.test nullref (ref.null eq))
+    i32.and
+    (ref.test nullref (ref.null i31))
+    i32.and
+    (ref.test nullref (ref.null struct))
+    i32.and
+    (ref.test nullref (ref.null array))
+    i32.and
+    (ref.test nullref (ref.null none))
+    i32.and
+    (ref.test nullexternref (ref.null extern))
+    i32.and
+    (ref.test nullexternref (ref.null noextern))
+    i32.and
+    (ref.test nullfuncref (ref.null func))
+    i32.and
+    (ref.test nullfuncref (ref.null nofunc))
+    i32.and
+  )
+)
+
+(assert_return (invoke "null-to-nullable-bottom-type") (i32.const 1))
+
+(module
+  (type $s (struct))
+  (func $f (export "non-null-to-nullable-bottom-type") (param externref) (result i32)
+    (ref.test nullref (struct.new_default $s))
+    (ref.test nullexternref (local.get 0))
+    i32.or
+    (ref.test nullfuncref (ref.func $f))
+    i32.or
+  )
+)
+
+(assert_return (invoke "non-null-to-nullable-bottom-type" (ref.extern 1)) (i32.const 0))
+
+(module
+  (func (export "null-to-non-nullable-bottom-type") (result i32)
+    (ref.test (ref none) (ref.null any))
+    (ref.test (ref none) (ref.null eq))
+    i32.or
+    (ref.test (ref none) (ref.null i31))
+    i32.or
+    (ref.test (ref none) (ref.null struct))
+    i32.or
+    (ref.test (ref none) (ref.null array))
+    i32.or
+    (ref.test (ref none) (ref.null none))
+    i32.or
+    (ref.test (ref noextern) (ref.null extern))
+    i32.or
+    (ref.test (ref noextern) (ref.null noextern))
+    i32.or
+    (ref.test (ref nofunc) (ref.null func))
+    i32.or
+    (ref.test (ref nofunc) (ref.null nofunc))
+    i32.or
+  )
+)
+
+(assert_return (invoke "null-to-non-nullable-bottom-type") (i32.const 0))
+
+(module
+  (type $s (struct))
+  (func $f (export "non-null-to-non-nullable-bottom-type") (param externref) (result i32)
+    (ref.test (ref none) (struct.new_default $s))
+    (ref.test (ref noextern) (local.get 0))
+    i32.or
+    (ref.test (ref nofunc) (ref.func $f))
+    i32.or
+  )
+)
+
+(assert_return (invoke "non-null-to-non-nullable-bottom-type" (ref.extern 1)) (i32.const 0))
diff --git a/tests/misc_testsuite/wide-arithmetic.wast b/tests/misc_testsuite/wide-arithmetic.wast
new file mode 100644
index 000000000..930832375
--- /dev/null
+++ b/tests/misc_testsuite/wide-arithmetic.wast
@@ -0,0 +1,323 @@
+(module
+  (func (export "i64.add128") (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.add128)
+  (func (export "i64.sub128") (param i64 i64 i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    local.get 2
+    local.get 3
+    i64.sub128)
+  (func (export "i64.mul_wide_s") (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_s)
+  (func (export "i64.mul_wide_u") (param i64 i64) (result i64 i64)
+    local.get 0
+    local.get 1
+    i64.mul_wide_u)
+)
+
+;; simple addition
+(assert_return (invoke "i64.add128"
+                  (i64.const 0) (i64.const 0)
+                  (i64.const 0) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.add128"
+                  (i64.const 0) (i64.const 1)
+                  (i64.const 1) (i64.const 0))
+               (i64.const 1) (i64.const 1))
+(assert_return (invoke "i64.add128"
+                  (i64.const 1) (i64.const 0)
+                  (i64.const -1) (i64.const 0))
+               (i64.const 0) (i64.const 1))
+(assert_return (invoke "i64.add128"
+                  (i64.const 1) (i64.const 1)
+                  (i64.const -1) (i64.const -1))
+               (i64.const 0) (i64.const 1))
+
+;; simple subtraction
+(assert_return (invoke "i64.sub128"
+                  (i64.const 0) (i64.const 0)
+                  (i64.const 0) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.sub128"
+                  (i64.const 0) (i64.const 0)
+                  (i64.const 1) (i64.const 0))
+               (i64.const -1) (i64.const -1))
+(assert_return (invoke "i64.sub128"
+                  (i64.const 0) (i64.const 1)
+                  (i64.const 1) (i64.const 1))
+               (i64.const -1) (i64.const -1))
+(assert_return (invoke "i64.sub128"
+                  (i64.const 0) (i64.const 0)
+                  (i64.const 1) (i64.const 1))
+               (i64.const -1) (i64.const -2))
+
+;; simple mul_wide
+(assert_return (invoke "i64.mul_wide_s" (i64.const 0) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 0) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 1) (i64.const 1))
+               (i64.const 1) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 1) (i64.const 1))
+               (i64.const 1) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -1) (i64.const -1))
+               (i64.const 1) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -1) (i64.const 1))
+               (i64.const -1) (i64.const -1))
+(assert_return (invoke "i64.mul_wide_u" (i64.const -1) (i64.const 1))
+               (i64.const -1) (i64.const 0))
+
+;; 20 randomly generated test cases for i64.add128
+(assert_return (invoke "i64.add128"
+                   (i64.const -2418420703207364752) (i64.const -1)
+                   (i64.const -1) (i64.const -1))
+               (i64.const -2418420703207364753) (i64.const -1))
+(assert_return (invoke "i64.add128"
+                   (i64.const 0) (i64.const 0)
+                   (i64.const -4579433644172935106) (i64.const -1))
+               (i64.const -4579433644172935106) (i64.const -1))
+(assert_return (invoke "i64.add128"
+                   (i64.const 0) (i64.const 0)
+                   (i64.const 1) (i64.const -1))
+               (i64.const 1) (i64.const -1))
+(assert_return (invoke "i64.add128"
+                   (i64.const 1) (i64.const 0)
+                   (i64.const 1) (i64.const 0))
+               (i64.const 2) (i64.const 0))
+(assert_return (invoke "i64.add128"
+                   (i64.const -1) (i64.const -1)
+                   (i64.const -1) (i64.const -1))
+               (i64.const -2) (i64.const -1))
+(assert_return (invoke "i64.add128"
+                   (i64.const 0) (i64.const -1)
+                   (i64.const 1) (i64.const 0))
+               (i64.const 1) (i64.const -1))
+(assert_return (invoke "i64.add128"
+                   (i64.const 0) (i64.const 0)
+                   (i64.const 0) (i64.const -1))
+               (i64.const 0) (i64.const -1))
+(assert_return (invoke "i64.add128"
+                   (i64.const 1) (i64.const 0)
+                   (i64.const -1) (i64.const -1))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.add128"
+                   (i64.const 0) (i64.const 6184727276166606191)
+                   (i64.const 0) (i64.const 1))
+               (i64.const 0) (i64.const 6184727276166606192))
+(assert_return (invoke "i64.add128"
+                   (i64.const -8434911321912688222) (i64.const -1)
+                   (i64.const 1) (i64.const -1))
+               (i64.const -8434911321912688221) (i64.const -2))
+(assert_return (invoke "i64.add128"
+                   (i64.const 1) (i64.const -1)
+                   (i64.const 0) (i64.const -1))
+               (i64.const 1) (i64.const -2))
+(assert_return (invoke "i64.add128"
+                   (i64.const 1) (i64.const -5148941131328838092)
+                   (i64.const 0) (i64.const 0))
+               (i64.const 1) (i64.const -5148941131328838092))
+(assert_return (invoke "i64.add128"
+                   (i64.const 1) (i64.const 1)
+                   (i64.const 1) (i64.const 0))
+               (i64.const 2) (i64.const 1))
+(assert_return (invoke "i64.add128"
+                   (i64.const -1) (i64.const -1)
+                   (i64.const -3636740005180858631) (i64.const -1))
+               (i64.const -3636740005180858632) (i64.const -1))
+(assert_return (invoke "i64.add128"
+                   (i64.const -5529682780229988275) (i64.const -1)
+                   (i64.const 0) (i64.const 0))
+               (i64.const -5529682780229988275) (i64.const -1))
+(assert_return (invoke "i64.add128"
+                   (i64.const 1) (i64.const -5381447440966559717)
+                   (i64.const 1020031372481336745) (i64.const 1))
+               (i64.const 1020031372481336746) (i64.const -5381447440966559716))
+(assert_return (invoke "i64.add128"
+                   (i64.const 1) (i64.const 1)
+                   (i64.const 0) (i64.const 0))
+               (i64.const 1) (i64.const 1))
+(assert_return (invoke "i64.add128"
+                   (i64.const -9133888546939907356) (i64.const -1)
+                   (i64.const 1) (i64.const 1))
+               (i64.const -9133888546939907355) (i64.const 0))
+(assert_return (invoke "i64.add128"
+                   (i64.const -4612047512704241719) (i64.const -1)
+                   (i64.const 0) (i64.const -1))
+               (i64.const -4612047512704241719) (i64.const -2))
+(assert_return (invoke "i64.add128"
+                   (i64.const 414720966820876428) (i64.const -1)
+                   (i64.const 1) (i64.const 0))
+               (i64.const 414720966820876429) (i64.const -1))
+
+
+;; 20 randomly generated test cases for i64.sub128
+(assert_return (invoke "i64.sub128"
+                   (i64.const 0) (i64.const -2459085471354756766)
+                   (i64.const -9151153060221070927) (i64.const -1))
+               (i64.const 9151153060221070927) (i64.const -2459085471354756766))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 4566502638724063423) (i64.const -4282658540409485563)
+                   (i64.const -6884077310018979971) (i64.const -1))
+               (i64.const -6996164124966508222) (i64.const -4282658540409485563))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 1) (i64.const 3118380319444903041)
+                   (i64.const 0) (i64.const 3283115686417695443))
+               (i64.const 1) (i64.const -164735366972792402))
+(assert_return (invoke "i64.sub128"
+                   (i64.const -7208415241680161810) (i64.const -1)
+                   (i64.const 1) (i64.const 0))
+               (i64.const -7208415241680161811) (i64.const -1))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 0) (i64.const 3944850126731328706)
+                   (i64.const 1) (i64.const 1))
+               (i64.const -1) (i64.const 3944850126731328704))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 1) (i64.const -1)
+                   (i64.const -1) (i64.const -1))
+               (i64.const 2) (i64.const -1))
+(assert_return (invoke "i64.sub128"
+                   (i64.const -1) (i64.const -1)
+                   (i64.const 4855833073346115923) (i64.const -6826437637438999645))
+               (i64.const -4855833073346115924) (i64.const 6826437637438999644))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 1) (i64.const 0)
+                   (i64.const -1) (i64.const -1))
+               (i64.const 2) (i64.const 0))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 1) (i64.const 0)
+                   (i64.const 1) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.sub128"
+                   (i64.const -1) (i64.const -1)
+                   (i64.const 0) (i64.const 0))
+               (i64.const -1) (i64.const -1))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 1) (i64.const -1)
+                   (i64.const -6365475388498096428) (i64.const -1))
+               (i64.const 6365475388498096429) (i64.const -1))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 6804238617560992346) (i64.const -1)
+                   (i64.const 0) (i64.const -1))
+               (i64.const 6804238617560992346) (i64.const 0))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 0) (i64.const 1)
+                   (i64.const 1) (i64.const -7756145513466453619))
+               (i64.const -1) (i64.const 7756145513466453619))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 1) (i64.const -1)
+                   (i64.const 1) (i64.const 1))
+               (i64.const 0) (i64.const -2))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 0) (i64.const 1)
+                   (i64.const 1) (i64.const 0))
+               (i64.const -1) (i64.const 0))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 1) (i64.const 5602881641763648953)
+                   (i64.const -2110589244314239080) (i64.const -1))
+               (i64.const 2110589244314239081) (i64.const 5602881641763648953))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 0) (i64.const 1)
+                   (i64.const -1) (i64.const -1))
+               (i64.const 1) (i64.const 1))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 0) (i64.const -1)
+                   (i64.const 3553816990259121806) (i64.const -2105235417856431622))
+               (i64.const -3553816990259121806) (i64.const 2105235417856431620))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 1861102705894987245) (i64.const 1)
+                   (i64.const 3713781778534059871) (i64.const 1))
+               (i64.const -1852679072639072626) (i64.const -1))
+(assert_return (invoke "i64.sub128"
+                   (i64.const 0) (i64.const -1)
+                   (i64.const 1) (i64.const 1832524486821761762))
+               (i64.const -1) (i64.const -1832524486821761764))
+
+;; 20 randomly generated test cases for i64.mul_wide_s
+(assert_return (invoke "i64.mul_wide_s" (i64.const 1) (i64.const 1))
+               (i64.const 1) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 0) (i64.const 6287758211025156705))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -6643537319803451357) (i64.const 1))
+               (i64.const -6643537319803451357) (i64.const -1))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -2483565146858803428) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 1) (i64.const 1))
+               (i64.const 1) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -3838951433439430085) (i64.const 3471602925362676030))
+               (i64.const 5186941893001237834) (i64.const -722475195264825124))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -8262495286814853129) (i64.const 7883241869666573970))
+               (i64.const -8557189786755031842) (i64.const -3530988912334554469))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 4278371902407959701) (i64.const 1))
+               (i64.const 4278371902407959701) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -8852706149487089182) (i64.const -1))
+               (i64.const 8852706149487089182) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 1) (i64.const -1))
+               (i64.const -1) (i64.const -1))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -1) (i64.const -4329244561838653387))
+               (i64.const 4329244561838653387) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -1) (i64.const -1))
+               (i64.const 1) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 697896157315764057) (i64.const 1))
+               (i64.const 697896157315764057) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 1) (i64.const 1))
+               (i64.const 1) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const -1) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 0) (i64.const -3769664482072947073))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 1) (i64.const 8414291037346403854))
+               (i64.const 8414291037346403854) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 1) (i64.const -1))
+               (i64.const -1) (i64.const -1))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 5014655679779318485) (i64.const -5080037812563681985))
+               (i64.const 2842857627777395563) (i64.const -1380983027057486843))
+(assert_return (invoke "i64.mul_wide_s" (i64.const 0) (i64.const 1))
+               (i64.const 0) (i64.const 0))
+
+;; 20 randomly generated test cases for i64.mul_wide_u
+(assert_return (invoke "i64.mul_wide_u" (i64.const -4734436040338162711) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 1) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 3270597527173764279) (i64.const 6636648075495406358))
+               (i64.const -5430303818902260550) (i64.const 1176674035141685826))
+(assert_return (invoke "i64.mul_wide_u" (i64.const -7771814344630108151) (i64.const 1))
+               (i64.const -7771814344630108151) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 1) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 1) (i64.const -7864138787704962081))
+               (i64.const -7864138787704962081) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 1) (i64.const 518555141550256010))
+               (i64.const 518555141550256010) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 1) (i64.const -1))
+               (i64.const -1) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 1118900477321231571) (i64.const -1))
+               (i64.const -1118900477321231571) (i64.const 1118900477321231570))
+(assert_return (invoke "i64.mul_wide_u" (i64.const -1) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const -5586890671027490027) (i64.const 1))
+               (i64.const -5586890671027490027) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 0) (i64.const 3603850799751152505))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const -1) (i64.const -1))
+               (i64.const 1) (i64.const 18446744073709551614))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 0) (i64.const 1))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const -7344082851774441644) (i64.const 3896439839137544024))
+               (i64.const 5738542512914895072) (i64.const 2345175459296971666))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 0) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 616395976148874061) (i64.const 0))
+               (i64.const 0) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 2810729703362889816) (i64.const -1))
+               (i64.const -2810729703362889816) (i64.const 2810729703362889815))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 1) (i64.const -1))
+               (i64.const -1) (i64.const 0))
+(assert_return (invoke "i64.mul_wide_u" (i64.const 1) (i64.const 0))
+               (i64.const 0) (i64.const 0))
diff --git a/tests/wast.rs b/tests/wast.rs
index e3f09012f..691e9c086 100644
--- a/tests/wast.rs
+++ b/tests/wast.rs
@@ -174,6 +174,8 @@ fn should_fail(test: &Path, strategy: Strategy) -> bool {
             "spec_testsuite/simd_store32_lane.wast",
             "spec_testsuite/simd_store64_lane.wast",
             "spec_testsuite/simd_store8_lane.wast",
+            // wide arithmetic
+            "misc_testsuite/wide-arithmetic.wast",
         ];
 
         if unsupported.iter().any(|part| test.ends_with(part)) {
@@ -281,6 +283,7 @@ fn run_wast(wast: &Path, strategy: Strategy, pooling: bool) -> anyhow::Result<()
     let use_shared_memory = feature_found_src(&wast_bytes, "shared_memory")
         || feature_found_src(&wast_bytes, "shared)");
     let extended_const = feature_found(wast, "extended-const") || memory64;
+    let wide_arithmetic = feature_found(wast, "wide-arithmetic");
 
     if pooling && use_shared_memory {
         log::warn!("skipping pooling test with shared memory");
@@ -303,6 +306,7 @@ fn run_wast(wast: &Path, strategy: Strategy, pooling: bool) -> anyhow::Result<()
         .wasm_tail_call(tail_call)
         .wasm_custom_page_sizes(custom_page_sizes)
         .wasm_extended_const(extended_const)
+        .wasm_wide_arithmetic(wide_arithmetic)
         .strategy(strategy);
 
     if is_cranelift {